From 96048a1eaa79804e69396fbfbc1865df8150e630 Mon Sep 17 00:00:00 2001 From: Robin van Zanten Date: Fri, 30 Jul 2021 09:10:18 +0200 Subject: [PATCH] feat(user): add password reset modal --- src/components/Account/Account.tsx | 9 ++++- .../__snapshots__/Payment.test.tsx.snap | 8 ++--- .../ResetPasswordForm.module.scss | 19 ++++++++++ .../ResetPasswordForm.test.tsx | 12 +++++++ .../ResetPasswordForm/ResetPasswordForm.tsx | 25 +++++++++++++ .../ResetPasswordForm.test.tsx.snap | 32 +++++++++++++++++ src/containers/AccountModal/AccountModal.tsx | 2 ++ .../AccountModal/forms/ResetPassword.tsx | 35 +++++++++++++++++++ src/i18n/locales/en_US/account.json | 6 ++++ src/i18n/locales/en_US/common.json | 3 +- src/i18n/locales/nl_NL/account.json | 6 ++++ src/i18n/locales/nl_NL/common.json | 3 +- src/stores/AccountStore.ts | 14 +++++++- 13 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 src/components/ResetPasswordForm/ResetPasswordForm.module.scss create mode 100644 src/components/ResetPasswordForm/ResetPasswordForm.test.tsx create mode 100644 src/components/ResetPasswordForm/ResetPasswordForm.tsx create mode 100644 src/components/ResetPasswordForm/__snapshots__/ResetPasswordForm.test.tsx.snap create mode 100644 src/containers/AccountModal/forms/ResetPassword.tsx diff --git a/src/components/Account/Account.tsx b/src/components/Account/Account.tsx index 786e2b634..4d277b6cc 100644 --- a/src/components/Account/Account.tsx +++ b/src/components/Account/Account.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import type { Consent, Customer, CustomerConsent, UpdateCustomerPayload } from 'types/account'; import type { CustomerFormValues, FormErrors, GenericFormValues } from 'types/form'; +import { useHistory } from 'react-router-dom'; import { formatConsentsFromValues, formatConsentValues } from '../../utils/collection'; import Visibility from '../../icons/Visibility'; @@ -14,6 +15,7 @@ import IconButton from '../IconButton/IconButton'; import LoadingOverlay from '../LoadingOverlay/LoadingOverlay'; import TextField from '../TextField/TextField'; import Checkbox from '../Checkbox/Checkbox'; +import { addQueryParam } from '../../utils/history'; import styles from './Account.module.scss'; @@ -51,6 +53,7 @@ const Account = ({ onReset, }: Props): JSX.Element => { const { t } = useTranslation('user'); + const history = useHistory(); const [editing, setEditing] = useState('none'); const [viewPassword, toggleViewPassword] = useToggle(); const consentValues = useMemo(() => formatConsentValues(publisherConsents, customerConsents), [publisherConsents, customerConsents]); @@ -74,6 +77,10 @@ const Account = ({ onReset && onReset(); }; + const editPasswordClickHandler = () => { + history.push(addQueryParam(history, 'u', 'reset-password')); + }; + useEffect(() => { !isLoading && setEditing('none'); }, [isLoading]); @@ -143,7 +150,7 @@ const Account = ({
{t('account.password')}

****************

-
diff --git a/src/components/Payment/__snapshots__/Payment.test.tsx.snap b/src/components/Payment/__snapshots__/Payment.test.tsx.snap index 155ba2572..f37a72642 100644 --- a/src/components/Payment/__snapshots__/Payment.test.tsx.snap +++ b/src/components/Payment/__snapshots__/Payment.test.tsx.snap @@ -19,7 +19,7 @@ exports[` renders and matches snapshot 1`] = `
user:payment.next_billing_date_on - 3/16/2021 + 16-3-2021

renders and matches snapshot 1`] = `

T712014024
- 5/5/2021 + 5-5-2021

renders and matches snapshot 1`] = `

T177974068
- 5/5/2021 + 5-5-2021

renders and matches snapshot 1`] = `

T996601696
- 5/5/2021 + 5-5-2021

diff --git a/src/components/ResetPasswordForm/ResetPasswordForm.module.scss b/src/components/ResetPasswordForm/ResetPasswordForm.module.scss new file mode 100644 index 000000000..3be5ad627 --- /dev/null +++ b/src/components/ResetPasswordForm/ResetPasswordForm.module.scss @@ -0,0 +1,19 @@ +@use '../../styles/variables'; +@use '../../styles/theme'; + +.title { + margin: 24px 0; + font-family: theme.$body-alt-font-family; + font-weight: 700; + font-size: 26px; +} + +.text { + margin-bottom: 24px; + font-family: theme.$body-alt-font-family; + font-size: 16px; +} + +.button { + margin-bottom: 8px; +} diff --git a/src/components/ResetPasswordForm/ResetPasswordForm.test.tsx b/src/components/ResetPasswordForm/ResetPasswordForm.test.tsx new file mode 100644 index 000000000..5a9ff3ce8 --- /dev/null +++ b/src/components/ResetPasswordForm/ResetPasswordForm.test.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import ResetPasswordForm from './ResetPasswordForm'; + +describe('', () => { + test('renders and matches snapshot', () => { + const { container } = render(); + + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/ResetPasswordForm/ResetPasswordForm.tsx b/src/components/ResetPasswordForm/ResetPasswordForm.tsx new file mode 100644 index 000000000..a037e4ba2 --- /dev/null +++ b/src/components/ResetPasswordForm/ResetPasswordForm.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import Button from '../Button/Button'; + +import styles from './ResetPasswordForm.module.scss'; + +type Props = { + onCancel: () => void; + onReset: () => void; +}; + +const ResetPasswordForm: React.FC = ({ onCancel, onReset }: Props) => { + const { t } = useTranslation('account'); + return ( +
+
{t('reset.reset_password')}
+

{t('reset.text')}

+
+ ); +}; + +export default ResetPasswordForm; diff --git a/src/components/ResetPasswordForm/__snapshots__/ResetPasswordForm.test.tsx.snap b/src/components/ResetPasswordForm/__snapshots__/ResetPasswordForm.test.tsx.snap new file mode 100644 index 000000000..bdb38e442 --- /dev/null +++ b/src/components/ResetPasswordForm/__snapshots__/ResetPasswordForm.test.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders and matches snapshot 1`] = ` +
+
+
+ reset.reset_password +
+

+ reset.text +

+ + +
+
+`; diff --git a/src/containers/AccountModal/AccountModal.tsx b/src/containers/AccountModal/AccountModal.tsx index 684d22a4c..89a3391de 100644 --- a/src/containers/AccountModal/AccountModal.tsx +++ b/src/containers/AccountModal/AccountModal.tsx @@ -10,6 +10,7 @@ import styles from './AccountModal.module.scss'; import Login from './forms/Login'; import ChooseOffer from './forms/ChooseOffer'; import Checkout from './forms/Checkout'; +import ResetPassword from './forms/ResetPassword'; const AccountModal = () => { const history = useHistory(); @@ -29,6 +30,7 @@ const AccountModal = () => { {view === 'login' ? : null} {view === 'choose-offer' ? : null} {view === 'checkout' ? : null} + {view === 'reset-password' ? : null} ); }; diff --git a/src/containers/AccountModal/forms/ResetPassword.tsx b/src/containers/AccountModal/forms/ResetPassword.tsx new file mode 100644 index 000000000..169a895db --- /dev/null +++ b/src/containers/AccountModal/forms/ResetPassword.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { useHistory } from 'react-router-dom'; + +import { resetPassword } from '../../../stores/AccountStore'; +import { removeQueryParam } from '../../../utils/history'; +import ResetPasswordForm from '../../../components/ResetPasswordForm/ResetPasswordForm'; + +const ResetPassword: React.FC = () => { + const history = useHistory(); + + const onCancelClickHandler = () => { + history.push(removeQueryParam(history, 'u')); + }; + + const onResetClickHandler = async () => { + const resetUrl = `${window.location.origin}/u/my-account?u=edit-password`; + + try { + const response = await resetPassword(resetUrl); + if (response.errors.length > 0) throw new Error(response.errors[0]); + + history.push('/u/logout'); + } catch (error: unknown) { + if (error instanceof Error) { + if (error.message.toLowerCase().includes('invalid param email')) { + console.info(error.message); + } + } + } + }; + + return ; +}; + +export default ResetPassword; diff --git a/src/i18n/locales/en_US/account.json b/src/i18n/locales/en_US/account.json index b9120c691..9788cd223 100644 --- a/src/i18n/locales/en_US/account.json +++ b/src/i18n/locales/en_US/account.json @@ -55,5 +55,11 @@ "week_plural": "weeks", "month_plural": "months", "year_plural": "years" + }, + "reset": { + "no": "No, thanks", + "reset_password": "Edit Password", + "text": "If you want to edit your password, click 'YES, Reset' to receive password reset instruction on your mail", + "yes": "Yes, reset" } } diff --git a/src/i18n/locales/en_US/common.json b/src/i18n/locales/en_US/common.json index d1890d3db..52f802094 100644 --- a/src/i18n/locales/en_US/common.json +++ b/src/i18n/locales/en_US/common.json @@ -7,6 +7,5 @@ "live": "LIVE", "play_item": "Play {{ title }}", "slide_left": "Slide left", - "slide_right": "Slide right", - "show_page": "Show page {{ number }}" + "slide_right": "Slide right" } diff --git a/src/i18n/locales/nl_NL/account.json b/src/i18n/locales/nl_NL/account.json index 9899abcc7..aa500fe2d 100644 --- a/src/i18n/locales/nl_NL/account.json +++ b/src/i18n/locales/nl_NL/account.json @@ -51,5 +51,11 @@ "month": "", "week": "", "year": "" + }, + "reset": { + "no": "", + "reset_password": "", + "text": "", + "yes": "" } } diff --git a/src/i18n/locales/nl_NL/common.json b/src/i18n/locales/nl_NL/common.json index aa73bdb84..9200a9f1c 100644 --- a/src/i18n/locales/nl_NL/common.json +++ b/src/i18n/locales/nl_NL/common.json @@ -7,6 +7,5 @@ "live": "", "play_item": "", "slide_left": "", - "slide_right": "", - "show_page": "" + "slide_right": "" } diff --git a/src/stores/AccountStore.ts b/src/stores/AccountStore.ts index 8ee889cf6..c6cd7c03c 100644 --- a/src/stores/AccountStore.ts +++ b/src/stores/AccountStore.ts @@ -59,7 +59,7 @@ const refreshJwtToken = async (sandbox: boolean, auth: AuthData) => { const authData = await getFreshJwtToken(sandbox, auth); if (authData) { - AccountStore.update(s => { + AccountStore.update((s) => { s.auth = { ...s.auth, ...authData }; }); } @@ -91,3 +91,15 @@ export const login = async (email: string, password: string) => { return afterLogin(cleengSandbox, response.responseData); }; + +export const resetPassword = async (resetUrl: string) => { + const { + config: { cleengId, cleengSandbox }, + } = ConfigStore.getRawState(); + const { user } = AccountStore.getRawState(); + + if (!cleengId) throw new Error('cleengId is not configured'); + if (!user?.email) throw new Error('invalid param email'); + + return await accountService.resetPassword({ customerEmail: user.email, publisherId: cleengId, resetUrl }, cleengSandbox); +};