fix: api calls

This commit is contained in:
Koosha Lahouti
2025-08-09 14:08:02 -07:00
parent fb9d691f6a
commit ca0680c78f
7 changed files with 398 additions and 53 deletions

277
package-lock.json generated
View File

@@ -14,7 +14,7 @@
"@mui/material": "^7.2.0", "@mui/material": "^7.2.0",
"@mui/stylis-plugin-rtl": "^7.2.0", "@mui/stylis-plugin-rtl": "^7.2.0",
"@rkheftan/harmony-ui": "^0.1.5", "@rkheftan/harmony-ui": "^0.1.5",
"@mui/x-date-pickers": "^8.9.0", "@mui/x-date-pickers": "^8.10.0",
"@mui/x-date-pickers-pro": "^8.9.0", "@mui/x-date-pickers-pro": "^8.9.0",
"axios": "^1.11.0", "axios": "^1.11.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
@@ -1452,6 +1452,281 @@
} }
} }
}, },
"node_modules/@mui/x-date-pickers": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.10.0.tgz",
"integrity": "sha512-3nY+SS2/JtqcptQodECIyWKsTvPBDAcXKkyW65R4rQUCrnV6tuzriSrzy/FEYqTK0hyXYPIGJhQ6A0FbtQ9AkQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.2",
"@mui/utils": "^7.2.0",
"@mui/x-internals": "8.10.0",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
"date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0",
"dayjs": "^1.10.7",
"luxon": "^3.0.2",
"moment": "^2.29.4",
"moment-hijri": "^2.1.2 || ^3.0.0",
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"date-fns": {
"optional": true
},
"date-fns-jalali": {
"optional": true
},
"dayjs": {
"optional": true
},
"luxon": {
"optional": true
},
"moment": {
"optional": true
},
"moment-hijri": {
"optional": true
},
"moment-jalaali": {
"optional": true
}
}
},
"node_modules/@mui/x-date-pickers-pro": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers-pro/-/x-date-pickers-pro-8.9.0.tgz",
"integrity": "sha512-Penske7PIdAgFUjD6J15O3sJqkAju6htogQNuEG4Jp1Fk1rQrqF6ZCG9jHDVeyf45ZeEt/1rDUEgyGKPtjyFAA==",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@babel/runtime": "^7.27.6",
"@mui/utils": "^7.2.0",
"@mui/x-date-pickers": "8.9.0",
"@mui/x-internals": "8.8.0",
"@mui/x-license": "8.9.0",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
"date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0",
"dayjs": "^1.10.7",
"luxon": "^3.0.2",
"moment": "^2.29.4",
"moment-hijri": "^2.1.2 || ^3.0.0",
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"date-fns": {
"optional": true
},
"date-fns-jalali": {
"optional": true
},
"dayjs": {
"optional": true
},
"luxon": {
"optional": true
},
"moment": {
"optional": true
},
"moment-hijri": {
"optional": true
},
"moment-jalaali": {
"optional": true
}
}
},
"node_modules/@mui/x-date-pickers-pro/node_modules/@mui/x-date-pickers": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.9.0.tgz",
"integrity": "sha512-MD2/F63Tdsodygp3Z2VtfvvQhAiEVXvleuK9mqXuD6a1cCPOENICCJC98y2AKbOcsbVd37o6HCvWFOQsfsy7TQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.27.6",
"@mui/utils": "^7.2.0",
"@mui/x-internals": "8.8.0",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
"date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
"date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0",
"dayjs": "^1.10.7",
"luxon": "^3.0.2",
"moment": "^2.29.4",
"moment-hijri": "^2.1.2 || ^3.0.0",
"moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"date-fns": {
"optional": true
},
"date-fns-jalali": {
"optional": true
},
"dayjs": {
"optional": true
},
"luxon": {
"optional": true
},
"moment": {
"optional": true
},
"moment-hijri": {
"optional": true
},
"moment-jalaali": {
"optional": true
}
}
},
"node_modules/@mui/x-date-pickers/node_modules/@mui/x-internals": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.10.0.tgz",
"integrity": "sha512-stYhWBeCKfV2/ltAWShZ3ZJ51otbqpMpC+krWWoIsxM8TuvGzwXw5YMU9L2fTb8hRstsiOCQfEzIn12Ii7+N0Q==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.2",
"@mui/utils": "^7.2.0",
"reselect": "^5.1.1",
"use-sync-external-store": "^1.5.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@mui/x-internals": {
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.8.0.tgz",
"integrity": "sha512-qTRK5oINkAjZ7sIHpSnESLNq1xtQUmmfmGscYUSEP0uHoYh6pKkNWH9+7yzggRHuTv+4011VBwN9s+efrk+xZg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.27.6",
"@mui/utils": "^7.2.0",
"reselect": "^5.1.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@mui/x-license": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/@mui/x-license/-/x-license-8.9.0.tgz",
"integrity": "sha512-mtCWT1l9rNTvs3N2NYd2liRN4IrbgB/E1QkF3BW3dL1rgBAOMamaCoQPAtK+L8UEPvB+zie3rs77/LAbdyS7oA==",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@babel/runtime": "^7.27.6",
"@mui/utils": "^7.2.0",
"@mui/x-internals": "8.8.0",
"@mui/x-telemetry": "8.5.3"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@mui/x-telemetry": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@mui/x-telemetry/-/x-telemetry-8.5.3.tgz",
"integrity": "sha512-vBLVBXCBWY44HonjRefpYjowEXa25k2AtAXkWk2tHfL3/unnnexrYPosuo/p4giIWer0pMy/bPqGY2qM0xlM+g==",
"hasInstallScript": true,
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@babel/runtime": "^7.27.6",
"@fingerprintjs/fingerprintjs": "^3.4.2",
"ci-info": "^4.2.0",
"conf": "^11.0.2",
"is-docker": "^3.0.0",
"node-machine-id": "^1.1.12"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",

View File

@@ -28,6 +28,10 @@
"agreementPart2": ".", "agreementPart2": ".",
"sent": "sent", "sent": "sent",
"country": "country", "country": "country",
"dateOfBirth": "Date of birth(optional)" "dateOfBirth": "Date of birth(optional)",
"submitSuccess": "Information successfully registered",
"submitError": "Error in registering information",
"submitting": "Submitting...",
"success": "Success"
} }
} }

