77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
"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<void>;
|
||
}
|
||
|
||
/**
|
||
* 投票交互统一入口。
|
||
*
|
||
* - 未登录 → 提示并跳登录页(登录后回到当前路径)
|
||
* - 已登录 → 打开投票弹窗 → 确认后调用本地 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<Artist | null>(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 };
|
||
}
|