-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[DEV-1272] update attribute email (#532)
* feat: update password from user profile * add translations & improve errors * changes after review * changes after review * add error state to password text field label * update message * Refactor personal-data page to handle both change password and change email * Add UX to edit email user attribute * Remove duplicated import * Fix distance from divider * Fix it.json indentation * Add changeset file * Handle errors and add expiredCodePage * Add error case and move signOut after router push * Use PageBackgroundWrapper component instead of Box * Rename state constant * Remove ExpiredCodeCard component * Move components to their own folder * Add 'use client' --------- Co-authored-by: jeremygordillo <[email protected]>
- Loading branch information
1 parent
d855202
commit eadf4e3
Showing
10 changed files
with
389 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"nextjs-website": minor | ||
--- | ||
|
||
Add form to update email attribute of the current user |
18 changes: 3 additions & 15 deletions
18
apps/nextjs-website/src/app/auth/account-activated/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
apps/nextjs-website/src/app/auth/email-confirmation/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
'use client'; | ||
import PageNotFound from '@/app/not-found'; | ||
import { Auth } from 'aws-amplify'; | ||
import { useRouter, useSearchParams } from 'next/navigation'; | ||
import { useEffect, useState } from 'react'; | ||
import Spinner from '@/components/atoms/Spinner/Spinner'; | ||
import ExpiredCode from '@/app/auth/expired-code/page'; | ||
|
||
enum State { | ||
loading = 'loading', | ||
expiredCode = 'expiredCode', | ||
error = 'error', | ||
} | ||
|
||
const EmailConfirmation = () => { | ||
const searchParams = useSearchParams(); | ||
const router = useRouter(); | ||
const code = searchParams.get('code'); | ||
|
||
const [state, setState] = useState<State>(State.loading); | ||
|
||
useEffect(() => { | ||
if (code) { | ||
Auth.verifyCurrentUserAttributeSubmit('email', code) | ||
.then(() => { | ||
// eslint-disable-next-line functional/immutable-data | ||
router.push('/auth/account-activated'); | ||
Auth.signOut(); | ||
}) | ||
.catch((error) => { | ||
switch (error.code) { | ||
case 'ExpiredCodeException': | ||
setState(State.expiredCode); | ||
break; | ||
case 'CodeMismatchException': | ||
case 'InternalErrorException': | ||
case 'LimitExceededException': | ||
default: | ||
setState(State.error); | ||
break; | ||
} | ||
}); | ||
} else { | ||
setState(State.error); | ||
} | ||
}, [code, router]); | ||
|
||
switch (state) { | ||
case State.error: | ||
return <PageNotFound />; | ||
case State.expiredCode: | ||
return <ExpiredCode />; | ||
default: | ||
return <Spinner />; | ||
} | ||
}; | ||
|
||
export default EmailConfirmation; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
'use client'; | ||
import { Button } from '@mui/material'; | ||
import Link from 'next/link'; | ||
import { useTranslations } from 'next-intl'; | ||
import PageBackgroundWrapper from '@/components/atoms/PageBackgroundWrapper/PageBackgroundWrapper'; | ||
import SingleCard from '@/components/atoms/SingleCard/SingleCard'; | ||
import { IllusError } from '@pagopa/mui-italia'; | ||
|
||
const ExpiredCode = () => { | ||
const t = useTranslations('auth'); | ||
|
||
return ( | ||
<PageBackgroundWrapper> | ||
<SingleCard | ||
icon={<IllusError />} | ||
title={t('expiredCode.expiredLink')} | ||
cta={ | ||
<Button | ||
variant='contained' | ||
component={Link} | ||
href='/profile/personal-data' | ||
> | ||
{t('expiredCode.goToProfile')} | ||
</Button> | ||
} | ||
/> | ||
</PageBackgroundWrapper> | ||
); | ||
}; | ||
|
||
export default ExpiredCode; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
apps/nextjs-website/src/components/molecules/EditEmailForm/EditEmailForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { ButtonNaked } from '@pagopa/mui-italia'; | ||
import { Stack, Typography } from '@mui/material'; | ||
import { useCallback, useState } from 'react'; | ||
|
||
import { useTranslations } from 'next-intl'; | ||
import { emailMatcher } from '@/helpers/auth.helpers'; | ||
import RequiredTextField, { | ||
ValidatorFunction, | ||
} from '@/components/molecules/RequiredTextField/RequiredTextField'; | ||
|
||
type EditEmailFormProps = { | ||
onSave: (email: string) => Promise<void>; | ||
// eslint-disable-next-line functional/no-return-void | ||
onCancel: () => void; | ||
}; | ||
|
||
type FormSchema = { | ||
email: string; | ||
}; | ||
|
||
const EditEmailForm = ({ onSave, onCancel }: EditEmailFormProps) => { | ||
const t = useTranslations('profile'); | ||
|
||
const [errors, setErrors] = useState<Partial<FormSchema>>({}); | ||
const [formValue, setFormValue] = useState<FormSchema>({ | ||
email: '', | ||
}); | ||
|
||
const emailValidators: ValidatorFunction[] = [ | ||
(value: string) => ({ | ||
valid: emailMatcher.test(value), | ||
error: t('changeEmail.wrongEmail'), | ||
}), | ||
]; | ||
|
||
const validateForm = useCallback(() => { | ||
const { email } = formValue; | ||
// eslint-disable-next-line functional/no-let | ||
let err = {}; | ||
|
||
if (!emailMatcher.test(email)) { | ||
err = { ...err, email: t('changeEmail.wrongEmail') }; | ||
} | ||
|
||
setErrors(err); | ||
const hasErrors = Object.keys(err).length > 0; | ||
return !hasErrors; | ||
}, [formValue, t]); | ||
|
||
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const { | ||
target: { value }, | ||
} = event; | ||
|
||
setFormValue(() => ({ email: value })); | ||
}; | ||
|
||
const handleSave = () => { | ||
if (!validateForm()) return; | ||
onSave(formValue.email).catch((error) => { | ||
if (error.code === 'NotAuthorizedException') { | ||
setErrors({ email: t('changeEmail.notAuthorizedException') }); | ||
} else { | ||
console.error(error); | ||
} | ||
}); | ||
}; | ||
|
||
const actions = ( | ||
<> | ||
<ButtonNaked | ||
color='primary' | ||
sx={{ paddingLeft: 0, paddingRight: 0 }} | ||
onClick={onCancel} | ||
> | ||
{t('changeEmail.cancel')} | ||
</ButtonNaked> | ||
<ButtonNaked variant='contained' color='primary' onClick={handleSave}> | ||
{t('changeEmail.save')} | ||
</ButtonNaked> | ||
</> | ||
); | ||
|
||
return ( | ||
<Stack gap={3}> | ||
<Stack | ||
alignItems={{ xs: 'flex-start', md: 'center' }} | ||
flexDirection={{ xs: 'column', md: 'row' }} | ||
gap={2} | ||
mt={{ xs: 1, md: 3 }} | ||
> | ||
<Typography | ||
variant='body2' | ||
flexGrow={1} | ||
fontSize={16} | ||
minWidth={{ xs: 'auto', md: '170px' }} | ||
> | ||
{t('changeEmail.title')} | ||
</Typography> | ||
<Stack flexDirection='row' gap={4} display={{ xs: 'none', md: 'flex' }}> | ||
{actions} | ||
</Stack> | ||
</Stack> | ||
<RequiredTextField | ||
label={t('personalData.fields.email')} | ||
value={formValue.email} | ||
onChange={handleEmailChange} | ||
helperText={t('changeEmail.wrongEmail')} | ||
customValidators={emailValidators} | ||
sx={{ marginBottom: { xs: 0, md: 3 } }} | ||
/> | ||
<Stack | ||
flexDirection='row' | ||
gap={4} | ||
display={{ xs: 'flex', md: 'none' }} | ||
sx={{ marginBottom: { xs: 3, md: 0 } }} | ||
> | ||
{actions} | ||
</Stack> | ||
</Stack> | ||
); | ||
}; | ||
|
||
export default EditEmailForm; |
Oops, something went wrong.