View File

@@ -31,6 +31,10 @@
"dateOfBirth": "تاریخ تولد(اختیاری)", "dateOfBirth": "تاریخ تولد(اختیاری)",
"invalidCountry": "کشور انتخاب شده صحیح نیست", "invalidCountry": "کشور انتخاب شده صحیح نیست",
"rules": "قوانین و مقررات", "rules": "قوانین و مقررات",
"alertSuccess": "ایمیل با موفقیت تایید شد" "alertSuccess": "ایمیل با موفقیت تایید شد",
"submitSuccess": "اطلاعات با موفقیت ثبت شد",
"submitError": "خطا در ثبت اطلاعات",
"submitting": "در حال تایید...",
"success": "موفقیت"
} }
} }

View File

@@ -163,6 +163,7 @@ export function EmailSection({
{!isVerifyingCode && !emailVerified && ( {!isVerifyingCode && !emailVerified && (
<Button <Button
type="button"
variant="text" variant="text"
onClick={onSendCodeClick} onClick={onSendCodeClick}
sx={{ sx={{

View File

@@ -74,15 +74,6 @@ export function SubmitSection({ onSubmit, loading, error, success }: Props) {
{t('completion.agreementPart2')} {t('completion.agreementPart2')}
</Typography> </Typography>
{/* <Button
variant="contained"
sx={{
width: { xs: '100%', sm: '247px' },
alignSelf: { xs: 'stretch', sm: 'center' },
}}
>
{t('completion.registerButton')}
</Button> */}
<Button <Button
variant="contained" variant="contained"
onClick={onSubmit} onClick={onSubmit}
@@ -95,7 +86,7 @@ export function SubmitSection({ onSubmit, loading, error, success }: Props) {
{loading {loading
? t('completion.submitting') ? t('completion.submitting')
: success : success
? t('completion.success') ? t('completion.registerButton')
: t('completion.registerButton')} : t('completion.registerButton')}
</Button> </Button>
{error && <Typography color="error">{error}</Typography>} {error && <Typography color="error">{error}</Typography>}

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Box, Typography, Button } from '@mui/material'; import { Box, Typography } from '@mui/material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Logo from '@/components/Logo'; import Logo from '@/components/Logo';
import { PersonalInfoFields } from './PersonalInfoFields'; import { PersonalInfoFields } from './PersonalInfoFields';
@@ -7,7 +7,7 @@ import { PasswordSection } from './PasswordSection';
import { EmailSection } from './EmailSection'; import { EmailSection } from './EmailSection';
import { SubmitSection } from './SubmitSection'; import { SubmitSection } from './SubmitSection';
import apiClient from '@/lib/apiClient'; import apiClient from '@/lib/apiClient';
import { sendEmailOtp, fetchAuthToken } from '@/lib/authToken'; import { Toast } from '@/components/Toast';
export function UserCompletionForm() { export function UserCompletionForm() {
const { t } = useTranslation('completionForm'); const { t } = useTranslation('completionForm');
@@ -48,6 +48,15 @@ export function UserCompletionForm() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false); 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('');
useEffect(() => { useEffect(() => {
if (password) { if (password) {
@@ -65,18 +74,16 @@ export function UserCompletionForm() {
useEffect(() => { useEffect(() => {
let timer: NodeJS.Timeout; let timer: NodeJS.Timeout;
if (buttonState === 'counting' && countdown > 0) { if (buttonState === 'counting' && countdown > 0) {
timer = setInterval( timer = setInterval(() => {
() => setCountdown((prev) => {
setCountdown((prev) => { if (prev <= 1) {
if (prev <= 1) { setButtonState('default');
setButtonState('default'); clearInterval(timer);
clearInterval(timer); return 0;
return 0; }
} return prev - 1;
return prev - 1; });
}), }, 1000);
1000,
);
} }
return () => clearInterval(timer); return () => clearInterval(timer);
}, [buttonState, countdown]); }, [buttonState, countdown]);
@@ -97,29 +104,67 @@ export function UserCompletionForm() {
setError(null); setError(null);
setLoading(true); setLoading(true);
setSuccess(false); setSuccess(false);
try { try {
await sendEmailOtp(EMAIL); const response = await apiClient.post('/User/SendEmailOtp', { email });
setSuccess(true); if (response.data?.success) {
} catch { setSentVerificationCode(response.data.message);
setError('Failed to send OTP'); setShowVerifyCodeSectionSuccess(true);
setCodeSent(true);
setButtonState('counting');
setCountdown(120);
setSuccess(true);
setSentVerificationCode(
response.data.message || 'کد با موفقیت ارسال شد',
);
} else {
setShowVerifyCodeSectionError(true);
setSentVerificationCode(response.data.message || 'مشکلی پیش آمده');
// setError(
// response.data?.message || t('completion.verificationCodeError'),
// );
}
} catch (err: any) {
setShowVerifyCodeSectionError(true);
setSentVerificationCode(err.response?.data?.message || 'مشکلی پیش آمده');
// setError(
// err.response?.data?.message || t('completion.verificationCodeError'),
// );
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
const handleVerifyCode = async () => { const handleVerifyCode = async () => {
if (!verificationCode) return; if (!verificationCode.trim()) {
setError('Please enter the verification code');
return;
}
setIsVerifyingCode(true); setIsVerifyingCode(true);
setError(null); setError(null);
try { try {
const tokenRes = await fetchAuthToken(email, verificationCode); const res = await apiClient.post('/User/ConfirmEmailOtp', {
localStorage.setItem('authToken', tokenRes.access_token); email,
apiClient.defaults.headers.common['Authorization'] = otp: verificationCode,
`Bearer ${tokenRes.access_token}`; });
setEmailVerified(true);
} catch { if (res.data?.success) {
setError('Invalid verification code'); setEmailVerified(true);
setSuccess(true);
if (res.data.access_token) {
localStorage.setItem('authToken', res.data.access_token);
apiClient.defaults.headers.common['Authorization'] =
`Bearer ${res.data.access_token}`;
}
} else {
setError(res.data?.message || 'Invalid verification code');
}
} catch (err: any) {
setError(
err.response?.data?.message ||
'Something went wrong while verifying the code',
);
} finally { } finally {
setIsVerifyingCode(false); setIsVerifyingCode(false);
} }
@@ -143,6 +188,7 @@ export function UserCompletionForm() {
message: string; message: string;
validations: { property: string; message: string }[]; validations: { property: string; message: string }[];
}>('/User/CompleteUserInformation', { }>('/User/CompleteUserInformation', {
userId: '9cb8ae51-95f9-4f66-99b4-1c7c4d48251b',
firstName, firstName,
lastName, lastName,
gender: sex === 'female' ? 2 : 1, gender: sex === 'female' ? 2 : 1,
@@ -155,21 +201,22 @@ export function UserCompletionForm() {
country, country,
}); });
if (data.success) { if (data.success) {
setSuccess(true); setToastMessage(data.message || t('completion.submitSuccess'));
setShowSuccessToast(true);
} else { } else {
setError(data.message || 'Validation error'); setToastMessage(data.message || t('completion.submitError'));
setShowErrorToast(true);
} }
} catch (err: any) { } catch (err: any) {
setError( setToastMessage(
err.response?.data?.message || err.message || 'An error occurred', err.response?.data?.message || err.message || 'خطای ناشناخته',
); );
setShowErrorToast(true);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
const EMAIL = 'klahouti81@gmail.com';
return ( return (
<Box <Box
sx={{ sx={{
@@ -238,6 +285,21 @@ export function UserCompletionForm() {
showValidations={showPasswordValidations} showValidations={showPasswordValidations}
/> />
<Toast
color="success"
open={showVerifyCodeSectionSuccess}
onClose={() => setShowVerifyCodeSectionSuccess(false)}
>
{sentVerificationCode}
</Toast>
<Toast
color="error"
open={showVerifyCodeSectionError}
onClose={() => setShowVerifyCodeSectionError(false)}
>
{sentVerificationCode}
</Toast>
<EmailSection <EmailSection
showEmail={showEmail} showEmail={showEmail}
setShowEmail={setShowEmail} setShowEmail={setShowEmail}
@@ -255,10 +317,20 @@ export function UserCompletionForm() {
isVerifyingCode={isVerifyingCode} isVerifyingCode={isVerifyingCode}
handleEditEmail={handleEditEmail} handleEditEmail={handleEditEmail}
/> />
<Toast
<Button onClick={handleSendCode} variant="contained" disabled={loading}> color="success"
{loading ? 'Sending…' : 'Send OTP'} open={showSuccessToast}
</Button> onClose={() => setShowSuccessToast(false)}
>
{toastMessage}
</Toast>
<Toast
color="error"
open={showErrorToast}
onClose={() => setShowErrorToast(false)}
>
{toastMessage}
</Toast>
<SubmitSection <SubmitSection
onSubmit={handleSubmit} onSubmit={handleSubmit}

View File

@@ -35,8 +35,6 @@ export async function fetchAuthToken(
email: string, email: string,
otpCode: string, otpCode: string,
): Promise<TokenResponse> { ): Promise<TokenResponse> {
await sendEmailOtp(email);
const body = new URLSearchParams({ const body = new URLSearchParams({
grant_type: 'otp', grant_type: 'otp',
client_id: 'harmony_identity', client_id: 'harmony_identity',