You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
4.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"use client";
import { useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import { AppLayout } from "@/components/layout/app-layout";
import { QuoteRouteHub } from "@/components/embed/quote-route-hub";
import { EmbedDemoLogin } from "@/components/embed/embed-demo-login";
import { SecondaryButton } from "@/components/ui/primary-button";
import { Skeleton } from "@/components/ui/skeleton";
import { ErrorBanner } from "@/components/ui/error-banner";
import {
isEmbedHostedShell,
parseEmbedEntryModule,
parseEmbedSsoParams,
stripEmbedSsoQueryFromUrl,
} from "@/lib/embed/sso-params";
import {
embedDemoLogin,
embedDemoLoginWithKey,
embedDemoLogout,
embedDemoMe,
type EmbedDemoSession,
} from "@/lib/frontend/api-client";
import { HostBridgeProvider } from "@/lib/embed/host-bridge-react";
import type { ChajiaModuleId } from "@/lib/embed/host-bridge";
export function EmbedDemoClient() {
const searchParams = useSearchParams();
const [booting, setBooting] = useState(true);
const [session, setSession] = useState<EmbedDemoSession | null>(null);
const [bootError, setBootError] = useState<string | null>(null);
const [hostedShell, setHostedShell] = useState(false);
const [entryModule, setEntryModule] = useState<ChajiaModuleId | null>(null);
useEffect(() => {
let cancelled = false;
void (async () => {
const params = new URLSearchParams(searchParams.toString());
const sso = parseEmbedSsoParams(params);
const hosted = isEmbedHostedShell(params);
const entry = parseEmbedEntryModule(params);
if (!cancelled) {
setHostedShell(hosted);
setEntryModule(entry);
}
// 宿主直传凭证:自动登录,跳过登录页(勿先 logout)
if (sso.mode === "api_key") {
const res = await embedDemoLoginWithKey("", sso.apiKey);
if (cancelled) return;
if (res.code === 0) {
stripEmbedSsoQueryFromUrl();
setSession(res.data);
setBootError(null);
setBooting(false);
return;
}
setBootError(res.message || "API Key 无效,无法自动登录");
setBooting(false);
return;
}
if (sso.mode === "password") {
const res = await embedDemoLogin("", sso.customerId, sso.password);
if (cancelled) return;
if (res.code === 0) {
stripEmbedSsoQueryFromUrl();
setSession(res.data);
setBootError(null);
setBooting(false);
return;
}
setBootError(res.message || "客户编号或密码错误,无法自动登录");
setBooting(false);
return;
}
// 无 SSO:尝试恢复已有 cookie 会话(刷新不掉登录)
const me = await embedDemoMe("");
if (cancelled) return;
if (me.code === 0) {
setSession(me.data);
setBootError(null);
setBooting(false);
return;
}
setSession(null);
setBooting(false);
})();
return () => {
cancelled = true;
};
}, [searchParams]);
const handleLogout = async () => {
await embedDemoLogout("");
setSession(null);
setBootError(null);
setHostedShell(false);
};
if (booting) {
return (
<AppLayout title="宿主嵌入演示">
<Skeleton className="h-64 w-full" />
</AppLayout>
);
}
if (!session) {
return (
<AppLayout title="宿主嵌入演示">
{bootError ? (
<div className="mb-4">
<ErrorBanner>{bootError}</ErrorBanner>
</div>
) : null}
<EmbedDemoLogin
onSuccess={(data) => {
setSession(data);
setBootError(null);
}}
/>
</AppLayout>
);
}
// 宿主嵌入/正式查价:不展示登录态外壳、账号区与说明卡,只留查价区
const quoteBody = (
<HostBridgeProvider initialModule={entryModule}>
<QuoteRouteHub
customerId={session.customer_id}
apiBaseUrl=""
hideChrome
/>
</HostBridgeProvider>
);
if (hostedShell) {
return <div className="min-h-[100dvh] bg-bg p-2 sm:p-3">{quoteBody}</div>;
}
return (
<AppLayout title="宿主嵌入演示">
<div className="mb-3 flex justify-end">
<SecondaryButton onClick={() => void handleLogout()}>
退出登录
</SecondaryButton>
</div>
{quoteBody}
</AppLayout>
);
}