style(auth): Apply updated design system guidelines to all authentication forms

This commit is contained in:
ghost2023
2026-05-28 13:47:36 +03:00
parent 6457b692b8
commit 10287b4bfd
5 changed files with 324 additions and 266 deletions

View File

@@ -21,7 +21,7 @@ export default function AuthLayout({ children, left }: AuthLayoutProps) {
return (
<div className="min-h-screen bg-background text-foreground">
<div className="grid min-h-screen lg:grid-cols-2">
<div className="relative hidden overflow-hidden bg-primary p-12 text-primary-foreground lg:flex lg:flex-col lg:justify-between">
<div className="relative hidden overflow-hidden bg-primary p-8 px-12 text-primary-foreground lg:flex lg:flex-col lg:justify-between">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgba(255,255,255,0.14),transparent_35%)]" />
<div className="relative z-10">
<div className="flex items-center gap-3">
@@ -29,16 +29,22 @@ export default function AuthLayout({ children, left }: AuthLayoutProps) {
<Train className="size-7" />
</div>
<div>
<h1 className="text-3xl font-black tracking-tight">EDR Freight</h1>
<p className="mt-1 text-sm opacity-80">Railway Logistics Platform</p>
<h1 className="text-xl font-bold">EDR Freight</h1>
<p className="mt-1 text-sm opacity-80">
Railway Logistics Platform
</p>
</div>
</div>
<div className="mt-20 max-w-lg">
<div className="mt-16 max-w-lg">
<div className="inline-flex rounded-full bg-white/10 px-4 py-2 text-sm font-semibold backdrop-blur">
{left.badge}
</div>
<h2 className="mt-6 text-5xl font-black leading-tight tracking-tight">{left.title}</h2>
<p className="mt-6 text-lg leading-8 opacity-85">{left.description}</p>
<h2 className="mt-6 text-4xl font-bold leading-tight tracking-tight">
{left.title}
</h2>
<p className="mt-6 text-lg leading-8 opacity-85">
{left.description}
</p>
</div>
<div className="mt-14 grid gap-5">
{left.features.map((item) => (
@@ -51,33 +57,21 @@ export default function AuthLayout({ children, left }: AuthLayoutProps) {
))}
</div>
</div>
<div className="relative z-10 rounded-[32px] border border-white/10 bg-white/10 p-6 backdrop-blur-xl">
<div className="flex items-center justify-between">
<div>
<p className="text-sm opacity-80">{left.stats.label}</p>
<h3 className="mt-2 text-4xl font-black">{left.stats.value}</h3>
</div>
<div className="rounded-2xl bg-white/10 px-4 py-2 text-sm font-semibold">{left.stats.footer}</div>
</div>
<div className="mt-6 h-3 overflow-hidden rounded-full bg-white/10">
<div className={`h-full rounded-full bg-white ${left.stats.progress}`} />
</div>
</div>
</div>
<div className="flex items-center justify-center p-6 md:p-10">
<div className="w-full max-w-xl">
<div className="w-full max-w-md">
<div className="mb-8 flex items-center gap-3 lg:hidden">
<div className="flex size-12 items-center justify-center rounded-2xl bg-primary text-primary-foreground">
<Train className="size-6" />
</div>
<div>
<h1 className="text-2xl font-bold">EDR Freight</h1>
<p className="text-sm text-muted-foreground">Railway Logistics Platform</p>
<p className="text-sm text-muted-foreground">
Railway Logistics Platform
</p>
</div>
</div>
<div className="rounded-[36px] border border-border bg-card p-8 shadow-2xl md:p-10">
{children}
</div>
<div>{children}</div>
</div>
</div>
</div>

View File

@@ -3,6 +3,14 @@ import { useNavigate } from "react-router-dom";
import { ArrowRight, Mail, Phone, Loader2 } from "lucide-react";
import useAuth from "@/hooks/useAuth";
import AuthLayout from "@/components/auth/AuthLayout";
import {
Button,
Input,
Field,
FieldLabel,
FieldError,
FieldGroup,
} from "@edr/ui-common";
type LoginMethod = "email" | "phone";
@@ -46,116 +54,117 @@ export default function LoginPage() {
"Enterprise-grade operations",
"Multi-corridor freight monitoring",
],
stats: { label: "Active Corridors", value: "24+", footer: "Operational", progress: "w-[95%]" },
stats: {
label: "Active Corridors",
value: "24+",
footer: "Operational",
progress: "w-[95%]",
},
}}
>
<div className="mb-8">
<div className="mb-4 flex size-16 items-center justify-center rounded-3xl bg-primary/10 text-primary">
<Mail className="size-8" />
<div className="mb-6">
<div className="mb-4 flex size-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
<Mail className="size-6" />
</div>
<h2 className="text-4xl font-black tracking-tight">Welcome back</h2>
<p className="mt-3 text-lg text-muted-foreground">
<h2 className="text-2xl font-black tracking-tight">Welcome back</h2>
<p className="mt-2 text-base text-muted-foreground">
Enter your credentials to access your portal
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div className="flex gap-2 rounded-lg bg-muted p-1">
<button
<Button
type="button"
variant={"ghost"}
size="sm"
onClick={() => setMethod("email")}
className={`flex flex-1 items-center justify-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-all ${
method === "email"
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground"
}`}
className={`flex-1 hover:bg-background/40! ${method === "email" ? "bg-background shadow border" : ""}`}
>
<Mail className="size-4" />
<Mail data-icon="inline-start" />
Email
</button>
<button
</Button>
<Button
type="button"
variant={"ghost"}
size="sm"
onClick={() => setMethod("phone")}
className={`flex flex-1 items-center justify-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-all ${
method === "phone"
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground"
}`}
className={`flex-1 hover:bg-background/40! ${method === "phone" ? "bg-background shadow border" : ""}`}
>
<Phone className="size-4" />
<Phone data-icon="inline-start" />
Phone
</button>
</Button>
</div>
<div>
<label className="mb-2 block text-sm font-semibold">
{method === "email" ? "Email Address" : "Phone Number"}
</label>
<input
type={method === "email" ? "email" : "tel"}
placeholder={method === "email" ? "name@company.com" : "+251..."}
value={identifier}
onChange={(e) => setIdentifier(e.target.value)}
required
disabled={loading}
className="h-13 w-full rounded-2xl border border-input bg-background px-4 outline-none transition focus:border-primary focus:ring-4 focus:ring-primary/10 disabled:opacity-60"
/>
</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
disabled={loading}
/>
</Field>
<div>
<div className="mb-2 flex items-center justify-between">
<label className="block text-sm font-semibold">Password</label>
<button
type="button"
className="text-xs text-primary hover:underline"
>
Forgot password?
</button>
</div>
<input
type="password"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
disabled={loading}
className="h-13 w-full rounded-2xl border border-input bg-background px-4 outline-none transition focus:border-primary focus:ring-4 focus:ring-primary/10 disabled:opacity-60"
/>
</div>
<Field>
<div className="flex items-center justify-between">
<FieldLabel>Password</FieldLabel>
<Button
type="button"
variant="link"
size="xs"
className="h-auto p-0"
>
Forgot password?
</Button>
</div>
<Input
type="password"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
disabled={loading}
/>
</Field>
</FieldGroup>
{error && (
<div className="rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
<div className="rounded-xl border border-red-200 bg-red-50 px-4 py-2.5 text-sm text-red-700">
{error}
</div>
)}
<button
type="submit"
disabled={loading}
className="flex h-14 w-full items-center justify-center gap-2 rounded-2xl bg-primary text-lg font-semibold text-primary-foreground shadow-lg transition hover:-translate-y-0.5 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
>
<Button type="submit" disabled={loading} size="lg" className="w-full">
{loading ? (
<>
<Loader2 className="size-5 animate-spin" />
<Loader2 className="animate-spin" data-icon="inline-start" />
Signing in...
</>
) : (
<>
Sign In
<ArrowRight className="size-5" />
<ArrowRight data-icon="inline-end" />
</>
)}
</button>
</Button>
<p className="text-center text-sm text-muted-foreground">
Don't have an account?{" "}
<button
<Button
type="button"
variant="link"
size="sm"
onClick={() => navigate("/signup")}
className="font-semibold text-primary hover:underline"
className="h-auto p-0 font-semibold"
>
Create an account
</button>
</Button>
</p>
</form>
</AuthLayout>

