Skip to content

Commit

Permalink
feat(app-signup): add email verification functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
rams23 committed Jan 3, 2021
1 parent f8d6bec commit c0ab0f9
Show file tree
Hide file tree
Showing 13 changed files with 79 additions and 5 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"scripts": {
"bootstrap": "npm ci && npx lerna bootstrap",
"build": "npx lerna run build --stream",
"start:emulators": "npx firebase emulators:start",
"test:firestore": "npm run test --prefix packages/firestore",
"scripts:initialize-firestore-emulator": "node scripts/load-initial-data-in-emulator.js",
"scripts:initialize-firestore-emulator:local": "npx env-cmd -f .env node scripts/load-initial-data-in-emulator.js"
Expand Down
8 changes: 6 additions & 2 deletions packages/game-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useLoggedUser } from '@pipeline/auth';

const Signup = React.lazy(() => import('./signup/components/Signup'));
const EmailVerificationRequired = React.lazy(() => import('./signup/components/EmailVerificationRequired'));
const VerifyEmail = React.lazy(() => import('./signup/components/VerifyEmail'));

function App() {
const bootstrapIsFinished = useBootstrapIsFinished();
Expand All @@ -17,15 +18,18 @@ function App() {
return bootstrapIsFinished ? (
<Suspense fallback={null}>
<Switch>
{user && !user.emailVerified && pathname !== RoutingPath.EmailVerificationRequired ? (
{user &&
!user.emailVerified &&
pathname !== RoutingPath.EmailVerificationRequired &&
pathname !== RoutingPath.VerifyEmail ? (
<Route path="*">
<Redirect to={RoutingPath.EmailVerificationRequired} />
</Route>
) : null}
<Route path={RoutingPath.Login} render={() => <div>Login</div>} />
<Route path={RoutingPath.Signup} component={Signup} />
<Route path={RoutingPath.EmailVerificationRequired} component={EmailVerificationRequired} />
<Route path={RoutingPath.VerifyEmail} component={() => <div>VerifyEmail</div>} />
<Route path={RoutingPath.VerifyEmail} component={VerifyEmail} />
<PrivateRoute path={RoutingPath.Dashboard} render={() => <div>Dashboard</div>} />
<Route path="*">
<Redirect to={RoutingPath.Signup} />
Expand Down
4 changes: 4 additions & 0 deletions packages/game-app/src/_shared/auth/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ export const useResendVerificationEmail = createRequestHook(
'auth.resendVerificationEmail',
actions.resendEmailVerification,
);

export const useEmailVerification = createRequestHook('auth.emailVerification', actions.verifyEmail, {
errorMessagesScope: 'auth.errors',
});
4 changes: 2 additions & 2 deletions packages/game-app/src/_shared/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { reducer, actions, name, selectors, AuthUser } from './slice';
import saga from './saga';
import useLoggedUser from './useLoggedUser';
import { useResendVerificationEmail } from './hooks';
import { useEmailVerification, useResendVerificationEmail } from './hooks';

export { reducer, actions, name, saga, selectors, useLoggedUser, useResendVerificationEmail };
export { reducer, actions, name, saga, selectors, useLoggedUser, useResendVerificationEmail, useEmailVerification };

export type { AuthUser };
5 changes: 5 additions & 0 deletions packages/game-app/src/_shared/auth/saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ function* resendVerificationEmail() {
yield call(() => firebase.auth().currentUser?.sendEmailVerification());
}

function* executeEmailVerification(action: ReturnType<typeof actions.verifyEmail>) {
yield call(() => firebase.auth().applyActionCode(action.payload.code));
}

export default function* authSaga() {
yield takeEvery(actions.initialize, initializeAuthSaga);
yield takeEvery(
actions.resendEmailVerification,
addRequestStatusManagement(resendVerificationEmail, 'auth.resendVerificationEmail'),
);
yield takeEvery(actions.verifyEmail, addRequestStatusManagement(executeEmailVerification, 'auth.emailVerification'));
}
1 change: 1 addition & 0 deletions packages/game-app/src/_shared/auth/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const name = slice.name;
export const actions = {
...slice.actions,
resendEmailVerification: createAction(`${name}/resendEmailVerification`),
verifyEmail: createAction<{ code: string }>(`${name}/verifyEmail`),
};
export const selectors = {
getCurrentUser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export interface RequestsKeys {
gameRoles: null;
devOpsMaturities: null;
'auth.resendVerificationEmail': null;
'auth.emailVerification': null;
}
3 changes: 2 additions & 1 deletion packages/game-app/src/_shared/routing/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { RoutingPath } from './routingPath';
import PrivateRoute from './PrivateRoute';
import useNavigateOnCondition from './useNavigateOnCondition';
import { useQueryParams } from './useQueryParams';

export { RoutingPath, PrivateRoute, useNavigateOnCondition };
export { RoutingPath, PrivateRoute, useNavigateOnCondition, useQueryParams };
9 changes: 9 additions & 0 deletions packages/game-app/src/_shared/routing/useQueryParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useLocation } from 'react-router-dom';

/**
* Hook to parse query params and get them as object
*/
export function useQueryParams<T = any>() {
const { search } = useLocation();
return (Object.fromEntries(new URLSearchParams(search).entries()) as unknown) as T;
}
5 changes: 5 additions & 0 deletions packages/game-app/src/assets/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ const translations = {
test1: 'Pipeline - The Game that Delivers!',
test2: 'Random stuff',
},
auth: {
errors: {
'auth/invalid-action-code': 'Verification link invalid or already used',
},
},
signup: {
verificationRequired: {
message: "You need to verify your email to start playing! If you don't find it, try in your spam",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { useEffect } from 'react';
import { VerifyEmailParams } from '../../types/emailValidationParams';
import { RoutingPath, useNavigateOnCondition, useQueryParams } from '@pipeline/routing';
import { useEmailVerification, useLoggedUser } from '@pipeline/auth';

type Props = {};

const VerifyEmail: React.FC<Props> = () => {
let params = useQueryParams<VerifyEmailParams>();

const { call, success, translatedError } = useEmailVerification();
const loggedUser = useLoggedUser();

useEffect(() => {
call({ code: params.oobCode });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useNavigateOnCondition(success, loggedUser ? RoutingPath.Dashboard : RoutingPath.Login);

return (
<div>
{translatedError ? (
<div>
<span className="error-message">{translatedError}</span>
</div>
) : null}
</div>
);
};

VerifyEmail.displayName = 'VerifyEmail';

export default VerifyEmail;
3 changes: 3 additions & 0 deletions packages/game-app/src/signup/components/VerifyEmail/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import VerifyEmail from './VerifyEmail';

export default VerifyEmail;
6 changes: 6 additions & 0 deletions packages/game-app/src/signup/types/emailValidationParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface VerifyEmailParams {
mode: string;
lang: string;
oobCode: string;
apiKey: string;
}

0 comments on commit c0ab0f9

Please sign in to comment.