-
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-997] Change password from user profile (#487)
- Loading branch information
1 parent
0de4416
commit c0fbcba
Showing
8 changed files
with
428 additions
and
37 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
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
50 changes: 35 additions & 15 deletions
50
apps/nextjs-website/src/components/atoms/InfoCardItem/InfoCardItem.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 |
---|---|---|
@@ -1,40 +1,60 @@ | ||
'use client'; | ||
|
||
import { Stack, Typography } from '@mui/material'; | ||
import { ReactNode } from 'react'; | ||
import { ReactNode, useCallback } from 'react'; | ||
import InfoCardEditButton from '../InfoCardEditButton/InfoCardEditButton'; | ||
|
||
export type InfoCardItemProps = { | ||
name: string; | ||
editable?: boolean; | ||
title: string; | ||
value?: string; | ||
valueFallback?: ReactNode; | ||
// eslint-disable-next-line functional/no-return-void | ||
onEdit?: () => void; | ||
}; | ||
|
||
export const InfoCardItem = ({ | ||
editable = false, | ||
title, | ||
value, | ||
valueFallback, | ||
onEdit, | ||
}: InfoCardItemProps) => { | ||
const handleClick = useCallback(() => { | ||
if (onEdit) { | ||
onEdit(); | ||
} | ||
}, [onEdit]); | ||
|
||
const editButton = editable ? ( | ||
<InfoCardEditButton onClick={handleClick} /> | ||
) : null; | ||
|
||
const valueComponent = value ? ( | ||
<Typography minHeight='24px' fontSize={16} flexGrow={1} fontWeight={700}> | ||
{value} | ||
</Typography> | ||
) : ( | ||
valueFallback | ||
); | ||
|
||
return ( | ||
<Stack my={{ xs: 1, md: 3 }} flexDirection={{ xs: 'column', md: 'row' }}> | ||
<Stack | ||
my={{ xs: 1, md: 3 }} | ||
flexDirection={{ xs: 'column', md: 'row' }} | ||
alignItems={{ xs: 'flex-start', md: 'center' }} | ||
gap={2} | ||
> | ||
<Typography | ||
variant='body2' | ||
fontSize={16} | ||
minWidth={{ xs: 'auto', md: '200px' }} | ||
minWidth={{ xs: 'auto', md: '170px' }} | ||
> | ||
{title} | ||
</Typography> | ||
{value ? ( | ||
<Typography | ||
minHeight={'24px'} | ||
fontSize={16} | ||
flexGrow={1} | ||
fontWeight={700} | ||
> | ||
{value} | ||
</Typography> | ||
) : ( | ||
valueFallback | ||
)} | ||
{valueComponent} | ||
{editButton} | ||
</Stack> | ||
); | ||
}; |
136 changes: 136 additions & 0 deletions
136
apps/nextjs-website/src/components/organisms/Auth/EditPasswordForm.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,136 @@ | ||
import { ButtonNaked } from '@pagopa/mui-italia'; | ||
import { Stack, Typography } from '@mui/material'; | ||
import { useCallback, useState } from 'react'; | ||
|
||
import { PasswordTextField } from './PasswordTextField'; | ||
import { useTranslations } from 'next-intl'; | ||
import { passwordMatcher } from '@/helpers/auth.helpers'; | ||
|
||
type EditPasswordFormProps = { | ||
onSave: (oldPassword: string, newPassword: string) => Promise<void>; | ||
// eslint-disable-next-line functional/no-return-void | ||
onCancel: () => void; | ||
}; | ||
|
||
type Passwords = { | ||
current_password: string; | ||
new_password: string; | ||
password_confirm: string; | ||
}; | ||
|
||
export const EditPasswordForm = ({ | ||
onSave, | ||
onCancel, | ||
}: EditPasswordFormProps) => { | ||
const t = useTranslations('profile'); | ||
const [errors, setErrors] = useState<Partial<Passwords>>({}); | ||
const [passwords, setPasswords] = useState<Passwords>({ | ||
current_password: '', | ||
new_password: '', | ||
password_confirm: '', | ||
}); | ||
|
||
const validateForm = useCallback(() => { | ||
const { current_password, new_password, password_confirm } = passwords; | ||
// eslint-disable-next-line functional/no-let | ||
let err = {}; | ||
|
||
if (!current_password) { | ||
err = { current_password: t('changePassword.requiredCurrentPassword') }; | ||
} | ||
|
||
if (!passwordMatcher.test(new_password)) { | ||
err = { ...err, new_password: t('changePassword.passwordPolicy') }; | ||
} else if (new_password !== password_confirm) { | ||
err = { ...err, new_password: t('changePassword.passwordsNotMatch') }; | ||
} | ||
|
||
setErrors(err); | ||
const hasErrors = Object.keys(err).length > 0; | ||
return !hasErrors; | ||
}, [passwords, t]); | ||
|
||
const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const { | ||
target: { value, name }, | ||
} = event; | ||
|
||
setPasswords((prev) => ({ ...prev, [name]: value })); | ||
}; | ||
|
||
const handleSave = () => { | ||
if (!validateForm()) return; | ||
onSave(passwords.current_password, passwords.new_password).catch( | ||
(error) => { | ||
if (error.code === 'NotAuthorizedException') { | ||
setErrors({ current_password: t('changePassword.wrongPassword') }); | ||
} else { | ||
console.error(error); | ||
} | ||
} | ||
); | ||
}; | ||
|
||
const actions = ( | ||
<> | ||
<ButtonNaked | ||
color='primary' | ||
sx={{ paddingLeft: 0, paddingRight: 0 }} | ||
onClick={onCancel} | ||
> | ||
{t('changePassword.cancel')} | ||
</ButtonNaked> | ||
<ButtonNaked variant='contained' color='primary' onClick={handleSave}> | ||
{t('changePassword.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('changePassword.title')} | ||
</Typography> | ||
<Stack flexDirection='row' gap={4} display={{ xs: 'none', md: 'flex' }}> | ||
{actions} | ||
</Stack> | ||
</Stack> | ||
<PasswordTextField | ||
id='current_password' | ||
label={t('changePassword.currentPassword')} | ||
hasError={Reflect.has(errors, 'current_password')} | ||
helperText={errors.current_password} | ||
value={passwords.current_password} | ||
onChange={handlePasswordChange} | ||
/> | ||
<PasswordTextField | ||
id='new_password' | ||
label={t('changePassword.newPassword')} | ||
value={passwords.new_password} | ||
hasError={Reflect.has(errors, 'new_password')} | ||
helperText={errors.new_password} | ||
onChange={handlePasswordChange} | ||
/> | ||
<PasswordTextField | ||
id='password_confirm' | ||
label={t('changePassword.confirmPassword')} | ||
value={passwords.password_confirm} | ||
onChange={handlePasswordChange} | ||
/> | ||
<Stack flexDirection='row' gap={4} display={{ xs: 'flex', md: 'none' }}> | ||
{actions} | ||
</Stack> | ||
</Stack> | ||
); | ||
}; |
Oops, something went wrong.