View File

@@ -3,9 +3,17 @@ import { useNavigate } from "react-router-dom";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { ArrowRight, Eye, EyeOff, LockKeyhole } from "lucide-react";
import { ArrowRight, Eye, EyeOff, LockKeyhole, Loader2 } from "lucide-react";
import useAuth from "@/hooks/useAuth";
import AuthLayout from "@/components/auth/AuthLayout";
import {
Button,
Input,
Field,
FieldLabel,
FieldError,
FieldGroup,
} from "@edr/ui-common";
const passwordSchema = z
.object({
@@ -72,81 +80,91 @@ export default function SetPasswordPage() {
stats: { label: "Security Protection", value: "256-bit", footer: "Encrypted", progress: "w-[98%]" },
}}
>
<div className="mb-8">
<div className="mb-4 flex size-16 items-center justify-center rounded-3xl bg-primary/10 text-primary">
<LockKeyhole className="size-8" />
<div className="mb-6">
<div className="mb-4 flex size-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
<LockKeyhole className="size-6" />
</div>
<h2 className="text-4xl font-black tracking-tight">Set Password</h2>
<p className="mt-3 text-lg text-muted-foreground">
Create a secure password for your EDR Freight account.
<h2 className="text-2xl font-black tracking-tight">Set Password</h2>
<p className="mt-2 text-base text-muted-foreground">
Create a secure password for your account.
</p>
</div>
{error && (
<div className="mb-6 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
<div className="mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-2.5 text-sm text-red-700">
{error}
</div>
)}
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<div>
<label className="mb-2 block text-sm font-semibold">Password</label>
<div className="relative">
<input
type={showPassword ? "text" : "password"}
placeholder="Enter password"
disabled={loading}
{...register("password")}
className="h-14 w-full rounded-2xl border border-input bg-background px-4 pr-14 outline-none transition focus:border-primary focus:ring-4 focus:ring-primary/10 disabled:opacity-60"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-muted-foreground"
>
{showPassword ? <EyeOff className="size-5" /> : <Eye className="size-5" />}
</button>
</div>
{errors.password && <p className="mt-1 text-sm text-red-500">{errors.password.message}</p>}
</div>
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4">
<FieldGroup>
<Field data-invalid={Boolean(errors.password)}>
<FieldLabel>Password</FieldLabel>
<div className="relative">
<Input
type={showPassword ? "text" : "password"}
placeholder="Enter password"
disabled={loading}
aria-invalid={Boolean(errors.password)}
className="pr-12"
{...register("password")}
/>
<Button
type="button"
variant="ghost"
size="icon-sm"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-1 top-1/2 -translate-y-1/2 text-muted-foreground"
>
{showPassword ? <EyeOff /> : <Eye />}
</Button>
</div>
<FieldError errors={[errors.password]} />
</Field>
<div>
<label className="mb-2 block text-sm font-semibold">Confirm Password</label>
<div className="relative">
<input
type={showConfirmPassword ? "text" : "password"}
placeholder="Confirm password"
disabled={loading}
{...register("confirmPassword")}
className="h-14 w-full rounded-2xl border border-input bg-background px-4 pr-14 outline-none transition focus:border-primary focus:ring-4 focus:ring-primary/10 disabled:opacity-60"
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-muted-foreground"
>
{showConfirmPassword ? <EyeOff className="size-5" /> : <Eye className="size-5" />}
</button>
</div>
{errors.confirmPassword && (
<p className="mt-1 text-sm text-red-500">{errors.confirmPassword.message}</p>
)}
</div>
<Field data-invalid={Boolean(errors.confirmPassword)}>
<FieldLabel>Confirm Password</FieldLabel>
<div className="relative">
<Input
type={showConfirmPassword ? "text" : "password"}
placeholder="Confirm password"
disabled={loading}
aria-invalid={Boolean(errors.confirmPassword)}
className="pr-12"
{...register("confirmPassword")}
/>
<Button
type="button"
variant="ghost"
size="icon-sm"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-1 top-1/2 -translate-y-1/2 text-muted-foreground"
>
{showConfirmPassword ? <EyeOff /> : <Eye />}
</Button>
</div>
<FieldError errors={[errors.confirmPassword]} />
</Field>
</FieldGroup>
<button
<Button
type="submit"
disabled={loading}
className="flex h-14 w-full items-center justify-center gap-2 rounded-2xl bg-primary text-lg font-semibold text-primary-foreground shadow-lg transition hover:-translate-y-0.5 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
size="lg"
className="w-full"
>
{loading ? (
"Saving..."
<>
<Loader2 className="animate-spin" data-icon="inline-start" />
Saving...
</>
) : (
<>
Save Password
<ArrowRight className="size-5" />
<ArrowRight data-icon="inline-end" />
</>
)}
</button>
</Button>
</form>
</AuthLayout>
);

