249 lines
9.8 KiB
TypeScript
249 lines
9.8 KiB
TypeScript
"use client";
|
||
|
||
import { useRouter } from "next/navigation";
|
||
import { useState } from "react";
|
||
import {
|
||
isNativeBossApp,
|
||
persistNativeSessionSnapshot,
|
||
} from "@/lib/boss-app-client";
|
||
|
||
type LoginResult = {
|
||
ok: boolean;
|
||
message: string;
|
||
account?: string;
|
||
displayName?: string;
|
||
sessionExpiresAt?: string;
|
||
restoreToken?: string;
|
||
};
|
||
|
||
function resolvePostLoginPath() {
|
||
return window.location.hostname === "admin.boss.hyzq.net"
|
||
? "/"
|
||
: "/conversations";
|
||
}
|
||
|
||
async function waitForLoginSessionReady(nativeClient: boolean) {
|
||
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||
const response = await fetch("/api/auth/session", {
|
||
cache: "no-store",
|
||
headers: nativeClient ? { "x-boss-native-app": "1" } : undefined,
|
||
}).catch(() => null);
|
||
if (response?.ok) return true;
|
||
await new Promise((resolve) => window.setTimeout(resolve, 120));
|
||
}
|
||
return false;
|
||
}
|
||
|
||
export function EnterpriseAdminLoginShell() {
|
||
const router = useRouter();
|
||
const [account, setAccount] = useState("");
|
||
const [password, setPassword] = useState("");
|
||
const [remember, setRemember] = useState(true);
|
||
const [message, setMessage] = useState("");
|
||
const [submitting, setSubmitting] = useState(false);
|
||
|
||
async function submit() {
|
||
if (!account.trim() || !password) {
|
||
setMessage("请填写账号和密码。");
|
||
return;
|
||
}
|
||
|
||
setSubmitting(true);
|
||
setMessage("");
|
||
const nativeClient = await isNativeBossApp();
|
||
|
||
try {
|
||
const response = await fetch("/api/auth/login", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
...(nativeClient ? { "x-boss-native-app": "1" } : {}),
|
||
},
|
||
body: JSON.stringify({
|
||
account,
|
||
password,
|
||
method: "password",
|
||
}),
|
||
});
|
||
|
||
const result = (await response.json()) as LoginResult;
|
||
if (!result.ok) {
|
||
setMessage(result.message);
|
||
return;
|
||
}
|
||
|
||
if (
|
||
nativeClient &&
|
||
result.restoreToken &&
|
||
result.account &&
|
||
result.displayName &&
|
||
result.sessionExpiresAt
|
||
) {
|
||
await persistNativeSessionSnapshot({
|
||
restoreToken: result.restoreToken,
|
||
account: result.account,
|
||
displayName: result.displayName,
|
||
expiresAt: result.sessionExpiresAt,
|
||
lastSyncedAt: new Date().toISOString(),
|
||
}).catch(() => undefined);
|
||
}
|
||
|
||
await waitForLoginSessionReady(nativeClient);
|
||
const targetPath = resolvePostLoginPath();
|
||
router.replace(targetPath, { scroll: false });
|
||
router.refresh();
|
||
window.setTimeout(() => {
|
||
if (window.location.pathname !== targetPath) {
|
||
window.location.replace(targetPath);
|
||
}
|
||
}, 180);
|
||
} catch {
|
||
setMessage("登录链路发生异常,请重试。");
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<main className="min-h-[100dvh] bg-[#eef7f1] px-5 py-6 text-[#102418] md:px-10 md:py-10">
|
||
<div className="mx-auto flex min-h-[calc(100dvh-48px)] max-w-6xl overflow-hidden rounded-[34px] border border-white/80 bg-white shadow-[0_24px_80px_rgba(16,36,24,0.12)]">
|
||
<section className="relative hidden flex-1 flex-col justify-between overflow-hidden bg-[#e9f8f0] px-12 py-12 lg:flex">
|
||
<div className="absolute -left-20 top-12 h-72 w-72 rounded-full bg-[#c8f5dd] blur-3xl" />
|
||
<div className="absolute -bottom-24 right-4 h-80 w-80 rounded-full bg-white/70 blur-2xl" />
|
||
<div className="relative">
|
||
<div className="flex items-center gap-4">
|
||
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-[#12c66a] text-[22px] font-black text-white shadow-[0_16px_36px_rgba(18,198,106,0.28)]">
|
||
B
|
||
</div>
|
||
<div>
|
||
<div className="text-[25px] font-black tracking-[-0.04em]">
|
||
Boss 企业管理后台
|
||
</div>
|
||
<div className="mt-1 text-[14px] font-medium text-[#66746c]">
|
||
统一管理企业账号、电脑节点、Skill 与风险
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-16 max-w-xl">
|
||
<div className="inline-flex rounded-full border border-[#bfead1] bg-white/72 px-4 py-2 text-[13px] font-semibold text-[#0b8f4a]">
|
||
平台级权限 · 企业账号 · 设备治理 · 风险审计
|
||
</div>
|
||
<h1 className="mt-7 text-[48px] font-black leading-[1.08] tracking-[-0.06em] text-[#102418]">
|
||
企业级电脑与 Agent 统一治理入口
|
||
</h1>
|
||
<p className="mt-5 text-[17px] leading-8 text-[#607269]">
|
||
面向 To B 交付场景,集中完成企业开通、账号授权、设备接入、Skill 分发与风险处置。
|
||
</p>
|
||
</div>
|
||
|
||
<div className="mt-10 grid max-w-xl grid-cols-3 gap-3">
|
||
{["企业开通", "设备授权", "风险治理"].map((item) => (
|
||
<div
|
||
key={item}
|
||
className="rounded-2xl border border-white/80 bg-white/72 px-4 py-4 text-[15px] font-bold text-[#17372a] shadow-sm"
|
||
>
|
||
{item}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="relative rounded-3xl border border-white/80 bg-white/70 p-5 text-[13px] leading-6 text-[#617168]">
|
||
仅限授权管理员访问。所有登录行为会进入审计链路,用于企业安全、客户成功和异常追踪。
|
||
</div>
|
||
</section>
|
||
|
||
<section className="flex w-full items-center justify-center px-5 py-8 md:px-10 lg:w-[470px]">
|
||
<div className="w-full max-w-[390px]">
|
||
<div className="mb-9 lg:hidden">
|
||
<div className="flex h-13 w-13 items-center justify-center rounded-2xl bg-[#12c66a] text-[20px] font-black text-white">
|
||
B
|
||
</div>
|
||
<h1 className="mt-5 text-[30px] font-black tracking-[-0.04em]">
|
||
Boss 企业管理后台
|
||
</h1>
|
||
<p className="mt-2 text-[14px] leading-6 text-[#66746c]">
|
||
统一管理企业账号、电脑节点、Skill 与风险
|
||
</p>
|
||
</div>
|
||
|
||
<div className="rounded-[28px] border border-[#dfe9e3] bg-white p-6 shadow-[0_18px_48px_rgba(16,36,24,0.08)] md:p-8">
|
||
<div>
|
||
<div className="text-[28px] font-black tracking-[-0.04em]">
|
||
登录企业后台
|
||
</div>
|
||
<p className="mt-2 text-[14px] leading-6 text-[#66746c]">
|
||
仅限授权管理员访问
|
||
</p>
|
||
</div>
|
||
|
||
<form
|
||
className="mt-8 space-y-5"
|
||
onSubmit={(event) => {
|
||
event.preventDefault();
|
||
void submit();
|
||
}}
|
||
>
|
||
<label className="block">
|
||
<span className="text-[13px] font-bold text-[#42554a]">账号</span>
|
||
<input
|
||
value={account}
|
||
onChange={(event) => setAccount(event.target.value)}
|
||
placeholder="输入管理员账号"
|
||
autoComplete="username"
|
||
className="mt-2 h-13 w-full rounded-2xl border border-[#dfe9e3] bg-[#f8fbf9] px-4 text-[16px] text-[#102418] outline-none transition focus:border-[#12c66a] focus:bg-white focus:ring-4 focus:ring-[#12c66a]/10"
|
||
/>
|
||
</label>
|
||
|
||
<label className="block">
|
||
<span className="text-[13px] font-bold text-[#42554a]">密码</span>
|
||
<input
|
||
value={password}
|
||
onChange={(event) => setPassword(event.target.value)}
|
||
placeholder="输入登录密码"
|
||
type="password"
|
||
autoComplete="current-password"
|
||
className="mt-2 h-13 w-full rounded-2xl border border-[#dfe9e3] bg-[#f8fbf9] px-4 text-[16px] text-[#102418] outline-none transition focus:border-[#12c66a] focus:bg-white focus:ring-4 focus:ring-[#12c66a]/10"
|
||
/>
|
||
</label>
|
||
|
||
<div className="flex items-center justify-between gap-3">
|
||
<label className="flex cursor-pointer items-center gap-2 text-[13px] font-semibold text-[#526258]">
|
||
<input
|
||
checked={remember}
|
||
onChange={(event) => setRemember(event.target.checked)}
|
||
type="checkbox"
|
||
className="h-4 w-4 accent-[#12c66a]"
|
||
/>
|
||
记住登录状态
|
||
</label>
|
||
<span className="text-[12px] text-[#8b9990]">HTTPS 安全会话</span>
|
||
</div>
|
||
|
||
<button
|
||
type="submit"
|
||
disabled={submitting}
|
||
className="flex h-13 w-full items-center justify-center rounded-2xl bg-[#12c66a] text-[16px] font-black text-white shadow-[0_16px_32px_rgba(18,198,106,0.22)] transition hover:bg-[#0fb85f] disabled:cursor-not-allowed disabled:bg-[#9adfba]"
|
||
>
|
||
{submitting ? "登录中..." : "登录"}
|
||
</button>
|
||
</form>
|
||
|
||
{message ? (
|
||
<div className="mt-5 rounded-2xl border border-[#bfead1] bg-[#eefaf3] px-4 py-3 text-[13px] leading-6 text-[#1c6b3e]">
|
||
{message}
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
|
||
<div className="mt-6 rounded-2xl border border-[#e0e9e4] bg-white/74 px-4 py-4 text-[12px] leading-6 text-[#6f7d75]">
|
||
登录代表你正在访问企业级管理后台。请确认账号权限来自企业或平台管理员授权。
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</main>
|
||
);
|
||
}
|