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