"use client"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { apiFetch, ApiError } from "@/lib/api"; import { getDeviceId, getDeviceLabel } from "@/lib/deviceId"; import VesselLogo from "@/components/ui/VesselLogo"; interface ActiveDevice { deviceId: string; label: string | null; lastUsedAt: string; } interface BusinessChoice { businessId: string; businessName: string; role: string; } const ROLE_LABELS: Record = { SUPER_ADMIN: "Owner", BRANCH_MANAGER: "Branch manager", DELIVERY: "Cashier", }; export default function LoginPage() { const router = useRouter(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); const [remember, setRemember] = useState(true); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [deviceConflict, setDeviceConflict] = useState(null); // Set when this email and password open accounts in more than one business. const [choices, setChoices] = useState(null); const [invited, setInvited] = useState(false); useEffect(() => { setInvited(new URLSearchParams(window.location.search).get("invited") === "1"); }, []); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); await signIn(); } async function signIn(businessId?: string) { setError(null); setDeviceConflict(null); setLoading(true); try { const result = await apiFetch<{ redirectTo: string }>("/api/auth/login", { method: "POST", body: JSON.stringify({ email, password, deviceId: getDeviceId(), deviceLabel: getDeviceLabel(), businessId, }), }); router.replace(result.redirectTo); return; } catch (err) { if (err instanceof ApiError && err.status === 300) { setChoices((err.body?.chooseBusiness as BusinessChoice[]) ?? []); } else if (err instanceof ApiError && err.status === 409) { setDeviceConflict((err.body?.activeDevices as ActiveDevice[]) ?? []); setError(err.message); } else if (err instanceof ApiError) { setError(err.message); } else { setError("Something went wrong. Please try again."); } } setLoading(false); } return (
{/* Brand lockup */}
vessel. erp

Login

Run every branch with confidence — sales, stock, M-Pesa and cash.

{choices && !deviceConflict ? (

Which business do you want to sign in to?

{error && choices.length === 0 &&

{error}

}
    {choices.map((c) => (
  • ))}
) : deviceConflict ? (

{error}

    {deviceConflict.map((d) => (
  • {d.label ?? "Unknown device"} · last used{" "} {new Date(d.lastUsedAt).toLocaleString()}
  • ))}
) : (
{/* Email */}
setEmail(e.target.value)} placeholder="you@company.com" className="tap-target w-full rounded-xl2 border border-depth-200 bg-white px-4 text-depth-900 placeholder:text-depth-400 shadow-sm transition focus:border-flow-500 focus:outline-none focus:ring-2 focus:ring-flow-500/25" />
{/* Password */}
setPassword(e.target.value)} className="tap-target w-full rounded-xl2 border border-depth-200 bg-white px-4 pr-11 text-depth-900 shadow-sm transition focus:border-flow-500 focus:outline-none focus:ring-2 focus:ring-flow-500/25" />
{/* Remember / forgot */}
Forgot password?
{invited && !error && (

Your password is set. Sign in with your email and new password.

)} {error && (

{error}

)} {/* Primary CTA — copper gradient pill */}
)}

New here?{" "} Create a business account

Water & eggs · multi-branch · one system.

); }