feat: 匹配成功页引导未注册用户注册,保存决策记录

- 未注册用户在匹配成功页看到注册引导卡片
- 注册后自动保存本次决策记录,收藏按钮同步出现
- 将 isRegistered() 调用改为 registered 响应式状态
- 更新 ROADMAP 标记已完成
This commit is contained in:
2026-02-26 14:18:32 +08:00
parent 4e60dc3cde
commit 26656f1e01
2 changed files with 49 additions and 7 deletions
+1 -1
View File
@@ -39,7 +39,7 @@
- `viewport-fit=cover` 适配刘海屏
### 首次体验引导优化
- 极速救场完成一轮后引导注册("注册保存记录")
- ~~极速救场完成一轮后引导注册("注册保存记录")~~(已完成,匹配成功页展示注册卡片,注册后自动保存记录)
- 盲盒模式先展示 demo / 动画,让用户看到价值再引导注册
- 统一两个模式的登录体验(目前极速救场不需登录,盲盒必须登录)
+48 -6
View File
@@ -20,11 +20,13 @@ import {
Share2,
Zap,
Heart,
UserPlus,
} from "lucide-react";
import { Restaurant, MatchType, RunnerUp, SceneType } from "@/types";
import { Restaurant, MatchType, RunnerUp, SceneType, UserProfile } from "@/types";
import { fireCelebration, playChime } from "@/lib/celebrate";
import { isRegistered } from "@/lib/userId";
import ShareCardModal from "@/components/ShareCardModal";
import AuthModal from "@/components/AuthModal";
interface MatchResultProps {
restaurant: Restaurant;
@@ -193,6 +195,8 @@ export default function MatchResult({
const isUnanimous = matchType === "unanimous";
const [favorited, setFavorited] = useState(false);
const [favLoading, setFavLoading] = useState(false);
const [registered, setRegistered] = useState(() => isRegistered());
const [showAuth, setShowAuth] = useState(false);
const showToast = useCallback((msg: string) => {
setToast(msg);
@@ -212,7 +216,7 @@ export default function MatchResult({
useEffect(() => {
if (historySavedRef.current) return;
if (!isRegistered()) return;
if (!registered) return;
if (matchType === "no_match") return;
historySavedRef.current = true;
@@ -227,14 +231,20 @@ export default function MatchResult({
participants: userCount,
}),
}).catch(() => {});
}, [userId, roomId, restaurant, matchType, userCount]);
}, [registered, userId, roomId, restaurant, matchType, userCount]);
const handleOpenShareCard = useCallback(() => {
setShowShareCard(true);
}, []);
const handleAuth = useCallback((profile: UserProfile) => {
setRegistered(true);
setShowAuth(false);
showToast(`欢迎,${profile.username}!记录已保存`);
}, [showToast]);
const handleFavorite = useCallback(async () => {
if (!isRegistered() || favorited || favLoading) return;
if (!registered || favorited || favLoading) return;
setFavLoading(true);
try {
const res = await fetch("/api/user/favorite", {
@@ -250,7 +260,7 @@ export default function MatchResult({
/* ignore */
}
setFavLoading(false);
}, [userId, restaurant, favorited, favLoading, showToast]);
}, [registered, userId, restaurant, favorited, favLoading, showToast]);
if (matchType === "no_match") {
return <NoMatchResult onReset={onReset} resetting={resetting} />;
@@ -344,7 +354,7 @@ export default function MatchResult({
animate={{ y: 0, opacity: 1 }}
transition={{ type: "spring", stiffness: 180, damping: 18, delay: 0.5 }}
>
{isRegistered() && (
{registered && (
<motion.button
onClick={handleFavorite}
disabled={favLoading}
@@ -466,6 +476,31 @@ export default function MatchResult({
</motion.button>
</motion.div>
{/* Registration nudge */}
{!registered && (
<motion.div
className="mt-5 w-full rounded-2xl bg-surface/80 p-4 ring-1 ring-border/50 backdrop-blur-sm"
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.75 }}
>
<p className="text-sm font-medium text-gray-300">
</p>
<p className="mt-1 text-xs text-muted">
+ 10
</p>
<motion.button
onClick={() => setShowAuth(true)}
className="mt-3 flex h-10 w-full items-center justify-center gap-2 rounded-xl bg-accent text-sm font-bold text-white shadow-lg shadow-accent/20 transition-colors hover:bg-accent-hover"
whileTap={{ scale: 0.95 }}
>
<UserPlus size={15} />
</motion.button>
</motion.div>
)}
{/* Runner ups */}
{!isUnanimous && runnerUpRestaurants.length > 0 && (
<motion.div
@@ -557,6 +592,13 @@ export default function MatchResult({
</div>
</motion.div>
<AuthModal
open={showAuth}
onClose={() => setShowAuth(false)}
onAuth={handleAuth}
defaultTab="register"
/>
<ShareCardModal
open={showShareCard}
onClose={() => setShowShareCard(false)}