chore: fix styles and change the apis
This commit is contained in:
1772
package-lock.json
generated
1772
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,36 +1,12 @@
|
||||
import {
|
||||
type SendEmailOtpPayload,
|
||||
type TokenApiResponse,
|
||||
type ConfirmEmailOtpPayload,
|
||||
type CompleteUserInfoPayload,
|
||||
type CompleteUserInfoResponse,
|
||||
type GenericApiResponse,
|
||||
} from './types';
|
||||
import axios from 'axios';
|
||||
} from '../types/completionFormApiTypes';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
|
||||
const AUTH_API_URL = 'https://accounts.business-harmony.com';
|
||||
|
||||
export const getTokenApi = async (): Promise<TokenApiResponse> => {
|
||||
const body = new URLSearchParams();
|
||||
body.set('grant_type', 'password');
|
||||
body.set('username', 'zareian.1381@gmail.com');
|
||||
body.set('password', '123@Qweasd');
|
||||
body.set('client_id', 'harmony_identity');
|
||||
body.set('scope', 'openid harmony_identity profile offline_access');
|
||||
|
||||
const { data } = await axios.post<TokenApiResponse>(
|
||||
`${AUTH_API_URL}/connect/token`,
|
||||
body.toString(),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
},
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const sendEmailOtpApi = async (
|
||||
payload: SendEmailOtpPayload,
|
||||
): Promise<GenericApiResponse & { codeSentAnyway?: boolean }> => {
|
||||
|
||||
@@ -14,11 +14,7 @@ import { format as formatJalali } from 'date-fns-jalali';
|
||||
import { format } from 'date-fns';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toLocaleDigits } from '@/utils/persianDigit';
|
||||
|
||||
interface DateOfBirthProps {
|
||||
value: Date | null;
|
||||
onChange: (date: Date | null) => void;
|
||||
}
|
||||
import { type DateOfBirthProps } from '../types/settingForm';
|
||||
|
||||
export default function DateOfBirth({ value, onChange }: DateOfBirthProps) {
|
||||
const { t, i18n } = useTranslation('completionForm');
|
||||
|
||||
@@ -13,24 +13,7 @@ import {
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TickCircle, Edit } from 'iconsax-react';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
|
||||
interface EmailSectionProps {
|
||||
showEmail: boolean;
|
||||
setShowEmail: (checked: boolean) => void;
|
||||
email: string;
|
||||
setEmail: (email: string) => void;
|
||||
correctEmail: boolean;
|
||||
codeSent: boolean;
|
||||
verificationCode: string;
|
||||
setVerificationCode: (code: string) => void;
|
||||
buttonState: 'default' | 'counting' | 'sent';
|
||||
getButtonLabel: () => string;
|
||||
handleSendCode: () => void;
|
||||
handleVerifyCode: () => void;
|
||||
emailVerified: boolean;
|
||||
loading: boolean;
|
||||
handleEditEmail: () => void;
|
||||
}
|
||||
import { type EmailSectionProps } from '../types/settingForm';
|
||||
|
||||
export function EmailSection({
|
||||
showEmail,
|
||||
|
||||
@@ -12,22 +12,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { TickCircle, Eye, EyeSlash, CloseCircle } from 'iconsax-react';
|
||||
import { PasswordValidationItem } from './PasswordValidation';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
|
||||
interface PasswordSectionProps {
|
||||
showPasswordSection: boolean;
|
||||
setShowPasswordSection: (checked: boolean) => void;
|
||||
password: string;
|
||||
setPassword: (password: string) => void;
|
||||
confirmPassword: string;
|
||||
setConfirmPassword: (confirmPassword: string) => void;
|
||||
matchPassword: boolean;
|
||||
hasNumber: boolean;
|
||||
hasMinLength: boolean;
|
||||
hasUpperAndLower: boolean;
|
||||
hasSpecialChar: boolean;
|
||||
validPassword: boolean;
|
||||
showValidations: boolean;
|
||||
}
|
||||
import { type PasswordSectionProps } from '../types/settingForm';
|
||||
|
||||
export function PasswordSection({
|
||||
showPasswordSection,
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { TickCircle } from 'iconsax-react';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
|
||||
interface ValidationItemProps {
|
||||
isValid: boolean;
|
||||
label: string;
|
||||
}
|
||||
import { type ValidationItemProps } from '../types/settingForm';
|
||||
|
||||
export function PasswordValidationItem({
|
||||
isValid,
|
||||
|
||||
@@ -10,27 +10,12 @@ import {
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Woman, Man } from 'iconsax-react';
|
||||
import { type Dispatch, type SetStateAction } from 'react';
|
||||
import DateOfBirth from './DateOfBirth';
|
||||
import { countries } from '../data/Countries';
|
||||
import { CountryFlag } from '@/components/CountryFlag';
|
||||
import { Icon } from '@rkheftan/harmony-ui';
|
||||
import { Gender } from './types';
|
||||
|
||||
interface PersonalInfoFieldsProps {
|
||||
firstName: string;
|
||||
setFirstName: (v: string) => void;
|
||||
lastName: string;
|
||||
setLastName: (v: string) => void;
|
||||
sex: Gender;
|
||||
setSex: Dispatch<SetStateAction<Gender>>;
|
||||
country: string;
|
||||
setCountry: (country: string) => void;
|
||||
nationalId: string;
|
||||
setNationalId: (v: string) => void;
|
||||
birthDate: Date | null;
|
||||
setBirthDate: (d: Date | null) => void;
|
||||
}
|
||||
import { type PersonalInfoFieldsProps } from '../types/settingForm';
|
||||
import { Gender } from '../types/settingForm';
|
||||
|
||||
export function PersonalInfoFields({
|
||||
firstName,
|
||||
|
||||
@@ -9,13 +9,9 @@ import {
|
||||
DialogContent,
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
interface Props {
|
||||
onSubmit: () => void;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
success: boolean;
|
||||
}
|
||||
export function SubmitSection({ onSubmit, loading, error, success }: Props) {
|
||||
import { type SubmitProps } from '../types/settingForm';
|
||||
|
||||
export function SubmitSection({ onSubmit, loading, error }: SubmitProps) {
|
||||
const { t, i18n } = useTranslation('completionForm');
|
||||
const [openDialog, setOpenDialog] = useState(false);
|
||||
|
||||
|
||||
@@ -1,50 +1,53 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Box, Typography, Button } from '@mui/material';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Logo from '@/components/Logo';
|
||||
import { PersonalInfoFields } from './PersonalInfoFields';
|
||||
import { PasswordSection } from './PasswordSection';
|
||||
import { EmailSection } from './EmailSection';
|
||||
import { SubmitSection } from './SubmitSection';
|
||||
import apiClient from '@/lib/apiClient';
|
||||
import { useToast } from '@rkheftan/harmony-ui';
|
||||
import { AxiosError } from 'axios';
|
||||
import { regex } from '../../../utils/regex';
|
||||
import { toLocaleDigits } from '../../../utils/persianDigit';
|
||||
import axios, { isAxiosError } from 'axios';
|
||||
import i18n from '@/config/i18n';
|
||||
import { Gender } from './types'; // ✅ Added
|
||||
|
||||
interface TokenApiResponse {
|
||||
access_token: string;
|
||||
}
|
||||
import { Gender } from '../types/settingForm';
|
||||
import { useApi } from '@/hooks/useApi';
|
||||
import {
|
||||
sendEmailOtpApi,
|
||||
confirmEmailOtpApi,
|
||||
completeUserInformationApi,
|
||||
} from '../api/userCompletion';
|
||||
import {
|
||||
type SendEmailOtpPayload,
|
||||
type ConfirmEmailOtpPayload,
|
||||
type CompleteUserInfoPayload,
|
||||
} from '../types/completionFormApiTypes';
|
||||
import { type ApiResponse } from '@/types/apiResponse';
|
||||
|
||||
export function UserCompletionForm() {
|
||||
const { t } = useTranslation('completionForm');
|
||||
const showToast = useToast();
|
||||
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
const [nationalId, setNationalId] = useState('');
|
||||
const [birthDate, setBirthDate] = useState<Date | null>(null);
|
||||
|
||||
// ✅ Corrected section: use Gender enum
|
||||
const [sex, setSex] = useState<Gender>(Gender.Female);
|
||||
const [country, setCountry] = useState('');
|
||||
|
||||
const [showPasswordSection, setShowPasswordSection] = useState(false);
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
|
||||
const [showEmail, setShowEmail] = useState(false);
|
||||
const [email, setEmail] = useState('');
|
||||
const [codeSent, setCodeSent] = useState(false);
|
||||
const [verificationCode, setVerificationCode] = useState('');
|
||||
|
||||
const [codeSent, setCodeSent] = useState(false);
|
||||
const [buttonState, setButtonState] = useState<'default' | 'counting'>(
|
||||
'default',
|
||||
);
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const [emailVerified, setEmailVerified] = useState(false);
|
||||
const [isVerifyingCode, setIsVerifyingCode] = useState(false);
|
||||
const [showPasswordValidations, setShowPasswordValidations] = useState(false);
|
||||
|
||||
const {
|
||||
hasNumber,
|
||||
@@ -55,25 +58,113 @@ export function UserCompletionForm() {
|
||||
correctEmail,
|
||||
} = regex(password, email);
|
||||
const matchPassword = password === confirmPassword;
|
||||
const [showPasswordValidations, setShowPasswordValidations] = useState(false);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const { execute: sendCode, data: sendCodeData } = useApi(
|
||||
async (payload: SendEmailOtpPayload) => {
|
||||
const result = await sendEmailOtpApi(payload);
|
||||
const conformingResult: ApiResponse = {
|
||||
...result,
|
||||
errorCode: result.errorCode || 0,
|
||||
validations: [],
|
||||
};
|
||||
return { data: conformingResult };
|
||||
},
|
||||
);
|
||||
|
||||
const showToast = useToast();
|
||||
const {
|
||||
execute: verifyCode,
|
||||
loading: isVerifyingCode,
|
||||
data: verifyCodeData,
|
||||
} = useApi(async (payload: ConfirmEmailOtpPayload) => {
|
||||
const result = await confirmEmailOtpApi(payload);
|
||||
const conformingResult: ApiResponse = {
|
||||
...result,
|
||||
errorCode: result.errorCode || 0,
|
||||
validations: [],
|
||||
};
|
||||
return { data: conformingResult };
|
||||
});
|
||||
|
||||
const {
|
||||
execute: submitForm,
|
||||
loading: isSubmitting,
|
||||
error: submitError,
|
||||
data: submitData,
|
||||
} = useApi(async (payload: CompleteUserInfoPayload) => {
|
||||
const result = await completeUserInformationApi(payload);
|
||||
const conformingResult: ApiResponse = {
|
||||
...result,
|
||||
errorCode: result.errorCode || 0,
|
||||
validations: [],
|
||||
};
|
||||
return { data: conformingResult };
|
||||
});
|
||||
|
||||
const getErrorMessage = (error: unknown): string | null => {
|
||||
if (!error) return null;
|
||||
if (error instanceof Error) return error.message;
|
||||
return String(error);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (password) {
|
||||
if (!validPassword) {
|
||||
setShowPasswordValidations(true);
|
||||
if (sendCodeData) {
|
||||
if (sendCodeData.success) {
|
||||
showToast({
|
||||
message: sendCodeData.message || t('completion.successfullCodeSent'),
|
||||
severity: 'success',
|
||||
});
|
||||
setCodeSent(true);
|
||||
setButtonState('counting');
|
||||
setCountdown(120);
|
||||
} else {
|
||||
const timer = setTimeout(() => setShowPasswordValidations(false), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
showToast({
|
||||
message: sendCodeData.message || t('completion.problem'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setShowPasswordValidations(false);
|
||||
}
|
||||
}, [sendCodeData, showToast, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (verifyCodeData) {
|
||||
if (verifyCodeData.success) {
|
||||
setEmailVerified(true);
|
||||
showToast({
|
||||
message: verifyCodeData.message || t('completion.codeVerified'),
|
||||
severity: 'success',
|
||||
});
|
||||
} else {
|
||||
showToast({
|
||||
message: verifyCodeData.message || t('completion.invalidCode'),
|
||||
severity: 'error',
|
||||
});
|
||||
setEmailVerified(false);
|
||||
}
|
||||
}
|
||||
}, [verifyCodeData, showToast, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (submitData) {
|
||||
showToast({
|
||||
message:
|
||||
submitData.message ||
|
||||
t(
|
||||
submitData.success
|
||||
? 'completion.submitSuccess'
|
||||
: 'completion.submitError',
|
||||
),
|
||||
severity: submitData.success ? 'success' : 'error',
|
||||
});
|
||||
} else if (submitError) {
|
||||
showToast({
|
||||
message: getErrorMessage(submitError) || t('completion.problem'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
}, [submitData, submitError, showToast, t]);
|
||||
|
||||
useEffect(() => {
|
||||
setShowPasswordValidations(password ? !validPassword : false);
|
||||
}, [password, validPassword]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -93,6 +184,37 @@ export function UserCompletionForm() {
|
||||
return () => clearInterval(timer);
|
||||
}, [buttonState, countdown]);
|
||||
|
||||
const handleSendCode = () => {
|
||||
sendCode({ email });
|
||||
};
|
||||
|
||||
const handleVerifyCode = () => {
|
||||
if (!verificationCode.trim()) {
|
||||
showToast({
|
||||
message: 'Please enter the verification code',
|
||||
severity: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
verifyCode({ email, otpCode: verificationCode });
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
submitForm({
|
||||
userId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
||||
firstName,
|
||||
lastName,
|
||||
gender: sex,
|
||||
nationalId,
|
||||
savePassword: showPasswordSection,
|
||||
password: showPasswordSection ? password : undefined,
|
||||
saveEmail: showEmail,
|
||||
email: showEmail ? email : undefined,
|
||||
birthDate,
|
||||
country,
|
||||
});
|
||||
};
|
||||
|
||||
const getButtonLabel = () => {
|
||||
if (buttonState === 'counting') {
|
||||
const m = String(Math.floor(countdown / 60)).padStart(2, '0');
|
||||
@@ -102,142 +224,6 @@ export function UserCompletionForm() {
|
||||
return t('completion.vericationCodeButton');
|
||||
};
|
||||
|
||||
const [tokenError, setTokenError] = useState<string | null>(null);
|
||||
const apiUrl = 'https://accounts.business-harmony.com';
|
||||
const tokenEndpoint = `${apiUrl}/connect/token`;
|
||||
const getToken = async () => {
|
||||
setTokenError(null);
|
||||
try {
|
||||
const body = new URLSearchParams();
|
||||
body.set('grant_type', 'password');
|
||||
body.set('username', 'zareian.1381@gmail.com');
|
||||
body.set('password', '123@Qweasd');
|
||||
body.set('client_id', 'harmony_identity');
|
||||
body.set('scope', 'openid harmony_identity profile offline_access');
|
||||
const response = await axios.post<TokenApiResponse>(
|
||||
tokenEndpoint,
|
||||
body.toString(),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
},
|
||||
);
|
||||
if (response.data?.access_token) {
|
||||
localStorage.setItem('authToken', response.data.access_token);
|
||||
} else {
|
||||
throw new Error('No access token in response');
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
let message = 'Failed to get token';
|
||||
if (isAxiosError(error) && error.response) {
|
||||
message = `Request failed with status ${error.response.status}`;
|
||||
} else if (error instanceof Error) {
|
||||
message = error.message;
|
||||
}
|
||||
setTokenError(message);
|
||||
}
|
||||
};
|
||||
const storedToken = localStorage.getItem('authToken');
|
||||
|
||||
const handleSendCode = async () => {
|
||||
setError(null);
|
||||
setLoading(true);
|
||||
setSuccess(false);
|
||||
try {
|
||||
const response = await apiClient.post(
|
||||
'/User/SendEmailOtp',
|
||||
{ email },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${storedToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (response.data?.success) {
|
||||
showToast({
|
||||
message: response.data.message || t('completion.successfullCodeSent'),
|
||||
severity: 'success',
|
||||
});
|
||||
setCodeSent(true);
|
||||
setButtonState('counting');
|
||||
setCountdown(120);
|
||||
} else if (response.data?.codeSentAnyway) {
|
||||
showToast({
|
||||
message: t('completion.codeSentBut') + (response.data.message || ''),
|
||||
severity: 'warning',
|
||||
});
|
||||
setCodeSent(true);
|
||||
setButtonState('counting');
|
||||
setCountdown(120);
|
||||
} else {
|
||||
showToast({
|
||||
message: response.data.message || t('completion.problem'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const err = error as AxiosError<{ message?: string }>;
|
||||
|
||||
showToast({
|
||||
message: err.response?.data?.message || t('completion.problem'),
|
||||
severity: 'error',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifyCode = async () => {
|
||||
if (!verificationCode.trim()) {
|
||||
setError('Please enter the verification code');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setIsVerifyingCode(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const res = await apiClient.post(
|
||||
'/User/ConfirmEmailOtp',
|
||||
{
|
||||
email,
|
||||
otpCode: verificationCode,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${storedToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (res.data?.success) {
|
||||
setEmailVerified(true);
|
||||
setSuccess(true);
|
||||
showToast({
|
||||
message: res.data.message || t('completion.codeVerified'),
|
||||
severity: 'success',
|
||||
});
|
||||
} else {
|
||||
showToast({
|
||||
message: res.data?.message || t('completion.invalidCode'),
|
||||
severity: 'error',
|
||||
});
|
||||
setEmailVerified(false);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const err = error as AxiosError<{ message?: string }>;
|
||||
showToast({
|
||||
message: err.response?.data?.message || '',
|
||||
severity: 'error',
|
||||
});
|
||||
setEmailVerified(false);
|
||||
} finally {
|
||||
setIsVerifyingCode(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditEmail = () => {
|
||||
setButtonState('default');
|
||||
setCodeSent(false);
|
||||
@@ -245,60 +231,6 @@ export function UserCompletionForm() {
|
||||
setVerificationCode('');
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setSuccess(false);
|
||||
try {
|
||||
const { data } = await apiClient.post<{
|
||||
success: boolean;
|
||||
errorCode: number;
|
||||
message: string;
|
||||
validations: { property: string; message: string }[];
|
||||
}>(
|
||||
'/User/CompleteUserInformation',
|
||||
{
|
||||
userId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
||||
firstName,
|
||||
lastName,
|
||||
gender: sex === Gender.Female ? 2 : 1, // ✅ Corrected
|
||||
nationalId,
|
||||
savePassword: showPasswordSection,
|
||||
password: showPasswordSection ? password : undefined,
|
||||
saveEmail: showEmail,
|
||||
email: showEmail ? email : undefined,
|
||||
birthDate,
|
||||
country,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${storedToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (data.success) {
|
||||
showToast({
|
||||
message: data.message || t('completion.submitSuccess'),
|
||||
severity: 'success',
|
||||
});
|
||||
} else {
|
||||
showToast({
|
||||
message: data.message || t('completion.submitError'),
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -345,8 +277,8 @@ export function UserCompletionForm() {
|
||||
setNationalId={setNationalId}
|
||||
birthDate={birthDate}
|
||||
setBirthDate={setBirthDate}
|
||||
sex={sex} // ✅ Corrected
|
||||
setSex={setSex} // ✅ Corrected
|
||||
sex={sex}
|
||||
setSex={setSex}
|
||||
country={country}
|
||||
setCountry={setCountry}
|
||||
/>
|
||||
@@ -384,21 +316,13 @@ export function UserCompletionForm() {
|
||||
loading={isVerifyingCode}
|
||||
handleEditEmail={handleEditEmail}
|
||||
/>
|
||||
|
||||
<SubmitSection
|
||||
onSubmit={handleSubmit}
|
||||
loading={loading}
|
||||
error={error}
|
||||
success={success}
|
||||
loading={isSubmitting}
|
||||
error={getErrorMessage(submitError)}
|
||||
success={!!submitData?.success}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={getToken}
|
||||
size="large"
|
||||
sx={{ textTransform: 'none' }}
|
||||
>
|
||||
Get Token
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export enum Gender {
|
||||
None = 0,
|
||||
Female = 1,
|
||||
Male = 2,
|
||||
}
|
||||
@@ -1,15 +1,3 @@
|
||||
export interface TokenRequestPayload {
|
||||
grant_type: 'password';
|
||||
username: string;
|
||||
password: string;
|
||||
client_id: string;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
export interface TokenApiResponse {
|
||||
access_token: string;
|
||||
}
|
||||
|
||||
export interface GenericApiResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
73
src/features/authentication/types/settingForm.ts
Normal file
73
src/features/authentication/types/settingForm.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { type Dispatch, type SetStateAction } from 'react';
|
||||
|
||||
export enum Gender {
|
||||
None = 0,
|
||||
Female = 1,
|
||||
Male = 2,
|
||||
}
|
||||
|
||||
export interface DateOfBirthProps {
|
||||
value: Date | null;
|
||||
onChange: (date: Date | null) => void;
|
||||
}
|
||||
|
||||
export interface EmailSectionProps {
|
||||
showEmail: boolean;
|
||||
setShowEmail: (checked: boolean) => void;
|
||||
email: string;
|
||||
setEmail: (email: string) => void;
|
||||
correctEmail: boolean;
|
||||
codeSent: boolean;
|
||||
verificationCode: string;
|
||||
setVerificationCode: (code: string) => void;
|
||||
buttonState: 'default' | 'counting' | 'sent';
|
||||
getButtonLabel: () => string;
|
||||
handleSendCode: () => void;
|
||||
handleVerifyCode: () => void;
|
||||
emailVerified: boolean;
|
||||
loading: boolean;
|
||||
handleEditEmail: () => void;
|
||||
}
|
||||
|
||||
export interface PasswordSectionProps {
|
||||
showPasswordSection: boolean;
|
||||
setShowPasswordSection: (checked: boolean) => void;
|
||||
password: string;
|
||||
setPassword: (password: string) => void;
|
||||
confirmPassword: string;
|
||||
setConfirmPassword: (confirmPassword: string) => void;
|
||||
matchPassword: boolean;
|
||||
hasNumber: boolean;
|
||||
hasMinLength: boolean;
|
||||
hasUpperAndLower: boolean;
|
||||
hasSpecialChar: boolean;
|
||||
validPassword: boolean;
|
||||
showValidations: boolean;
|
||||
}
|
||||
|
||||
export interface ValidationItemProps {
|
||||
isValid: boolean;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface PersonalInfoFieldsProps {
|
||||
firstName: string;
|
||||
setFirstName: (v: string) => void;
|
||||
lastName: string;
|
||||
setLastName: (v: string) => void;
|
||||
sex: Gender;
|
||||
setSex: Dispatch<SetStateAction<Gender>>;
|
||||
country: string;
|
||||
setCountry: (country: string) => void;
|
||||
nationalId: string;
|
||||
setNationalId: (v: string) => void;
|
||||
birthDate: Date | null;
|
||||
setBirthDate: (d: Date | null) => void;
|
||||
}
|
||||
|
||||
export interface SubmitProps {
|
||||
onSubmit: () => void;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
success: boolean;
|
||||
}
|
||||
@@ -1,26 +1,46 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { type ApiResponse } from '@/types/apiResponse';
|
||||
|
||||
type ApiFunction<T> = () => Promise<{ data: T }>;
|
||||
// Define options for the hook
|
||||
interface UseApiOptions {
|
||||
// If true, the API call will be executed immediately on mount
|
||||
immediate?: boolean;
|
||||
}
|
||||
|
||||
export function useApi<T>(apiFunction: ApiFunction<T>) {
|
||||
export function useApi<T extends ApiResponse, P extends any[]>(
|
||||
apiFunction: (...args: P) => Promise<{ data: T }>,
|
||||
options: UseApiOptions = {},
|
||||
) {
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<unknown>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const execute = useCallback(
|
||||
async (...args: P) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await apiFunction();
|
||||
const response = await apiFunction(...args);
|
||||
|
||||
setData(response.data);
|
||||
} catch (err) {
|
||||
// TODO: can handle some common errors here, 400 and 500 errors
|
||||
setError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
},
|
||||
[apiFunction],
|
||||
);
|
||||
|
||||
fetchData();
|
||||
}, [apiFunction]);
|
||||
// If the 'immediate' option is true, execute the function on mount
|
||||
useEffect(() => {
|
||||
if (options.immediate) {
|
||||
// We pass undefined as params for the initial call.
|
||||
execute(...(undefined as unknown as P));
|
||||
}
|
||||
}, [execute, options.immediate]);
|
||||
|
||||
return { data, loading, error };
|
||||
return { data, loading, error, execute };
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ const apiClient = axios.create({
|
||||
|
||||
// Set default headers
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${localStorage.getItem('authToken')}`,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
13
src/types/apiResponse.ts
Normal file
13
src/types/apiResponse.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface ApiResponse {
|
||||
success: boolean;
|
||||
errorCode: number;
|
||||
message: string;
|
||||
validations: ApiResponseValidation[];
|
||||
}
|
||||
|
||||
export interface ApiResponseValidation {
|
||||
message: string;
|
||||
code: number;
|
||||
property: string;
|
||||
severity: number;
|
||||
}
|
||||
Reference in New Issue
Block a user