UI-UX/src/app/me/MeContent.tsx

147 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState } from "react";
import { Gift, Star, LogOut } from "lucide-react";
import { signOut } from "next-auth/react";
import toast from "react-hot-toast";
import UserHeader from "@/components/me/UserHeader";
import QuotaCard from "@/components/me/QuotaCard";
import StatsGrid from "@/components/me/StatsGrid";
import SignInCalendar from "@/components/me/SignInCalendar";
import MyFanSupport from "@/components/me/MyFanSupport";
import { MOCK_USER, getFanSupports, type MockUser } from "@/lib/mock-user";
import { useVoteStore } from "@/lib/store";
interface MeContentProps {
session: {
id: string;
nickname: string;
};
}
export default function MeContent({ session }: MeContentProps) {
const remaining = useVoteStore((s) => s.remainingVotes);
const used = useVoteStore((s) => s.usedVotes);
const dailyQuota = useVoteStore((s) => s.dailyQuota);
const storeArtists = useVoteStore((s) => s.artists);
// 本地签到状态(数据库就绪后由 /api/me/signin 提供)
const [signedInToday, setSignedInToday] = useState(MOCK_USER.todaySignedIn);
const [weeklySignIn, setWeeklySignIn] = useState(MOCK_USER.weeklySignIn);
const user: MockUser = {
...MOCK_USER,
id: session.id,
nickname: session.nickname,
remainingVotes: remaining,
usedVotes: used,
dailyQuota,
todaySignedIn: signedInToday,
weeklySignIn,
totalVotes: MOCK_USER.totalVotes + used,
};
// 用 store 里最新的艺人排名重算 "我的应援" 当前排名
const supports = getFanSupports().map((s) => {
const fresh = storeArtists.find((a) => a.id === s.artist.id);
return fresh ? { ...s, artist: fresh } : s;
});
const handleInvite = async () => {
const url =
typeof window !== "undefined"
? `${window.location.origin}?invite=${session.id}`
: "";
if (typeof navigator !== "undefined" && navigator.share) {
try {
await navigator.share({
title: "CYBER STAR · 一起为偶像应援",
text: "邀请你加入虚拟偶像 Top12 出道企划,双方各得 +5 票!",
url,
});
return;
} catch {
return;
}
}
try {
await navigator.clipboard.writeText(url);
toast.success("邀请链接已复制 · 朋友注册后双方各 +5 票");
} catch {
toast.error("复制失败,请手动复制地址");
}
};
const handleSignIn = () => {
if (signedInToday) {
toast("今日已签到", { icon: "✓" });
return;
}
// 在 weeklySignIn 数组里找到今天的位置(第一个 false
const idx = weeklySignIn.findIndex((v) => !v);
if (idx === -1) {
toast("本周已全部签到");
return;
}
const next = [...weeklySignIn];
next[idx] = true;
setWeeklySignIn(next);
setSignedInToday(true);
toast.success("签到成功 · 获得 +3 票");
};
const handleLogout = () => {
toast("正在退出登录…", { icon: "👋" });
signOut({ callbackUrl: "/" });
};
return (
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8 sm:py-10 space-y-8">
<UserHeader user={user} />
<QuotaCard
remaining={user.remainingVotes}
daily={user.dailyQuota}
onInvite={handleInvite}
/>
<StatsGrid user={user} />
<section>
<div className="flex items-center gap-2 mb-3">
<Gift size={14} className="text-purple-300" />
<h2 className="font-display text-sm tracking-[0.25em] text-white uppercase">
</h2>
</div>
<SignInCalendar
weekly={user.weeklySignIn}
todaySigned={user.todaySignedIn}
onSignIn={handleSignIn}
/>
</section>
<section>
<div className="flex items-center gap-2 mb-3">
<Star size={14} className="text-purple-300" />
<h2 className="font-display text-sm tracking-[0.25em] text-white uppercase">
</h2>
</div>
<MyFanSupport supports={supports} />
</section>
<div className="pt-4 border-t border-white/[0.06] flex justify-center">
<button
type="button"
onClick={handleLogout}
className="inline-flex items-center gap-2 px-4 h-10 rounded-full bg-white/[0.04] border border-white/10 text-white/55 hover:text-pink-400 hover:border-pink-500/40 transition-colors font-display text-xs tracking-widest uppercase"
>
<LogOut size={13} />
退
</button>
</div>
</div>
);
}