64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
const STEPS = [
|
|
{ id: "destinations", emoji: "🌍", title: "探索目的地", desc: "筛选城市、收藏与对比" },
|
|
{ id: "matcher", emoji: "🎯", title: "智能匹配", desc: "问卷推荐最适合你的城市" },
|
|
{ id: "trip", emoji: "🗓️", title: "规划行程", desc: "多城串联并估算预算" },
|
|
{ id: "tools", emoji: "🛠️", title: "用好工具", desc: "签证、保险、网速与账本" },
|
|
];
|
|
|
|
export default function OnboardingTour() {
|
|
const [open, setOpen] = useState(false);
|
|
const [step, setStep] = useState(0);
|
|
|
|
useEffect(() => {
|
|
if (localStorage.getItem("nomadro-onboarded")) return;
|
|
const t = setTimeout(() => setOpen(true), 2500);
|
|
return () => clearTimeout(t);
|
|
}, []);
|
|
|
|
const finish = () => {
|
|
localStorage.setItem("nomadro-onboarded", "1");
|
|
setOpen(false);
|
|
};
|
|
|
|
const next = () => {
|
|
if (step >= STEPS.length - 1) {
|
|
finish();
|
|
return;
|
|
}
|
|
const s = STEPS[step];
|
|
if (s.id === "matcher") {
|
|
window.dispatchEvent(new CustomEvent("open-matcher"));
|
|
} else {
|
|
document.getElementById(s.id)?.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
setStep((n) => n + 1);
|
|
};
|
|
|
|
if (!open) return null;
|
|
|
|
const current = STEPS[step];
|
|
|
|
return (
|
|
<div className="onboard-card" role="dialog" aria-label="新手引导">
|
|
<button className="onboard-skip" onClick={finish}>跳过</button>
|
|
<div className="onboard-step">
|
|
<span className="onboard-emoji">{current.emoji}</span>
|
|
<strong>{current.title}</strong>
|
|
<p>{current.desc}</p>
|
|
<div className="onboard-dots">
|
|
{STEPS.map((_, i) => (
|
|
<span key={i} className={i === step ? "active" : ""} />
|
|
))}
|
|
</div>
|
|
<button className="btn btn-primary btn-sm" onClick={next}>
|
|
{step >= STEPS.length - 1 ? "开始探索 🚀" : "下一步 →"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|