feat: 匹配成功页引导未注册用户注册,保存决策记录
- 未注册用户在匹配成功页看到注册引导卡片 - 注册后自动保存本次决策记录,收藏按钮同步出现 - 将 isRegistered() 调用改为 registered 响应式状态 - 更新 ROADMAP 标记已完成
This commit is contained in:
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
- `viewport-fit=cover` 适配刘海屏
|
- `viewport-fit=cover` 适配刘海屏
|
||||||
|
|
||||||
### 首次体验引导优化
|
### 首次体验引导优化
|
||||||
- 极速救场完成一轮后引导注册("注册保存记录")
|
- ~~极速救场完成一轮后引导注册("注册保存记录")~~(已完成,匹配成功页展示注册卡片,注册后自动保存记录)
|
||||||
- 盲盒模式先展示 demo / 动画,让用户看到价值再引导注册
|
- 盲盒模式先展示 demo / 动画,让用户看到价值再引导注册
|
||||||
- 统一两个模式的登录体验(目前极速救场不需登录,盲盒必须登录)
|
- 统一两个模式的登录体验(目前极速救场不需登录,盲盒必须登录)
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ import {
|
|||||||
Share2,
|
Share2,
|
||||||
Zap,
|
Zap,
|
||||||
Heart,
|
Heart,
|
||||||
|
UserPlus,
|
||||||
} from "lucide-react";
|
} 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 { fireCelebration, playChime } from "@/lib/celebrate";
|
||||||
import { isRegistered } from "@/lib/userId";
|
import { isRegistered } from "@/lib/userId";
|
||||||
import ShareCardModal from "@/components/ShareCardModal";
|
import ShareCardModal from "@/components/ShareCardModal";
|
||||||
|
import AuthModal from "@/components/AuthModal";
|
||||||
|
|
||||||
interface MatchResultProps {
|
interface MatchResultProps {
|
||||||
restaurant: Restaurant;
|
restaurant: Restaurant;
|
||||||
@@ -193,6 +195,8 @@ export default function MatchResult({
|
|||||||
const isUnanimous = matchType === "unanimous";
|
const isUnanimous = matchType === "unanimous";
|
||||||
const [favorited, setFavorited] = useState(false);
|
const [favorited, setFavorited] = useState(false);
|
||||||
const [favLoading, setFavLoading] = useState(false);
|
const [favLoading, setFavLoading] = useState(false);
|
||||||
|
const [registered, setRegistered] = useState(() => isRegistered());
|
||||||
|
const [showAuth, setShowAuth] = useState(false);
|
||||||
|
|
||||||
const showToast = useCallback((msg: string) => {
|
const showToast = useCallback((msg: string) => {
|
||||||
setToast(msg);
|
setToast(msg);
|
||||||
@@ -212,7 +216,7 @@ export default function MatchResult({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (historySavedRef.current) return;
|
if (historySavedRef.current) return;
|
||||||
if (!isRegistered()) return;
|
if (!registered) return;
|
||||||
if (matchType === "no_match") return;
|
if (matchType === "no_match") return;
|
||||||
|
|
||||||
historySavedRef.current = true;
|
historySavedRef.current = true;
|
||||||
@@ -227,14 +231,20 @@ export default function MatchResult({
|
|||||||
participants: userCount,
|
participants: userCount,
|
||||||
}),
|
}),
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}, [userId, roomId, restaurant, matchType, userCount]);
|
}, [registered, userId, roomId, restaurant, matchType, userCount]);
|
||||||
|
|
||||||
const handleOpenShareCard = useCallback(() => {
|
const handleOpenShareCard = useCallback(() => {
|
||||||
setShowShareCard(true);
|
setShowShareCard(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleAuth = useCallback((profile: UserProfile) => {
|
||||||
|
setRegistered(true);
|
||||||
|
setShowAuth(false);
|
||||||
|
showToast(`欢迎,${profile.username}!记录已保存`);
|
||||||
|
}, [showToast]);
|
||||||
|
|
||||||
const handleFavorite = useCallback(async () => {
|
const handleFavorite = useCallback(async () => {
|
||||||
if (!isRegistered() || favorited || favLoading) return;
|
if (!registered || favorited || favLoading) return;
|
||||||
setFavLoading(true);
|
setFavLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/user/favorite", {
|
const res = await fetch("/api/user/favorite", {
|
||||||
@@ -250,7 +260,7 @@ export default function MatchResult({
|
|||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
setFavLoading(false);
|
setFavLoading(false);
|
||||||
}, [userId, restaurant, favorited, favLoading, showToast]);
|
}, [registered, userId, restaurant, favorited, favLoading, showToast]);
|
||||||
|
|
||||||
if (matchType === "no_match") {
|
if (matchType === "no_match") {
|
||||||
return <NoMatchResult onReset={onReset} resetting={resetting} />;
|
return <NoMatchResult onReset={onReset} resetting={resetting} />;
|
||||||
@@ -344,7 +354,7 @@ export default function MatchResult({
|
|||||||
animate={{ y: 0, opacity: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
transition={{ type: "spring", stiffness: 180, damping: 18, delay: 0.5 }}
|
transition={{ type: "spring", stiffness: 180, damping: 18, delay: 0.5 }}
|
||||||
>
|
>
|
||||||
{isRegistered() && (
|
{registered && (
|
||||||
<motion.button
|
<motion.button
|
||||||
onClick={handleFavorite}
|
onClick={handleFavorite}
|
||||||
disabled={favLoading}
|
disabled={favLoading}
|
||||||
@@ -466,6 +476,31 @@ export default function MatchResult({
|
|||||||
</motion.button>
|
</motion.button>
|
||||||
</motion.div>
|
</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 */}
|
{/* Runner ups */}
|
||||||
{!isUnanimous && runnerUpRestaurants.length > 0 && (
|
{!isUnanimous && runnerUpRestaurants.length > 0 && (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -557,6 +592,13 @@ export default function MatchResult({
|
|||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
<AuthModal
|
||||||
|
open={showAuth}
|
||||||
|
onClose={() => setShowAuth(false)}
|
||||||
|
onAuth={handleAuth}
|
||||||
|
defaultTab="register"
|
||||||
|
/>
|
||||||
|
|
||||||
<ShareCardModal
|
<ShareCardModal
|
||||||
open={showShareCard}
|
open={showShareCard}
|
||||||
onClose={() => setShowShareCard(false)}
|
onClose={() => setShowShareCard(false)}
|
||||||
|
|||||||
Reference in New Issue
Block a user