diff --git a/login-workflow/docs/components/change-password-dialog.md b/login-workflow/docs/components/change-password-dialog.md index b19f0870..04b68fcd 100644 --- a/login-workflow/docs/components/change-password-dialog.md +++ b/login-workflow/docs/components/change-password-dialog.md @@ -28,8 +28,26 @@ import { AuthContextProvider, ChangePasswordDialog } from '@brightlayer-ui/react | nextLabel | `string` | The label to display for the next button. | `t('bluiCommon:ACTIONS.OKAY')` | | currentPasswordChange | `(currentPassword: string) => void` | Callback called when the current password field input changes. | | | enableButton | `boolean \| (() => boolean)` | True if the next button should be enabled. | `false` | -| onSubmit | `() => void` | Called when the next button is clicked. | | +| onFinish | `() => void` | Called when the button is clicked on success screen. | | +| onSubmit | `() => void \| Promise` | Called when the next button is clicked. | | | onPrevious | `() => void` | Callback called when the previous/back/cancel button is clicked. | | | loading | `boolean` | Whether or not the dialog is loading. | | +| showSuccessScreen | `boolean` | Used to determine whether to show a success screen after the form is submitted. | | +| slots | `ChangePasswordDialogSlots` | Components to use in place of the defaults. See [ChangePasswordDialogSlots](#changepassworddialogslots) | | +| slotProps | `ChangePasswordDialogSlotsProps` | Props to pass to the custom slot components. See [ChangePasswordDialogSlotsProps](#changepassworddialogslotsprops) | | + + +### ChangePasswordDialogSlots + +| Key | Type | Description | +| ------------- | -------------------------------------------- | ------------------------------------------------------------------------------ | +| SuccessScreen | `(props: SuccessScreenProps) => JSX.Element` | A custom success screen component to render. See [SuccessScreen](./success.md) | + +### ChangePasswordDialogSlotsProps + +| Key | Type | Description | +| ------------- | -------------------- | --------------------------------------------------------------------------------------- | +| SuccessScreen | `SuccessScreenProps` | Props to pass to the custom success screen component. See [SuccessScreen](./success.md) | + Props from the underlying MUI [Dialog](https://mui.com/material-ui/react-dialog/) are also available. \ No newline at end of file diff --git a/login-workflow/example/src/actions/AuthUIActions.tsx b/login-workflow/example/src/actions/AuthUIActions.tsx index 412c6b8f..bb8d9dcd 100644 --- a/login-workflow/example/src/actions/AuthUIActions.tsx +++ b/login-workflow/example/src/actions/AuthUIActions.tsx @@ -172,8 +172,6 @@ export const ProjectAuthUIActions: AuthUIActionsWithApp = (appHelper) => ({ if (isRandomFailure()) { throw new Error('Sorry, there was a problem sending your request.'); } - LocalStorage.clearAuthCredentials(); - appHelper.onUserNotAuthenticated(); return; }, }); diff --git a/login-workflow/example/src/components/ChangePassword.tsx b/login-workflow/example/src/components/ChangePassword.tsx index 1a1bfdb2..ea145a38 100644 --- a/login-workflow/example/src/components/ChangePassword.tsx +++ b/login-workflow/example/src/components/ChangePassword.tsx @@ -1,15 +1,25 @@ import React from 'react'; import { ChangePasswordDialog } from '@brightlayer-ui/react-auth-workflow'; import { useApp } from '../contexts/AppContextProvider'; +import { LocalStorage } from '../store/local-storage'; +import { useNavigate } from 'react-router-dom'; export const ChangePassword = (): JSX.Element => { const app = useApp(); + const navigate = useNavigate(); + const logOut = (): void => { + app.setShowChangePasswordDialog(false); + LocalStorage.clearAuthCredentials(); + app.onUserNotAuthenticated(); + navigate('/login'); + }; return ( app.setShowChangePasswordDialog(false)} onSubmit={(): void => app.setShowChangePasswordDialog(false)} + onFinish={(): void => logOut()} /> ); }; diff --git a/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialog.test.tsx b/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialog.test.tsx index a0b1160d..3d6024ed 100644 --- a/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialog.test.tsx +++ b/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialog.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import '@testing-library/jest-dom'; -import { cleanup, fireEvent, render, RenderResult, screen } from '@testing-library/react'; +import { cleanup, fireEvent, render, RenderResult, screen, waitFor } from '@testing-library/react'; import { ChangePasswordDialog } from './ChangePasswordDialog'; import { AuthContextProvider } from '../../contexts'; import { BrowserRouter } from 'react-router-dom'; @@ -71,4 +71,32 @@ describe('Change Password Dialog tests', () => { fireEvent.change(confirmPasswordInput, { target: { value: 'Abc@1234' } }); expect(confirmPasswordInput).toHaveValue('Abc@1234'); }); + + it('should show success screen, when okay button is clicked', async () => { + const { getByLabelText } = renderer({ + open: true, + showSuccessScreen: true, + PasswordProps: { + newPasswordLabel: 'New Password', + confirmPasswordLabel: 'Confirm New Password', + onPasswordChange: updateFields, + passwordRequirements: [], + }, + }); + + const currentPasswordInput = getByLabelText('Current Password'); + fireEvent.change(currentPasswordInput, { target: { value: 'Abc@1234' } }); + const newPasswordInput = getByLabelText('New Password'); + const confirmPasswordInput = getByLabelText('Confirm New Password'); + fireEvent.change(newPasswordInput, { target: { value: 'Abc@1234' } }); + expect(newPasswordInput).toHaveValue('Abc@1234'); + fireEvent.change(confirmPasswordInput, { target: { value: 'Abc@1234' } }); + expect(confirmPasswordInput).toHaveValue('Abc@1234'); + + fireEvent.click(screen.getByText('Okay')); + expect(screen.getByText('Okay')).toBeEnabled(); + fireEvent.click(screen.getByText('Okay')); + + await waitFor(() => expect(screen.getByText('Your password was successfully reset.'))); + }); }); diff --git a/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialog.tsx b/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialog.tsx index 293cd141..408f1aea 100644 --- a/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialog.tsx +++ b/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialog.tsx @@ -4,6 +4,7 @@ import { useAuthContext } from '../../contexts'; import { useLanguageLocale } from '../../hooks'; import { ChangePasswordDialogBase } from './ChangePasswordDialogBase'; import { ChangePasswordDialogProps } from './types'; +import CheckCircle from '@mui/icons-material/CheckCircle'; /** * Component that renders a dialog with textField to enter current password and a change password form with a new password and confirm password inputs. @@ -16,7 +17,12 @@ import { ChangePasswordDialogProps } from './types'; * @param nextLabel label to display for the next button * @param currentPasswordChange called when the current password field changes * @param enableButton boolean to enable and disable the button + * @param onFinish function called when the button is clicked on success screen * @param onSubmit Callback function to call when the form is submitted + * @param onPrevious function called when the previous button is clicked + * @param showSuccessScreen boolean that determines whether to show the success screen or not + * @param slots used for ChangePasswordDialog SuccessScreen props + * @param slotProps props that will be passed to the SuccessScreen component * * @category Component */ @@ -30,7 +36,8 @@ export const ChangePasswordDialog: React.FC = (props) const [confirmInput, setConfirmInput] = useState(''); const [showErrorDialog, setShowErrorDialog] = useState(false); const [isLoading, setIsLoading] = useState(false); - const { actions, navigate, routeConfig } = useAuthContext(); + const [showSuccessScreen, setShowSuccessScreen] = useState(false); + const { actions } = useAuthContext(); const { open, @@ -41,6 +48,7 @@ export const ChangePasswordDialog: React.FC = (props) nextLabel = t('bluiCommon:ACTIONS.OKAY'), onPrevious, onSubmit, + onFinish, PasswordProps, ErrorDialogProps, } = props; @@ -63,14 +71,27 @@ export const ChangePasswordDialog: React.FC = (props) try { setIsLoading(true); await actions.changePassword(currentInput, passwordInput); - await onSubmit(); + if (props.showSuccessScreen === false) { + onFinish(); + } + setShowSuccessScreen(true); } catch { setShowErrorDialog(true); } finally { setIsLoading(false); } } - }, [checkPasswords, currentInput, passwordInput, actions, setIsLoading, setShowErrorDialog, onSubmit]); + }, [ + checkPasswords, + currentInput, + passwordInput, + actions, + setIsLoading, + setShowErrorDialog, + onSubmit, + onFinish, + props.showSuccessScreen, + ]); const passwordProps = { newPasswordLabel: t('bluiAuth:CHANGE_PASSWORD.NEW_PASSWORD'), @@ -113,12 +134,31 @@ export const ChangePasswordDialog: React.FC = (props) currentPasswordChange={(currentPwd): void => setCurrentInput(currentPwd)} enableButton={checkPasswords} onPrevious={onPrevious} - PasswordProps={passwordProps} - ErrorDialogProps={errorDialogProps} onSubmit={async (): Promise => { await changePasswordSubmit(); - navigate(routeConfig.LOGIN); }} + PasswordProps={passwordProps} + ErrorDialogProps={errorDialogProps} + slotProps={{ + SuccessScreen: { + icon: , + messageTitle: t('bluiAuth:PASSWORD_RESET.SUCCESS_MESSAGE'), + message: t('bluiAuth:CHANGE_PASSWORD.SUCCESS_MESSAGE'), + onDismiss: (): void => { + onFinish(); + }, + WorkflowCardActionsProps: { + showPrevious: false, + fullWidthButton: true, + showNext: true, + nextLabel: t('bluiCommon:ACTIONS.DONE'), + onNext: (): void => { + onFinish(); + }, + }, + }, + }} + showSuccessScreen={showSuccessScreen} /> ); }; diff --git a/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialogBase.tsx b/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialogBase.tsx index 358af2bd..45374a27 100644 --- a/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialogBase.tsx +++ b/login-workflow/src/components/ChangePasswordDialog/ChangePasswordDialogBase.tsx @@ -16,6 +16,7 @@ import { SetPassword } from '../SetPassword'; import { PasswordTextField } from '../PasswordTextField'; import { BasicDialog } from '../Dialog'; import { Spinner } from '../../components'; +import { SuccessScreenBase, SuccessScreenProps } from '../../screens'; /** * Component that renders a dialog with textField to enter current password and a change password form with a new password and confirm password inputs. @@ -28,10 +29,14 @@ import { Spinner } from '../../components'; * @param nextLabel label to display for the next button * @param currentPasswordChange called when the current password field changes * @param enableButton boolean to enable and disable the button + * @param onFinish function called when the button is clicked on success screen * @param onSubmit callback function to call when the form is submitted * @param onPrevious called when the previous button is clicked * @param sx styles passed to the underlying root component * @param loading boolean that indicates whether the loading spinner should be displayed + * @param showSuccessScreen boolean that determines whether to show the success screen or not + * @param slots used for ChangePasswordDialog SuccessScreen props + * @param slotProps props that will be passed to the SuccessScreen component * * @category Component */ @@ -52,8 +57,12 @@ export const ChangePasswordDialogBase: React.FC = (pr ErrorDialogProps, PasswordProps, loading, + showSuccessScreen, + slots, + slotProps, } = props; const theme = useTheme(); + const matchesMD = useMediaQuery(theme.breakpoints.down('md')); const matchesSM = useMediaQuery(theme.breakpoints.down('sm')); const [currentPassword, setCurrentPassword] = useState(''); const [buttonState, setButtonState] = useState(true); @@ -68,81 +77,104 @@ export const ChangePasswordDialogBase: React.FC = (pr setButtonState(!enableButton); }, [enableButton]); + const getSuccessScreen = ( + _props: SuccessScreenProps, + SuccessScreen?: (props: SuccessScreenProps) => JSX.Element + ): JSX.Element => + SuccessScreen ? ( + SuccessScreen(_props) + ) : ( + + ); + return ( - - - {dialogTitle} - - - {dialogDescription} - - - + + { - const { current } = PasswordProps.passwordRef; - if (e.key === 'Enter' && current) { - current.focus(); - } + > + {dialogTitle} + + - - - - - - - - - + + + + + + + )} ); }; diff --git a/login-workflow/src/components/ChangePasswordDialog/types.ts b/login-workflow/src/components/ChangePasswordDialog/types.ts index 7fb564d8..47294b4b 100644 --- a/login-workflow/src/components/ChangePasswordDialog/types.ts +++ b/login-workflow/src/components/ChangePasswordDialog/types.ts @@ -1,7 +1,16 @@ +import { SuccessScreenProps } from '../../screens'; import { DialogProps } from '@mui/material'; import { BasicDialogProps } from '../Dialog'; import { SetPasswordProps } from '../SetPassword'; +export type ChangePasswordDialogSlots = { + SuccessScreen?: (props?: SuccessScreenProps) => JSX.Element; +}; + +export type ChangePasswordDialogSlotsProps = { + SuccessScreen?: SuccessScreenProps; +}; + export type ChangePasswordDialogProps = DialogProps & { PasswordProps?: SetPasswordProps } & { ErrorDialogProps?: BasicDialogProps; } & { @@ -42,6 +51,12 @@ export type ChangePasswordDialogProps = DialogProps & { PasswordProps?: SetPassw */ enableButton?: boolean | (() => boolean); + /** + * Function called when the button is clicked on success screen + * @returns void + */ + onFinish?: () => void; + /** * Callback function to call when the form is submitted * @returns void | Promise @@ -54,5 +69,23 @@ export type ChangePasswordDialogProps = DialogProps & { PasswordProps?: SetPassw */ onPrevious?: () => void; + /** + * Boolean that indicates whether the loading spinner should be displayed + */ loading?: boolean; + + /** + * Used to determine whether to show a success screen after the form is submitted + */ + showSuccessScreen?: boolean; + + /** + * Used for ChangePasswordDialog SuccessScreen + */ + slots?: ChangePasswordDialogSlots; + + /** + * Applied to slot from SuccessScreen + */ + slotProps?: ChangePasswordDialogSlotsProps; };