mirror of
https://github.com/massbug/judge4c.git
synced 2025-05-17 23:12:23 +00:00
refactor(problems): migrate description and solution to feature-based structure
- Remove old parallel route implementations (@Description and @Solutions) - Add new feature-based components for problem description and solution - Create content and panel components for both features - Implement skeleton loading states - Use cached data fetching - Update MDX rendering and scroll area implementations
This commit is contained in:
parent
f464fb7636
commit
5f3eb72d00
@ -1,16 +0,0 @@
|
|||||||
import { Suspense } from "react";
|
|
||||||
import { Loading } from "@/components/loading";
|
|
||||||
|
|
||||||
interface DescriptionLayoutProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DescriptionLayout({ children }: DescriptionLayoutProps) {
|
|
||||||
return (
|
|
||||||
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-3xl bg-background">
|
|
||||||
<Suspense fallback={<Loading />}>
|
|
||||||
{children}
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
import prisma from "@/lib/prisma";
|
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import MdxPreview from "@/components/mdx-preview";
|
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
||||||
import ProblemDescriptionFooter from "@/components/features/playground/problem/description/footer";
|
|
||||||
|
|
||||||
interface DescriptionPageProps {
|
|
||||||
params: Promise<{ id: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function DescriptionPage({ params }: DescriptionPageProps) {
|
|
||||||
const { id } = await params;
|
|
||||||
|
|
||||||
if (!id) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const problem = await prisma.problem.findUnique({
|
|
||||||
where: { id },
|
|
||||||
select: {
|
|
||||||
title: true,
|
|
||||||
description: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!problem) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="relative flex-1">
|
|
||||||
<div className="absolute h-full w-full">
|
|
||||||
<ScrollArea className="h-full [&>[data-radix-scroll-area-viewport]>div:min-w-0 [&>[data-radix-scroll-area-viewport]>div]:!block bg-background">
|
|
||||||
<MdxPreview source={problem.description} className="p-4 md:p-6" />
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ProblemDescriptionFooter title={problem.title} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import { Suspense } from "react";
|
|
||||||
import { Loading } from "@/components/loading";
|
|
||||||
|
|
||||||
interface SolutionsLayoutProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function SolutionsLayout({ children }: SolutionsLayoutProps) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col h-full border border-t-0 border-muted rounded-b-3xl bg-background">
|
|
||||||
<Suspense fallback={<Loading />}>
|
|
||||||
{children}
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
import prisma from "@/lib/prisma";
|
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import MdxPreview from "@/components/mdx-preview";
|
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import ProblemSolutionFooter from "@/components/features/playground/problem/solution/footer";
|
|
||||||
|
|
||||||
interface SolutionsPageProps {
|
|
||||||
params: Promise<{ id: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function SolutionsPage({ params }: SolutionsPageProps) {
|
|
||||||
const { id } = await params;
|
|
||||||
|
|
||||||
if (!id) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const problem = await prisma.problem.findUnique({
|
|
||||||
where: { id },
|
|
||||||
select: {
|
|
||||||
title: true,
|
|
||||||
solution: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!problem) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="relative flex-1">
|
|
||||||
<div className="absolute h-full w-full">
|
|
||||||
<ScrollArea className="h-full [&>[data-radix-scroll-area-viewport]>div:min-w-0 [&>[data-radix-scroll-area-viewport]>div]:!block bg-background">
|
|
||||||
<MdxPreview source={problem.solution} className="p-4 md:p-6" />
|
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ProblemSolutionFooter title={problem.title} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
56
src/features/problems/description/components/content.tsx
Normal file
56
src/features/problems/description/components/content.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { getCachedProblem } from "@/lib/prisma";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
||||||
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
|
interface DescriptionContentProps {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DescriptionContent = async ({ id }: DescriptionContentProps) => {
|
||||||
|
const problem = await getCachedProblem(id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollArea className="h-full">
|
||||||
|
<MdxRenderer
|
||||||
|
source={problem?.description ?? "description not found"}
|
||||||
|
className="p-4 md:p-6"
|
||||||
|
/>
|
||||||
|
<ScrollBar orientation="horizontal" />
|
||||||
|
</ScrollArea>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DescriptionContentSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col p-4 md:p-6">
|
||||||
|
{/* Title skeleton */}
|
||||||
|
<Skeleton className="h-8 w-3/4 mb-6" />
|
||||||
|
|
||||||
|
{/* Content skeletons */}
|
||||||
|
<Skeleton className="h-4 w-full mb-4" />
|
||||||
|
<Skeleton className="h-4 w-5/6 mb-4" />
|
||||||
|
<Skeleton className="h-4 w-2/3 mb-4" />
|
||||||
|
<Skeleton className="h-4 w-full mb-4" />
|
||||||
|
<Skeleton className="h-4 w-4/5 mb-4" />
|
||||||
|
|
||||||
|
{/* Example section heading */}
|
||||||
|
<Skeleton className="h-6 w-1/4 mb-4 mt-8" />
|
||||||
|
|
||||||
|
{/* Example content */}
|
||||||
|
<Skeleton className="h-4 w-full mb-4" />
|
||||||
|
<Skeleton className="h-4 w-5/6 mb-4" />
|
||||||
|
|
||||||
|
{/* Code block skeleton */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<Skeleton className="h-40 w-full rounded-md" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* More content */}
|
||||||
|
<Skeleton className="h-4 w-full mb-4" />
|
||||||
|
<Skeleton className="h-4 w-3/4 mb-4" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { DescriptionContent, DescriptionContentSkeleton };
|
25
src/features/problems/description/components/panel.tsx
Normal file
25
src/features/problems/description/components/panel.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import {
|
||||||
|
DescriptionContent,
|
||||||
|
DescriptionContentSkeleton,
|
||||||
|
} from "@/features/problems/description/components/content";
|
||||||
|
|
||||||
|
interface DescriptionPanelProps {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DescriptionPanel = ({ id }: DescriptionPanelProps) => {
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-3xl bg-background overflow-hidden">
|
||||||
|
<div className="relative flex-1">
|
||||||
|
<div className="absolute h-full w-full">
|
||||||
|
<Suspense fallback={<DescriptionContentSkeleton />}>
|
||||||
|
<DescriptionContent id={id} />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { DescriptionPanel };
|
56
src/features/problems/solution/components/content.tsx
Normal file
56
src/features/problems/solution/components/content.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { getCachedProblem } from "@/lib/prisma";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
||||||
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
|
interface SolutionContentProps {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SolutionContent = async ({ id }: SolutionContentProps) => {
|
||||||
|
const problem = await getCachedProblem(id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollArea className="h-full">
|
||||||
|
<MdxRenderer
|
||||||
|
source={problem?.solution ?? "solution not found"}
|
||||||
|
className="p-4 md:p-6"
|
||||||
|
/>
|
||||||
|
<ScrollBar orientation="horizontal" />
|
||||||
|
</ScrollArea>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SolutionContentSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col p-4 md:p-6">
|
||||||
|
{/* Title skeleton */}
|
||||||
|
<Skeleton className="h-8 w-3/4 mb-6" />
|
||||||
|
|
||||||
|
{/* Content skeletons */}
|
||||||
|
<Skeleton className="h-4 w-full mb-4" />
|
||||||
|
<Skeleton className="h-4 w-5/6 mb-4" />
|
||||||
|
<Skeleton className="h-4 w-2/3 mb-4" />
|
||||||
|
<Skeleton className="h-4 w-full mb-4" />
|
||||||
|
<Skeleton className="h-4 w-4/5 mb-4" />
|
||||||
|
|
||||||
|
{/* Example section heading */}
|
||||||
|
<Skeleton className="h-6 w-1/4 mb-4 mt-8" />
|
||||||
|
|
||||||
|
{/* Example content */}
|
||||||
|
<Skeleton className="h-4 w-full mb-4" />
|
||||||
|
<Skeleton className="h-4 w-5/6 mb-4" />
|
||||||
|
|
||||||
|
{/* Code block skeleton */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<Skeleton className="h-40 w-full rounded-md" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* More content */}
|
||||||
|
<Skeleton className="h-4 w-full mb-4" />
|
||||||
|
<Skeleton className="h-4 w-3/4 mb-4" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { SolutionContent, SolutionContentSkeleton };
|
25
src/features/problems/solution/components/panel.tsx
Normal file
25
src/features/problems/solution/components/panel.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import {
|
||||||
|
SolutionContent,
|
||||||
|
SolutionContentSkeleton,
|
||||||
|
} from "@/features/problems/solution/components/content";
|
||||||
|
|
||||||
|
interface SolutionPanelProps {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SolutionPanel = ({ id }: SolutionPanelProps) => {
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-3xl bg-background overflow-hidden">
|
||||||
|
<div className="relative flex-1">
|
||||||
|
<div className="absolute h-full w-full">
|
||||||
|
<Suspense fallback={<SolutionContentSkeleton />}>
|
||||||
|
<SolutionContent id={id} />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { SolutionPanel };
|
Loading…
Reference in New Issue
Block a user