mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 21:08:12 +00:00
refactor login page to simplify identifier normalization and remove unused login methods
This commit is contained in:
@@ -1,11 +1,7 @@
|
||||
import { type FormEvent, useState } from "react";
|
||||
import { parsePhoneNumberFromString } from "libphonenumber-js";
|
||||
import {
|
||||
Eye,
|
||||
EyeOff,
|
||||
Mail,
|
||||
Smartphone,
|
||||
UserRound,
|
||||
ArrowUpRight,
|
||||
Globe,
|
||||
ChevronDown,
|
||||
@@ -14,65 +10,15 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { useAuth } from "@/auth/useAuth";
|
||||
|
||||
type LoginMode = "email" | "phone" | "username";
|
||||
|
||||
const loginModes: Array<{
|
||||
value: LoginMode;
|
||||
label: string;
|
||||
icon: typeof Mail;
|
||||
placeholder: string;
|
||||
}> = [
|
||||
{
|
||||
value: "email",
|
||||
label: "Email",
|
||||
icon: Mail,
|
||||
placeholder: "name@company.com",
|
||||
},
|
||||
{
|
||||
value: "phone",
|
||||
label: "Phone",
|
||||
icon: Smartphone,
|
||||
placeholder: "09XXXXXXXX",
|
||||
},
|
||||
{
|
||||
value: "username",
|
||||
label: "Username",
|
||||
icon: UserRound,
|
||||
placeholder: "username",
|
||||
},
|
||||
];
|
||||
|
||||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
const usernamePattern = /^[a-zA-Z0-9._-]{3,32}$/;
|
||||
|
||||
const normalizeIdentifier = (mode: LoginMode, value: string) => {
|
||||
const trimmed = value.trim();
|
||||
|
||||
if (mode === "email") {
|
||||
if (!emailPattern.test(trimmed.toLowerCase())) {
|
||||
throw new Error("Enter a valid email address.");
|
||||
}
|
||||
|
||||
return trimmed.toLowerCase();
|
||||
/** 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}`;
|
||||
}
|
||||
|
||||
if (mode === "phone") {
|
||||
const parsed = parsePhoneNumberFromString(trimmed, "ET");
|
||||
|
||||
if (!parsed?.isValid()) {
|
||||
throw new Error("Enter a valid Ethiopian phone number.");
|
||||
}
|
||||
|
||||
return parsed.number;
|
||||
}
|
||||
|
||||
if (!usernamePattern.test(trimmed)) {
|
||||
throw new Error(
|
||||
"Username must be 3-32 characters and use letters, numbers, ., _, or -.",
|
||||
);
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
return v.toLowerCase();
|
||||
};
|
||||
|
||||
const LOGIN_IMAGE = "/assets/login.png";
|
||||
@@ -214,7 +160,6 @@ const FormFooter = () => (
|
||||
const LoginPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const { login, verifyMfa } = useAuth();
|
||||
const [mode, setMode] = useState<LoginMode>("email");
|
||||
const [identifier, setIdentifier] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [otp, setOtp] = useState("");
|
||||
@@ -224,15 +169,13 @@ const LoginPage = () => {
|
||||
const [normalizedIdentifier, setNormalizedIdentifier] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const currentMode = loginModes.find((item) => item.value === mode)!;
|
||||
|
||||
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const normalized = normalizeIdentifier(mode, identifier);
|
||||
const normalized = normaliseIdentifier(identifier);
|
||||
setNormalizedIdentifier(normalized);
|
||||
|
||||
const result = await login({ email: normalized, password });
|
||||
@@ -284,32 +227,14 @@ const LoginPage = () => {
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-gray-800">
|
||||
Sign in method
|
||||
</label>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={mode}
|
||||
onChange={(event) => setMode(event.target.value as LoginMode)}
|
||||
className={`${fieldClass} appearance-none pr-10`}
|
||||
>
|
||||
{loginModes.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown className="pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-gray-800">
|
||||
{currentMode.label} <span className="text-red-500">*</span>
|
||||
Email or Phone <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={identifier}
|
||||
onChange={(event) => setIdentifier(event.target.value)}
|
||||
placeholder={currentMode.placeholder}
|
||||
placeholder="name@company.com or 09XXXXXXXX"
|
||||
autoComplete="username"
|
||||
className={fieldClass}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user