View File

@@ -3,11 +3,19 @@ import { useNavigate } from "react-router-dom";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { ArrowRight, UserPlus } from "lucide-react";
import { ArrowRight, UserPlus, Loader2 } from "lucide-react";
import { userType } from "@/enums/userType";
import useAuth from "@/hooks/useAuth";
import type { SignupPayload } from "@/types/auth";
import AuthLayout from "@/components/auth/AuthLayout";
import {
Button,
Input,
Field,
FieldLabel,
FieldError,
FieldGroup,
} from "@edr/ui-common";
const userSchema = z.object({
email: z.string().email("Invalid email address"),
@@ -86,107 +94,115 @@ export default function SignupPage() {
stats: { label: "Active Corridors", value: "24+", footer: "Operational", progress: "w-[95%]" },
}}
>
<div className="mb-8">
<div className="mb-4 flex size-16 items-center justify-center rounded-3xl bg-primary/10 text-primary">
<UserPlus className="size-8" />
<div className="mb-6">
<div className="mb-4 flex size-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
<UserPlus className="size-6" />
</div>
<h2 className="text-4xl font-black tracking-tight">Create Account</h2>
<p className="mt-3 text-lg text-muted-foreground">
Register to access EDR Freight services and railway logistics operations.
<h2 className="text-2xl font-black tracking-tight">Create Account</h2>
<p className="mt-2 text-base text-muted-foreground">
Register to access EDR Freight services.
</p>
</div>
{error && (
<div className="mb-6 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
<div className="mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-2.5 text-sm text-red-700">
{error}
</div>
)}
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<div>
<label className="mb-2 block text-sm font-semibold">Full Name</label>
<input
type="text"
placeholder="John Doe"
disabled={loading}
{...register("name.en")}
className="h-13 w-full rounded-2xl border border-input bg-background px-4 outline-none transition focus:border-primary focus:ring-4 focus:ring-primary/10 disabled:opacity-60"
/>
{errors.name?.en && <p className="mt-1 text-sm text-red-500">{errors.name.en.message}</p>}
</div>
<div>
<label className="mb-2 block text-sm font-semibold">Username</label>
<input
type="text"
placeholder="john_doe"
disabled={loading}
{...register("username")}
className="h-13 w-full rounded-2xl border border-input bg-background px-4 outline-none transition focus:border-primary focus:ring-4 focus:ring-primary/10 disabled:opacity-60"
/>
{errors.username && <p className="mt-1 text-sm text-red-500">{errors.username.message}</p>}
</div>
<div>
<label className="mb-2 block text-sm font-semibold">Email Address</label>
<input
type="email"
placeholder="john@example.com"
disabled={loading}
{...register("email")}
className="h-13 w-full rounded-2xl border border-input bg-background px-4 outline-none transition focus:border-primary focus:ring-4 focus:ring-primary/10 disabled:opacity-60"
/>
{errors.email && <p className="mt-1 text-sm text-red-500">{errors.email.message}</p>}
</div>
<div>
<label className="mb-2 block text-sm font-semibold">Phone Number</label>
<div className="flex gap-2">
<input
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4">
<FieldGroup className="gap-4">
<Field data-invalid={Boolean(errors.name?.en)}>
<FieldLabel>Full Name</FieldLabel>
<Input
type="text"
placeholder="John Doe"
disabled={loading}
{...register("countryCode")}
className="h-13 w-28 rounded-2xl border border-input bg-background px-4 outline-none transition focus:border-primary focus:ring-4 focus:ring-primary/10 disabled:opacity-60"
aria-invalid={Boolean(errors.name?.en)}
{...register("name.en")}
/>
<input
type="tel"
placeholder="912345678"
disabled={loading}
{...register("phone")}
className="h-13 flex-1 rounded-2xl border border-input bg-background px-4 outline-none transition focus:border-primary focus:ring-4 focus:ring-primary/10 disabled:opacity-60"
/>
</div>
{(errors.countryCode || errors.phone) && (
<p className="mt-1 text-sm text-red-500">
{errors.countryCode?.message || errors.phone?.message}
</p>
)}
</div>
<FieldError errors={[errors.name?.en]} />
</Field>
<button
<div className="grid grid-cols-2 gap-4">
<Field data-invalid={Boolean(errors.username)}>
<FieldLabel>Username</FieldLabel>
<Input
type="text"
placeholder="john_doe"
disabled={loading}
aria-invalid={Boolean(errors.username)}
{...register("username")}
/>
<FieldError errors={[errors.username]} />
</Field>
<Field data-invalid={Boolean(errors.email)}>
<FieldLabel>Email Address</FieldLabel>
<Input
type="email"
placeholder="john@example.com"
disabled={loading}
aria-invalid={Boolean(errors.email)}
{...register("email")}
/>
<FieldError errors={[errors.email]} />
</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>
</FieldGroup>
<Button
type="submit"
disabled={loading}
className="flex h-14 w-full items-center justify-center gap-2 rounded-2xl bg-primary text-lg font-semibold text-primary-foreground shadow-lg transition hover:-translate-y-0.5 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
size="lg"
className="w-full"
>
{loading ? (
"Creating..."
<>
<Loader2 className="animate-spin" data-icon="inline-start" />
Creating...
</>
) : (
<>
Create Account
<ArrowRight className="size-5" />
<ArrowRight data-icon="inline-end" />
</>
)}
</button>
</Button>
<p className="text-center text-sm text-muted-foreground">
Already have an account?
<button
<Button
type="button"
variant="link"
size="sm"
onClick={() => navigate("/login")}
className="ml-2 font-semibold text-primary hover:underline"
className="h-auto p-0 font-semibold"
>
Sign In
</button>
</Button>
</p>
</form>
</AuthLayout>

View File

@@ -3,10 +3,18 @@ import { useNavigate } from "react-router-dom";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { ArrowRight, MailCheck, RotateCw } from "lucide-react";
import { ArrowRight, MailCheck, RotateCw, Loader2 } from "lucide-react";
import { verificationCodeType } from "@/enums/verificationCodeType";
import useAuth from "@/hooks/useAuth";
import AuthLayout from "@/components/auth/AuthLayout";
import {
Button,
Input,
Field,
FieldLabel,
FieldError,
FieldGroup,
} from "@edr/ui-common";
const otpSchema = z.object({
code: z.string().regex(/^\d{6}$/, "OTP must be exactly 6 digits"),
@@ -88,92 +96,105 @@ export default function VerificationOtpPage() {
stats: { label: "Verification Security", value: "99.9%", footer: "Protected", progress: "w-[99%]" },
}}
>
<div className="mb-8">
<div className="mb-4 flex size-16 items-center justify-center rounded-3xl bg-primary/10 text-primary">
<MailCheck className="size-8" />
<div className="mb-6">
<div className="mb-4 flex size-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
<MailCheck className="size-6" />
</div>
<h2 className="text-4xl font-black tracking-tight">OTP Verification</h2>
<p className="mt-3 text-lg text-muted-foreground">Enter the 6-digit code sent to:</p>
<div className="mt-4 rounded-2xl border border-border bg-muted/50 px-4 py-3">
<p className="font-semibold">{maskedPhone}</p>
<h2 className="text-2xl font-black tracking-tight">OTP Verification</h2>
<p className="mt-2 text-base text-muted-foreground">Enter the 6-digit code sent to:</p>
<div className="mt-3 rounded-xl border border-border bg-muted/50 px-4 py-2">
<p className="text-sm font-semibold">{maskedPhone}</p>
</div>
</div>
{error && (
<div className="mb-6 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
<div className="mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-2.5 text-sm text-red-700">
{error}
</div>
)}
{resentMessage && (
<div className="mb-6 rounded-2xl border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-700">
<div className="mb-4 rounded-xl border border-blue-200 bg-blue-50 px-4 py-2.5 text-sm text-blue-700">
{resentMessage}
</div>
)}
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<div>
<label className="mb-2 block text-sm font-semibold">Verification Code</label>
<input
type="text"
inputMode="numeric"
autoComplete="one-time-code"
maxLength={6}
placeholder="123456"
disabled={verifying}
{...register("code")}
className="h-16 w-full rounded-2xl border border-input bg-background px-5 text-center text-3xl font-black tracking-[12px] outline-none transition focus:border-primary focus:ring-4 focus:ring-primary/10 disabled:opacity-60"
/>
<div className="mt-2 flex items-center justify-between">
{errors.code ? (
<p className="text-sm text-red-500">{errors.code.message}</p>
) : (
<p className="text-sm text-muted-foreground">Enter the OTP sent to your phone</p>
)}
<span className="text-xs text-muted-foreground">{otpValue.length}/6</span>
</div>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4">
<FieldGroup>
<Field data-invalid={Boolean(errors.code)}>
<FieldLabel>Verification Code</FieldLabel>
<Input
type="text"
inputMode="numeric"
autoComplete="one-time-code"
maxLength={6}
placeholder="123456"
disabled={verifying}
aria-invalid={Boolean(errors.code)}
className="h-14 text-center text-2xl font-black tracking-[10px]"
{...register("code")}
/>
<div className="mt-1.5 flex items-center justify-between">
{errors.code ? (
<FieldError errors={[errors.code]} />
) : (
<p className="text-xs text-muted-foreground">Enter the OTP sent to your phone</p>
)}
<span className="text-xs text-muted-foreground">{otpValue.length}/6</span>
</div>
</Field>
</FieldGroup>
<button
<Button
type="submit"
disabled={verifying || otpValue.length !== 6}
className="flex h-14 w-full items-center justify-center gap-2 rounded-2xl bg-primary text-lg font-semibold text-primary-foreground shadow-lg transition hover:-translate-y-0.5 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
size="lg"
className="w-full"
>
{verifying ? (
"Verifying..."
<>
<Loader2 className="animate-spin" data-icon="inline-start" />
Verifying...
</>
) : (
<>
Verify Account
<ArrowRight className="size-5" />
<ArrowRight data-icon="inline-end" />
</>
)}
</button>
</Button>
<button
<Button
type="button"
variant="outline"
onClick={handleResend}
disabled={resending}
className="flex h-14 w-full items-center justify-center gap-2 rounded-2xl border border-border bg-background text-base font-semibold transition hover:bg-muted disabled:cursor-not-allowed disabled:opacity-60"
className="w-full"
>
{resending ? (
"Sending..."
<>
<Loader2 className="animate-spin" data-icon="inline-start" />
Sending...
</>
) : (
<>
<RotateCw className="size-5" />
<RotateCw data-icon="inline-start" />
Resend Code
</>
)}
</button>
</Button>
<p className="text-center text-sm text-muted-foreground">
Didn't receive the code?
<button
<Button
type="button"
variant="link"
size="sm"
onClick={handleResend}
className="ml-2 font-semibold text-primary hover:underline"
className="h-auto p-0 font-semibold"
>
Send again
</button>
</Button>
</p>
</form>
</AuthLayout>