mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
114 lines
3.2 KiB
TypeScript
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>
|
|
);
|
|
}
|