geo/frontend/app/(dashboard)/onboarding/page.tsx

298 lines
7.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 { useState, useEffect, useCallback } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { Loader2 } from "lucide-react";
import { OnboardingProgress } from "./OnboardingProgress";
import { Step1BrandName } from "./Step1BrandName";
import { Step2Competitors } from "./Step2Competitors";
import { Step3Platforms } from "./Step3Platforms";
import { Step4HealthReport } from "./Step4HealthReport";
import { Step5ActionSuggestions } from "./Step5ActionSuggestions";
import { api } from "@/lib/api";
import type { BrandHealthReport } from "@/types/onboarding";
interface OnboardingState {
currentStep: number;
brandName: string;
competitors: string[];
platforms: string[];
frequency: "daily" | "weekly" | "monthly";
brandId: string | null;
healthReport: BrandHealthReport | null;
}
const initialState: OnboardingState = {
currentStep: 1,
brandName: "",
competitors: [],
platforms: [],
frequency: "weekly",
brandId: null,
healthReport: null,
};
export default function OnboardingPage() {
const { data: session, status } = useSession();
const router = useRouter();
const [state, setState] = useState<OnboardingState>(initialState);
const [isCreatingBrand, setIsCreatingBrand] = useState(false);
const [error, setError] = useState<string | null>(null);
// 检查用户是否已完成引导
useEffect(() => {
const checkOnboardingStatus = async () => {
if (!session?.accessToken) return;
try {
const data = await api.onboarding.checkOnboardingStatus(
session.accessToken,
) as { completed: boolean };
if (data.completed) {
router.push("/dashboard");
}
} catch (err) {
// 如果API不可用继续引导流程
console.error("检查引导状态失败:", err);
}
};
if (status === "authenticated") {
checkOnboardingStatus();
}
}, [session?.accessToken, status, router]);
// 创建品牌
const createBrand = useCallback(async () => {
if (!session?.accessToken || !state.brandName) return null;
try {
setIsCreatingBrand(true);
setError(null);
const data = await api.onboarding.createOnboardingBrand(
session.accessToken,
{
name: state.brandName,
competitors: state.competitors,
platforms: state.platforms,
frequency: state.frequency,
},
) as { brand_id: string };
return data.brand_id;
} catch (err) {
console.error("创建品牌失败:", err);
setError(err instanceof Error ? err.message : "创建品牌失败");
return null;
} finally {
setIsCreatingBrand(false);
}
}, [session?.accessToken, state.brandName, state.competitors, state.platforms, state.frequency]);
// Step 1: 品牌名称
const handleStep1Next = (brandName: string) => {
setState((prev) => ({
...prev,
brandName,
currentStep: 2,
}));
};
const handleStep1Skip = () => {
router.push("/dashboard");
};
// Step 2: 竞品
const handleStep2Next = (competitors: string[]) => {
setState((prev) => ({
...prev,
competitors,
currentStep: 3,
}));
};
const handleStep2Back = () => {
setState((prev) => ({
...prev,
currentStep: 1,
}));
};
const handleStep2Skip = async () => {
setState((prev) => ({
...prev,
competitors: [],
currentStep: 3,
}));
};
// Step 3: 平台
const handleStep3Next = async (platforms: string[], frequency: "daily" | "weekly" | "monthly") => {
const brandId = await createBrand();
if (brandId) {
setState((prev) => ({
...prev,
platforms,
frequency,
brandId,
currentStep: 4,
}));
}
};
const handleStep3Back = () => {
setState((prev) => ({
...prev,
currentStep: 2,
}));
};
const handleStep3Skip = async () => {
// 使用默认平台
const defaultPlatforms = ["wenxin", "kimi", "tongyi", "baidu_ai", "yuanbao", "qingyan", "doubao"];
const brandId = await createBrand();
if (brandId) {
setState((prev) => ({
...prev,
platforms: defaultPlatforms,
brandId,
currentStep: 4,
}));
}
};
// Step 4: 健康报告
const handleStep4Next = (report: BrandHealthReport) => {
setState((prev) => ({
...prev,
healthReport: report,
currentStep: 5,
}));
};
const handleStep4Back = () => {
setState((prev) => ({
...prev,
currentStep: 3,
}));
};
// Step 5: 行动建议
const handleStep5Complete = () => {
router.push("/dashboard");
};
const handleStep5Back = () => {
if (state.brandId) {
setState((prev) => ({
...prev,
currentStep: 4,
}));
}
};
const handleStep5Skip = () => {
router.push("/dashboard");
};
// 加载状态
if (status === "loading") {
return (
<div className="flex min-h-screen items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
</div>
);
}
// 未登录重定向
if (status === "unauthenticated") {
router.push("/login");
return null;
}
return (
<div className="min-h-screen bg-gradient-to-b from-background to-muted/20 px-4 py-8">
<div className="mx-auto max-w-4xl">
{/* 顶部 Logo */}
<div className="mb-8 text-center">
<h1 className="text-2xl font-bold">GEO </h1>
<p className="text-muted-foreground"></p>
</div>
{/* 进度指示器 */}
<div className="mb-8">
<OnboardingProgress currentStep={state.currentStep} />
</div>
{/* 错误提示 */}
{error && (
<div className="mb-4 rounded-lg bg-destructive/10 p-4 text-destructive text-center">
{error}
</div>
)}
{/* 创建品牌加载状态 */}
{isCreatingBrand && (
<div className="mb-4 rounded-lg bg-primary/10 p-4 text-center">
<Loader2 className="mx-auto h-6 w-6 animate-spin text-primary" />
<p className="mt-2 text-sm">...</p>
</div>
)}
{/* 步骤内容 */}
<div className="mt-8">
{state.currentStep === 1 && (
<Step1BrandName
initialValue={state.brandName}
onNext={handleStep1Next}
onSkip={handleStep1Skip}
/>
)}
{state.currentStep === 2 && (
<Step2Competitors
brandName={state.brandName}
initialCompetitors={state.competitors}
onNext={handleStep2Next}
onBack={handleStep2Back}
onSkip={handleStep2Skip}
/>
)}
{state.currentStep === 3 && (
<Step3Platforms
brandName={state.brandName}
competitors={state.competitors}
initialPlatforms={state.platforms}
onNext={handleStep3Next}
onBack={handleStep3Back}
onSkip={handleStep3Skip}
/>
)}
{state.currentStep === 4 && state.brandId && (
<Step4HealthReport
brandId={state.brandId}
brandName={state.brandName}
competitors={state.competitors}
platforms={state.platforms}
onNext={handleStep4Next}
onBack={handleStep4Back}
/>
)}
{state.currentStep === 5 && state.brandId && (
<Step5ActionSuggestions
brandId={state.brandId}
brandName={state.brandName}
onComplete={handleStep5Complete}
onBack={handleStep5Back}
onSkip={handleStep5Skip}
/>
)}
</div>
</div>
</div>
);
}