mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 19:58:11 +00:00
209 lines
5.8 KiB
TypeScript
209 lines
5.8 KiB
TypeScript
import { type FormEvent, useState } from "react";
|
|
import {
|
|
Alert,
|
|
Box,
|
|
Button,
|
|
Center,
|
|
Group,
|
|
Image,
|
|
PasswordInput,
|
|
PinInput,
|
|
Stack,
|
|
Text,
|
|
TextInput,
|
|
Title,
|
|
} from "@mantine/core";
|
|
import { AlertCircle, ArrowLeft } from "lucide-react";
|
|
import { useNavigate } from "react-router-dom";
|
|
|
|
import { useAuth } from "@/auth/useAuth";
|
|
import AuthShell from "@/components/auth/AuthShell";
|
|
import { extractApiError } from "@/utils/result";
|
|
|
|
/** Normalise Ethiopian local phone (09…/07…) to E.164; pass email through unchanged. */
|
|
const normaliseIdentifier = (raw: string): string => {
|
|
const v = raw.trim();
|
|
const digits = v.replace(/\D/g, "");
|
|
if (digits.length >= 9 && (v.startsWith("0") || v.startsWith("+251"))) {
|
|
const local = digits.startsWith("251")
|
|
? digits.slice(3)
|
|
: digits.replace(/^0/, "");
|
|
return `+251${local}`;
|
|
}
|
|
return v.toLowerCase();
|
|
};
|
|
|
|
const EDR_LOGO = "/assets/logo.svg";
|
|
|
|
const LoginPage = () => {
|
|
const navigate = useNavigate();
|
|
const { login, verifyMfa } = useAuth();
|
|
const [identifier, setIdentifier] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [otp, setOtp] = useState("");
|
|
const [needsMfa, setNeedsMfa] = useState(false);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [normalizedIdentifier, setNormalizedIdentifier] = useState("");
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
setSubmitting(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const normalized = normaliseIdentifier(identifier);
|
|
setNormalizedIdentifier(normalized);
|
|
|
|
const result = await login({ email: normalized, password });
|
|
if (result.mfaRequired) {
|
|
setNeedsMfa(true);
|
|
return;
|
|
}
|
|
|
|
// navigate("/dashboard/overview", { replace: true });
|
|
} catch (err) {
|
|
setError(extractApiError(err).message);
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const handleVerifyMfa = async (event: FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
setSubmitting(true);
|
|
setError(null);
|
|
|
|
try {
|
|
await verifyMfa({ email: normalizedIdentifier, otp: otp.trim() });
|
|
navigate("/dashboard/overview", { replace: true });
|
|
} catch (err) {
|
|
setError(extractApiError(err).message);
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const loginForm = (
|
|
<Box component="form" onSubmit={handleSubmit}>
|
|
<Center mb={{ base: "md", sm: "lg" }}>
|
|
<Image src={EDR_LOGO} alt="EDR Freight" h={{ base: 36, sm: 44 }} w="auto" fit="contain" />
|
|
</Center>
|
|
|
|
<Stack gap={6} mb={{ base: "md", sm: "lg" }} ta="center">
|
|
<Title order={1} fz={{ base: "xl", sm: "26px" }} fw={700} lh={1.2}>
|
|
Welcome back!
|
|
</Title>
|
|
<Text size="sm" c="dimmed">
|
|
Log in to access the freight backoffice & explore all logistics
|
|
resources.
|
|
</Text>
|
|
</Stack>
|
|
|
|
<Stack gap="md">
|
|
<TextInput
|
|
label="Email or Phone"
|
|
placeholder="name@company.com or 09XXXXXXXX"
|
|
autoComplete="username"
|
|
required
|
|
disabled={submitting}
|
|
value={identifier}
|
|
onChange={(event) => setIdentifier(event.target.value)}
|
|
/>
|
|
|
|
<PasswordInput
|
|
label="Password"
|
|
placeholder="Enter your password"
|
|
required
|
|
disabled={submitting}
|
|
value={password}
|
|
onChange={(event) => setPassword(event.target.value)}
|
|
/>
|
|
|
|
{error ? (
|
|
<Alert color="red" variant="light" icon={<AlertCircle size={18} />}>
|
|
{error}
|
|
</Alert>
|
|
) : null}
|
|
|
|
<Button type="submit" color="edr-green" fullWidth loading={submitting}>
|
|
Sign In
|
|
</Button>
|
|
</Stack>
|
|
</Box>
|
|
);
|
|
|
|
const mfaForm = (
|
|
<Box component="form" onSubmit={handleVerifyMfa}>
|
|
<Center mb={{ base: "md", sm: "lg" }}>
|
|
<Image src={EDR_LOGO} alt="EDR Freight" h={{ base: 36, sm: 44 }} w="auto" fit="contain" />
|
|
</Center>
|
|
|
|
<Stack gap={6} mb={{ base: "md", sm: "lg" }} ta="center">
|
|
<Title order={1} fz={{ base: "xl", sm: "26px" }} fw={700} lh={1.2}>
|
|
Multi-factor verification
|
|
</Title>
|
|
<Text size="sm" c="dimmed">
|
|
We sent a verification code to{" "}
|
|
<Text component="span" fw={500} c="var(--mantine-color-text)">
|
|
{normalizedIdentifier}
|
|
</Text>
|
|
. Enter it below to complete sign in.
|
|
</Text>
|
|
</Stack>
|
|
|
|
<Stack gap="md">
|
|
<Stack gap={6} align="center">
|
|
<Text size="sm" fw={500} c="edr-text">
|
|
Verification code
|
|
</Text>
|
|
<PinInput
|
|
length={6}
|
|
type="number"
|
|
oneTimeCode
|
|
value={otp}
|
|
placeholder="0"
|
|
disabled={submitting}
|
|
styles={{ input: { textAlign: "center" } }}
|
|
onChange={setOtp}
|
|
/>
|
|
</Stack>
|
|
|
|
{error ? (
|
|
<Alert color="red" variant="light" icon={<AlertCircle size={18} />}>
|
|
{error}
|
|
</Alert>
|
|
) : null}
|
|
|
|
<Group grow>
|
|
<Button
|
|
type="button"
|
|
variant="default"
|
|
leftSection={<ArrowLeft size={14} />}
|
|
disabled={submitting}
|
|
onClick={() => {
|
|
setNeedsMfa(false);
|
|
setOtp("");
|
|
setError(null);
|
|
}}
|
|
>
|
|
Back
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
color="edr-green"
|
|
loading={submitting}
|
|
disabled={otp.trim().length !== 6}
|
|
>
|
|
Verify
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</Box>
|
|
);
|
|
|
|
return <AuthShell>{!needsMfa ? loginForm : mfaForm}</AuthShell>;
|
|
};
|
|
|
|
export default LoginPage;
|