refactor(layouts): overhaul problem and problemset page structures

- Simplify ProblemLayout to use children prop and remove ProblemStoreProvider
- Replace PlaygroundHeader with dedicated ProblemHeader component
- Streamline ProblemsetLayout with new ProblemsetHeader
- Remove deprecated BackButton in favor of NavigateBackButton
- Delete unused ProblemStoreProvider and related dependencies
This commit is contained in:
cfngc4594 2025-05-06 21:04:45 +08:00
parent 829370fafd
commit aed942e7e2
7 changed files with 73 additions and 205 deletions

View File

@ -1,90 +1,25 @@
import prisma from "@/lib/prisma";
import { notFound } from "next/navigation";
import { getUserLocale } from "@/i18n/locale";
import ProblemPage from "@/app/(app)/problems/[id]/page";
import { ProblemStoreProvider } from "@/providers/problem-store-provider";
import { PlaygroundHeader } from "@/components/features/playground/header";
import { ProblemHeader } from "@/features/problems/components/problem-header";
interface ProblemProps {
interface ProblemLayoutProps {
children: React.ReactNode;
params: Promise<{ id: string }>;
Description: React.ReactNode;
Solutions: React.ReactNode;
Submissions: React.ReactNode;
Details: React.ReactNode;
Code: React.ReactNode;
Testcase: React.ReactNode;
Bot: React.ReactNode;
}
export default async function ProblemLayout({
children,
params,
Description,
Solutions,
Submissions,
Details,
Code,
Testcase,
Bot,
}: ProblemProps) {
}: ProblemLayoutProps) {
const { id } = await params;
if (!id) {
return notFound();
}
const [
problem,
editorLanguageConfigs,
languageServerConfigs,
submissions,
] = await Promise.all([
prisma.problem.findUnique({
where: { id },
include: {
templates: true,
testcases: {
include: {
data: true,
},
},
},
}),
prisma.editorLanguageConfig.findMany(),
prisma.languageServerConfig.findMany(),
prisma.submission.findMany({
where: { problemId: id },
}),
]);
if (!problem) {
return notFound();
}
const locale = await getUserLocale();
return (
<div className="flex flex-col h-screen">
<ProblemStoreProvider
problemId={id}
problem={problem}
editorLanguageConfigs={editorLanguageConfigs}
languageServerConfigs={languageServerConfigs}
submissions={submissions}
>
<PlaygroundHeader />
<main className="flex flex-grow overflow-y-hidden p-2.5 pt-0">
<ProblemPage
locale={locale}
Description={Description}
Solutions={Solutions}
Submissions={Submissions}
Details={Details}
Code={Code}
Testcase={Testcase}
Bot={Bot}
/>
</main>
</ProblemStoreProvider>
<ProblemHeader />
{children}
</div>
);
}

View File

