Split Login page using tailwind css & shadcn
import { useState } from "react"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
import { Link, useNavigate } from "react-router-dom"
import { toast } from "sonner"
import { Loader2, Eye, EyeOff, CheckCircle2, Zap } from "lucide-react"
import { Button } from "@hms/shared/components/ui/button"
import { Input } from "@hms/shared/components/ui/input"
import { Label } from "@hms/shared/components/ui/label"
const loginSchema = z.object({
email: z.string().email("Please enter a valid email address"),
password: z.string().min(6, "Password must be at least 6 characters"),
})
type LoginFormValues = z.infer<typeof loginSchema>
export default function SplitLogin() {
const navigate = useNavigate()
const [showPassword, setShowPassword] = useState(false)
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<LoginFormValues>({
resolver: zodResolver(loginSchema),
})
async function onSubmit(data: LoginFormValues) {
await new Promise((r) => setTimeout(r, 1000))
localStorage.setItem("access_token", "mock-token")
localStorage.setItem(
"user",
JSON.stringify({ email: data.email, name: data.email.split("@")[0], role: "PATIENT" })
)
toast.success("Welcome back!")
navigate("/dashboard")
}
return (
<div className="flex min-h-svh">
{/* Left Panel - Dark Branding */}
<div className="hidden lg:flex lg:w-1/2 bg-zinc-950 text-white flex-col justify-between p-10">
<div>
<h1 className="text-3xl font-bold tracking-tight">Welcome Back</h1>
<p className="mt-2 text-zinc-400">Sign in to continue to your workspace</p>
</div>
<div className="space-y-6">
<div className="flex items-start gap-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-zinc-800">
<CheckCircle2 className="h-5 w-5 text-zinc-300" />
</div>
<div>
<h3 className="font-semibold">Secure Access</h3>
<p className="text-sm text-zinc-400">
Your data is protected with enterprise-grade security
</p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-zinc-800">
<Zap className="h-5 w-5 text-zinc-300" />
</div>
<div>
<h3 className="font-semibold">Fast Performance</h3>
<p className="text-sm text-zinc-400">
Lightning-fast access to all your resources
</p>
</div>
</div>
</div>
<p className="text-sm text-zinc-500">
© {new Date().getFullYear()} Desk. All rights reserved.
</p>
</div>
{/* Right Panel - Login Form */}
<div className="flex w-full lg:w-1/2 items-center justify-center bg-white p-6 sm:p-10">
<div className="w-full max-w-md space-y-8">
<div>
<h2 className="text-2xl font-bold text-zinc-900">Sign In</h2>
<p className="mt-1 text-sm text-zinc-500">
Enter your credentials to access your account
</p>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
<div className="space-y-2">
<Label htmlFor="email" className="text-zinc-700">
Email
</Label>
<Input
id="email"
type="email"
placeholder="name@example.com"
autoComplete="email"
className="h-11 rounded-lg border-zinc-300 bg-white text-zinc-900 placeholder:text-zinc-400 focus-visible:border-zinc-900 focus-visible:ring-zinc-900/20"
aria-invalid={!!errors.email}
{...register("email")}
/>
{errors.email && (
<p className="text-sm text-destructive">{errors.email.message}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="password" className="text-zinc-700">
Password
</Label>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Enter your password"
autoComplete="current-password"
className="h-11 rounded-lg border-zinc-300 bg-white pr-10 text-zinc-900 placeholder:text-zinc-400 focus-visible:border-zinc-900 focus-visible:ring-zinc-900/20"
aria-invalid={!!errors.password}
{...register("password")}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-zinc-600"
tabIndex={-1}
aria-label={showPassword ? "Hide password" : "Show password"}
>
{showPassword ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</button>
</div>
{errors.password && (
<p className="text-sm text-destructive">{errors.password.message}</p>
)}
</div>
<Button
type="submit"
size="lg"
className="w-full h-11 rounded-lg bg-zinc-900 text-white hover:bg-zinc-800"
disabled={isSubmitting}
>
{isSubmitting && <Loader2 className="animate-spin" />}
{isSubmitting ? "Signing in..." : "Sign In"}
</Button>
</form>
<p className="text-sm text-zinc-500 text-center">
Don't have an account?{" "}
<Link to="/auth/register" className="text-zinc-900 font-medium hover:underline">
Register
</Link>
</p>
</div>
</div>
</div>
)
}
Example output
