"use client"; import { useState } from "react"; import { useTranslations } from "next-intl"; import { useRouter, Link } from "@/i18n/routing"; import { apiPost, ApiClientError } from "@/lib/api/client"; import type { AuthTokenResponse } from "@/lib/api/types"; import { useAuthStore } from "@/lib/stores/auth.store"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { LabeledField } from "@/components/ui/labeled-field"; import { OtpInput } from "@/components/ui/otp-input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; type LoginTab = "otp" | "password"; export default function LoginPage() { const t = useTranslations("auth"); const router = useRouter(); const setAuth = useAuthStore((s) => s.setAuth); const [tab, setTab] = useState("otp"); // OTP state const [phone, setPhone] = useState("09121234567"); const [code, setCode] = useState(""); const [otpStep, setOtpStep] = useState<"phone" | "otp">("phone"); // Password state const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const authErrorMessage = (err: unknown) => { if (err instanceof ApiClientError) { switch (err.code) { case "RATE_LIMITED": return t("rateLimited"); case "SMS_FAILED": return t("smsFailed"); case "INVALID_OTP": return t("invalidOtp"); case "INVALID_TOKEN": case "NOT_FOUND": return tab === "password" ? t("invalidCredentials") : t("notFound"); default: return err.message; } } return err instanceof Error ? err.message : t("title"); }; const sendOtp = async () => { setLoading(true); setError(null); try { await apiPost("/api/auth/send-otp", { phone }); setOtpStep("otp"); } catch (e) { if (e instanceof ApiClientError && e.code === "NOT_FOUND") { // No account → take them to register with phone pre-filled router.push(`/register?phone=${encodeURIComponent(phone)}`); return; } setError(authErrorMessage(e)); } finally { setLoading(false); } }; const verifyOtp = async () => { setLoading(true); setError(null); try { const data = await apiPost("/api/auth/verify-otp", { phone, code, }); setAuth(data); router.push("/pos"); } catch (e) { setError(authErrorMessage(e)); } finally { setLoading(false); } }; const loginWithPassword = async () => { if (!username.trim() || !password) { setError(t("invalidCredentials")); return; } setLoading(true); setError(null); try { const data = await apiPost("/api/auth/login", { username: username.trim(), password, }); setAuth(data); router.push("/pos"); } catch (e) { setError(authErrorMessage(e)); } finally { setLoading(false); } }; const switchTab = (next: LoginTab) => { setTab(next); setError(null); setOtpStep("phone"); setCode(""); }; return (
{t("title")}

{t("subtitle")}

{/* Tab switcher */}
{/* ───── OTP tab ───── */} {tab === "otp" && otpStep === "phone" && (
{ e.preventDefault(); if (!loading) void sendOtp(); }} > setPhone(e.target.value)} placeholder={t("phonePlaceholder")} dir="ltr" className="text-end" autoComplete="tel" />
)} {tab === "otp" && otpStep === "otp" && (
{ e.preventDefault(); if (!loading) void verifyOtp(); }} >
)} {/* ───── Password tab ───── */} {tab === "password" && (
{ e.preventDefault(); if (!loading) void loginWithPassword(); }} > setUsername(e.target.value)} placeholder={t("usernamePlaceholder")} dir="ltr" className="text-start" autoComplete="username" autoFocus /> setPassword(e.target.value)} placeholder={t("passwordPlaceholder")} dir="ltr" className="text-start" autoComplete="current-password" />
)} {error && (

{error}

)}

{t("noAccount")}{" "} {t("registerLink")}

); }