refactor(query-keys): expand factory and migrate auth, notifications, about

Routes the registration-status, auth invalidation, notifications
preferences, and Settings about-section api-version reads through
the centralised queryKeys factory. Adds factory entries for the
remaining settings/admin surfaces a follow-up will migrate, and
extends the walker test to fail on any future bare-literal
queryKey in src/app/auth, src/app/notifications, and the
about-section. Also swaps the dashboard tile Suspense fallback
from null to a layout-stable card placeholder so a future RSC
hoist of any tile slot can't trigger CLS.
This commit is contained in:
Marc-André Bombeck committed 2026-05-21 17:13:57 +02:00
1 parent 59befdca73
commit 0bf07abd9d
7 files changed
+106 -14

No files matched your search

+2 -2
View File
@@ -85,7 +85,7 @@ export default function LoginPage() {
return;
}
await queryClient.invalidateQueries({ queryKey: ["auth"] });
await queryClient.invalidateQueries({ queryKey: queryKeys.auth() });
router.push(getRedirectTarget());
} catch (err) {
const { key, params } = describePasskeyError(err);
@@ -114,7 +114,7 @@ export default function LoginPage() {
return;
}
await queryClient.invalidateQueries({ queryKey: ["auth"] });
await queryClient.invalidateQueries({ queryKey: queryKeys.auth() });
router.push(getRedirectTarget());
} catch {
setError(t("auth.loginFailed"));
+2 -1
View File
@@ -12,6 +12,7 @@ import { Label } from "@/components/ui/label";
import { PasswordStrength } from "@/components/ui/password-strength";
import { useTranslations } from "@/lib/i18n/context";
import { detectBrowserTimezone } from "@/lib/tz/format";
import { queryKeys } from "@/lib/query-keys";
export default function RegisterPage() {
const router = useRouter();
@@ -54,7 +55,7 @@ export default function RegisterPage() {
return;
}
await queryClient.invalidateQueries({ queryKey: ["auth"] });
await queryClient.invalidateQueries({ queryKey: queryKeys.auth() });
router.push("/");
} catch {
setError(t("auth.registerFailed"));
+9 -9
View File
@@ -12,6 +12,7 @@ import {
EVENT_DEFAULT_ENABLED,
type EventType,
} from "@/lib/notifications/types";
import { queryKeys } from "@/lib/query-keys";
interface ChannelInfo {
id: string;
@@ -86,7 +87,7 @@ export default function NotificationsPage() {
const queryClient = useQueryClient();
const { data, isLoading, isError } = useQuery({
queryKey: ["notifications", "preferences"],
queryKey: queryKeys.notificationsPreferences(),
queryFn: async () => {
const res = await fetch("/api/notifications/preferences");
if (!res.ok) throw new Error("Failed to load preferences");
@@ -112,12 +113,11 @@ export default function NotificationsPage() {
},
onMutate: async ({ channelId, eventType, enabled }) => {
await queryClient.cancelQueries({
queryKey: ["notifications", "preferences"],
queryKey: queryKeys.notificationsPreferences(),
});
const prev = queryClient.getQueryData<PreferencesData>([
"notifications",
"preferences",
]);
const prev = queryClient.getQueryData<PreferencesData>(
queryKeys.notificationsPreferences(),
);
if (prev) {
const existing = prev.preferences.find(
(p) => p.channelId === channelId && p.eventType === eventType,
@@ -130,7 +130,7 @@ export default function NotificationsPage() {
)
: [...prev.preferences, { channelId, eventType, enabled }];
queryClient.setQueryData<PreferencesData>(
["notifications", "preferences"],
queryKeys.notificationsPreferences(),
{ ...prev, preferences: newPreferences },
);
}
@@ -139,14 +139,14 @@ export default function NotificationsPage() {
onError: (_err, _vars, context) => {
if (context?.prev) {
queryClient.setQueryData(
["notifications", "preferences"],
queryKeys.notificationsPreferences(),
context.prev,
);
}
},
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ["notifications", "preferences"],
queryKey: queryKeys.notificationsPreferences(),
});
},
});
+19 -1
View File
@@ -1413,7 +1413,25 @@ export default function DashboardPage() {
* future RSC hoists of any tile slot won't need a
* second pass to add the fallback infrastructure.
*/}
<Suspense fallback={null}>{entry.node}</Suspense>
{/*
* v1.4.41 W-FRONTEND-FACTORY (UX M1) — swap the
* `null` fallback for a layout-stable placeholder
* that mirrors the trend-card chrome. The body
* stays synchronous today so the fallback rarely
* paints, but a future RSC hoist of any tile slot
* would otherwise leave the grid track empty and
* trigger CLS as the cell paints in.
*/}
<Suspense
fallback={
<div
aria-hidden="true"
className="bg-card border-border h-full w-full rounded-xl border p-4 md:p-6"
/>
}
>
{entry.node}
</Suspense>
</div>
))}
</div>