chore: LoginRegisterForm and AuthenticationContainer created
This commit is contained in:
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
138
src/features/authentication/components/LoginRegiserForm.tsx
Normal file
138
src/features/authentication/components/LoginRegiserForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
3
src/features/authentication/types/auth-types.ts
Normal file
3
src/features/authentication/types/auth-types.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export type AuthType = 'email' | 'phone';
|
||||
|
||||
export type AuthMode = 'register' | 'login';
|
||||
@@ -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
|
||||
|
||||
2
src/utils/regexes/isEmail.tsx
Normal file
2
src/utils/regexes/isEmail.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export const isEmail = (value: string) =>
|
||||
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value);
|
||||
Reference in New Issue
Block a user