chore: LoginRegisterForm and AuthenticationContainer created

This commit is contained in:
مهرزاد قدرتی
2025-07-26 13:26:33 +03:30
parent d2efafa5a9
commit 2e10a5496c
14 changed files with 628 additions and 17 deletions

View File

@@ -1,14 +1,14 @@
import { CssBaseline } from '@mui/material';
import './App.css';
import { LanguageManager } from './components/LanguageManager';
import { LoginPage } from './features/authentication/routes/LoginPage';
import { AuthenticationPage } from './features/authentication/routes/AuthenticationPage';
function App() {
return (
<>
<CssBaseline />
<LanguageManager />
<LoginPage />
<AuthenticationPage />
</>
);
}

View File

@@ -0,0 +1,25 @@
import React, { useState, type JSX } from 'react';
import { LoginRegisterForm } from './LoginRegiserForm';
import type { AuthMode, AuthType } from '../types/auth-types';
export const AuthenticationContainer = (): JSX.Element => {
const [authMode, setAuthMode] = useState<AuthMode>('login');
const [authType, setAuthType] = useState<AuthType>('phone');
const [currentStep, setCurrentStep] = useState<
'emailOrPassword' | 'verify' | 'enterPassword'
>('emailOrPassword');
const handleLoginRegister = (value: string) => {};
return (
<>
{currentStep === 'emailOrPassword' && (
<LoginRegisterForm
authType={authType}
setAuthType={setAuthType}
onLoginRegisterSubmit={handleLoginRegister}
/>
)}
</>
);
};

View File

@@ -0,0 +1,138 @@
import { Box, Button, Stack, TextField, Typography } from '@mui/material';
import { useRef, useState, type Dispatch } from 'react';
import { useTranslation } from 'react-i18next';
import { CountryCodeSelector } from './CountryCodeSelector';
import { Google } from 'iconsax-reactjs';
import { isNumeric } from '@/utils/regexes/isNumeric';
import type { AuthMode, AuthType } from '../types/auth-types';
import { isEmail } from '@/utils/regexes/isEmail';
export interface LoginRegisterFormProps {
authType: AuthType;
setAuthType: Dispatch<AuthType>;
onLoginRegisterSubmit: (value: string) => void;
}
export function LoginRegisterForm({
authType,
setAuthType,
}: LoginRegisterFormProps) {
const { t, i18n } = useTranslation('authentication');
const [value, setValue] = useState('');
const [countryCode, setCountryCode] = useState('+98');
const textFieldRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const dir = i18n.dir();
const [error, setError] = useState<string>();
const [touched, setTouched] = useState<boolean>(false);
const inputError: boolean = touched && !!error;
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
setValue(newValue);
// If the new value contains only digits (or is empty), it's a phone number
if (isNumeric(newValue)) {
setAuthType('phone');
} else {
setAuthType('email');
}
};
const handleBlur = () => {
setTouched(true);
validateInput(value, authType);
};
const validateInput = (value: string, authType: AuthType) => {
if (!value) {
setError(t('loginForm.thisFieldIsRequired'));
} else if (authType === 'email' && !isEmail(value)) {
setError(t('loginForm.emailIsInvalid'));
} else if (authType === 'phone' && false /* TODO */) {
setError(t('loginForm.emailIsInvalid'));
} else {
setError(undefined);
}
};
const isInputValid = (value: string, authType: AuthType): boolean => {
if (!value) {
return false;
}
if (authType === 'email' && !isEmail(value)) {
return false;
}
if (authType === 'phone' && false /* TODO */) {
return false;
}
return true;
};
const handleSubmit = () => {
if (isInputValid(value, authType)) {
} else {
inputRef.current?.focus();
validateInput(value, authType);
}
};
const showAdornment = authType === 'phone' && value.length > 0;
return (
<Box sx={{ width: '100%' }}>
<Stack spacing={1}>
<Typography variant="h5">{t('loginForm.title')}</Typography>
<Typography variant="body2" color="text.secondary">
{t('loginForm.description')}
</Typography>
</Stack>
<TextField
ref={textFieldRef}
inputRef={inputRef}
label={t('loginForm.emailOrPhoneLabel')}
value={value}
onChange={handleInputChange}
onBlur={handleBlur}
error={inputError}
helperText={inputError ? error : ''}
autoFocus
slotProps={{
htmlInput: { dir: 'auto', sx: { lineHeight: 1.5, paddingX: 0 } },
input: {
startAdornment: dir === 'ltr' && (
<CountryCodeSelector
value={countryCode}
onChange={setCountryCode}
show={showAdornment}
menuAnchor={textFieldRef.current}
onCloseFocusRef={inputRef}
/>
),
endAdornment: dir === 'rtl' && (
<CountryCodeSelector
value={countryCode}
onChange={setCountryCode}
show={showAdornment}
menuAnchor={textFieldRef.current}
onCloseFocusRef={inputRef}
/>
),
},
}}
sx={{ my: 4 }}
/>
<Stack spacing={2}>
<Button onClick={handleSubmit}>{t('loginForm.submitButton')}</Button>
<Button variant="outlined" startIcon={<Google variant="Bold" />}>
{t('loginForm.loginWithGoogle')}
</Button>
</Stack>
</Box>
);
}

View File

@@ -16,7 +16,7 @@ export function SmsOtpForm({ value, type }: SmsOtpProps) {
<Box
sx={{
display: 'flex',
flexDirection: 'row',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 4,

View File

@@ -3,13 +3,9 @@ import Logo from '@/components/Logo';
import { Paper } from '@mui/material';
import { SmsOtpForm } from '../components/SmsOtpForm';
import { useState } from 'react';
import { AuthenticationContainer } from '../components/AuthenticationContainer';
export function LoginPage() {
const [phoneOrEmail, setPhoneOrEmail] = useState<string>(
'sajadmirjalili82@gmail.com',
);
const [type, setType] = useState<'phone' | 'email'>('phone');
export function AuthenticationPage() {
return (
<FlexBox
direction="column"
@@ -29,8 +25,7 @@ export function LoginPage() {
width: '34.5rem',
}}
>
{/* <LoginForm /> */}
<SmsOtpForm value={phoneOrEmail} type={type} />
<AuthenticationContainer />
</Paper>
</FlexBox>
);

View File

@@ -0,0 +1,3 @@
export type AuthType = 'email' | 'phone';
export type AuthMode = 'register' | 'login';

View File

@@ -1,11 +1,10 @@
import { blue } from '@mui/material/colors';
import type { Palette } from './color.type';
export const PALETTE: Palette = {
primary: {
light: {
main: '#212121',
dark: '#000000',
light: '#616161',
main: blue.A400,
contrastText: '#FFFFFF',
},
// TODO

View File

@@ -0,0 +1,2 @@
export const isEmail = (value: string) =>
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value);