@ -1,5 +1,4 @@
import { Banner } from "@/components/banner";
import { AvatarButton } from "@/components/avatar-button";
import { ProblemsetHeader } from "@/features/problemset/components/problemset-header";
interface ProblemsetLayoutProps {
children: React.ReactNode;
@ -7,14 +6,9 @@ interface ProblemsetLayoutProps {
export default function ProblemsetLayout({ children }: ProblemsetLayoutProps) {
return (
<div className="relative h-screen flex flex-col">
<Banner />
<div className="absolute top-2 right-4">
<AvatarButton />
</div>
<main className="h-full container mx-auto p-4">
{children}
</main>
<div className="h-full flex flex-col">
<ProblemsetHeader />
{children}
</div>
);
}

View File

@ -1,45 +0,0 @@
import { cn } from "@/lib/utils";
import { auth } from "@/lib/auth";
import BackButton from "@/components/back-button";
import { RunCodeButton } from "@/components/run-code";
import { AvatarButton } from "@/components/avatar-button";
import BotVisibilityToggle from "@/components/bot-visibility-toggle";
interface PlaygroundHeaderProps {
className?: string;
}
export async function PlaygroundHeader({
className,
...props
}: PlaygroundHeaderProps) {
const session = await auth();
return (
<header
{...props}
className={cn("relative", className)}
>
<nav className="z-0 relative h-12 w-full flex shrink-0 items-center px-2.5">
<div className="w-full flex justify-between">
<div className="w-full flex items-center justify-between">
<div className="flex items-center">
<BackButton href="/problemset" />
</div>
<div className="relative flex items-center gap-2">
<BotVisibilityToggle />
<AvatarButton />
</div>
</div>
</div>
</nav>
<div className="z-10 absolute left-1/2 top-0 h-full -translate-x-1/2 py-2">
<div className="relative flex">
<div className="relative flex overflow-hidden rounded">
<RunCodeButton session={session} className="bg-muted text-muted-foreground hover:bg-muted/50" />
</div>
</div>
</div>
</header>
);
}

View File

@ -1,20 +1,21 @@
import Link from "next/link";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { useTranslations } from "next-intl";
import { ArrowLeftIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
interface BackButtonProps {
interface NavigateBackButtonProps {
href: string;
className?: string;
}
export default function BackButton({
href,
className,
...props
}: BackButtonProps) {
const NavigateBackButton = ({ href, className }: NavigateBackButtonProps) => {
const t = useTranslations();
return (
@ -25,7 +26,6 @@ export default function BackButton({
variant="ghost"
className={cn("h-8 w-auto p-2", className)}
asChild
{...props}
>
<Link href={href}>
<ArrowLeftIcon size={16} aria-hidden="true" />
@ -38,4 +38,6 @@ export default function BackButton({
</Tooltip>
</TooltipProvider>
);
}
};
export { NavigateBackButton };

View File

@ -0,0 +1,33 @@
import { cn } from "@/lib/utils";
import { Suspense } from "react";
import { UserAvatar, UserAvatarSkeleton } from "@/components/user-avatar";
import { JudgeCodeButton } from "@/features/problems/components/judge-code-button";
import { NavigateBackButton } from "@/features/problems/components/navigate-back-button";
interface ProblemHeaderProps {
className?: string;
}
const ProblemHeader = ({ className }: ProblemHeaderProps) => {
return (
<header
className={cn("relative flex h-12 flex-none items-center", className)}
>
<div className="container mx-auto flex h-full items-center justify-between px-4">
<div className="flex items-center">
<NavigateBackButton href="/problemset" />
</div>
<div className="flex items-center">
<Suspense fallback={<UserAvatarSkeleton />}>
<UserAvatar />
</Suspense>
</div>
</div>
<div className="absolute inset-y-0 left-1/2 z-10 flex -translate-x-1/2 items-center">
<JudgeCodeButton />
</div>
</header>
);
};
export { ProblemHeader };

View File

@ -0,0 +1,18 @@
import { Suspense } from "react";
import { UserAvatar, UserAvatarSkeleton } from "@/components/user-avatar";
const ProblemsetHeader = () => {
return (
<header className="flex h-12 flex-none items-center">
<div className="container mx-auto flex h-full items-center justify-end px-4">
<div className="flex items-center">
<Suspense fallback={<UserAvatarSkeleton />}>
<UserAvatar />
</Suspense>
</div>
</div>
</header>
);
};
export { ProblemsetHeader };

View File

@ -1,69 +0,0 @@
"use client";
import {
EditorLanguage,
type Submission,
type EditorLanguageConfig,
type LanguageServerConfig,
} from "@/generated/client";
import { useStore } from "zustand";
import { type ProblemWithDetails } from "@/types/prisma";
import { type ReactNode, createContext, useRef, useContext } from "react";
import { type ProblemStore, createProblemStore } from "@/stores/problem-store";
export type ProblemStoreApi = ReturnType<typeof createProblemStore>;
export const ProblemStoreContext = createContext<ProblemStoreApi | undefined>(undefined);
export interface ProblemStoreProviderProps {
children: ReactNode;
problemId: string;
problem: ProblemWithDetails;
editorLanguageConfigs: EditorLanguageConfig[];
languageServerConfigs: LanguageServerConfig[];
submissions: Submission[];
}
export const ProblemStoreProvider = ({
children,
problemId,
problem,
editorLanguageConfigs,
languageServerConfigs,
submissions,
}: ProblemStoreProviderProps) => {
const storeRef = useRef<ProblemStoreApi | null>(null);
if (storeRef.current === null) {
storeRef.current = createProblemStore({
hydrated: false,
editor: null,
markers: [],
webSocket: null,
globalLang: EditorLanguage.c,
currentLang: EditorLanguage.c,
currentValue: "",
problemId,
problem,
editorLanguageConfigs,
languageServerConfigs,
submissions,
});
}
return (
<ProblemStoreContext.Provider value={storeRef.current}>
{children}
</ProblemStoreContext.Provider>
);
};
export const useProblemStore = <T,>(selector: (store: ProblemStore) => T): T => {
const problemStoreContext = useContext(ProblemStoreContext);
if (!problemStoreContext) {
throw new Error("useProblemStore must be used within ProblemStoreProvider");
}
return useStore(problemStoreContext, selector);
};