fix: changing email and convert from dialog to normal textfields

This commit is contained in:
Koosha Lahouti
2025-09-29 18:57:39 +03:30
parent ac9a1a3a10
commit 0e2425fe3c
12 changed files with 456 additions and 353 deletions

6
package-lock.json generated
View File

@@ -16,7 +16,7 @@
"@mui/x-data-grid-premium": "^8.10.0",
"@mui/x-date-pickers": "^8.10.0",
"@rkheftan/harmony-ui": "^0.2.89",
"@rollup/rollup-win32-x64-msvc": "^4.46.3",
"@rollup/rollup-darwin-arm64": "^4.46.3",
"axios": "^1.11.0",
"date-fns": "^4.1.0",
"date-fns-jalali": "^4.0.0-0",
@@ -1962,9 +1962,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
@@ -2228,7 +2226,9 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]

View File

@@ -19,7 +19,7 @@
"@mui/x-data-grid-premium": "^8.10.0",
"@mui/x-date-pickers": "^8.10.0",
"@rkheftan/harmony-ui": "^0.2.89",
"@rollup/rollup-win32-x64-msvc": "^4.46.3",
"@rollup/rollup-darwin-arm64": "^4.46.3",
"axios": "^1.11.0",
"date-fns": "^4.1.0",
"date-fns-jalali": "^4.0.0-0",

View File

@@ -72,7 +72,9 @@
"errorConfirmCode": "Failed to confirm code",
"errorChangePhone": "Failed to change phone number",
"verificationCodeSent": "Verification code sent",
"onlyOneAccountAllowed": "You can only link one email account."
"onlyOneAccountAllowed": "You can only link one email account.",
"editEmailOrGoogle": "Change Email / Google",
"emailText": "Your new email will replace your previous email (<1>{{email}}</1>)"
},
"active": {
"activeDevices": "Active devices",
@@ -147,4 +149,4 @@
"description": "Encourage repeat purchases by sending discount codes and points to your customers."
}
}
}
}

View File

