"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { login, webauthnAuthOptions, webauthnAuthVerify } from "@/lib/api"; import { useAuth } from "@/lib/auth"; import { useTranslation } from "@/lib/i18n"; export default function LoginPage() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); const [loading, setLoading] = useState(false); const [biometricLoading, setBiometricLoading] = useState(false); const [error, setError] = useState(""); const [biometricAvailable, setBiometricAvailable] = useState(false); const [savedEmail, setSavedEmail] = useState(""); const { setAuth } = useAuth(); const { t } = useTranslation(); const router = useRouter(); useEffect(() => { // Check if WebAuthn is available if (typeof window !== "undefined" && window.PublicKeyCredential) { window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable?.() .then(available => setBiometricAvailable(available)) .catch(() => {}); } // Load last used email for biometric const last = localStorage.getItem("taskteam_biometric_email"); if (last) setSavedEmail(last); }, []); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!email.trim()) { setError(t("auth.email")); return; } setLoading(true); setError(""); try { const result = await login({ email: email.trim(), password: password || undefined }); setAuth(result.data.token, result.data.user); router.push("/tasks"); } catch (err) { setError(err instanceof Error ? err.message : t("common.error")); } finally { setLoading(false); } } async function handleBiometricLogin() { const biometricEmail = email.trim() || savedEmail; if (!biometricEmail) { setError("Zadejte email pro biometricke prihlaseni"); return; } setBiometricLoading(true); setError(""); try { // Get auth options from server const optionsRes = await webauthnAuthOptions(biometricEmail); const options = optionsRes.data; // Create PublicKey credential request const allowCredentials = (options.allowCredentials as Array<{ id: string; type: string }>).map(cred => ({ id: base64urlToBuffer(cred.id), type: cred.type as PublicKeyCredentialType, })); const credential = await navigator.credentials.get({ publicKey: { challenge: base64urlToBuffer(options.challenge as string), allowCredentials, timeout: 60000, userVerification: "required" as UserVerificationRequirement, }, }) as PublicKeyCredential; if (!credential) throw new Error("Biometricke overeni selhalo"); // Send credential_id to server to get JWT const credentialId = bufferToBase64url(credential.rawId); const result = await webauthnAuthVerify(credentialId); // Save email for next time localStorage.setItem("taskteam_biometric_email", biometricEmail); setAuth(result.data.token, result.data.user); router.push("/tasks"); } catch (err) { const msg = err instanceof Error ? err.message : "Biometricke prihlaseni selhalo"; if (msg.includes("No biometric") || msg.includes("not found")) { setError("Pro tento ucet neni nastaveno biometricke prihlaseni. Nastavte ho v Nastaveni."); } else { setError(msg); } } finally { setBiometricLoading(false); } } return (

{t("auth.login")}

{error && (
{error}
)}
setEmail(e.target.value)} className="w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" placeholder={t("auth.emailPlaceholder")} autoFocus autoComplete="email" />
setPassword(e.target.value)} className="w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none pr-10" placeholder="******" autoComplete="current-password" />
{/* Biometric Login */} {biometricAvailable && ( <>
nebo
{savedEmail && !email && (

Posledni ucet: {savedEmail}

)} )}
{t("auth.forgotPassword")}

{t("auth.noAccount")}{" "} {t("auth.registerBtn")}

); } // --- WebAuthn buffer helpers --- function base64urlToBuffer(base64url: string): ArrayBuffer { const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/"); const pad = base64.length % 4 === 0 ? "" : "=".repeat(4 - (base64.length % 4)); const binary = atob(base64 + pad); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); return bytes.buffer; } function bufferToBase64url(buffer: ArrayBuffer): string { const bytes = new Uint8Array(buffer); let binary = ""; for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]); return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); }