Files
Account/src/features/authentication/components/ForgetPassword/ChangePassword.tsx
2025-09-29 12:03:02 +03:30

252 lines
7.7 KiB
TypeScript

import { useRef, useState } from 'react';
import { AuthenticationCard } from '../AuthenticationCard';
import { Edit2, Eye, EyeSlash, TickCircle } from 'iconsax-react';
import {
Box,
Button,
IconButton,
Stack,
TextField,
Typography,
} from '@mui/material';
import { useTranslation } from 'react-i18next';
import { containsNumber } from '@/utils/regexes/containsNumber';
import { containsSymbol } from '@/utils/regexes/containsSymbol';
import { least8Chars } from '@/utils/regexes/least8Chars';
import { hasUpperAndLowerLetter } from '@/utils/regexes/hasUpperAndLowerLetter';
import type { ResetPasswordRequest } from '../../types/userTypes';
import type { AuthType } from '../../types/authTypes';
import type { CountryCode } from '@/types/commonTypes';
import { resetPassword } from '../../api/authorizationAPI';
import { Icon, useToast } from '@rkheftan/harmony-ui';
import { useApi } from '@/hooks/useApi';
export interface ChangePasswordProps {
onEditInfo: () => void;
onPasswordChanged: () => void;
forgetPasswordInfo: string;
infoType: AuthType;
countryCode: CountryCode;
}
export const ChangePassword = ({
onEditInfo,
onPasswordChanged,
forgetPasswordInfo,
infoType,
countryCode,
}: ChangePasswordProps) => {
const { t } = useTranslation('authentication');
const [passValue, setPassValue] = useState<string>('');
const [confirmPassValue, setConfirmPassValue] = useState<string>('');
const [inputTouched, setInputTouched] = useState<boolean>(false);
const [confirmInputTouched, setConfirmInputTouched] =
useState<boolean>(false);
const [showPassword, setShowPassword] = useState<boolean>(false);
const [showConfirmPassword, setShowConfirmPassword] =
useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null);
const confirmInputRef = useRef<HTMLInputElement>(null);
const toast = useToast();
const { loading: resetPasswordLoading, execute: resetPasswordCall } =
useApi(resetPassword);
const passwordValidationRules = [
{ title: t('forgetPassword.includingANumber'), validator: containsNumber },
{ title: t('forgetPassword.atLeast8Characters'), validator: least8Chars },
{
title: t('forgetPassword.containsAnUppercaseAndLowercaseLetter'),
validator: hasUpperAndLowerLetter,
},
{ title: t('forgetPassword.ContainsASymbol'), validator: containsSymbol },
];
const handleBlur = () => {
setInputTouched(true);
};
const handleConfirmPassBlur = () => {
setConfirmInputTouched(true);
};
const handleSubmit = async () => {
if (!passValue || !isValidPassword(passValue)) {
setInputTouched(true);
inputRef.current?.focus();
} else if (passValue !== confirmPassValue) {
setConfirmInputTouched(true);
confirmInputRef.current?.focus();
} else {
const apiRequest: ResetPasswordRequest = {
email: infoType === 'email' ? forgetPasswordInfo : undefined,
phoneNumber:
infoType === 'phone' ? countryCode + forgetPasswordInfo : undefined,
newPassword: passValue,
confirmNewPassword: confirmPassValue,
};
const res = await resetPasswordCall(apiRequest);
if (!res) return;
if (res.success) {
onPasswordChanged();
toast({
message: t('forgetPassword.passwordChangedSuccessfully'),
severity: 'success',
});
} else {
toast({
message: res.message,
severity: 'error',
});
}
}
};
const isValidPassword = (value: string) => {
return (
containsNumber(value) &&
containsSymbol(value) &&
least8Chars(value) &&
hasUpperAndLowerLetter(value)
);
};
return (
<AuthenticationCard>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 4,
mb: 0.5,
}}
>
<Typography variant="h5">
{t('forgetPassword.changePassword')}
</Typography>
<Button
variant="outlined"
size="large"
sx={{ textTransform: 'lowercase', width: 'auto' }}
endIcon={<Icon Component={Edit2} />}
onClick={onEditInfo}
>
{forgetPasswordInfo}
</Button>
</Box>
<Typography variant="body2" color="textSecondary" sx={{ mt: 1, mb: 2 }}>
{t('forgetPassword.createANewPassword')}
</Typography>
<TextField
label={t('forgetPassword.newPassword')}
type={showPassword ? 'text' : 'password'}
value={passValue}
inputRef={inputRef}
onChange={(e) => setPassValue(e.target.value)}
onBlur={handleBlur}
error={inputTouched && !isValidPassword(passValue)}
autoFocus
slotProps={{
htmlInput: { sx: { lineHeight: 1.5, paddingInlineStart: 1 } },
input: {
startAdornment: confirmPassValue &&
isValidPassword(passValue) &&
passValue === confirmPassValue && (
<Icon
Component={TickCircle}
variant="Bold"
color="success.main"
/>
),
endAdornment: passValue ? (
<IconButton
color="primary"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<Icon Component={Eye} />
) : (
<Icon Component={EyeSlash} />
)}
</IconButton>
) : (
''
),
},
}}
sx={{ mt: 4 }}
/>
{!isValidPassword(passValue) && (
<Stack spacing={1} sx={{ mt: 2 }}>
{passwordValidationRules.map((rule) => (
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
<Icon
Component={TickCircle}
variant={rule.validator(passValue) ? 'Bold' : 'Linear'}
color={
rule.validator(passValue) ? 'success.main' : 'primary.light'
}
/>
<Typography variant="body2">{rule.title}</Typography>
</Stack>
))}
</Stack>
)}
<TextField
label={t('forgetPassword.confirmPassword')}
type={showConfirmPassword ? 'text' : 'password'}
value={confirmPassValue}
inputRef={confirmInputRef}
onChange={(e) => setConfirmPassValue(e.target.value)}
onBlur={handleConfirmPassBlur}
error={confirmInputTouched && confirmPassValue !== passValue}
slotProps={{
htmlInput: { sx: { lineHeight: 1.5, paddingInlineStart: 1 } },
input: {
startAdornment: confirmPassValue &&
isValidPassword(passValue) &&
passValue === confirmPassValue && (
<Icon
Component={TickCircle}
variant="Bold"
color="success.main"
/>
),
endAdornment: confirmPassValue ? (
<IconButton
color="primary"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
>
{showConfirmPassword ? (
<Icon Component={Eye} />
) : (
<Icon Component={EyeSlash} />
)}
</IconButton>
) : (
''
),
},
}}
sx={{ my: 4 }}
/>
<Stack spacing={1}>
<Button loading={resetPasswordLoading} onClick={handleSubmit}>
{t('forgetPassword.confirm')}
</Button>
</Stack>
</AuthenticationCard>
);
};