@@ -72,7 +72,9 @@
"errorConfirmCode": "تایید کد با خطا مواجه شد",
"errorChangePhone": "تغییر تلفن همراه با خطا مواجه شد",
"verificationCodeSent": "کد تایید ارسال شد",
"onlyOneAccountAllowed": "شما فقط می‌توانید یک حساب ایمیل را متصل کنید"
"onlyOneAccountAllowed": "شما فقط می‌توانید یک حساب ایمیل را متصل کنید",
"editEmailOrGoogle": "تغییر ایمیل / گوگل",
"emailText": "ایمیل جدید شما جایگزین ایمل قبلی ({{email}}) خواهد شد"
},
"active": {
"activeDevices": "نشست های فعال",
@@ -147,4 +149,4 @@
"description": "با ارسال کد تخفیف و امتیاز به مشتریان کسب و کارتان آنها را به خرید مجدد ترغیب کنید"
}
}
}
}

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { toLocaleDigits } from '@/utils/persianDigit';
@@ -14,27 +14,34 @@ export function CountDownTimer({
const { i18n } = useTranslation();
const [secondsLeft, setSecondsLeft] = useState(initialSeconds);
const onCompleteRef = useRef(onComplete);
useEffect(() => {
onCompleteRef.current = onComplete;
}, [onComplete]);
useEffect(() => {
setSecondsLeft(initialSeconds);
}, [initialSeconds]);
const timer = setInterval(() => {
useEffect(() => {
if (secondsLeft <= 0) return;
const id = setInterval(() => {
setSecondsLeft((prev) => {
if (prev <= 1) {
clearInterval(timer);
onComplete?.();
clearInterval(id);
onCompleteRef.current?.();
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(timer);
}, [initialSeconds, onComplete]);
return () => clearInterval(id);
}, [secondsLeft]);
const formatTime = (totalSeconds: number) => {
const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0');
const seconds = String(totalSeconds % 60).padStart(2, '0');
return toLocaleDigits(`${minutes}:${seconds}`, i18n.language);
const m = String(Math.floor(totalSeconds / 60)).padStart(2, '0');
const s = String(totalSeconds % 60).padStart(2, '0');
return toLocaleDigits(`${m}:${s}`, i18n.language);
};
return <span>{formatTime(secondsLeft)}</span>;

View File

@@ -92,7 +92,7 @@ export function PasswordDialog({
fullWidth
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }}
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 1 }, mt: 2 }}
InputProps={{
endAdornment: (
<InputAdornment position="end">
@@ -121,7 +121,7 @@ export function PasswordDialog({
fullWidth
value={password}
onChange={(e) => setPassword(e.target.value)}
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 }, mt: 2 }}
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 1 }, mt: 2 }}
InputProps={{
endAdornment: (
<InputAdornment position="end">
@@ -169,7 +169,7 @@ export function PasswordDialog({
? t('securityForm.notCompatibility')
: ' '
}
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }}
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 1 } }}
InputProps={{
endAdornment: (
<InputAdornment position="end">

View File

@@ -1,13 +1,13 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { CardContainer } from '@/components/CardContainer';
import { Box, CircularProgress, Button } from '@mui/material';
import { PageWrapper } from '../PageWrapper';
import { CardContainer } from '@/components/CardContainer';
import SocialMediaList from './socialMedia/SocialMediaList';
import SocialMediaMenu from './socialMedia/SocialMediaMenu';
import SocialMediaDialog from './socialMedia/SocialMediaDialog';
import useMediaQuery from '@mui/material/useMediaQuery';
import type { Theme } from '@mui/material/styles';
import { Box, CircularProgress } from '@mui/material';
import SocialEditForm from './socialMedia/SocialEditForm';
import SocialMediaMenu, {
type SocialChoice,
} from './socialMedia/SocialMediaMenu';
import { useApi } from '@/hooks/useApi';
import {
sendEmailCode,
@@ -20,51 +20,46 @@ import { useProfile } from '../../hooks/useProfile';
export function SocialMedia() {
const { t } = useTranslation('setting');
const toast = useToast();
const [openDialog, setOpenDialog] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [emailInput, setEmailInput] = useState('');
const [verificationCode, setVerificationCode] = useState('');
const [dialogStep, setDialogStep] = useState<'enterEmail' | 'enterCode'>(
'enterEmail',
const [email, setEmail] = useState<EmailAccount[]>([]);
const [emailError, setEmailError] = useState<string | undefined>();
const [verificationCodeError, setVerificationCodeError] = useState<
string | undefined
>();
const [isCodeSent, setIsCodeSent] = useState(false);
const [buttonState, setButtonState] = useState<'default' | 'counting'>(
'default',
);
const [emailList, setEmailList] = useState<EmailAccount[]>([]);
const [formError, setFormError] = useState<string | null>(null);
const toast = useToast();
const [isVerified, setIsVerified] = useState(false);
const { isLoadingProfile, refetchProfile } = useProfile();
const { loading: isSendingCode, execute: executeSendCode } =
useApi(sendEmailCode);
const { loading: isConfirming, execute: executeConfirmCode } =
useApi(confirmEmailCode);
const { loading: isChangingEmail, execute: executeChangeEmail } =
useApi(changeEmail);
const fullScreen = useMediaQuery((theme: Theme) =>
theme?.breakpoints.down('sm'),
const isBusy = useMemo(
() => isSendingCode || isConfirming || isChangingEmail,
[isSendingCode, isConfirming, isChangingEmail],
);
const downMd = useMediaQuery((theme: Theme) => theme?.breakpoints.down('md'));
const computedMaxWidth = (fullScreen ? 'xs' : downMd ? 'sm' : 'md') as
| 'xs'
| 'sm'
| 'md'
| 'lg'
| 'xl';
const isSubmitting = isConfirming || isChangingEmail;
useEffect(() => {
const loadProfile = async () => {
const profileData = await refetchProfile();
if (!profileData) return;
if (profileData?.success) {
const { email } = profileData;
if (!email) return;
setEmailList([
setEmail([
{
email,
provider: email.includes('@gmail.') ? 'google' : 'email',
@@ -72,33 +67,48 @@ export function SocialMedia() {
},
]);
} else {
toast({
message: profileData.message,
severity: 'error',
});
toast({ message: profileData.message, severity: 'error' });
}
};
void loadProfile();
}, [refetchProfile, toast]);
loadProfile();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const resetDialog = () => {
setOpenDialog(false);
const resetState = () => {
setEmailInput('');
setVerificationCode('');
setFormError(null);
setDialogStep('enterEmail');
setEmailError(undefined);
setVerificationCodeError(undefined);
setIsCodeSent(false);
setButtonState('default');
setIsVerified(false);
};
const startEmailEdit = () => {
resetState();
setIsEditing(true);
};
const buttonLabel = email.length
? t('settingForm.editEmailOrGoogle')
: t('settingForm.addEmailOrSocialButton');
const validateEmail = (value: string) => {
if (!value) {
setEmailError(t('settingForm.thisFieldIsRequired'));
return false;
}
const ok = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value);
if (!ok) {
setEmailError(t('settingForm.emailIsInvalid'));
return false;
}
setEmailError(undefined);
return true;
};
const handleSendCode = async () => {
if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(emailInput)) {
setFormError(t('settingForm.emailIsInvalid'));
return;
}
setFormError(null);
if (!validateEmail(emailInput)) return;
const sendCodeData = await executeSendCode({ email: emailInput });
if (!sendCodeData) return;
if (sendCodeData.success) {
@@ -106,28 +116,27 @@ export function SocialMedia() {
message: t('settingForm.verificationCodeSent'),
severity: 'success',
});
setDialogStep('enterCode');
setTimeout(
() => {
resetDialog();
},
3 * 60 * 1000,
);
setIsCodeSent(true);
setButtonState('counting');
} else {
toast({
message: sendCodeData.message || t('settingForm.error'),
severity: 'error',
});
}
};
const handleConfirmAndChangeEmail = async () => {
if (verificationCode.length < 4) {
setFormError(t('settingForm.verificationCodeRequired'));
const handleVerifyClick = async () => {
if (!verificationCode || verificationCode.length < 4) {
setVerificationCodeError(t('settingForm.verificationCodeRequired'));
return;
}
setFormError(null);
setVerificationCodeError(undefined);
const confirmData = await executeConfirmCode({
email: emailInput,
verifyCode: verificationCode,
});
if (!confirmData) return;
if (!confirmData.success || !confirmData.confirm) {
@@ -138,33 +147,53 @@ export function SocialMedia() {
return;
}
if (emailList.length >= 1) {
toast({
message: t('settingForm.onlyOneAccountAllowed'),
severity: 'error',
});
return;
}
const changeEmailData = await executeChangeEmail({ email: emailInput });
if (!changeEmailData) return;
if (changeEmailData.success) {
setEmailList([
setEmail([
{
email: emailInput,
provider: emailInput.includes('@gmail.') ? 'google' : 'email',
time: t('settingForm.justNow'),
},
]);
setIsVerified(true);
toast({
message: t('settingForm.emailChangedSuccessfully'),
severity: 'success',
});
resetDialog();
setIsEditing(false);
refetchProfile({ force: true });
} else {
toast({
message: changeEmailData.message || t('settingForm.error'),
severity: 'error',
});
}
};
const handleBlur = (field: 'email' | 'verificationCode') => {
if (field === 'email') validateEmail(emailInput);
if (field === 'verificationCode') {
if (!verificationCode || verificationCode.length < 4) {
setVerificationCodeError(t('settingForm.verificationCodeRequired'));
} else {
setVerificationCodeError(undefined);
}
}
};
const onMenuSelect = (choice: SocialChoice) => {
if (choice === 'email') {
startEmailEdit();
return;
}
if (choice === 'google') {
toast({
message: t('settingForm.googleLinkComingSoon'),
severity: 'info',
});
}
};
@@ -173,8 +202,51 @@ export function SocialMedia() {
<CardContainer
title={t('settingForm.titleSocial')}
subtitle={t('settingForm.descriptionSocial')}
highlighted={isEditing}
action={
<SocialMediaMenu t={t} onOpenDialog={() => setOpenDialog(true)} />
isEditing ? (
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
type="button"
variant="text"
onClick={() => setIsEditing(false)}
size="large"
sx={{
color: 'primary.main',
textTransform: 'none',
width: { xs: '100%', sm: 'auto' },
}}
>
{t('settingForm.rejectButton')}
</Button>
<Button
type="submit"
form="emailForm"
size="large"
variant="outlined"
sx={{
borderRadius: 1,
bgcolor: 'primary.main',
color: 'primary.contrastText',
whiteSpace: 'nowrap',
textTransform: 'none',
}}
disabled={isSubmitting}
>
{isSubmitting ? (
<CircularProgress size={24} color="inherit" />
) : (
t('settingForm.saveButton')
)}
</Button>
</Box>
) : (
<SocialMediaMenu
t={t}
onSelect={onMenuSelect}
buttonLabel={buttonLabel}
/>
)
}
>
{isLoadingProfile ? (
@@ -184,30 +256,45 @@ export function SocialMedia() {
justifyContent: 'center',
alignItems: 'center',
p: 4,
minHeight: '100px',
minHeight: '150px',
}}
>
<CircularProgress />
</Box>
) : isEditing ? (
<Box
component="form"
id="emailForm"
noValidate
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
if (!isBusy) void handleVerifyClick();
}}
>
<SocialEditForm
email={emailInput}
setEmail={setEmailInput}
emails={email.map((e) => ({ address: e.email }))}
verificationCode={verificationCode}
setVerificationCode={setVerificationCode}
isVerified={isVerified}
isVerifying={isBusy}
isSendingCode={isSendingCode}
isCodeSent={isCodeSent}
setIsCodeSent={setIsCodeSent}
buttonState={buttonState}
setButtonState={setButtonState}
handleSendCode={handleSendCode}
handleVerifyClick={handleVerifyClick}
handleBlur={handleBlur}
emailError={emailError}
verificationCodeError={verificationCodeError}
/>
</Box>
) : (
<SocialMediaList t={t} emailList={emailList} />
<SocialMediaList emailList={email} />
)}
<SocialMediaDialog
open={openDialog}
onClose={resetDialog}
t={t}
emailInput={emailInput}
setEmailInput={setEmailInput}
verificationCode={verificationCode}
setVerificationCode={setVerificationCode}
apiError={formError}
isLoading={isSendingCode || isConfirming || isChangingEmail}
dialogStep={dialogStep}
onSendCode={handleSendCode}
onConfirmEmail={handleConfirmAndChangeEmail}
fullScreen={fullScreen}
computedMaxWidth={computedMaxWidth}
/>
</CardContainer>
</PageWrapper>
);

View File

@@ -0,0 +1,199 @@
import {
Box,
Typography,
TextField,
Button,
IconButton,
InputAdornment,
CircularProgress,
} from '@mui/material';
import { Edit2, TickCircle } from 'iconsax-react';
import { CountDownTimer } from '@/components/CountDownTimer';
import { Icon } from '@rkheftan/harmony-ui';
import { useRef } from 'react';
import { useTranslation } from 'react-i18next';
type SocialEditFormProps = {
email: string;
setEmail: (v: string) => void;
emails: { address: string }[];
verificationCode: string;
setVerificationCode: (v: string) => void;
isVerified: boolean;
isVerifying: boolean;
isSendingCode: boolean;
isCodeSent: boolean;
setIsCodeSent: (v: boolean) => void;
buttonState: 'default' | 'counting';
setButtonState: (v: 'default' | 'counting') => void;
handleSendCode: () => void;
handleVerifyClick: () => void;
handleBlur: (field: 'email' | 'verificationCode') => void;
emailError?: string;
verificationCodeError?: string;
};
export default function SocialEditForm({
email,
setEmail,
emails,
verificationCode,
setVerificationCode,
isVerified,
isVerifying,
isSendingCode,
isCodeSent,
setIsCodeSent,
buttonState,
setButtonState,
handleSendCode,
handleVerifyClick,
handleBlur,
emailError,
verificationCodeError,
}: SocialEditFormProps) {
const { t } = useTranslation('setting');
const textFieldRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
return (
<Box sx={{ px: { sm: 4, xs: 2 }, my: 4 }}>
<Box sx={{ mb: 4 }}>
<Typography variant="h6">
{t('settingForm.editEmailOrGoogle')}
</Typography>
<Typography variant="body2" color="text.secondary">
{t('settingForm.emailText', {
email: emails.map((e) => e.address).join(', '),
})}
</Typography>
</Box>
<Box
sx={{
display: 'flex',
gap: 2,
alignItems: 'center',
pb: 2,
flexDirection: { xs: 'column', sm: 'row' },
}}
>
<TextField
fullWidth
name="email"
label={t('settingForm.newEmail')}
type="email"
value={email}
ref={textFieldRef}
inputRef={inputRef}
onBlur={() => handleBlur('email')}
error={!!emailError}
helperText={emailError}
onChange={(e) => setEmail(e.target.value)}
placeholder="abc@email.com"
autoComplete="email"
inputMode="email"
disabled={isCodeSent}
slotProps={{
input: {
endAdornment: isCodeSent ? (
<InputAdornment position="end">
<IconButton
size="small"
onClick={() => {
setButtonState('default');
setVerificationCode('');
setIsCodeSent(false);
}}
edge="end"
>
<Icon
Component={Edit2}
color="primary.main"
variant="Bold"
/>
</IconButton>
</InputAdornment>
) : undefined,
},
}}
/>
{isVerified ? (
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
color: 'success.main',
}}
>
<Icon Component={TickCircle} />
<Typography>{t('settingForm.successButton')}</Typography>
</Box>
) : (
<Button
variant="text"
loading={isSendingCode}
onClick={handleSendCode}
sx={{ color: 'primary.main', width: { xs: '100%', sm: 208 } }}
disabled={buttonState === 'counting'}
>
{buttonState === 'counting' ? (
<CountDownTimer
initialSeconds={60}
onComplete={() => setButtonState('default')}
/>
) : (
t('settingForm.verificationCodeButton')
)}
</Button>
)}
</Box>
{isCodeSent && !isSendingCode && !isVerified && (
<Box
sx={{
display: 'flex',
gap: 2,
alignItems: 'center',
flexDirection: { xs: 'column', sm: 'row' },
mb: 2,
pt: 2,
}}
>
<TextField
fullWidth
name="verificationCode"
label={t('settingForm.verificationCode')}
type="tel"
value={verificationCode}
error={!!verificationCodeError}
helperText={verificationCodeError}
onBlur={() => handleBlur('verificationCode')}
onChange={(e) =>
setVerificationCode(e.target.value.replace(/\D/g, ''))
}
placeholder={t('settingForm.verificationCode')}
autoComplete="one-time-code"
inputMode="numeric"
/>
<Button
variant="contained"
onClick={handleVerifyClick}
disabled={isVerifying || verificationCode.length === 0}
sx={{ bgcolor: 'primary.main', width: { xs: '100%', sm: 200 } }}
>
{isVerifying ? (
<CircularProgress size={20} />
) : (
t('settingForm.checkCode')
)}
</Button>
</Box>
)}
</Box>
);
}

View File

@@ -1,172 +0,0 @@
import React, { type ReactElement, type ElementType, useState } from 'react';
import {
Box,
Button,
CircularProgress,
Dialog,
DialogContent,
DialogTitle,
IconButton,
TextField,
Typography,
} from '@mui/material';
import Slide from '@mui/material/Slide';
import type { TransitionProps } from '@mui/material/transitions';
import { CloseCircle } from 'iconsax-react';
import { Icon } from '@rkheftan/harmony-ui';
import { type SocialMediaDialogProps } from '@/features/profile/types/settingsType';
import { isEmail } from '@/utils/regexes/isEmail';
const MobileSlide = React.forwardRef(function MobileSlide(
props: TransitionProps & { children: ReactElement<unknown, ElementType> },
ref: React.Ref<unknown>,
) {
return <Slide direction="up" ref={ref} {...props} />;
});
export default function SocialMediaDialog({
open,
onClose,
t,
emailInput,
setEmailInput,
fullScreen,
verificationCode,
setVerificationCode,
isLoading,
dialogStep,
onSendCode,
onConfirmEmail,
}: SocialMediaDialogProps) {
const [emailError, setEmailError] = useState<string | undefined>();
const [touched, setTouched] = useState(false);
const validateEmail = (value: string) => {
if (!value) {
setEmailError(t('settingForm.thisFieldIsRequired'));
return false;
}
if (!isEmail(value)) {
setEmailError(t('settingForm.emailIsInvalid'));
return false;
}
setEmailError(undefined);
return true;
};
const handleSubmit: React.FormEventHandler<HTMLFormElement> = (e) => {
e.preventDefault();
e.stopPropagation();
if (isLoading) return;
if (dialogStep === 'enterEmail') {
setTouched(true);
if (!validateEmail(emailInput)) return;
onSendCode();
} else {
onConfirmEmail();
}
};
return (
<Dialog
open={open}
onClose={onClose}
fullWidth
fullScreen={fullScreen}
maxWidth="xs"
scroll="body"
keepMounted
TransitionComponent={fullScreen ? MobileSlide : undefined}
PaperProps={{ sx: { mx: 1 } }}
>
<DialogTitle sx={{ p: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<IconButton onClick={onClose} disabled={isLoading}>
<Icon Component={CloseCircle} size="large" color="primary.main" />
</IconButton>
<Box component="span" fontWeight="bold" fontSize={18}>
{t('settingForm.addEmailButton')}
</Box>
</Box>
</DialogTitle>
<Box component="form" onSubmit={handleSubmit}>
<DialogContent
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
px: { xs: 2, sm: 3 },
}}
>
<Box>
<Typography fontWeight="bold">
{t('settingForm.newEmail')}
</Typography>
<Typography variant="body2" color="text.secondary">
{t('settingForm.dialogHeader')}
</Typography>
</Box>
<TextField
fullWidth
type="email"
value={emailInput}
onChange={(e) => {
setEmailInput(e.target.value);
if (touched) validateEmail(e.target.value);
}}
onBlur={() => {
setTouched(true);
validateEmail(emailInput);
}}
label={t('settingForm.email')}
placeholder="abc@email.com"
autoComplete="email"
inputMode="email"
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 1 }, mt: 1 }}
autoFocus={dialogStep === 'enterEmail'}
disabled={isLoading || dialogStep === 'enterCode'}
error={touched && !!emailError}
helperText={touched && emailError ? emailError : ' '}
/>
{dialogStep === 'enterCode' && (
<TextField
fullWidth
type="text"
value={verificationCode}
onChange={(e) => setVerificationCode(e.target.value)}
label={t('settingForm.verificationCode')}
autoComplete="one-time-code"
inputMode="numeric"
sx={{ '& .MuiOutlinedInput-root': { borderRadius: 1 } }}
autoFocus
disabled={isLoading}
/>
)}
</DialogContent>
<Box sx={{ px: 3, pb: 2 }}>
<Button
fullWidth
sx={{ height: 48, textTransform: 'none', borderRadius: 1 }}
variant="contained"
type="submit"
disabled={isLoading || (dialogStep === 'enterEmail' && !emailInput)}
>
{isLoading ? (
<CircularProgress size={24} color="inherit" />
) : dialogStep === 'enterEmail' ? (
t('settingForm.verificationCodeButton')
) : (
t('settingForm.confirmAndSave')
)}
</Button>
</Box>
</Box>
</Dialog>
);
}

View File

@@ -19,12 +19,7 @@ export default function SocialMediaList({ emailList }: SocialMediaListProps) {
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
minWidth: 0,
}}
sx={{ display: 'flex', alignItems: 'center', gap: 1, minWidth: 0 }}
>
<Box
sx={{
@@ -37,15 +32,14 @@ export default function SocialMediaList({ emailList }: SocialMediaListProps) {
borderRadius: 0.5,
}}
>
{item.provider === 'google' && (
{item.provider === 'google' ? (
<Icon
Component={Google}
size="medium"
color="primary.main"
variant="Bold"
/>
)}
{item.provider === 'email' && (
) : (
<Icon
Component={Sms}
size="medium"

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import {
Box,
Button,
@@ -7,84 +7,62 @@ import {
ListItemIcon,
ListItemText,
} from '@mui/material';
import { Message, Google, ArrowDown3 } from 'iconsax-react';
import { Message, Google, ArrowDown2 } from 'iconsax-react';
import { Icon } from '@rkheftan/harmony-ui';
import { type SocialMediaMenuProps } from '@/features/profile/types/settingsType';
export default function SocialMediaMenu({
t,
onOpenDialog,
}: SocialMediaMenuProps) {
export type SocialChoice = 'email' | 'google';
type Props = {
t: (key: string) => string;
onSelect: (choice: SocialChoice) => void;
buttonLabel: string;
};
export default function SocialMediaMenu({ t, onSelect, buttonLabel }: Props) {
const [anchor, setAnchor] = useState<null | HTMLElement>(null);
const openMenu = Boolean(anchor);
const [open, setOpen] = useState(false);
const handleClickMenu = (e: React.MouseEvent<HTMLButtonElement>) => {
setOpen(true);
setAnchor(e.currentTarget);
};
const handleCloseMenu = () => {
setOpen(false);
setAnchor(null);
};
return (
<Box
sx={{
display: 'flex',
justifyContent: 'flex-start',
gap: 1,
}}
>
<Box
<Box sx={{ display: 'flex', justifyContent: 'flex-start', gap: 1 }}>
<Button
onClick={(e) => setAnchor(e.currentTarget)}
variant="outlined"
sx={{
border: '1px solid',
borderRadius: 1,
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
alignItems: { xs: 'stretch', sm: 'flex-start' },
justifyContent: 'space-between',
alignItems: 'center',
whiteSpace: 'nowrap',
textTransform: 'none',
gap: 1,
}}
aria-haspopup="menu"
aria-expanded={openMenu ? 'true' : undefined}
aria-controls={openMenu ? 'social-menu' : undefined}
>
<Button
onClick={handleClickMenu}
variant="outlined"
sx={{
border: '1px solid',
borderRadius: 1,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
whiteSpace: 'nowrap',
backgroundColor: open ? 'primary.light' : 'primary.default',
textTransform: 'none',
}}
>
<Box component="span">{t('settingForm.addEmailOrSocialButton')}</Box>
<Icon
Component={ArrowDown3}
size="small"
color="primary.main"
variant="Outline"
/>
</Button>
</Box>
<Box component="span">{buttonLabel}</Box>
<Icon
Component={ArrowDown2}
size="small"
color="primary.main"
variant="Bold"
/>
</Button>
<Menu
id="social-menu"
anchorEl={anchor}
open={openMenu}
onClose={handleCloseMenu}
PaperProps={{
sx: {
width: anchor ? anchor.offsetWidth : 'auto',
// maxWidth: '90vw',
},
}}
onClose={() => setAnchor(null)}
PaperProps={{ sx: { width: anchor ? anchor.offsetWidth : 'auto' } }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
>
<MenuItem
onClick={() => {
handleCloseMenu();
onOpenDialog();
setAnchor(null);
onSelect('email');
}}
>
<ListItemIcon>
@@ -92,7 +70,13 @@ export default function SocialMediaMenu({
</ListItemIcon>
<ListItemText>{t('settingForm.email')}</ListItemText>
</MenuItem>
<MenuItem>
<MenuItem
onClick={() => {
setAnchor(null);
onSelect('google');
}}
>
<ListItemIcon>
<Icon Component={Google} size="medium" color="primary.main" />
</ListItemIcon>

View File

@@ -69,7 +69,7 @@ export interface SocialMediaDialogProps {
}
export interface SocialMediaListProps {
t: (key: string) => string;
// t: (key: string) => string;
emailList: readonly {
email: string;
provider: 'email' | 'google' | 'apple';