"use client"; import Link from "next/link"; import { useState } from "react"; import { Bell, CheckCircle2, Database, KeyRound, Loader2, Mail, MessageSquare, Plus, ScrollText, Send, ServerCog, ShieldAlert, ShieldBan, ShieldCheck, ShieldOff, Trash2, XCircle, type LucideIcon, } from "lucide-react"; import { authErrorMessage } from "@/hooks/use-auth"; import { useAcceptUnbanRequest, useCreateDbViewerSession, useCreateIpBan, useCurrentUser, useDeleteAccount, useDeleteIpBan, useIpBans, useRejectUnbanRequest, useSecurityEvents, useSystemLogs, useSystemSecrets, useSystemStatus, useUnbanRequests, useUserApiKeys, } from "@/hooks/use-auth"; import { useCompanies } from "@/hooks/use-companies"; import { useCreateNotificationDestination, useDeleteNotificationDestination, useNotificationDestinations, useTestNotificationDestination, useUpdateNotificationDestination, } from "@/hooks/use-notification-destinations"; import { FormField } from "@/components/ui/form-field"; import { Select } from "@/components/ui/select"; import { SystemSecretRow } from "@/components/ui/system-secret-row"; import { UserApiKeyRow } from "@/components/ui/user-api-key-row"; import { isLocalConvenience } from "@/lib/auth"; import { formatDateTime } from "@/lib/format"; import { SEVERITY_LABELS, SEVERITY_LEVELS, type LinkedCompany, type LogCategory, type NotificationType, type SeverityLevel, } from "@/lib/types"; function TestResultBadge({ result }: { result: { success: boolean; error: string | null } }) { return ( {result.success ? "Test sent" : result.error} ); } function LinkedCompanyChips({ companies }: { companies: LinkedCompany[] }) { if (companies.length === 0) { return (

Not linked to any company — will be removed automatically.

); } return (
{companies.map((company) => ( {company.name} ))}
); } function DestinationRow({ destination, }: { destination: { id: string; type: NotificationType; destination_value: string; enabled: boolean; verified: boolean; minimum_severity: SeverityLevel; companies: LinkedCompany[]; }; }) { const update = useUpdateNotificationDestination(); const remove = useDeleteNotificationDestination(); const test = useTestNotificationDestination(); const Icon = destination.type === "sms" ? MessageSquare : Mail; return (
  • {destination.destination_value}

    {destination.type} · min severity {destination.minimum_severity}

    update.mutate({ destinationId: destination.id, payload: { enabled: e.target.checked }, }) } className="focus-ring rounded border-slate-300" /> Enabled
    {test.data && (
    )}
  • ); } function AddDestinationForm() { const [type, setType] = useState("email"); const [value, setValue] = useState(""); const [minimumSeverity, setMinimumSeverity] = useState("medium"); const [companyIds, setCompanyIds] = useState>(new Set()); const create = useCreateNotificationDestination(); const { data: systemStatus } = useSystemStatus(); const { data: companies } = useCompanies(); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (companyIds.size === 0) return; create.mutate( { type, destination_value: value, minimum_severity: minimumSeverity, company_ids: [...companyIds], }, { onSuccess: () => setValue("") }, ); }; const toggleCompany = (id: string) => { setCompanyIds((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; return (
    setMinimumSeverity(v as SeverityLevel)} options={SEVERITY_LEVELS.map((s) => ({ value: s, label: SEVERITY_LABELS[s] }))} triggerClassName="px-3 py-2 text-sm" />
    {companies && companies.length > 0 ? (

    Companies to notify for *

    {companies.map((c) => { const selected = companyIds.has(c.id); return ( ); })}
    ) : (

    Add a company first — destinations must be linked to at least one.

    )} {create.isError &&

    {authErrorMessage(create.error)}

    } {type === "sms" && systemStatus && !systemStatus.sms_enabled && (

    SMS delivery is temporarily disabled while carrier registration (10DLC) for our sending number completes. You can still add your number now — it'll start receiving alerts automatically once registration finishes and delivery is re-enabled.

    )}
    ); } const LOG_CATEGORY_STYLES: Record = { internal_error: { dot: "bg-red-500", label: "Internal error", text: "text-red-700" }, api_error: { dot: "bg-amber-500", label: "API error", text: "text-amber-700" }, important: { dot: "bg-blue-500", label: "Important", text: "text-blue-700" }, normal: { dot: "bg-slate-300", label: "Normal", text: "text-slate-500" }, }; function LoggingBox() { const { data: logs, isLoading } = useSystemLogs(); return (
    Logging

    A live feed of what the application is doing, most recent first.

    {isLoading ? (

    Loading…

    ) : logs && logs.length > 0 ? (
      {logs.map((log, i) => { const style = LOG_CATEGORY_STYLES[log.category]; return (
    • {formatDateTime(log.ts)} {log.logger} {log.event}
    • ); })}
    ) : (

    No log entries yet.

    )}
    ); } function UserApiKeysBox() { const { data: apiKeys, isLoading } = useUserApiKeys(); const { data: systemStatus } = useSystemStatus(); return (
    Your API keys

    Set your own key for each provider below to power discovery, enrichment, and monitoring for your companies.

    {isLoading ? (

    Loading…

    ) : (
    {apiKeys?.map((k) => ( ))}
    )}
    ); } function ServerSecretsBox() { const { data: secrets, isLoading } = useSystemSecrets(); return (
    Server secrets

    Server-wide configuration shared by every visitor: Cloudflare Turnstile CAPTCHA and the Resend API key used for transactional security email.

    {isLoading ? (

    Loading…

    ) : (
    {secrets?.map((s) => )}
    )}
    ); } const SECURITY_EVENT_LABELS: Record = { login_success: "Signed in", login_failed: "Failed sign-in attempt", account_locked: "Account locked", password_reset_requested: "Password reset requested", password_reset_completed: "Password reset completed", email_verification_sent: "Verification email sent", email_verified: "Email verified", server_secret_updated: "Server secret updated", api_key_updated: "API key updated", }; const SECURITY_EVENT_DOT: Record = { login_success: "bg-green-500", login_failed: "bg-amber-500", account_locked: "bg-red-500", password_reset_requested: "bg-blue-500", password_reset_completed: "bg-blue-500", email_verification_sent: "bg-slate-300", email_verified: "bg-green-500", server_secret_updated: "bg-purple-500", api_key_updated: "bg-purple-500", }; function AccountActivityBox() { const { data: events, isLoading } = useSecurityEvents(); return (
    Account activity

    Recent sign-ins and security events for your account.

    {isLoading ? (

    Loading…

    ) : events && events.length > 0 ? (
      {events.map((event, i) => (
    • {formatDateTime(event.created_at)} {SECURITY_EVENT_LABELS[event.event_type] ?? event.event_type} {event.ip_address}
    • ))}
    ) : (

    No activity yet.

    )}
    ); } function ActionButton({ onClick, pending, disabled, colorClass, icon: Icon, label, }: { onClick: () => void; pending: boolean; disabled?: boolean; colorClass: string; icon: LucideIcon; label: string; }) { return ( ); } function BanIpForm() { const [ipAddress, setIpAddress] = useState(""); const createBan = useCreateIpBan(); const handleBan = () => { if (!ipAddress.trim()) return; createBan.mutate({ ip_address: ipAddress.trim() }, { onSuccess: () => setIpAddress("") }); }; return (
    setIpAddress(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") handleBan(); }} placeholder="e.g. 203.0.113.42" className="focus-ring block w-full rounded-md border border-slate-300 px-3 py-1.5 font-mono text-sm text-slate-900 shadow-sm transition-colors duration-150 placeholder:font-sans placeholder:text-slate-400" /> {createBan.isError && (

    {authErrorMessage(createBan.error)}

    )}
    ); } function IpBansBox() { const { data: bans, isLoading: bansLoading } = useIpBans(); const { data: unbanRequests, isLoading: requestsLoading } = useUnbanRequests(); const deleteBan = useDeleteIpBan(); const acceptRequest = useAcceptUnbanRequest(); const rejectRequest = useRejectUnbanRequest(); return (
    IP bans

    IPs permanently blocked after repeated abuse, and pending unban requests.

    Ban an IP manually

    Banned IPs

    {bansLoading ? (

    Loading…

    ) : bans && bans.length > 0 ? (
      {bans.map((ban) => (
    • {ban.ip_address}

      {ban.reason} · banned {formatDateTime(ban.banned_at)}

      deleteBan.mutate(ban.ip_address)} pending={deleteBan.isPending && deleteBan.variables === ban.ip_address} colorClass="bg-red-600 hover:bg-red-700" icon={ShieldOff} label="Unban" />
    • ))}
    ) : (

    No IPs currently banned.

    )}

    Unban requests

    {requestsLoading ? (

    Loading…

    ) : unbanRequests && unbanRequests.length > 0 ? (
      {unbanRequests.map((req) => (
    • {req.ip_address} {formatDateTime(req.created_at)} {req.message &&

      {req.message}

      }
      acceptRequest.mutate(req.id)} pending={acceptRequest.isPending && acceptRequest.variables === req.id} disabled={rejectRequest.isPending} colorClass="bg-green-600 hover:bg-green-700" icon={CheckCircle2} label="Unban" /> rejectRequest.mutate(req.id)} pending={rejectRequest.isPending && rejectRequest.variables === req.id} disabled={acceptRequest.isPending} colorClass="bg-red-600 hover:bg-red-700" icon={XCircle} label="Reject" />
    • ))}
    ) : (

    No unban requests.

    )}
    ); } function DatabaseViewerBox() { const { data: systemStatus } = useSystemStatus(); const createSession = useCreateDbViewerSession(); const [error, setError] = useState(null); const isLocal = systemStatus ? isLocalConvenience(systemStatus) : false; const dbViewerUrl = process.env.NEXT_PUBLIC_DB_VIEWER_URL; const handleOpen = async () => { setError(null); try { const { token } = await createSession.mutateAsync(); window.open(`${dbViewerUrl}/_auth?token=${encodeURIComponent(token)}`, "_blank", "noopener,noreferrer"); } catch { setError("Couldn't open the database viewer. Try again."); } }; return (
    Database

    Open a web-based Postgres client (Adminer) in a new tab to view and edit rows directly. PostgreSQL is pre-selected - just enter the database password to sign in. Local developers and server admins only.

    {isLocal ? ( Open database viewer ) : dbViewerUrl ? ( ) : (

    Not configured for this deployment.

    )} {error &&

    {error}

    }
    ); } function DeleteAccountBox() { const { data: systemStatus } = useSystemStatus(); const [password, setPassword] = useState(""); const deleteAccount = useDeleteAccount(); const isLocal = systemStatus ? isLocalConvenience(systemStatus) : false; // The local-dev bypass account has no password_hash at all (it never // registers/logs in) - there's nothing to type to confirm deletion, and // deleting it would just get silently re-created on the next request. if (isLocal) return null; return (
    Delete account

    Permanently deletes your account and everything in it - companies, monitoring history, reports, and API keys. This can't be undone.

    { e.preventDefault(); deleteAccount.mutate({ password }); }} className="mt-4 flex flex-wrap items-end gap-2" >
    setPassword(e.target.value)} className="focus-ring mt-1 block w-full rounded-md border border-red-300 px-3 py-2 text-sm text-slate-900 shadow-sm transition-colors duration-150" />
    {deleteAccount.isError && (

    {authErrorMessage(deleteAccount.error)}

    )}
    ); } export default function SettingsPage() { const { data: user } = useCurrentUser(); const { data: systemStatus } = useSystemStatus(); const { data: destinations, isLoading } = useNotificationDestinations(); return (

    Settings

    Manage where alerts are delivered and check system configuration.

    Notification destinations

    Alerts are dispatched to every enabled destination whose minimum severity is met.

    {isLoading ? (

    Loading…

    ) : destinations && destinations.length > 0 ? (
      {destinations.map((d) => ( ))}
    ) : (

    No notification destinations yet — add one above.

    )}
    System configuration
    Account
    {user?.email ?? "—"}
    Auth mode
    {systemStatus?.auth_mode ?? "—"}
    LLM provider
    {systemStatus?.llm_provider ?? "—"}
    Search provider
    {systemStatus?.search_provider ?? "—"}
    SMS delivery
    {systemStatus?.sms_enabled ? "Enabled" : "Disabled"} {systemStatus?.sms_enabled && ` (${systemStatus.sms_provider})`}
    Company enrichment (NinjaPear)
    {systemStatus?.ninjapear_configured ? systemStatus.ninjapear_credit_balance !== null ? `Enabled — ${systemStatus.ninjapear_credit_balance} credits remaining` : "Enabled — credit balance unavailable" : "Not configured"}
    {systemStatus?.components.map((c) => (
    {c.name}
    {c.status}
    ))}
    {user?.is_admin && ( <> )}
    ); }