"use client"; import Link from "next/link"; import { signIn } from "next-auth/react"; import { useRouter } from "next/navigation"; import { useRef, useState } from "react"; export default function SignInPage() { const router = useRouter(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [totp, setTotp] = useState(""); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const [mfaRequired, setMfaRequired] = useState(false); const totpInputRef = useRef(null); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setLoading(true); setError(""); const result = await signIn("credentials", { email, password, ...(mfaRequired ? { totp } : {}), redirect: false, }); if (result?.error) { // Auth.js v5: CredentialsSignin subclasses forward their `code` via // SignInResponse.code (and sometimes also as result.error). // Check both fields for compatibility across beta versions. const code = result.code ?? result.error; if (code === "MFA_REQUIRED_SETUP") { // User's role requires MFA but it hasn't been set up yet — redirect to setup setLoading(false); router.push("/account/security?mfa_required=1"); return; } if (code === "MFA_REQUIRED") { setMfaRequired(true); setLoading(false); // Focus the TOTP input after render setTimeout(() => totpInputRef.current?.focus(), 100); return; } if (code === "INVALID_TOTP") { setError("Invalid verification code. Please try again."); setTotp(""); setLoading(false); return; } setError("Invalid email or password"); // Reset MFA state on credential error if (mfaRequired) { setMfaRequired(false); setTotp(""); } } else { // Full-page navigation instead of router.push to guarantee a fresh // server request with the new session cookie. Soft navigation keeps // the React tree (incl. QueryClient with cached pre-auth errors and // the Next.js Router Cache) alive, which caused the recurring bug // where the dashboard rendered with empty widgets until the user // pressed Ctrl+R. Skipping setLoading(false) prevents a visual flash // while the navigation happens. window.location.assign("/dashboard"); return; } setLoading(false); } function handleBackToLogin() { setMfaRequired(false); setTotp(""); setError(""); } return (
CapaKraken Control Center

Resource planning that stays readable under pressure.

Estimates, staffing, chargeability, and timelines in one workspace with sharper structure for day-to-day planning.

Visibility

Clearer data density, stronger contrast, faster scanning.

Planning

Dynamic staffing, resources, and chargeability in one flow.

Control

Theme-aware UI that works in bright and dark environments.

Welcome Back

{mfaRequired ? "Two-Factor Authentication" : "Sign in to CapaKraken"}

{mfaRequired ? "Enter the 6-digit code from your authenticator app." : "Resource Planning, staffing, and forecasting."}

{error && (
{error}
)} {!mfaRequired && ( <>
setEmail(e.target.value)} className="app-input" placeholder="you@company.com" required />
Forgot password?
setPassword(e.target.value)} className="app-input" placeholder="--------" required autoComplete="current-password" />
)} {mfaRequired && (
setTotp(e.target.value.replace(/\D/g, "").slice(0, 6))} className="app-input text-center text-2xl font-mono tracking-[0.4em]" placeholder="000000" required />

Open your authenticator app (e.g. Google Authenticator, Authy) and enter the current code.

)} {mfaRequired && ( )}
); }