Skip to content

Commit

Permalink
feat(clerk-js): Create <ResetPasswordSuccess /> page
Browse files Browse the repository at this point in the history
Display a success message after password was successfully changed.
  • Loading branch information
panteliselef committed May 2, 2023
1 parent 6155f5b commit 3fbf8e7
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 27 deletions.
21 changes: 4 additions & 17 deletions packages/clerk-js/src/ui/components/SignIn/ResetPassword.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { useState } from 'react';

import { clerkInvalidFAPIResponse } from '../../../core/errors';
import { withRedirectToHomeSingleSessionGuard } from '../../common';
import { useCoreClerk, useCoreSignIn, useSignInContext } from '../../contexts';
import { useCoreSignIn } from '../../contexts';
import { Col, descriptors, localizationKeys } from '../../customizables';
import { Card, CardAlert, Form, Header, useCardState, withCardStateProvider } from '../../elements';
import { useSupportEmail } from '../../hooks/useSupportEmail';
Expand All @@ -11,12 +9,9 @@ import { handleError, useFormControl } from '../../utils';

export const _ResetPassword = () => {
const signIn = useCoreSignIn();
const { navigateAfterSignIn } = useSignInContext();
const card = useCardState();
const { navigate } = useRouter();
const { setActive } = useCoreClerk();
const supportEmail = useSupportEmail();
const [isCompleted, setCompleted] = useState(false);

const passwordField = useFormControl('password', '', {
type: 'password',
Expand Down Expand Up @@ -49,11 +44,7 @@ export const _ResetPassword = () => {

switch (status) {
case 'complete':
setCompleted(true);
setTimeout(() => {
void setActive({ session: createdSessionId, beforeEmit: navigateAfterSignIn });
}, 2000);
return;
return navigate(`../reset-password-success?createdSessionId=${createdSessionId}`);
case 'needs_second_factor':
return navigate('../factor-two');
default:
Expand Down Expand Up @@ -90,12 +81,8 @@ export const _ResetPassword = () => {
<Form.Control {...confirmField.props} />
</Form.ControlRow>
<Form.SubmitButton
isDisabled={!canSubmit || isCompleted}
localizationKey={
isCompleted
? localizationKeys('signIn.resetPassword.formButtonPrimary__complete')
: localizationKeys('signIn.resetPassword.formButtonPrimary')
}
isDisabled={!canSubmit}
localizationKey={localizationKeys('signIn.resetPassword.formButtonPrimary')}
/>
</Form.Root>
</Col>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useEffect } from 'react';

import { withRedirectToHomeSingleSessionGuard } from '../../common';
import { useCoreClerk, useSignInContext } from '../../contexts';
import { Col, descriptors, localizationKeys, Text } from '../../customizables';
import { Card, CardAlert, Header, useCardState, withCardStateProvider } from '../../elements';
import { Flex, Spinner } from '../../primitives';
import { useRouter } from '../../router';

const useActivateSession = () => {
const { queryString } = useRouter();
const { setActive } = useCoreClerk();
const { navigateAfterSignIn } = useSignInContext();

useEffect(() => {
let timeoutId: ReturnType<typeof setTimeout>;
if (new URLSearchParams(queryString).has('createdSessionId')) {
const createdSessionId = new URLSearchParams(queryString).get('createdSessionId');
timeoutId = setTimeout(() => {
void setActive({ session: createdSessionId, beforeEmit: navigateAfterSignIn });
}, 2000);
}

return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
});
};
export const _ResetPassword = () => {
const card = useCardState();
useActivateSession();
return (
<Card>
<CardAlert>{card.error}</CardAlert>
<Header.Root>
<Header.Title localizationKey={localizationKeys('signIn.resetPassword.title')} />
</Header.Root>
<Col
elementDescriptor={descriptors.main}
gap={8}
>
<Text
localizationKey={localizationKeys('signIn.resetPassword.successMessage')}
variant='smallRegular'
colorScheme='inherit'
/>
<Flex
direction='row'
center
>
<Spinner
size='xl'
colorScheme='primary'
/>
</Flex>
</Col>
</Card>
);
};

export const ResetPasswordSuccess = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_ResetPassword));
4 changes: 4 additions & 0 deletions packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ComponentContext, useCoreClerk, useSignInContext, withCoreSessionSwitch
import { Flow } from '../../customizables';
import { Route, Switch, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
import { ResetPassword } from './ResetPassword';
import { ResetPasswordSuccess } from './ResetPasswordSuccess';
import { SignInAccountSwitcher } from './SignInAccountSwitcher';
import { SignInFactorOne } from './SignInFactorOne';
import { SignInFactorTwo } from './SignInFactorTwo';
Expand Down Expand Up @@ -36,6 +37,9 @@ function SignInRoutes(): JSX.Element {
<Route path='reset-password'>
<ResetPassword />
</Route>
<Route path='reset-password-success'>
<ResetPasswordSuccess />
</Route>
<Route path='sso-callback'>
<SignInSSOCallback
afterSignInUrl={signInContext.afterSignInUrl}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import type { ResetPasswordCodeFactor } from '@clerk/types';

import { useCoreSignIn } from '../../contexts';
import { Flow, localizationKeys } from '../../customizables';
import type { SignInFactorOneCodeCard } from './SignInFactorOneCodeForm';
import { SignInFactorOneCodeForm } from './SignInFactorOneCodeForm';

type SignInForgotPasswordCardProps = SignInFactorOneCodeCard & { factor: ResetPasswordCodeFactor };

export const SignInFactorOneForgotPasswordCard = (props: SignInForgotPasswordCardProps) => {
const { supportedFirstFactors, identifier } = useCoreSignIn();

const strategy = supportedFirstFactors.find(factor => (factor as any)?.safeIdentifier === identifier)?.strategy;
const isPhoneStrategy = strategy === 'phone_code';

return (
<Flow.Part part='resetPassword'>
<SignInFactorOneCodeForm
{...props}
showAlternativeMethods={false}
cardTitle={localizationKeys('signIn.forgotPassword.title_email')}
cardTitle={
isPhoneStrategy
? localizationKeys('signIn.forgotPassword.title_phone')
: localizationKeys('signIn.forgotPassword.title_email')
}
cardSubtitle={localizationKeys('signIn.forgotPassword.subtitle')}
formTitle={localizationKeys('signIn.forgotPassword.formTitle')}
formSubtitle={localizationKeys('signIn.forgotPassword.formSubtitle')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useCoreClerk, useCoreSignIn, useEnvironment, useSignInContext } from '.
import { Col, descriptors, localizationKeys } from '../../customizables';
import { Card, CardAlert, Footer, Form, Header, useCardState } from '../../elements';
import { useSupportEmail } from '../../hooks/useSupportEmail';
import { useRouter } from '../../router';
import { handleError, useFormControl } from '../../utils';

type SignInFactorTwoBackupCodeCardProps = {
Expand All @@ -17,6 +18,7 @@ export const SignInFactorTwoBackupCodeCard = (props: SignInFactorTwoBackupCodeCa
const { displayConfig } = useEnvironment();
const { navigateAfterSignIn } = useSignInContext();
const { setActive } = useCoreClerk();
const { navigate } = useRouter();
const supportEmail = useSupportEmail();
const card = useCardState();
const codeControl = useFormControl('code', '', {
Expand All @@ -25,13 +27,16 @@ export const SignInFactorTwoBackupCodeCard = (props: SignInFactorTwoBackupCodeCa
isRequired: true,
});

const handleBackupCodeSubmit: React.FormEventHandler = async e => {
const handleBackupCodeSubmit: React.FormEventHandler = e => {
e.preventDefault();
return signIn
.attemptSecondFactor({ strategy: 'backup_code', code: codeControl.value })
.then(res => {
switch (res.status) {
case 'complete':
if (signIn.resetPasswordFlow) {
return navigate(`../reset-password-success?createdSessionId=${res.createdSessionId}`);
}
return setActive({ session: res.createdSessionId, beforeEmit: navigateAfterSignIn });
default:
return console.error(clerkInvalidFAPIResponse(res.status, supportEmail));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import type { PhoneCodeFactor, SignInResource, TOTPFactor } from '@clerk/types';
import React from 'react';

import { clerkInvalidFAPIResponse } from '../../../core/errors';
import { useCoreClerk, useCoreSignIn, useOptions, useSignInContext } from '../../contexts';
import { useCoreSignIn, useOptions } from '../../contexts';
import { localizationKeys, Text } from '../../customizables';
import type { VerificationCodeCardProps } from '../../elements';
import { useCardState, VerificationCodeCard } from '../../elements';
import { useSupportEmail } from '../../hooks/useSupportEmail';
import type { LocalizationKey } from '../../localization';
import { useRouter } from '../../router';
import { handleError } from '../../utils';

export type SignInFactorTwoCodeCard = Pick<VerificationCodeCardProps, 'onShowAlternativeMethodsClicked'> & {
Expand All @@ -28,8 +29,7 @@ type SignInFactorTwoCodeFormProps = SignInFactorTwoCodeCard & {
export const SignInFactorTwoCodeForm = (props: SignInFactorTwoCodeFormProps) => {
const signIn = useCoreSignIn();
const card = useCardState();
const { navigateAfterSignIn } = useSignInContext();
const { setActive } = useCoreClerk();
const { navigate } = useRouter();
const supportEmail = useSupportEmail();
const { experimental_enableClerkImages } = useOptions();

Expand Down Expand Up @@ -57,7 +57,7 @@ export const SignInFactorTwoCodeForm = (props: SignInFactorTwoCodeFormProps) =>
await resolve();
switch (res.status) {
case 'complete':
return setActive({ session: res.createdSessionId, beforeEmit: navigateAfterSignIn });
return navigate(`../reset-password-success?createdSessionId=${res.createdSessionId}`);
default:
return console.error(clerkInvalidFAPIResponse(res.status, supportEmail));
}
Expand All @@ -80,6 +80,7 @@ export const SignInFactorTwoCodeForm = (props: SignInFactorTwoCodeFormProps) =>
}
onShowAlternativeMethodsClicked={props.onShowAlternativeMethodsClicked}
>
{/* TODO: Conditionally render + localization */}
<Text
localizationKey={'We need to verify your identity before resetting your password.'}
variant='smallRegular'
Expand Down
2 changes: 1 addition & 1 deletion packages/localizations/src/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export const enUS: LocalizationResource = {
resetPassword: {
title: 'Reset Password',
formButtonPrimary: 'Reset Password',
formButtonPrimary__complete: 'Password Changed',
successMessage: 'Your password was successfully changes. Now signing you in, please wait a moment.',
},
emailCode: {
title: 'Check your email',
Expand Down
4 changes: 1 addition & 3 deletions packages/types/src/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,7 @@ type _LocalizationResource = {
resetPassword: {
title: LocalizationValue;
formButtonPrimary: LocalizationValue;
// TODO: remove this
formButtonPrimary__complete: LocalizationValue;
// formButtonReset: LocalizationValue;
successMessage: LocalizationValue;
};
emailCode: {
title: LocalizationValue;
Expand Down

0 comments on commit 3fbf8e7

Please sign in to comment.