perf : cache
This commit is contained in:
@@ -10,9 +10,9 @@ import { AdminHeader } from "@/components/admin/admin-header";
|
||||
import { EditorPresence } from "@/components/admin/editor-presence";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getAdminSession } from "@/lib/auth/server";
|
||||
import { getCachedCatalogOptions } from "@/lib/cache/catalog";
|
||||
import { toTeamDraft } from "@/lib/guides/team-draft";
|
||||
import {
|
||||
getCatalogOptions,
|
||||
getAdminGuideContent,
|
||||
getGuideEditorHeader,
|
||||
} from "@/lib/guides/queries";
|
||||
@@ -54,7 +54,7 @@ export default async function StructuredEditorPage({
|
||||
?? guide.sections.find((section) => section.slug === page)?.title
|
||||
?? page;
|
||||
const [catalog, content] = await Promise.all([
|
||||
getCatalogOptions(),
|
||||
getCachedCatalogOptions(),
|
||||
getAdminGuideContent(guide.slug, page),
|
||||
]);
|
||||
if (!catalog || !content) notFound();
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Suspense } from "react";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { connection } from "next/server";
|
||||
|
||||
import { AdminHeader } from "@/components/admin/admin-header";
|
||||
import { GuideSectionNav } from "@/components/admin/guide-section-nav";
|
||||
import { CommentBoardContent } from "@/components/comments/comment-board-content";
|
||||
import { CommentBoardSkeleton } from "@/components/comments/comment-skeleton";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Image from "@/components/ui/resilient-image";
|
||||
import { getAdminSession } from "@/lib/auth/server";
|
||||
import { getGuideEditorHeader } from "@/lib/guides/queries";
|
||||
|
||||
export const instant = false;
|
||||
|
||||
export default async function GuideCommentPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ character: string }>;
|
||||
}) {
|
||||
await connection();
|
||||
if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin");
|
||||
const { character } = await params;
|
||||
const guide = await getGuideEditorHeader(character);
|
||||
if (!guide) notFound();
|
||||
const extras = guide.sections
|
||||
.filter((section) => section.kind === "extra")
|
||||
.map(({ id, slug, title }) => ({ id, slug, title }));
|
||||
|
||||
return (
|
||||
<div className="min-h-svh">
|
||||
<AdminHeader />
|
||||
<main lang="en" className="mx-auto flex w-full max-w-7xl flex-col gap-6 px-4 pb-4 sm:px-6 sm:pb-6 lg:px-8 lg:pb-8">
|
||||
<div className="flex flex-col">
|
||||
<header className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="icon" render={<Link href="/admin" />} nativeButton={false} aria-label="Back to dashboard">
|
||||
<ArrowLeftIcon />
|
||||
</Button>
|
||||
<div className="relative size-16 shrink-0 overflow-hidden rounded-lg border">
|
||||
<Image src={guide.coverUrl} alt={guide.name} fill sizes="64px" className="object-cover" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm text-muted-foreground">{guide.name}</p>
|
||||
<h1 className="font-heading text-3xl font-semibold">Comment</h1>
|
||||
</div>
|
||||
</header>
|
||||
<GuideSectionNav guideId={guide.id} characterKey={guide.characterKey} current="comment" extras={extras} />
|
||||
</div>
|
||||
<Suspense fallback={<CommentBoardSkeleton />}>
|
||||
<CommentBoardContent target={`guide:${guide.id}`} />
|
||||
</Suspense>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({ admin: vi.fn(), updateTag: vi.fn(), save: vi.fn() }));
|
||||
vi.mock("next/cache", () => ({ updateTag: mocks.updateTag }));
|
||||
vi.mock("@/lib/auth/server", () => ({ requireAdmin: mocks.admin }));
|
||||
vi.mock("@/lib/audit-log", () => ({ auditActor: () => ({ id: "admin", label: "Admin" }) }));
|
||||
vi.mock("@/lib/glossary-repository", () => ({ createGlossaryAlias: vi.fn(), deleteGlossaryAlias: vi.fn() }));
|
||||
vi.mock("@/lib/catalog/sync", () => ({ cancelCatalogSyncJob: vi.fn(), getCatalogSyncJob: vi.fn(), startCatalogSyncJob: vi.fn() }));
|
||||
vi.mock("@/lib/guides/queries", () => ({
|
||||
getAdminGuideContentById: vi.fn(), getAdminTeamImportGuide: vi.fn(),
|
||||
listAdminTeamImportsForCharacter: vi.fn(), listAdminTeamImportGuides: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/guides/mutations", () => ({
|
||||
createExtraSection: mocks.save, createGuide: mocks.save, deleteExtraSection: mocks.save,
|
||||
permanentlyDeleteGuide: mocks.save, reorderExtraSections: mocks.save, restoreGuide: mocks.save,
|
||||
saveArtifacts: mocks.save, saveConstellations: mocks.save, saveExtraSection: mocks.save,
|
||||
saveTeams: mocks.save, setGuideVisibility: mocks.save, saveWeapons: mocks.save,
|
||||
trashGuide: mocks.save, updateGuideBasics: mocks.save,
|
||||
GuideVersionConflictError: class extends Error { currentVersion = 9; },
|
||||
}));
|
||||
|
||||
import { moveGuideToTrash, restoreStructuredGuide, saveArtifactSection, saveGuideExtra, saveGuideOverview, saveWeaponSection } from "./actions";
|
||||
import { GuideVersionConflictError } from "@/lib/guides/mutations";
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
mocks.admin.mockResolvedValue({ user: { id: "admin" } });
|
||||
mocks.save.mockResolvedValue({ version: 10 });
|
||||
});
|
||||
|
||||
describe("guide save cache freshness", () => {
|
||||
const guideId = "550e8400-e29b-41d4-a716-446655440000";
|
||||
it.each([saveArtifactSection, saveGuideOverview, saveWeaponSection, moveGuideToTrash, restoreStructuredGuide])(
|
||||
"expires the public page immediately after a successful action", async (action) => {
|
||||
expect(await action(guideId, {})).toMatchObject({ status: "saved", version: 10 });
|
||||
expect(mocks.updateTag).toHaveBeenCalledWith(`buzz:page:${guideId}`);
|
||||
},
|
||||
);
|
||||
it("invalidates custom extras without changing their section id or version input", async () => {
|
||||
const sectionId = "550e8400-e29b-41d4-a716-446655440001";
|
||||
const input = { expectedVersion: 9 };
|
||||
await saveGuideExtra(guideId, sectionId, input);
|
||||
expect(mocks.save).toHaveBeenCalledWith(guideId, sectionId, input, expect.any(Object));
|
||||
expect(mocks.updateTag).toHaveBeenCalledWith(`buzz:page:${guideId}`);
|
||||
});
|
||||
it("preserves conflicts without invalidating or reporting a save", async () => {
|
||||
mocks.save.mockRejectedValue(new GuideVersionConflictError(9));
|
||||
expect(await saveArtifactSection(guideId, {})).toMatchObject({ status: "conflict", currentVersion: 9 });
|
||||
expect(mocks.updateTag).not.toHaveBeenCalled();
|
||||
});
|
||||
it("authorizes before writes or cache invalidation", async () => {
|
||||
mocks.admin.mockRejectedValue(new Error("unauthorized"));
|
||||
await expect(saveArtifactSection(guideId, {})).rejects.toThrow("unauthorized");
|
||||
expect(mocks.save).not.toHaveBeenCalled();
|
||||
expect(mocks.updateTag).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
+20
-15
@@ -3,7 +3,7 @@
|
||||
import { updateTag } from "next/cache";
|
||||
import * as z from "zod";
|
||||
|
||||
import { GLOSSARY_CACHE_TAG, pageCacheTag } from "@/lib/cache/tags";
|
||||
import { DIRECTORY_CACHE_TAG, GLOSSARY_CACHE_TAG, pageCacheTag } from "@/lib/cache/tags";
|
||||
import {
|
||||
createGlossaryAlias,
|
||||
deleteGlossaryAlias,
|
||||
@@ -56,9 +56,11 @@ function mutationContext(
|
||||
return { actor: auditActor(admin.user), overwrite: intent?.overwrite };
|
||||
}
|
||||
|
||||
async function runGuideAction(action: () => Promise<{ version?: number }>): Promise<GuideActionResult> {
|
||||
async function runGuideAction(action: () => Promise<{ version?: number }>, guideId: string): Promise<GuideActionResult> {
|
||||
try {
|
||||
const result = await action();
|
||||
updateTag(pageCacheTag(guideId));
|
||||
updateTag(DIRECTORY_CACHE_TAG);
|
||||
return { status: "saved", version: result.version };
|
||||
} catch (cause) {
|
||||
if (cause instanceof GuideVersionConflictError) {
|
||||
@@ -75,6 +77,8 @@ export async function createStructuredGuide(input: unknown): Promise<CreateGuide
|
||||
const admin = await requireAdmin();
|
||||
try {
|
||||
const guide = await createGuide(input, mutationContext(admin));
|
||||
updateTag(pageCacheTag(guide.id));
|
||||
updateTag(DIRECTORY_CACHE_TAG);
|
||||
return { status: "saved", version: guide.version, location: `/admin/${guide.characterKey}` };
|
||||
} catch (cause) {
|
||||
return { status: "error", message: cause instanceof Error ? cause.message : "สร้าง Guide ไม่สำเร็จ" };
|
||||
@@ -83,27 +87,27 @@ export async function createStructuredGuide(input: unknown): Promise<CreateGuide
|
||||
|
||||
export async function saveWeaponSection(guideId: string, input: unknown, intent?: SaveIntent) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => saveWeapons(guideId, input, mutationContext(admin, intent)));
|
||||
return runGuideAction(() => saveWeapons(guideId, input, mutationContext(admin, intent)), guideId);
|
||||
}
|
||||
|
||||
export async function saveGuideOverview(guideId: string, input: unknown, intent?: SaveIntent) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => updateGuideBasics(guideId, input, mutationContext(admin, intent)));
|
||||
return runGuideAction(() => updateGuideBasics(guideId, input, mutationContext(admin, intent)), guideId);
|
||||
}
|
||||
|
||||
export async function saveArtifactSection(guideId: string, input: unknown, intent?: SaveIntent) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => saveArtifacts(guideId, input, mutationContext(admin, intent)));
|
||||
return runGuideAction(() => saveArtifacts(guideId, input, mutationContext(admin, intent)), guideId);
|
||||
}
|
||||
|
||||
export async function saveConstellationSection(guideId: string, input: unknown, intent?: SaveIntent) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => saveConstellations(guideId, input, mutationContext(admin, intent)));
|
||||
return runGuideAction(() => saveConstellations(guideId, input, mutationContext(admin, intent)), guideId);
|
||||
}
|
||||
|
||||
export async function saveTeamSection(guideId: string, input: unknown, intent?: SaveIntent) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => saveTeams(guideId, input, mutationContext(admin, intent)));
|
||||
return runGuideAction(() => saveTeams(guideId, input, mutationContext(admin, intent)), guideId);
|
||||
}
|
||||
|
||||
export async function listTeamImportGuides(currentGuideId: string) {
|
||||
@@ -157,6 +161,7 @@ export async function createGuideExtra(guideId: string, input: unknown) {
|
||||
mutationContext(admin),
|
||||
);
|
||||
updateTag(pageCacheTag(guideId));
|
||||
updateTag(DIRECTORY_CACHE_TAG);
|
||||
return {
|
||||
status: "saved" as const,
|
||||
version: section.version,
|
||||
@@ -185,8 +190,8 @@ export async function saveGuideExtra(
|
||||
const parsedSectionId = z.string().uuid().parse(sectionId);
|
||||
const result = await runGuideAction(() =>
|
||||
saveExtraSection(parsedGuideId, parsedSectionId, input, mutationContext(admin, intent)),
|
||||
guideId,
|
||||
);
|
||||
if (result.status === "saved") updateTag(pageCacheTag(guideId));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -198,8 +203,8 @@ export async function deleteGuideExtra(guideId: string, sectionId: string) {
|
||||
z.string().uuid().parse(sectionId),
|
||||
mutationContext(admin),
|
||||
),
|
||||
guideId,
|
||||
);
|
||||
if (result.status === "saved") updateTag(pageCacheTag(guideId));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -211,19 +216,19 @@ export async function reorderGuideExtras(guideId: string, input: unknown) {
|
||||
input,
|
||||
mutationContext(admin),
|
||||
),
|
||||
guideId,
|
||||
);
|
||||
if (result.status === "saved") updateTag(pageCacheTag(guideId));
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function moveGuideToTrash(guideId: string) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => trashGuide(guideId, mutationContext(admin)));
|
||||
return runGuideAction(() => trashGuide(guideId, mutationContext(admin)), guideId);
|
||||
}
|
||||
|
||||
export async function restoreStructuredGuide(guideId: string) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => restoreGuide(guideId, mutationContext(admin)));
|
||||
return runGuideAction(() => restoreGuide(guideId, mutationContext(admin)), guideId);
|
||||
}
|
||||
|
||||
export async function setStructuredGuideVisibility(
|
||||
@@ -237,7 +242,7 @@ export async function setStructuredGuideVisibility(
|
||||
}).safeParse(input);
|
||||
if (!parsed.success) return { status: "error" as const, message: "ข้อมูลการเผยแพร่ไม่ถูกต้อง" };
|
||||
if (!parsed.data.isPublic) {
|
||||
return runGuideAction(() => setGuideVisibility(guideId, parsed.data, mutationContext(admin)));
|
||||
return runGuideAction(() => setGuideVisibility(guideId, parsed.data, mutationContext(admin)), guideId);
|
||||
}
|
||||
|
||||
const guide = await getAdminGuideContentById(guideId);
|
||||
@@ -252,12 +257,12 @@ export async function setStructuredGuideVisibility(
|
||||
if (issues.length) {
|
||||
return { status: "error" as const, message: issues.join(" • "), issues };
|
||||
}
|
||||
return runGuideAction(() => setGuideVisibility(guideId, parsed.data, mutationContext(admin)));
|
||||
return runGuideAction(() => setGuideVisibility(guideId, parsed.data, mutationContext(admin)), guideId);
|
||||
}
|
||||
|
||||
export async function deleteStructuredGuide(guideId: string) {
|
||||
const admin = await requireAdmin();
|
||||
return runGuideAction(() => permanentlyDeleteGuide(guideId, mutationContext(admin)));
|
||||
return runGuideAction(() => permanentlyDeleteGuide(guideId, mutationContext(admin)), guideId);
|
||||
}
|
||||
|
||||
export async function startCatalogSync() {
|
||||
|
||||
@@ -12,8 +12,8 @@ export default async function AdminCommentsPage({ searchParams }: { searchParams
|
||||
const initialTarget = query.target;
|
||||
const initialComment = initialTarget && query.comment && /^[0-9a-f-]{36}$/i.test(query.comment) ? query.comment : undefined;
|
||||
const [initial, targets] = await Promise.all([listComments({ viewer, inbox: true, grouped: true, target: initialTarget }), listCommentTargets()]);
|
||||
return <><AdminHeader /><main className="mx-auto flex w-full max-w-7xl flex-col gap-6 p-4 sm:p-6 lg:p-8">
|
||||
<header><h1 className="font-heading text-3xl font-semibold">ความคิดเห็น</h1><p className="mt-2 text-muted-foreground">ตอบกลับและจัดการความคิดเห็นจากทุกไกด์</p></header>
|
||||
return <><AdminHeader /><main lang="en" className="mx-auto flex w-full max-w-7xl flex-col gap-6 p-4 sm:p-6 lg:p-8">
|
||||
<header><h1 className="font-heading text-3xl font-semibold">Comments</h1><p className="mt-2 text-muted-foreground">Reply to and manage comments from every guide</p></header>
|
||||
<AdminCommentInbox key={`${initialTarget ?? "all"}:${initialComment ?? ""}`} initialTarget={initialTarget} initialComment={initialComment} initial={initial} targets={targets} />
|
||||
</main></>;
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ import { CreateGuideFlow } from "@/components/admin/create-guide-flow";
|
||||
import { AdminHeader } from "@/components/admin/admin-header";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getAdminSession } from "@/lib/auth/server";
|
||||
import { getActiveCatalog, listGuideCards } from "@/lib/guides/queries";
|
||||
import { getCachedCatalogOptions } from "@/lib/cache/catalog";
|
||||
import { listGuideCards } from "@/lib/guides/queries";
|
||||
|
||||
export default async function CreateGuidePage() {
|
||||
await connection();
|
||||
if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin");
|
||||
const [catalog, guides] = await Promise.all([
|
||||
getActiveCatalog(),
|
||||
getCachedCatalogOptions(),
|
||||
listGuideCards(),
|
||||
]);
|
||||
if (!catalog) redirect("/admin?sync=required");
|
||||
|
||||
@@ -5,13 +5,13 @@ import { AdminHeader } from "@/components/admin/admin-header";
|
||||
import { GlossaryManager } from "@/components/admin/glossary-manager";
|
||||
import { getAdminSession } from "@/lib/auth/server";
|
||||
import { listGlossaryAliases } from "@/lib/glossary-repository";
|
||||
import { getCatalogOptions } from "@/lib/guides/queries";
|
||||
import { getCachedCatalogOptions } from "@/lib/cache/catalog";
|
||||
|
||||
export default async function GlossaryPage() {
|
||||
await connection();
|
||||
if (!(await getAdminSession())) redirect("/auth/login?next=%2Fadmin");
|
||||
const [catalog, aliases] = await Promise.all([
|
||||
getCatalogOptions(),
|
||||
getCachedCatalogOptions(),
|
||||
listGlossaryAliases(),
|
||||
]);
|
||||
if (!catalog) redirect("/admin");
|
||||
|
||||
@@ -2,12 +2,12 @@ import { redirect } from "next/navigation";
|
||||
import { connection } from "next/server";
|
||||
|
||||
import { ConstellationImageGenerator } from "@/components/admin/constellation-image-generator";
|
||||
import { getActiveCatalog } from "@/lib/guides/queries";
|
||||
import { getCachedCatalogOptions } from "@/lib/cache/catalog";
|
||||
|
||||
export default async function ConstellationImagePage() {
|
||||
await connection();
|
||||
|
||||
const catalog = await getActiveCatalog();
|
||||
const catalog = await getCachedCatalogOptions();
|
||||
if (!catalog) redirect("/admin?sync=required");
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,13 +2,13 @@ import { redirect } from "next/navigation";
|
||||
import { connection } from "next/server";
|
||||
|
||||
import { OverviewImageGenerator } from "@/components/admin/overview-image-generator";
|
||||
import { getActiveCatalog } from "@/lib/guides/queries";
|
||||
import { getCachedActiveCatalog } from "@/lib/cache/catalog";
|
||||
import { elementOrder } from "@/lib/overview-image";
|
||||
|
||||
export default async function OverviewImagePage() {
|
||||
await connection();
|
||||
|
||||
const catalog = await getActiveCatalog();
|
||||
const catalog = await getCachedActiveCatalog();
|
||||
if (!catalog) redirect("/admin?sync=required");
|
||||
|
||||
const elementIconUrls = Object.fromEntries(
|
||||
|
||||
@@ -2,12 +2,12 @@ import { redirect } from "next/navigation";
|
||||
import { connection } from "next/server";
|
||||
|
||||
import { TeamImageGenerator } from "@/components/admin/team-image-generator";
|
||||
import { getActiveCatalog } from "@/lib/guides/queries";
|
||||
import { getCachedCatalogOptions } from "@/lib/cache/catalog";
|
||||
|
||||
export default async function TeamImagePage() {
|
||||
await connection();
|
||||
|
||||
const catalog = await getActiveCatalog();
|
||||
const catalog = await getCachedCatalogOptions();
|
||||
if (!catalog) redirect("/admin?sync=required");
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,12 +2,12 @@ import { redirect } from "next/navigation";
|
||||
import { connection } from "next/server";
|
||||
|
||||
import { WeaponImageGenerator } from "@/components/admin/weapon-image-generator";
|
||||
import { getActiveCatalog } from "@/lib/guides/queries";
|
||||
import { getCachedCatalogOptions } from "@/lib/cache/catalog";
|
||||
|
||||
export default async function WeaponImagePage() {
|
||||
await connection();
|
||||
|
||||
const catalog = await getActiveCatalog();
|
||||
const catalog = await getCachedCatalogOptions();
|
||||
if (!catalog) redirect("/admin?sync=required");
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { CommentLocaleProvider } from "@/components/comments/comment-locale";
|
||||
import { AdminUnreadCommentsProvider } from "@/components/comments/admin-unread-comments";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
|
||||
@@ -13,7 +14,7 @@ export default function AdminLayout({ children }: LayoutProps<"/admin">) {
|
||||
</main>
|
||||
}
|
||||
>
|
||||
<AdminUnreadCommentsProvider>{children}</AdminUnreadCommentsProvider>
|
||||
<CommentLocaleProvider locale="en"><AdminUnreadCommentsProvider>{children}</AdminUnreadCommentsProvider></CommentLocaleProvider>
|
||||
</Suspense>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
export default function AdminLoading() {
|
||||
return <main role="status" aria-label="Loading admin page" className="mx-auto flex w-full max-w-7xl flex-col gap-6 p-4 sm:p-6 lg:p-8">
|
||||
<span className="sr-only">Loading admin page…</span>
|
||||
<Skeleton className="h-10 w-64 max-w-full" />
|
||||
<Skeleton className="h-12 w-full" />
|
||||
<Skeleton className="h-64 w-full" />
|
||||
</main>;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { StygianEditor } from "@/components/admin/stygian-editor";
|
||||
import { StygianSyncButton } from "@/components/admin/stygian-sync-button";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { getAdminSession } from "@/lib/auth/server";
|
||||
import { getActiveCatalog } from "@/lib/guides/queries";
|
||||
import { getCachedCatalogOptions } from "@/lib/cache/catalog";
|
||||
import { getStygianSchedule, listStygianSchedules } from "@/lib/stygian/repository";
|
||||
|
||||
export default async function AdminStygianPage({ searchParams }: { searchParams: Promise<Record<string, string | string[] | undefined>> }) {
|
||||
@@ -16,7 +16,7 @@ export default async function AdminStygianPage({ searchParams }: { searchParams:
|
||||
const schedules = await listStygianSchedules();
|
||||
const raw = Array.isArray(query.schedule) ? query.schedule[0] : query.schedule;
|
||||
const selectedId = Number(raw) || schedules[0]?.scheduleId;
|
||||
const [schedule, catalog] = await Promise.all([selectedId ? getStygianSchedule(selectedId) : null, getActiveCatalog()]);
|
||||
const [schedule, catalog] = await Promise.all([selectedId ? getStygianSchedule(selectedId) : null, getCachedCatalogOptions()]);
|
||||
return (
|
||||
<div className="min-h-svh"><AdminHeader /><main className="mx-auto flex w-full max-w-7xl flex-col gap-8 p-4 sm:p-6 lg:p-8">
|
||||
<header><p className="text-sm font-medium text-primary">Stygian Onslaught</p><h1 className="font-heading text-4xl font-semibold">Stygian Guide</h1></header>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { CommentBoardSkeleton } from "@/components/comments/comment-skeleton";
|
||||
import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "@/components/ui/empty";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { getCachedStygianSchedule, getCachedStygianSchedules } from "@/lib/cache/stygian";
|
||||
import { getActiveCatalog } from "@/lib/guides/queries";
|
||||
import { getCachedCatalogOptions } from "@/lib/cache/catalog";
|
||||
|
||||
export const instant = false;
|
||||
|
||||
@@ -22,7 +22,7 @@ export default async function StygianPage({ searchParams }: { searchParams: Prom
|
||||
const rawSchedule = Array.isArray(query.schedule) ? query.schedule[0] : query.schedule;
|
||||
const requestedId = Number(rawSchedule);
|
||||
const selectedId = schedules.some((entry) => entry.scheduleId === requestedId) ? requestedId : schedules[0]?.scheduleId;
|
||||
const [schedule, catalog] = await Promise.all([selectedId ? getCachedStygianSchedule(selectedId) : null, getActiveCatalog()]);
|
||||
const [schedule, catalog] = await Promise.all([selectedId ? getCachedStygianSchedule(selectedId) : null, getCachedCatalogOptions()]);
|
||||
return (
|
||||
<div className="flex min-h-svh flex-col bg-background">
|
||||
<LiveRefresh endpoint="/api/events/stygian" />
|
||||
|
||||
Reference in New Issue
Block a user