feat: add email sign up alert (#61218)

Co-authored-by: Niraj Nandish <nirajnandish@icloud.com>
Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com>
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
Ahmad Abdolsaheb
2025-09-11 11:14:00 +03:00
committed by GitHub
parent 29513a4d6d
commit 09dc696c29
27 changed files with 415 additions and 317 deletions
+2 -1
View File
@@ -147,7 +147,8 @@ model user {
/// Valuable for selectively performing random logic.
rand Float?
savedChallenges SavedChallenge[] // Undefined | SavedChallenge[]
sendQuincyEmail Boolean
// Nullable tri-state: null (likely new user), true (subscribed), false (unsubscribed)
sendQuincyEmail Boolean?
theme String? // Undefined
timezone String? // Undefined
twitter String? // Null | Undefined
+1 -1
View File
@@ -79,7 +79,7 @@ export const newUser = (email: string) => ({
progressTimestamps: [expect.any(Number)],
rand: null, // TODO(Post-MVP): delete from schema (it's not used or required).
savedChallenges: [],
sendQuincyEmail: false,
sendQuincyEmail: null,
theme: 'default',
timezone: null,
twitter: null,
-20
View File
@@ -332,26 +332,6 @@ describe('auth0 plugin', () => {
expect(res.headers.location).toMatch(HOME_LOCATION);
});
test('should redirect to email-sign-up if the user has not acceptedPrivacyTerms', async () => {
mockAuthSuccess();
// Using an italian path to make sure redirection works.
const italianReturnTo = 'https://www.freecodecamp.org/italian/settings';
const res = await fastify.inject({
method: 'GET',
url: '/auth/auth0/callback?state=valid',
cookies: {
'login-returnto': sign(italianReturnTo)
}
});
expect(res.headers.location).toEqual(
expect.stringContaining(
'https://www.freecodecamp.org/italian/email-sign-up?'
)
);
});
test('should populate the user with the correct data', async () => {
mockAuthSuccess();
+7 -21
View File
@@ -15,10 +15,7 @@ import {
} from '../utils/env';
import { findOrCreateUser } from '../routes/helpers/auth-helpers';
import { createAccessToken } from '../utils/tokens';
import {
getLoginRedirectParams,
getPrefixedLandingPath
} from '../utils/redirection';
import { getLoginRedirectParams } from '../utils/redirection';
declare module 'fastify' {
interface FastifyInstance {
@@ -108,8 +105,7 @@ export const auth0Client: FastifyPluginCallbackTypebox = fp(
}
}
const { returnTo, pathPrefix, origin } = getLoginRedirectParams(req);
const redirectBase = getPrefixedLandingPath(origin, pathPrefix);
const { returnTo } = getLoginRedirectParams(req);
let token;
try {
@@ -166,24 +162,14 @@ export const auth0Client: FastifyPluginCallbackTypebox = fp(
});
}
const { id, acceptedPrivacyTerms } = await findOrCreateUser(
fastify,
email
);
const { id } = await findOrCreateUser(fastify, email);
reply.setAccessTokenCookie(createAccessToken(id));
if (acceptedPrivacyTerms) {
void reply.redirectWithMessage(returnTo, {
type: 'success',
content: 'flash.signin-success'
});
} else {
void reply.redirectWithMessage(`${redirectBase}/email-sign-up`, {
type: 'success',
content: 'flash.signin-success'
});
}
void reply.redirectWithMessage(returnTo, {
type: 'success',
content: 'flash.signin-success'
});
});
done();
+3 -1
View File
@@ -151,7 +151,8 @@ const testUserData: Prisma.userCreateInput = {
],
yearsTopContributor: ['2018'],
twitter: '@foobar',
linkedin: 'linkedin.com/foobar'
linkedin: 'linkedin.com/foobar',
sendQuincyEmail: false
};
const minimalUserData: Prisma.userCreateInput = {
@@ -301,6 +302,7 @@ const publicUserData = {
profileUI: testUserData.profileUI,
savedChallenges: testUserData.savedChallenges,
twitter: 'https://twitter.com/foobar',
sendQuincyEmail: testUserData.sendQuincyEmail,
username: testUserData.username,
usernameDisplay: testUserData.usernameDisplay,
website: testUserData.website,
+1
View File
@@ -697,6 +697,7 @@ export const userGetRoutes: FastifyPluginCallbackTypebox = (
user: {
[username]: {
...removeNulls(publicUser),
sendQuincyEmail: publicUser.sendQuincyEmail,
...normalizeFlags(flags),
picture: publicUser.picture ?? '',
email: email ?? '',
+1
View File
@@ -26,6 +26,7 @@ vi.spyOn(globalThis, 'fetch').mockImplementation(mockedFetch);
// This is used to build a test user.
const testUserData: Prisma.userCreateInput = {
...createUserInput(defaultUserEmail),
sendQuincyEmail: true,
username: 'foobar',
usernameDisplay: 'Foo Bar',
progressTimestamps: [1520002973119, 1520440323273],
+1 -1
View File
@@ -108,7 +108,7 @@ export const getSessionUser = {
})
),
profileUI: Type.Optional(profileUI),
sendQuincyEmail: Type.Boolean(),
sendQuincyEmail: Type.Union([Type.Null(), Type.Boolean()]), // // Tri-state: null (likely new user), true (subscribed), false (unsubscribed)
theme: Type.String(),
twitter: Type.Optional(Type.String()),
website: Type.Optional(Type.String()),
+1 -1
View File
@@ -82,7 +82,7 @@ export function createUserInput(email: string) {
showPortfolio: false,
showTimeLine: false
},
sendQuincyEmail: false,
sendQuincyEmail: null,
theme: 'default',
username,
usernameDisplay: username,