130 lines
5.3 KiB
TypeScript
130 lines
5.3 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Trophy } from "lucide-react";
|
|
import { Link, useSearchParams } from "react-router-dom";
|
|
import { getVisitorEventRewardBalance, getVisitorRewardHistory } from "../lib/api";
|
|
import { getAttendeeToken } from "../lib/attendeeAuth";
|
|
import { HeaderMobile } from "../components/HeaderMobile";
|
|
import { MobileShell } from "../components/MobileShell";
|
|
import { readStoredVisitorContext } from "../types/visitorContext";
|
|
|
|
function historyLabel(source?: string, description?: string): string {
|
|
if (description && String(description).trim()) return String(description).trim();
|
|
const m: Record<string, string> = {
|
|
registration: "Registration",
|
|
question_ask: "Asked a question",
|
|
question_vote: "Question vote",
|
|
quiz_poll: "Quiz",
|
|
poll: "Poll or survey",
|
|
manual: "Adjustment",
|
|
feedback: "Feedback",
|
|
rating: "Rating",
|
|
quiz: "Quiz",
|
|
};
|
|
return m[source || ""] || source || "Points";
|
|
}
|
|
|
|
export function RewardsPage() {
|
|
const [searchParams] = useSearchParams();
|
|
const urlEventId = searchParams.get("event_id")?.trim() || "";
|
|
const ctx = readStoredVisitorContext();
|
|
const eventId = urlEventId || ctx?.eventId || "";
|
|
const [totalPoints, setTotalPoints] = useState<number | null>(null);
|
|
const [historyItems, setHistoryItems] = useState<
|
|
Awaited<ReturnType<typeof getVisitorRewardHistory>>["items"]
|
|
>([]);
|
|
const [historyLoading, setHistoryLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!eventId || !getAttendeeToken()) {
|
|
setTotalPoints(null);
|
|
setHistoryItems([]);
|
|
return;
|
|
}
|
|
let cancelled = false;
|
|
getVisitorEventRewardBalance(eventId)
|
|
.then((n) => {
|
|
if (!cancelled) setTotalPoints(Math.max(0, n));
|
|
})
|
|
.catch(() => {
|
|
if (!cancelled) setTotalPoints(0);
|
|
});
|
|
setHistoryLoading(true);
|
|
getVisitorRewardHistory(eventId, { page: 1, limit: 30 })
|
|
.then((r) => {
|
|
if (!cancelled) setHistoryItems(r.items);
|
|
})
|
|
.catch(() => {
|
|
if (!cancelled) setHistoryItems([]);
|
|
})
|
|
.finally(() => {
|
|
if (!cancelled) setHistoryLoading(false);
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [eventId]);
|
|
|
|
const displayTotal =
|
|
!getAttendeeToken() || !eventId ? "—" : totalPoints === null ? "…" : String(totalPoints);
|
|
|
|
return (
|
|
<MobileShell className="relative">
|
|
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={eventId || null} />
|
|
<div className="eb-gradient-auth flex flex-1 flex-col items-center px-6 pb-10 pt-6">
|
|
<Trophy className="size-[68px] text-amber-300" strokeWidth={1.25} />
|
|
<h1 className="mt-1 font-inter text-xl font-semibold text-white">Your Rewards</h1>
|
|
<Link
|
|
to={eventId ? `/leaderboard?event_id=${encodeURIComponent(eventId)}` : "/leaderboard"}
|
|
className="mt-6 w-full max-w-[354px] rounded-full border border-[rgba(214,200,200,0.6)] bg-[#ef8d1d] py-3 text-center text-xs font-semibold uppercase tracking-wide text-white shadow-[0_4px_4px_rgba(0,0,0,0.15)]"
|
|
>
|
|
Leaderboard
|
|
</Link>
|
|
|
|
<div className="mt-6 w-full max-w-[354px] rounded-xl bg-white shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]">
|
|
<div className="p-4">
|
|
<div className="eb-rank-highlight flex flex-col items-center justify-center gap-2 rounded-3xl border-4 border-white py-4 text-white shadow-[0_9px_16px_rgba(0,0,0,0.14)]">
|
|
<p className="font-inter text-base font-semibold uppercase tracking-wide">Total Points</p>
|
|
<p className="font-inter text-5xl font-bold tabular-nums drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)]">
|
|
{displayTotal}
|
|
</p>
|
|
</div>
|
|
<h2 className="mt-6 font-inter text-base font-semibold text-[#24282f]">Points history</h2>
|
|
<ul className="mt-3 flex flex-col gap-3">
|
|
{historyLoading && (
|
|
<li className="text-xs text-[rgba(36,40,47,0.67)]">Loading…</li>
|
|
)}
|
|
{!historyLoading && historyItems.length === 0 && (
|
|
<li className="text-xs text-[rgba(36,40,47,0.67)]">No transactions yet.</li>
|
|
)}
|
|
{!historyLoading &&
|
|
historyItems.map((row) => {
|
|
const signed = row.type === "redeemed" ? "-" : "+";
|
|
const cls =
|
|
row.type === "redeemed"
|
|
? "bg-[#fff0f0] text-[#c02424]"
|
|
: row.type === "earned"
|
|
? "bg-[#ebffee] text-[#009951]"
|
|
: "bg-[#f3f4f6] text-[#24282f]";
|
|
return (
|
|
<li
|
|
key={row._id}
|
|
className="flex gap-4 border-b border-[#ededed] pb-3 last:border-0"
|
|
>
|
|
<span className={`shrink-0 rounded px-3 py-2 text-xs font-medium shadow-sm ${cls}`}>
|
|
{signed}
|
|
{row.points} pts
|
|
</span>
|
|
<span className="text-xs leading-snug text-[rgba(36,40,47,0.67)]">
|
|
{historyLabel(row.source, row.description)}
|
|
</span>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</MobileShell>
|
|
);
|
|
}
|