feat(auth): Implement phone number login and introduce PhoneInput component

This commit is contained in:
ghost2023
2026-05-28 13:49:49 +03:00
parent 10287b4bfd
commit edfd885b4c
3 changed files with 81 additions and 33 deletions

View File

@@ -0,0 +1,43 @@
import { Field, FieldLabel, FieldError, Input } from "@edr/ui-common";
interface PhoneInputProps {
disabled?: boolean;
countryCode?: React.ComponentProps<typeof Input>;
phone?: React.ComponentProps<typeof Input>;
countryCodeError?: { message?: string };
phoneError?: { message?: string };
label?: string;
}
export default function PhoneInput({
disabled,
countryCode: countryCodeProps,
phone: phoneProps,
countryCodeError,
phoneError,
label = "Phone Number",
}: PhoneInputProps) {
return (
<Field data-invalid={Boolean(countryCodeError || phoneError)}>
<FieldLabel>{label}</FieldLabel>
<div className="flex gap-2">
<Input
type="text"
disabled={disabled}
className="w-20"
aria-invalid={Boolean(countryCodeError)}
{...countryCodeProps}
/>
<Input
type="tel"
placeholder="912345678"
disabled={disabled}
className="flex-1"
aria-invalid={Boolean(phoneError)}
{...phoneProps}
/>
</div>
<FieldError errors={[countryCodeError, phoneError]} />
</Field>
);
}

View File

@@ -11,6 +11,7 @@ import {
FieldError,
FieldGroup,
} from "@edr/ui-common";
import PhoneInput from "@/components/auth/PhoneInput";
type LoginMethod = "email" | "phone";
@@ -19,6 +20,8 @@ export default function LoginPage() {
const { login } = useAuth();
const [method, setMethod] = useState<LoginMethod>("email");
const [identifier, setIdentifier] = useState("");
const [countryCode, setCountryCode] = useState("+251");
const [phoneNumber, setPhoneNumber] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
@@ -28,7 +31,10 @@ export default function LoginPage() {
setError(null);
setLoading(true);
try {
const result = await login({ email: identifier, password });
const loginId = method === "email"
? identifier
: `${countryCode}${phoneNumber.startsWith("0") ? phoneNumber.slice(1) : phoneNumber}`;
const result = await login({ email: loginId, password });
if (result.success) {
navigate("/");
} else {
@@ -97,19 +103,31 @@ export default function LoginPage() {
</div>
<FieldGroup>
<Field>
<FieldLabel>
{method === "email" ? "Email Address" : "Phone Number"}
</FieldLabel>
<Input
type={method === "email" ? "email" : "tel"}
placeholder={method === "email" ? "name@company.com" : "+251..."}
value={identifier}
onChange={(e) => setIdentifier(e.target.value)}
required
{method === "email" ? (
<Field>
<FieldLabel>Email Address</FieldLabel>
<Input
type="email"
placeholder="name@company.com"
value={identifier}
onChange={(e) => setIdentifier(e.target.value)}
required
disabled={loading}
/>
</Field>
) : (
<PhoneInput
disabled={loading}
countryCode={{
value: countryCode,
onChange: (e: React.ChangeEvent<HTMLInputElement>) => setCountryCode(e.target.value),
}}
phone={{
value: phoneNumber,
onChange: (e: React.ChangeEvent<HTMLInputElement>) => setPhoneNumber(e.target.value),
}}
/>
</Field>
)}
<Field>
<div className="flex items-center justify-between">

View File

@@ -8,6 +8,7 @@ import { userType } from "@/enums/userType";
import useAuth from "@/hooks/useAuth";
import type { SignupPayload } from "@/types/auth";
import AuthLayout from "@/components/auth/AuthLayout";
import PhoneInput from "@/components/auth/PhoneInput";
import {
Button,
Input,
@@ -150,27 +151,13 @@ export default function SignupPage() {
</Field>
</div>
<Field data-invalid={Boolean(errors.countryCode || errors.phone)}>
<FieldLabel>Phone Number</FieldLabel>
<div className="flex gap-2">
<Input
type="text"
disabled={loading}
className="w-20"
aria-invalid={Boolean(errors.countryCode)}
{...register("countryCode")}
/>
<Input
type="tel"
placeholder="912345678"
disabled={loading}
className="flex-1"
aria-invalid={Boolean(errors.phone)}
{...register("phone")}
/>
</div>
<FieldError errors={[errors.countryCode, errors.phone]} />
</Field>
<PhoneInput
disabled={loading}
countryCode={{ ...register("countryCode") }}
phone={{ ...register("phone") }}
countryCodeError={errors.countryCode}
phoneError={errors.phone}
/>
</FieldGroup>
<Button