"use client"; import { useState, useCallback } from "react"; import { useRouter, usePathname } from "next/navigation"; import { useSession } from "next-auth/react"; import toast from "react-hot-toast"; import { useVoteStore } from "@/lib/store"; import type { Artist } from "@/types/artist"; interface UseVoteActionResult { /** 当前投票目标艺人(null 时弹窗关闭) */ target: Artist | null; /** 触发投票(自动检查登录态) */ openVote: (artist: Artist) => void; /** 关闭投票弹窗 */ closeVote: () => void; /** 确认投票(已登录态下调用) */ confirmVote: (artist: Artist, count: number) => Promise; } /** * 投票交互统一入口。 * * - 未登录 → 提示并跳登录页(登录后回到当前路径) * - 已登录 → 打开投票弹窗 → 确认后调用本地 store + 尝试调用 API * - 任意态 → 用 toast 反馈结果 * * 注意:当前已取消所有投票数量限制(无每日上限 / 无单艺人上限)。 */ export function useVoteAction(): UseVoteActionResult { const router = useRouter(); const pathname = usePathname(); const { status } = useSession(); const recordVote = useVoteStore((s) => s.vote); const [target, setTarget] = useState(null); const openVote = useCallback( (artist: Artist) => { if (status === "loading") return; // 会话还在加载,等一下 if (status === "unauthenticated") { toast("请先登录后再为偶像投票", { icon: "🔐" }); const back = encodeURIComponent(pathname || "/"); setTimeout(() => router.push(`/login?callbackUrl=${back}`), 350); return; } setTarget(artist); }, [status, pathname, router], ); const closeVote = useCallback(() => setTarget(null), []); const confirmVote = useCallback( async (artist: Artist, count: number) => { // 1. 立即更新本地 store + 反馈(UI 0 延迟) recordVote(artist.id, count); toast.success(`已为 ${artist.name} 投出 ${count} 票`); setTarget(null); // 2. 后台 fire-and-forget 调用真实 API(5 秒超时,失败静默忽略) const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), 5000); fetch("/api/vote", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ artistId: artist.id, count }), signal: ctrl.signal, }) .catch(() => {}) .finally(() => clearTimeout(timer)); }, [recordVote], ); return { target, openVote, closeVote, confirmVote }; }