Files
gitlab-instance-0a899031_salon/app/components/MyRegistrationStatus.tsx
2026-03-16 20:24:28 -05:00

165 lines
5.6 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Image from "next/image";
import Link from "next/link";
const WECHAT_STORAGE_KEY = "salon_join_wechat";
interface RecordData {
id?: string;
wechatId?: string;
nickname?: string;
media?: string;
is_volunteer?: boolean;
}
interface UserRecord {
hasRecord: boolean;
record: RecordData | null;
isComplete: boolean;
isApproved: boolean;
isRejected: boolean;
vip: boolean;
}
function getStatusLabel(data: UserRecord): string {
if (data.isComplete || (data.vip && data.hasRecord)) return "报名成功";
if (data.isRejected) return "审核拒绝";
if (data.isApproved) return "审核通过";
return "审核中";
}
function getStatusColor(status: string): string {
if (status === "报名成功") return "bg-emerald-50 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300";
if (status === "审核通过") return "bg-emerald-50 dark:bg-emerald-900/30 text-emerald-600 dark:text-emerald-400";
if (status === "审核拒绝") return "bg-red-50 dark:bg-red-900/30 text-red-600 dark:text-red-400";
return "bg-amber-50 dark:bg-amber-900/30 text-amber-600 dark:text-amber-400";
}
export default function MyRegistrationStatus() {
const [data, setData] = useState<UserRecord | null>(null);
const [loading, setLoading] = useState(true);
const fetchStatus = () => {
if (typeof window === "undefined") return;
try {
const stored = localStorage.getItem(WECHAT_STORAGE_KEY);
const wechatId = (stored || "").trim();
if (!wechatId) {
setLoading(false);
setData(null);
return;
}
setLoading(true);
fetch(`/api/join/by-wechat?wechat_id=${encodeURIComponent(wechatId)}&_=${Date.now()}`, { cache: "no-store" })
.then((r) => r.json())
.then((d) => {
if (d?.hasRecord) {
const rec = d.record ?? null;
setData({
hasRecord: !!d.hasRecord,
record: rec ? { ...rec, wechatId: rec.wechatId || wechatId } : null,
isComplete: !!d.isComplete,
isApproved: !!d.isApproved,
isRejected: !!d.isRejected,
vip: !!d.vip,
});
} else {
setData(null);
}
})
.catch(() => setData(null))
.finally(() => setLoading(false));
} catch {
setLoading(false);
}
};
useEffect(() => {
if (typeof window === "undefined") return;
let cancelled = false;
fetchStatus();
const onUpdate = () => {
if (!cancelled) fetchStatus();
};
const onVisibilityChange = () => {
if (document.visibilityState === "visible" && !cancelled) fetchStatus();
};
const onPageShow = (e: PageTransitionEvent) => {
if (e.persisted && !cancelled) fetchStatus();
};
window.addEventListener("vip:updated", onUpdate);
document.addEventListener("visibilitychange", onVisibilityChange);
window.addEventListener("pageshow", onPageShow);
return () => {
cancelled = true;
window.removeEventListener("vip:updated", onUpdate);
document.removeEventListener("visibilitychange", onVisibilityChange);
window.removeEventListener("pageshow", onPageShow);
};
}, []);
if (loading || !data?.hasRecord || !data.record) return null;
const rec = data.record;
const status = getStatusLabel(data);
const statusColor = getStatusColor(status);
return (
<section className="mb-8 sm:mb-10">
<div className="rounded-xl sm:rounded-2xl bg-white dark:bg-stone-900/80 border border-stone-200/80 dark:border-stone-700/80 p-4 sm:p-6 overflow-hidden">
<h3 className="text-sm font-medium text-stone-500 dark:text-stone-400 mb-3 flex items-center gap-2">
<span>👤</span>
</h3>
<div className="flex items-center gap-3 sm:gap-4">
<div className="relative shrink-0">
<div className="relative w-12 h-12 sm:w-14 sm:h-14 rounded-full overflow-hidden border-2 border-stone-200 dark:border-stone-600">
{rec.media ? (
<Image
src={rec.media}
alt=""
fill
sizes="56px"
className="object-cover"
unoptimized
/>
) : (
<div className="w-full h-full flex items-center justify-center bg-amber-100 dark:bg-amber-900/40 text-xl sm:text-2xl">
👤
</div>
)}
</div>
{rec.is_volunteer && (
<span className="absolute -bottom-1 left-1/2 -translate-x-1/2 px-1.5 py-0.5 rounded text-[10px] font-medium bg-amber-500 text-white whitespace-nowrap">
</span>
)}
</div>
<div className="flex-1 min-w-0">
<p className="font-semibold text-stone-800 dark:text-stone-100 truncate">
{rec.nickname || "—"}
</p>
{rec.wechatId && (
<p className="text-xs text-stone-500 dark:text-stone-400 font-mono truncate mt-0.5">
: {rec.wechatId}
</p>
)}
<span
className={`inline-block mt-2 px-2.5 py-1 rounded-lg text-xs font-medium ${statusColor}`}
>
{status}
</span>
</div>
<Link
href="/join"
className="shrink-0 text-sm font-medium text-amber-600 dark:text-amber-400 hover:text-amber-700 dark:hover:text-amber-300"
>
</Link>
</div>
</div>
</section>
);
}