Files
boss/src/components/enterprise-admin-login-shell.tsx
2026-05-17 02:20:08 +08:00

249 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
);
}