chore: add icons and toast
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
TextField,
|
||||
Box,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TickCircle, Edit } from 'iconsax-react';
|
||||
import { Toast } from '@/components/Toast';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
|
||||
interface EmailSectionProps {
|
||||
showEmail: boolean;
|
||||
@@ -50,15 +50,11 @@ export function EmailSection({
|
||||
handleEditEmail,
|
||||
}: EmailSectionProps) {
|
||||
const { t } = useTranslation('completionForm');
|
||||
const [showSuccessToast, setShowSuccessToast] = useState(false);
|
||||
const [showErrorToast, setShowErrorToast] = useState(false);
|
||||
|
||||
const onSendCodeClick = () => {
|
||||
if (!correctEmail) {
|
||||
setShowErrorToast(true);
|
||||
return;
|
||||
}
|
||||
setShowErrorToast(false);
|
||||
handleSendCode();
|
||||
};
|
||||
|
||||
@@ -68,14 +64,11 @@ export function EmailSection({
|
||||
|
||||
useEffect(() => {
|
||||
if (emailVerified) {
|
||||
setShowSuccessToast(true);
|
||||
}
|
||||
}, [emailVerified]);
|
||||
|
||||
const fieldSx = {
|
||||
flex: '1 1 260px',
|
||||
// maxWidth: emailVerified ? '634px' : '462px',
|
||||
// width: '100%',
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -130,15 +123,11 @@ export function EmailSection({
|
||||
startAdornment:
|
||||
!isVerifyingCode && emailVerified ? (
|
||||
<InputAdornment position="end">
|
||||
<TickCircle
|
||||
size="24"
|
||||
color="#2e7d32"
|
||||
<Icon
|
||||
Component={TickCircle}
|
||||
size="medium"
|
||||
variant="Bold"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
marginRight: '4px',
|
||||
}}
|
||||
color="success.main"
|
||||
/>
|
||||
</InputAdornment>
|
||||
) : null,
|
||||
@@ -146,10 +135,10 @@ export function EmailSection({
|
||||
buttonState === 'counting' ? (
|
||||
<InputAdornment position="start">
|
||||
<IconButton onClick={handleEditEmail}>
|
||||
<Edit
|
||||
size="20"
|
||||
color="#2979FF"
|
||||
style={{ position: 'absolute', left: 0 }}
|
||||
<Icon
|
||||
Component={Edit}
|
||||
color="primary.main"
|
||||
size="medium"
|
||||
/>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
@@ -226,22 +215,6 @@ export function EmailSection({
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Toast
|
||||
color="success"
|
||||
open={showSuccessToast}
|
||||
onClose={() => setShowSuccessToast(false)}
|
||||
>
|
||||
{t('completion.alertSuccess')}
|
||||
</Toast>
|
||||
|
||||
<Toast
|
||||
color="error"
|
||||
open={showErrorToast}
|
||||
onClose={() => setShowErrorToast(false)}
|
||||
>
|
||||
{t('completion.emailCorrectForm')}
|
||||
</Toast>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ import {
|
||||
InputAdornment,
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TickCircle, Eye, EyeSlash, CloseCircle } from 'iconsax-react';
|
||||
import { TickCircle, Eye, EyeSlash, CloseCircle, Size } from 'iconsax-react';
|
||||
import { PasswordValidationItem } from './PasswordValidation';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
|
||||
interface PasswordSectionProps {
|
||||
showPasswordSection: boolean;
|
||||
@@ -60,8 +61,6 @@ export function PasswordSection({
|
||||
|
||||
const fieldSx = {
|
||||
flex: '1 1 260px',
|
||||
maxWidth: '309px',
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -127,25 +126,31 @@ export function PasswordSection({
|
||||
}}
|
||||
>
|
||||
{validPassword && (
|
||||
<TickCircle
|
||||
size="24"
|
||||
color="#14AE5C"
|
||||
variant="Bold"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
marginRight: '16px',
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ position: 'absolute', left: 0, px: 2 }}>
|
||||
<Icon
|
||||
Component={TickCircle}
|
||||
size="medium"
|
||||
color="success.main"
|
||||
variant="Bold"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<IconButton
|
||||
onClick={handleTogglePasswordEye}
|
||||
sx={{ ml: validPassword ? 0.5 : 'auto' }}
|
||||
>
|
||||
{showPasswordText ? (
|
||||
<Eye size="24" color="#2979FF" />
|
||||
<Icon
|
||||
Component={Eye}
|
||||
color="primary.main"
|
||||
size="medium"
|
||||
/>
|
||||
) : (
|
||||
<EyeSlash size="24" color="#2979FF" />
|
||||
<Icon
|
||||
Component={EyeSlash}
|
||||
size="medium"
|
||||
color="primary.main"
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</Box>
|
||||
@@ -184,27 +189,30 @@ export function PasswordSection({
|
||||
>
|
||||
{confirmPassword.length > 0 &&
|
||||
(matchPassword ? (
|
||||
<TickCircle
|
||||
size="24"
|
||||
color="#14AE5C"
|
||||
variant="Bold"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
marginRight: '16px',
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ position: 'absolute', left: 0, px: 2 }}>
|
||||
<Icon
|
||||
Component={TickCircle}
|
||||
size="medium"
|
||||
color="success.main"
|
||||
variant="Bold"
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<CloseCircle
|
||||
size="24"
|
||||
color="red"
|
||||
variant="Bold"
|
||||
style={{
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
marginRight: '16px',
|
||||
left: 0,
|
||||
alignItems: 'center',
|
||||
px: 2,
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Icon
|
||||
Component={CloseCircle}
|
||||
size="medium"
|
||||
color="error.main"
|
||||
variant="Bold"
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
<IconButton
|
||||
@@ -217,9 +225,17 @@ export function PasswordSection({
|
||||
}}
|
||||
>
|
||||
{showPasswordRepititonText ? (
|
||||
<Eye size="24" color="#2979FF" />
|
||||
<Icon
|
||||
Component={Eye}
|
||||
color="primary.main"
|
||||
size="medium"
|
||||
/>
|
||||
) : (
|
||||
<EyeSlash size="24" color="#2979FF" />
|
||||
<Icon
|
||||
Component={EyeSlash}
|
||||
size="medium"
|
||||
color="primary.main"
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</Box>
|
||||
@@ -252,7 +268,6 @@ export function PasswordSection({
|
||||
<Box
|
||||
sx={{
|
||||
flex: '1 1 260px',
|
||||
maxWidth: '317px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
@@ -269,7 +284,6 @@ export function PasswordSection({
|
||||
<Box
|
||||
sx={{
|
||||
flex: '1 1 260px',
|
||||
maxWidth: '317px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { TickCircle } from 'iconsax-react';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
|
||||
interface ValidationItemProps {
|
||||
isValid: boolean;
|
||||
@@ -20,11 +21,13 @@ export function PasswordValidationItem({
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
<TickCircle
|
||||
size="16"
|
||||
color={isValid ? '#14AE5C' : '#2979FF'}
|
||||
<Icon
|
||||
Component={TickCircle}
|
||||
size="small"
|
||||
color={isValid ? 'success.main' : 'primary.main'}
|
||||
variant={isValid ? 'Bold' : 'Outline'}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.primary"
|
||||
|
||||
@@ -10,15 +10,11 @@ import {
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Woman, Man } from 'iconsax-react';
|
||||
import { useState, type Dispatch, type SetStateAction } from 'react';
|
||||
import { countries } from '../data/Countries';
|
||||
import { type Dispatch, type SetStateAction } from 'react';
|
||||
import DateOfBirth from './DateOfBirth';
|
||||
|
||||
interface CountryType {
|
||||
name: string;
|
||||
fa: string;
|
||||
flag: string;
|
||||
}
|
||||
import { countries } from '../data/Countries';
|
||||
import { CountryFlag } from '@/components/CountryFlag';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
|
||||
interface PersonalInfoFieldsProps {
|
||||
firstName: string;
|
||||
@@ -50,47 +46,18 @@ export function PersonalInfoFields({
|
||||
setCountry,
|
||||
}: PersonalInfoFieldsProps) {
|
||||
const { t } = useTranslation('completionForm');
|
||||
const [countryError, setCountryError] = useState(false);
|
||||
|
||||
// Language check to pass down for date picker
|
||||
// const isFarsi = i18n.language === 'fa' || i18n.language === 'fa-IR';
|
||||
const countryOptions = countries.map((c) => ({
|
||||
code: c.code,
|
||||
label: t(c.label, { ns: 'countries' }),
|
||||
}));
|
||||
const currentCountry = countryOptions.find((c) => c.code === country) || null;
|
||||
|
||||
const handleChangeSex = (e: SelectChangeEvent<'male' | 'female'>) => {
|
||||
setSex(e.target.value as 'female' | 'male');
|
||||
};
|
||||
|
||||
const handleChangeCountry = (
|
||||
_: React.SyntheticEvent,
|
||||
newValue: string | CountryType | null,
|
||||
) => {
|
||||
if (typeof newValue === 'string') {
|
||||
setCountry(newValue);
|
||||
setCountryError(false);
|
||||
} else if (newValue === null) {
|
||||
setCountry('');
|
||||
setCountryError(false);
|
||||
} else {
|
||||
setCountry(newValue.name);
|
||||
setCountryError(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCountryBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
const inputValue = e.target.value.trim();
|
||||
// Check if input matches any country's fa name
|
||||
const exists = countries.some((c) => c.fa === inputValue);
|
||||
setCountryError(Boolean(inputValue && !exists));
|
||||
};
|
||||
|
||||
const handleInputChange = (_: React.SyntheticEvent, value: string) => {
|
||||
const exists = countries.some((c) => c.fa === value.trim());
|
||||
setCountryError(Boolean(value.trim() && !exists));
|
||||
};
|
||||
|
||||
const fieldSx = {
|
||||
flex: '1 1 260px',
|
||||
maxWidth: '309px',
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -153,13 +120,13 @@ export function PersonalInfoFields({
|
||||
>
|
||||
<MenuItem value="female">
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Woman size={20} color="#F50057" />
|
||||
<Icon Component={Woman} color="#F50057" size="small" />
|
||||
{t('completion.woman')}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
<MenuItem value="male">
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Man size={20} color="#0091EA" />
|
||||
<Icon Component={Man} size="small" color="#0091EA" />
|
||||
{t('completion.man')}
|
||||
</Box>
|
||||
</MenuItem>
|
||||
@@ -188,40 +155,23 @@ export function PersonalInfoFields({
|
||||
}}
|
||||
>
|
||||
<Autocomplete
|
||||
options={countries}
|
||||
getOptionLabel={(c) => (typeof c === 'string' ? c : c.fa)}
|
||||
value={countries.find((c) => c.name === country) || null}
|
||||
onChange={handleChangeCountry}
|
||||
onInputChange={handleInputChange}
|
||||
freeSolo
|
||||
noOptionsText=""
|
||||
renderOption={(props, c) => (
|
||||
<Box
|
||||
component="li"
|
||||
{...props}
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: 1 }}
|
||||
>
|
||||
<img
|
||||
src={`https://flagcdn.com/w40/${c.flag}.png`}
|
||||
width="24"
|
||||
height="24"
|
||||
style={{ borderRadius: '2px', border: '1px solid #ccc' }}
|
||||
alt={c.name}
|
||||
/>
|
||||
{c.fa}
|
||||
sx={fieldSx}
|
||||
options={countryOptions}
|
||||
getOptionLabel={(option) => option.label}
|
||||
value={currentCountry}
|
||||
onChange={(_, newValue) => {
|
||||
setCountry(newValue?.code || '');
|
||||
}}
|
||||
renderOption={(props, option) => (
|
||||
<Box component="li" {...props} key={option.code}>
|
||||
<CountryFlag code={option.code} />
|
||||
{option.label}
|
||||
</Box>
|
||||
)}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={t('completion.country')}
|
||||
variant="outlined"
|
||||
error={countryError}
|
||||
helperText={countryError ? t('completion.invalidCountry') : ''}
|
||||
onBlur={handleCountryBlur}
|
||||
/>
|
||||
<TextField {...params} label={t('completion.country')} />
|
||||
)}
|
||||
sx={fieldSx}
|
||||
clearOnEscape
|
||||
/>
|
||||
|
||||
<Box sx={fieldSx}>
|
||||
|
||||
@@ -7,7 +7,8 @@ import { PasswordSection } from './PasswordSection';
|
||||
import { EmailSection } from './EmailSection';
|
||||
import { SubmitSection } from './SubmitSection';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
import { Toast } from '@/components/Toast';
|
||||
import { useToast } from '@rkheftan/harmony-ui';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
export function UserCompletionForm() {
|
||||
const { t } = useTranslation('completionForm');
|
||||
@@ -48,19 +49,8 @@ export function UserCompletionForm() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [showSuccessToast, setShowSuccessToast] = useState(false);
|
||||
const [showErrorToast, setShowErrorToast] = useState(false);
|
||||
const [toastMessage, setToastMessage] = useState('');
|
||||
|
||||
const [showVerifyCodeSectionSuccess, setShowVerifyCodeSectionSuccess] =
|
||||
useState(false);
|
||||
const [showVerifyCodeSectionError, setShowVerifyCodeSectionError] =
|
||||
useState(false);
|
||||
const [sentVerificationCode, setSentVerificationCode] = useState('');
|
||||
|
||||
const [showVerifiedSuccess, setShowVerifiedSuccess] = useState(false);
|
||||
const [showVerifiedError, setShowVerifiedError] = useState(false);
|
||||
const [emailVerifiedMessage, setEmailVerifiedMessage] = useState('');
|
||||
const showToast = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (password) {
|
||||
@@ -111,28 +101,34 @@ export function UserCompletionForm() {
|
||||
try {
|
||||
const response = await apiClient.post('/User/SendEmailOtp', { email });
|
||||
if (response.data?.success) {
|
||||
setShowVerifyCodeSectionSuccess(true);
|
||||
setSentVerificationCode(
|
||||
response.data.message || 'کد با موفقیت ارسال شد',
|
||||
);
|
||||
showToast({
|
||||
message: response.data.message || t('completion.successfullCodeSent'),
|
||||
severity: 'success',
|
||||
});
|
||||
setCodeSent(true);
|
||||
setButtonState('counting');
|
||||
setCountdown(120);
|
||||
} else if (response.data?.codeSentAnyway) {
|
||||
setShowVerifyCodeSectionSuccess(true);
|
||||
setSentVerificationCode(
|
||||
'کد ارسال شد، اما: ' + (response.data.message || ''),
|
||||
);
|
||||
showToast({
|
||||
message: t('completion.codeSentBut') + (response.data.message || ''),
|
||||
severity: 'warning',
|
||||
});
|
||||
setCodeSent(true);
|
||||
setButtonState('counting');
|
||||
setCountdown(120);
|
||||
} else {
|
||||
setShowVerifyCodeSectionError(true);
|
||||
setSentVerificationCode(response.data.message || 'مشکلی پیش آمده');
|
||||
showToast({
|
||||
message: response.data.message || t('completion.problem'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
setShowVerifyCodeSectionError(true);
|
||||
setSentVerificationCode(err.response?.data?.message || 'مشکلی پیش آمده');
|
||||
} catch (error: unknown) {
|
||||
const err = error as AxiosError<{ message?: string }>;
|
||||
|
||||
showToast({
|
||||
message: err.response?.data?.message || t('completion.problem'),
|
||||
severity: 'error',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -141,6 +137,7 @@ export function UserCompletionForm() {
|
||||
const handleVerifyCode = async () => {
|
||||
if (!verificationCode.trim()) {
|
||||
setError('Please enter the verification code');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -156,18 +153,23 @@ export function UserCompletionForm() {
|
||||
if (res.data?.success) {
|
||||
setEmailVerified(true);
|
||||
setSuccess(true);
|
||||
setShowVerifiedSuccess(true);
|
||||
setEmailVerifiedMessage(res.data.message || 'کد با موفقیت تایید شد');
|
||||
showToast({
|
||||
message: res.data.message || t('completion.codeVerified'),
|
||||
severity: 'success',
|
||||
});
|
||||
} else {
|
||||
// setError(res.data?.message || 'Invalid verification code');
|
||||
setShowVerifiedError(true);
|
||||
setEmailVerifiedMessage(res.data?.message || 'کد تایید نامعتبر است');
|
||||
showToast({
|
||||
message: res.data?.message || t('completion.invalidCode'),
|
||||
severity: 'error',
|
||||
});
|
||||
setEmailVerified(false);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.response?.data?.message || 'خطای ناشناخته');
|
||||
setShowVerifiedError(true);
|
||||
setEmailVerifiedMessage(err.response?.data?.message || 'خطای ناشناخته');
|
||||
} catch (error: unknown) {
|
||||
const err = error as AxiosError<{ message?: string }>;
|
||||
showToast({
|
||||
message: err.response?.data?.message || '',
|
||||
severity: 'error',
|
||||
});
|
||||
setEmailVerified(false);
|
||||
} finally {
|
||||
setIsVerifyingCode(false);
|
||||
@@ -205,17 +207,23 @@ export function UserCompletionForm() {
|
||||
country,
|
||||
});
|
||||
if (data.success) {
|
||||
setToastMessage(data.message || t('completion.submitSuccess'));
|
||||
setShowSuccessToast(true);
|
||||
showToast({
|
||||
message: data.message || t('completion.submitSuccess'),
|
||||
severity: 'success',
|
||||
});
|
||||
} else {
|
||||
setToastMessage(data.message || t('completion.submitError'));
|
||||
setShowErrorToast(true);
|
||||
showToast({
|
||||
message: data.message || t('completion.submitError'),
|
||||
severity: 'success',
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
setToastMessage(
|
||||
err.response?.data?.message || err.message || 'خطای ناشناخته',
|
||||
);
|
||||
setShowErrorToast(true);
|
||||
} catch (error: unknown) {
|
||||
const err = error as AxiosError<{ message?: string }>;
|
||||
showToast({
|
||||
message:
|
||||
err.response?.data?.message || err.message || t('completion.problem'),
|
||||
severity: 'error',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -289,37 +297,6 @@ export function UserCompletionForm() {
|
||||
showValidations={showPasswordValidations}
|
||||
/>
|
||||
|
||||
<Toast
|
||||
color="success"
|
||||
open={showVerifyCodeSectionSuccess}
|
||||
onClose={() => setShowVerifyCodeSectionSuccess(false)}
|
||||
>
|
||||
{sentVerificationCode}
|
||||
</Toast>
|
||||
|
||||
<Toast
|
||||
color="error"
|
||||
open={showVerifyCodeSectionError}
|
||||
onClose={() => setShowVerifyCodeSectionError(false)}
|
||||
>
|
||||
{sentVerificationCode}
|
||||
</Toast>
|
||||
|
||||
<Toast
|
||||
color="success"
|
||||
open={showVerifiedSuccess}
|
||||
onClose={() => setShowVerifiedSuccess(false)}
|
||||
>
|
||||
{emailVerifiedMessage}
|
||||
</Toast>
|
||||
|
||||
<Toast
|
||||
color="error"
|
||||
open={showVerifiedError}
|
||||
onClose={() => setShowVerifiedError(false)}
|
||||
>
|
||||
{emailVerifiedMessage}
|
||||
</Toast>
|
||||
<EmailSection
|
||||
showEmail={showEmail}
|
||||
setShowEmail={setShowEmail}
|
||||
@@ -337,21 +314,6 @@ export function UserCompletionForm() {
|
||||
isVerifyingCode={isVerifyingCode}
|
||||
handleEditEmail={handleEditEmail}
|
||||
/>
|
||||
<Toast
|
||||
color="success"
|
||||
open={showSuccessToast}
|
||||
onClose={() => setShowSuccessToast(false)}
|
||||
>
|
||||
{toastMessage}
|
||||
</Toast>
|
||||
<Toast
|
||||
color="error"
|
||||
open={showErrorToast}
|
||||
onClose={() => setShowErrorToast(false)}
|
||||
>
|
||||
{toastMessage}
|
||||
</Toast>
|
||||
|
||||
<SubmitSection
|
||||
onSubmit={handleSubmit}
|
||||
loading={loading}
|
||||
|
||||
Reference in New Issue
Block a user