'use client'; /** * LoginPage — Tenant admin sign-in page for the SentryAgent developer portal. * * Posts credentials to `POST /api/tenants/login` on the AgentIdP backend and * stores the returned JWT in localStorage so that protected pages (e.g. * /analytics) can read it via the `useAuth` hook. * * @module app/login/page */ import React, { useState, type FormEvent } from 'react'; import { useRouter } from 'next/navigation'; import { AUTH_TOKEN_KEY } from '@/hooks/useAuth'; /** Shape of the successful login response from the AgentIdP API. */ interface LoginResponse { access_token: string; } /** * Renders the portal login form and handles credential submission. * * @returns JSX element */ export default function LoginPage(): React.ReactElement { const router = useRouter(); const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000'; const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(null); const [submitting, setSubmitting] = useState(false); /** * Submits the login form and stores the JWT on success. * * @param e - The form submission event */ async function handleSubmit(e: FormEvent): Promise { e.preventDefault(); setError(null); setSubmitting(true); try { const res = await fetch(`${apiUrl}/api/tenants/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); if (!res.ok) { const body = (await res.json().catch(() => ({}))) as { message?: string }; setError(body.message ?? 'Invalid credentials. Please try again.'); return; } const data = (await res.json()) as LoginResponse; localStorage.setItem(AUTH_TOKEN_KEY, data.access_token); router.replace('/analytics'); } catch { setError('Network error. Please check your connection and try again.'); } finally { setSubmitting(false); } } return (

Sign in

Access your SentryAgent tenant dashboard

void handleSubmit(e)} className="rounded-2xl border border-slate-200 bg-white p-8 shadow-sm" >
setEmail(e.target.value)} className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-brand-500 focus:outline-none focus:ring-1 focus:ring-brand-500" placeholder="admin@example.com" />
setPassword(e.target.value)} className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-brand-500 focus:outline-none focus:ring-1 focus:ring-brand-500" />
{error !== null && (

{error}

)}
); }