"use client"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { FormEvent, Suspense, useEffect, useMemo, useState } from "react"; import { ArrowRight, Building2, ShieldCheck, Stethoscope } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Select } from "@/components/ui/select"; import type { ApiResult } from "@/modules/core/server/api"; import type { Organization } from "@/modules/core/types"; type AuthMode = "login" | "register" | "setup"; type AuthPanelProps = { mode: AuthMode; }; const modeCopy: Record = { login: { title: "登录", badge: "Welcome back", submit: "进入系统" }, register: { title: "注册账号", badge: "Staff account", submit: "创建账号" }, setup: { title: "初始化机构", badge: "First run", submit: "完成初始化" }, }; async function readApiResult>(response: Response): Promise> { const text = await response.text(); if (!text) { return { success: false, reason: response.ok ? "服务器返回了空响应" : `请求失败:${response.status}`, }; } try { return JSON.parse(text) as ApiResult; } catch { return { success: false, reason: response.ok ? "服务器返回了无法解析的数据" : `请求失败:${response.status}`, }; } } function AuthPanelContent({ mode }: AuthPanelProps): React.ReactElement { const router = useRouter(); const searchParams = useSearchParams(); const redirectTo = searchParams.get("redirectTo") ?? "/app/dashboard"; const invitationToken = searchParams.get("invite") ?? ""; const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); const [hasAccounts, setHasAccounts] = useState(null); const [organizations, setOrganizations] = useState([]); const copy = modeCopy[mode]; const isSetup = mode === "setup"; const isLogin = mode === "login"; useEffect(() => { let isMounted = true; async function loadState(): Promise { const [bootstrapResponse, sessionResponse, organizationsResponse] = await Promise.all([ fetch("/api/auth/bootstrap"), fetch("/api/auth/session"), fetch("/api/organizations"), ]); const bootstrap = await readApiResult<{ setupRequired: boolean }>(bootstrapResponse); const session = await readApiResult<{ account: unknown | null }>(sessionResponse); const organizationsResult = await readApiResult<{ organizations: Organization[] }>(organizationsResponse); if (!isMounted || !bootstrap.success || !session.success) { if (isMounted) { setMessage(!bootstrap.success ? bootstrap.reason : session.reason); } return; } if (organizationsResult.success) { setOrganizations(organizationsResult.organizations); } const initialized = !bootstrap.setupRequired; setHasAccounts(initialized); if (mode !== "setup" && !initialized) { router.replace("/setup"); return; } if (mode === "setup" && initialized) { router.replace("/login"); return; } if (session.account) { router.replace("/app/dashboard"); } } void loadState(); return () => { isMounted = false; }; }, [mode, router]); const secondaryAction = useMemo(() => { if (isSetup) { return null; } if (isLogin) { return { href: "/register", label: "注册" }; } return { href: "/login", label: "登录" }; }, [isLogin, isSetup]); async function handleSubmit(event: FormEvent): Promise { event.preventDefault(); setMessage(""); setIsPending(true); const formData = new FormData(event.currentTarget); const name = String(formData.get("name") ?? ""); const email = String(formData.get("email") ?? ""); const password = String(formData.get("password") ?? ""); const organization = String(formData.get("organization") ?? ""); const organizationId = String(formData.get("organizationId") ?? ""); const endpoint = isLogin ? "/api/auth/login" : isSetup ? "/api/auth/setup" : "/api/auth/register"; const response = await fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ name, email, password, organization, organizationId, invitationToken, }), }); const result = await readApiResult<{ account: unknown; pendingApproval?: boolean }>(response); setIsPending(false); if (!result.success) { setMessage(result.reason); return; } if (result.pendingApproval) { setMessage(result.reason); return; } router.refresh(); router.replace(isLogin ? redirectTo : "/app/dashboard"); } if (hasAccounts === null) { return
; } return (
TeaTea 康护
{copy.badge}

{copy.title}

简洁、稳定、面向机构照护。

档案 护理 预警
{isSetup ? (

{copy.title}

{isSetup ? ( ) : null} {mode === "register" && invitationToken ? (

正在使用机构邀请注册,完成后将自动加入机构。

) : null} {mode === "register" && !invitationToken && organizations.length > 0 ? ( ) : null} {message ? (

{message}

) : null}
{secondaryAction ? (
{secondaryAction.label}
) : null}
); } export function AuthPanel({ mode }: AuthPanelProps): React.ReactElement { return ( }> ); }