refactor(gamification): collapse achievement consumers onto shared hook

v1.4.34 IW-F-Perf — migrate `<RecentAchievementsCard>`, the
`/achievements` mother page, and `<AchievementUnlockNotifier>` from
ad-hoc `useQuery` blocks onto `useAchievementsQuery()`.

Pre-change the dashboard cold mount fired
`/api/gamification/achievements` twice — once for the card, once for
the notifier — because the notifier carried a per-user discriminator
on its queryKey and TanStack treated it as a fresh cache cell. The
shared hook routes all three consumers through the same
`queryKeys.gamificationAchievements()` slot, so a single network
call now backs every surface. The notifier keeps its 2-minute
refetch cadence; long-open tabs still surface new-unlock toasts and
the card picks up the same payload from the shared cache without
issuing a parallel request.
This commit is contained in:
Marc-André Bombeck committed 2026-05-16 20:40:16 +02:00
1 parent ca7ca0e7d6
commit 3b77b6a073
3 files changed
+23 -67

No files matched your search

+6 -19
View File
@@ -1,6 +1,5 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import {
Activity,
AlertTriangle,
@@ -36,20 +35,13 @@ import { EmptyState } from "@/components/ui/empty-state";
import { useAuth } from "@/hooks/use-auth";
import { useTranslations } from "@/lib/i18n/context";
import { formatDate } from "@/lib/format";
import { useAchievementsQuery } from "@/lib/queries/use-achievements-query";
import {
ACHIEVEMENT_CATEGORY_ORDER,
type AchievementCategory,
type AchievementMetrics,
type AchievementProgress,
type AchievementSummary,
} from "@/lib/gamification/achievements";
interface AchievementsData {
summary: AchievementSummary;
achievements: AchievementProgress[];
metrics: AchievementMetrics;
}
const iconMap: Record<string, LucideIcon> = {
Activity,
Flame,
@@ -271,16 +263,11 @@ export default function AchievementsPage() {
const { isAuthenticated, isLoading: authLoading } = useAuth();
const { t } = useTranslations();
const { data, isLoading } = useQuery({
queryKey: ["gamification", "achievements"],
queryFn: async () => {
const res = await fetch("/api/gamification/achievements");
if (!res.ok) throw new Error("Failed to load achievements");
const json = await res.json();
return json.data as AchievementsData;
},
enabled: isAuthenticated,
});
// v1.4.34 IW-F-Perf — mother page rides the shared cache slot
// alongside `<RecentAchievementsCard>` and
// `<AchievementUnlockNotifier>` so the three consumers never trigger
// more than one network call on a cold dashboard mount.
const { data, isLoading } = useAchievementsQuery();
if (authLoading || isLoading) {
return (