From f82fed54d5eb133e747fca2e7e1faa163b2070f9 Mon Sep 17 00:00:00 2001 From: Koosha Lahouti Date: Wed, 13 Aug 2025 17:59:40 +0330 Subject: [PATCH] fix: styles --- src/components/CountryFlag.tsx | 2 +- src/features/authentication/api/types.ts | 44 +++++++ .../authentication/api/userCompletion.ts | 56 +++++++++ .../components/EmailSection.tsx | 97 ++++++--------- .../components/PasswordSection.tsx | 117 ++++++------------ .../components/PasswordValidation.tsx | 1 - .../components/PersonalInfoFields.tsx | 79 ++++++------ .../components/SubmitSection.tsx | 10 +- .../components/UserCompletionForm.tsx | 70 +++++++++-- .../authentication/components/types.ts | 5 + src/utils/regex.ts | 4 +- 11 files changed, 289 insertions(+), 196 deletions(-) create mode 100644 src/features/authentication/api/types.ts create mode 100644 src/features/authentication/api/userCompletion.ts create mode 100644 src/features/authentication/components/types.ts diff --git a/src/components/CountryFlag.tsx b/src/components/CountryFlag.tsx index 7daa422..f94c42e 100644 --- a/src/components/CountryFlag.tsx +++ b/src/components/CountryFlag.tsx @@ -26,7 +26,7 @@ export function CountryFlag({ code }: CountryFlagProps) { alt={displayName} width="24" height="16" - style={{ borderRadius: '2px', border: '1px solid #ccc' }} + style={{ borderRadius: 0.25, border: '1px solid' }} /> {displayName} diff --git a/src/features/authentication/api/types.ts b/src/features/authentication/api/types.ts new file mode 100644 index 0000000..09c8062 --- /dev/null +++ b/src/features/authentication/api/types.ts @@ -0,0 +1,44 @@ +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; + errorCode?: number; +} + +export interface SendEmailOtpPayload { + email: string; +} + +export interface ConfirmEmailOtpPayload { + email: string; + otpCode: string; +} + +export interface CompleteUserInfoPayload { + userId: string; + firstName: string; + lastName: string; + gender: 0 | 1 | 2; + nationalId: string; + birthDate: Date | null; + country: string; + savePassword?: boolean; + password?: string; + saveEmail?: boolean; + email?: string; +} + +export interface CompleteUserInfoResponse extends GenericApiResponse { + validations: { property: string; message: string }[] | null; +} diff --git a/src/features/authentication/api/userCompletion.ts b/src/features/authentication/api/userCompletion.ts new file mode 100644 index 0000000..1b0d86d --- /dev/null +++ b/src/features/authentication/api/userCompletion.ts @@ -0,0 +1,56 @@ +import { + type SendEmailOtpPayload, + type TokenApiResponse, + type ConfirmEmailOtpPayload, + type CompleteUserInfoPayload, + type CompleteUserInfoResponse, + type GenericApiResponse, +} from './types'; +import axios from 'axios'; +import apiClient from '@/lib/apiClient'; + +const AUTH_API_URL = 'https://accounts.business-harmony.com'; + +export const getTokenApi = async (): Promise => { + 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( + `${AUTH_API_URL}/connect/token`, + body.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }, + ); + return data; +}; + +export const sendEmailOtpApi = async ( + payload: SendEmailOtpPayload, +): Promise => { + const { data } = await apiClient.post('/User/SendEmailOtp', payload); + return data; +}; + +export const confirmEmailOtpApi = async ( + payload: ConfirmEmailOtpPayload, +): Promise => { + const { data } = await apiClient.post('/User/ConfirmEmailOtp', payload); + return data; +}; + +export const completeUserInformationApi = async ( + payload: CompleteUserInfoPayload, +): Promise => { + const { data } = await apiClient.post( + '/User/CompleteUserInformation', + payload, + ); + return data; +}; diff --git a/src/features/authentication/components/EmailSection.tsx b/src/features/authentication/components/EmailSection.tsx index 9188462..19c1b41 100644 --- a/src/features/authentication/components/EmailSection.tsx +++ b/src/features/authentication/components/EmailSection.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { TextField, Box, @@ -28,7 +28,7 @@ interface EmailSectionProps { handleSendCode: () => void; handleVerifyCode: () => void; emailVerified: boolean; - isVerifyingCode: boolean; + loading: boolean; handleEditEmail: () => void; } @@ -46,15 +46,13 @@ export function EmailSection({ handleSendCode, handleVerifyCode, emailVerified, - isVerifyingCode, + loading, handleEditEmail, }: EmailSectionProps) { const { t } = useTranslation('completionForm'); const onSendCodeClick = () => { - if (!correctEmail) { - return; - } + if (!correctEmail) return; handleSendCode(); }; @@ -62,15 +60,6 @@ export function EmailSection({ setShowEmail(e.target.checked); }; - useEffect(() => { - if (emailVerified) { - } - }, [emailVerified]); - - const fieldSx = { - flex: '1 1 260px', - }; - return ( <> @@ -90,7 +79,6 @@ export function EmailSection({ - {showEmail && ( setEmail(e.target.value)} error={email.length > 0 && !correctEmail} - sx={fieldSx} - InputProps={{ - startAdornment: - !isVerifyingCode && emailVerified ? ( - - - - ) : null, - endAdornment: - buttonState === 'counting' ? ( - - + sx={{ flex: '1 1 260px' }} + slotProps={{ + input: { + startAdornment: + !loading && emailVerified ? ( + - - - ) : null, - }} - inputProps={{ - style: { - paddingLeft: buttonState === 'counting' ? '0px' : undefined, + + ) : null, + endAdornment: + buttonState === 'counting' ? ( + + + + + + ) : null, + sx: { + paddingLeft: buttonState === 'counting' ? 0 : undefined, + }, }, }} /> - - {!isVerifyingCode && !emailVerified && ( + {!loading && !emailVerified && ( {error && {error}} diff --git a/src/features/authentication/components/UserCompletionForm.tsx b/src/features/authentication/components/UserCompletionForm.tsx index cd0afb9..43d2b26 100644 --- a/src/features/authentication/components/UserCompletionForm.tsx +++ b/src/features/authentication/components/UserCompletionForm.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { Box, Typography } from '@mui/material'; +import { Box, Typography, Button } from '@mui/material'; import { useTranslation } from 'react-i18next'; import Logo from '@/components/Logo'; import { PersonalInfoFields } from './PersonalInfoFields'; @@ -11,7 +11,13 @@ 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; +} export function UserCompletionForm() { const { t } = useTranslation('completionForm'); @@ -20,7 +26,9 @@ export function UserCompletionForm() { const [lastName, setLastName] = useState(''); const [nationalId, setNationalId] = useState(''); const [birthDate, setBirthDate] = useState(null); - const [sex, setSex] = useState<'female' | 'male'>('female'); + + // ✅ Corrected section: use Gender enum + const [sex, setSex] = useState(Gender.Female); const [country, setCountry] = useState(''); const [showPasswordSection, setShowPasswordSection] = useState(false); @@ -38,15 +46,14 @@ export function UserCompletionForm() { const [emailVerified, setEmailVerified] = useState(false); const [isVerifyingCode, setIsVerifyingCode] = useState(false); - const correctEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); - const { hasNumber, hasMinLength, hasUpperAndLower, hasSpecialChar, validPassword, - } = regex(password); + correctEmail, + } = regex(password, email); const matchPassword = password === confirmPassword; const [showPasswordValidations, setShowPasswordValidations] = useState(false); @@ -95,6 +102,42 @@ export function UserCompletionForm() { return t('completion.vericationCodeButton'); }; + const [tokenError, setTokenError] = useState(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( + 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 () => { @@ -218,7 +261,7 @@ export function UserCompletionForm() { userId: '3fa85f64-5717-4562-b3fc-2c963f66afa6', firstName, lastName, - gender: sex === 'female' ? 2 : 1, + gender: sex === Gender.Female ? 2 : 1, // ✅ Corrected nationalId, savePassword: showPasswordSection, password: showPasswordSection ? password : undefined, @@ -302,8 +345,8 @@ export function UserCompletionForm() { setNationalId={setNationalId} birthDate={birthDate} setBirthDate={setBirthDate} - sex={sex} - setSex={setSex} + sex={sex} // ✅ Corrected + setSex={setSex} // ✅ Corrected country={country} setCountry={setCountry} /> @@ -338,7 +381,7 @@ export function UserCompletionForm() { handleSendCode={handleSendCode} handleVerifyCode={handleVerifyCode} emailVerified={emailVerified} - isVerifyingCode={isVerifyingCode} + loading={isVerifyingCode} handleEditEmail={handleEditEmail} /> + ); diff --git a/src/features/authentication/components/types.ts b/src/features/authentication/components/types.ts new file mode 100644 index 0000000..37b0033 --- /dev/null +++ b/src/features/authentication/components/types.ts @@ -0,0 +1,5 @@ +export enum Gender { + None = 0, + Female = 1, + Male = 2, +} diff --git a/src/utils/regex.ts b/src/utils/regex.ts index a4c68f4..60636dd 100644 --- a/src/utils/regex.ts +++ b/src/utils/regex.ts @@ -1,8 +1,9 @@ -export function regex(password: string) { +export function regex(password: string, email: string) { const hasNumber = /\d/.test(password); const hasMinLength = password.length >= 8; const hasUpperAndLower = /[A-Z]/.test(password) && /[a-z]/.test(password); const hasSpecialChar = /[!@#$%^&*]/.test(password); + const correctEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); return { hasNumber, @@ -11,5 +12,6 @@ export function regex(password: string) { hasSpecialChar, validPassword: hasNumber && hasMinLength && hasUpperAndLower && hasSpecialChar, + correctEmail, }; }