Files
emaui/apps/portal/src/app/features/auth/pages/LoginPage.tsx
2026-06-05 12:19:48 +03:00

114 lines
3.2 KiB
TypeScript

import { useState } from 'react';
import {
Paper,
TextInput,
PasswordInput,
Button,
Stack,
Title,
Center,
Text,
Anchor,
} from '@mantine/core';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useNavigate, Link } from 'react-router-dom';
import { useAppDispatch } from '../../../store/hooks';
import { loginSuccess, setUser } from '../store/auth.slice';
import type { LoginPayload, AuthUser } from '../types/auth.types';
import { notify } from '@ema-platform/ui';
const BASE_API_URL =
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
'http://localhost:3001/api';
const schema = z.object({
email: z.string().email('Enter a valid email'),
password: z.string().min(0, 'Password must be at least 6 characters'),
});
type FormValues = z.infer<typeof schema>;
export function LoginPage() {
const navigate = useNavigate();
const dispatch = useAppDispatch();
const [isLoading, setIsLoading] = useState(false);
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
});
const onSubmit = async (values: FormValues) => {
setIsLoading(true);
try {
const res = await fetch(`${BASE_API_URL}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values),
});
if (!res.ok) throw new Error('Login failed');
const data = (await res.json()) as LoginPayload;
dispatch(loginSuccess(data));
const meRes = await fetch(`${BASE_API_URL}/auth/me`, {
headers: { Authorization: `Bearer ${data.token}` },
});
if (!meRes.ok) throw new Error('Failed to fetch user');
const me = (await meRes.json()) as AuthUser;
dispatch(setUser(me));
if (me.status === 'accepted') {
navigate('/dashboard');
} else {
navigate('/set-password', {
state: { email: me.email, phoneNumber: me.phoneNumber },
});
}
} catch {
notify.error('Invalid email or password');
} finally {
setIsLoading(false);
}
};
return (
<Center h="100vh">
<Paper p="xl" shadow="md" radius="md" w={400}>
<Stack gap="md">
<Title order={3}>Sign in to Portal</Title>
<form onSubmit={handleSubmit(onSubmit)}>
<Stack gap="sm">
<TextInput
label="Email"
placeholder="you@example.com"
error={errors.email?.message}
{...register('email')}
/>
<PasswordInput
label="Password"
placeholder="Your password"
error={errors.password?.message}
{...register('password')}
/>
<Button type="submit" loading={isLoading} fullWidth mt="sm">
Sign in
</Button>
</Stack>
</form>
<Text ta="center" size="sm" c="dimmed">
Don't have an account?{' '}
<Anchor component={Link} to="/signup">
Sign up
</Anchor>
</Text>
</Stack>
</Paper>
</Center>
);
}