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

292 lines
7.1 KiB
TypeScript

"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 { Step0HealthScore } from "./Step0HealthScore";
import { Step1BrandName } from "./Step1BrandName";
import { Step2Competitors } from "./Step2Competitors";
import { Step3Platforms } from "./Step3Platforms";
import { Step4HealthReport } from "./Step4HealthReport";
import { Step5ActionSuggestions } from "./Step5ActionSuggestions";
import { useOnboardingData } from "@/lib/hooks/use-onboarding-data";
import type { BrandHealthReport, OnboardingState } from "@/types/onboarding";
import type { HealthScoreResponse } from "@/lib/api/health-score";
const initialState: OnboardingState = {
currentStep: 0,
brandName: "",
competitors: [],
platforms: [],
frequency: "weekly",
brandId: null,
healthReport: null,
preCheckResult: null,
};
export default function OnboardingPage() {
const { status } = useSession();
const router = useRouter();
const [state, setState] = useState<OnboardingState>(initialState);
const {
isCompleted,
isLoading: isStatusLoading,
createBrand: hookCreateBrand,
isCreatingBrand,
mutationError,
} = useOnboardingData();
const error = mutationError?.message ?? null;
useEffect(() => {
if (isCompleted) {
router.push("/dashboard");
}
}, [isCompleted, router]);
const createBrand = useCallback(async () => {
if (!state.brandName) return null;
const brandId = await hookCreateBrand({
name: state.brandName,
competitors: state.competitors,
platforms: state.platforms,
frequency: state.frequency,
});
return brandId;
}, [
hookCreateBrand,
state.brandName,
state.competitors,
state.platforms,
state.frequency,
]);
const handleStep0Next = (
brandName: string,
healthScore: HealthScoreResponse | null,
) => {
setState((prev) => ({
...prev,
brandName,
preCheckResult: healthScore,
currentStep: 1,
}));
};
const handleStep1Next = (brandName: string) => {
setState((prev) => ({
...prev,
brandName,
currentStep: 2,
}));
};
const handleStep1Skip = () => {
router.push("/dashboard");
};
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,
}));
};
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,
}));
}
};
const handleStep4Next = (report: BrandHealthReport) => {
setState((prev) => ({
...prev,
healthReport: report,
currentStep: 5,
}));
};
const handleStep4Back = () => {
setState((prev) => ({
...prev,
currentStep: 3,
}));
};
const handleStep5Complete = () => {
router.push("/dashboard");
};
const handleStep5Back = () => {
if (state.brandId) {
setState((prev) => ({
...prev,
currentStep: 4,
}));
}
};
const handleStep5Skip = () => {
router.push("/dashboard");
};
if (status === "loading" || isStatusLoading) {
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") {
if (state.currentStep === 0) {
// Step0 不需要登录
} else {
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">
<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 === 0 && (
<Step0HealthScore
initialBrandName={state.brandName}
onNext={handleStep0Next}
/>
)}
{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
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>
);
}