feat: login form and page with country code part and animation

This commit is contained in:
Sajad Mirjalili
2025-07-18 02:54:03 +03:30
parent 81904f7fd6
commit 381e274851
12 changed files with 249 additions and 27 deletions

View File

@@ -0,0 +1,35 @@
import { Box, Typography } from '@mui/material';
interface CountryCodeAdornmentProps {
show: boolean;
}
/**
* An animated country code adornment that fades and slides into view.
* Its visibility is controlled by the `show` prop.
*/
export function CountryCodeAdornment({ show }: CountryCodeAdornmentProps) {
return (
<Box
sx={{
// Animate width and opacity based on the 'show' prop
width: show ? 'auto' : 0,
opacity: show ? 1 : 0,
transition: (theme) =>
theme.transitions.create(['width', 'opacity'], {
duration: theme.transitions.duration.short,
}),
// Prevent content from wrapping or spilling out during animation
overflow: 'hidden',
whiteSpace: 'nowrap',
}}
>
{/* This inner Box prevents the content from being squeezed during the transition */}
<Box sx={{ display: 'flex', alignItems: 'center', px: 1, gap: 0.25 }}>
<Typography variant="body1" color="text.primary">
+41
</Typography>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,72 @@
import {
Box,
Button,
InputAdornment,
Stack,
TextField,
Typography,
} from '@mui/material';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { CountryCodeAdornment } from './CountryCodeAdornment';
const isNumeric = (value: string) => /^\d*$/.test(value);
export function LoginForm() {
const { t } = useTranslation('authentication');
const [value, setValue] = useState('');
const [inputType, setInputType] = useState<'phone' | 'email'>('phone');
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)) {
setInputType('phone');
} else {
setInputType('email');
}
};
const showAdornment = inputType === '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
label={t('loginForm.emailOrPhoneLabel')}
value={value}
onChange={handleInputChange}
slotProps={{
htmlInput: { dir: 'ltr' },
input: {
endAdornment: (
<InputAdornment
position="start"
sx={{
mr: 0,
// transition: (theme) => theme.transitions.create('margin'),
}}
>
<CountryCodeAdornment show={showAdornment} />
</InputAdornment>
),
},
}}
sx={{ my: 4 }}
/>
<Stack spacing={2}>
<Button>{t('loginForm.submitButton')}</Button>
<Button variant="outlined">{t('loginForm.loginWithGoogle')}</Button>
</Stack>
</Box>
);
}

View File

View File

@@ -0,0 +1,30 @@
import { FlexBox } from '@/components/components/common/FlexBox';
import Logo from '@/components/Logo';
import { Paper } from '@mui/material';
import { LoginForm } from '../components/LoginForm';
export function LoginPage() {
return (
<FlexBox
direction="column"
align="center"
justify="center"
sx={{
minHeight: '100vh',
gap: 3,
}}
>
<Logo />
<Paper
elevation={1}
square
sx={{
p: 6,
width: '34.5rem',
}}
>
<LoginForm />
</Paper>
</FlexBox>
);
}