feat(problemset): add metadata, description, and solution forms for new problem creation

This commit is contained in:
cfngc4594 2025-04-05 23:17:36 +08:00
parent ba676b3213
commit 424f24694b
3 changed files with 405 additions and 0 deletions

View File

@ -0,0 +1,142 @@
"use client";
import { z } from "zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation";
import DockView from "@/components/dockview";
import { ArrowRightIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import MdxPreview from "@/components/mdx-preview";
import { zodResolver } from "@hookform/resolvers/zod";
import { ScrollArea } from "@/components/ui/scroll-area";
import MarkdownEditor from "@/components/markdown-editor";
import { useNewProblemStore } from "@/app/(app)/dashboard/@admin/problemset/new/store";
import { problemSchema } from "@/components/features/dashboard/admin/problemset/new/schema";
import { newProblemMetadataSchema } from "@/components/features/dashboard/admin/problemset/new/components/metadata-form";
export const newProblemDescriptionSchema = problemSchema.pick({
description: true,
});
type NewProblemDescriptionSchema = z.infer<
typeof newProblemDescriptionSchema
>;
export default function NewProblemDescriptionForm() {
const {
hydrated,
displayId,
title,
difficulty,
published,
description,
setData,
} = useNewProblemStore();
const router = useRouter();
const form = useForm<NewProblemDescriptionSchema>({
resolver: zodResolver(newProblemDescriptionSchema),
defaultValues: {
description: description || "",
},
});
const onSubmit = (data: NewProblemDescriptionSchema) => {
setData(data);
router.push("/dashboard/problemset/new/solution");
};
useEffect(() => {
if (!hydrated) return;
try {
newProblemMetadataSchema.parse({
displayId,
title,
difficulty,
published,
});
} catch {
router.push("/dashboard/problemset/new/metadata");
}
}, [difficulty, displayId, hydrated, published, router, title]);
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="h-full space-y-8 p-4"
>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem className="h-full flex flex-col">
<FormLabel className="flex flex-none items-center justify-between">
<span>Description</span>
<Button className="h-8 w-auto" type="submit">
Next
<ArrowRightIcon size={16} aria-hidden="true" />
</Button>
</FormLabel>
<FormControl>
<DockView
storageKey="dockview:new-problem"
options={[
{
id: "MdxPreview",
title: "Mdx Preview",
component: "MdxPreview",
tabComponent: "MdxPreview",
icon: "FileTextIcon",
node: (
<div className="h-full border-x border-muted relative">
<div className="absolute h-full w-full">
<ScrollArea className="h-full">
<MdxPreview
source={field.value}
className="p-4 md:p-6"
/>
</ScrollArea>
</div>
</div>
),
},
{
id: "MarkdownEditor",
title: "Markdown Editor",
component: "MarkdownEditor",
tabComponent: "MarkdownEditor",
icon: "FileTextIcon",
node: (
<MarkdownEditor
value={field.value}
onChange={field.onChange}
/>
),
position: { referencePanel: "MdxPreview", direction: "right" },
},
]}
/>
</FormControl>
<FormDescription>
This is your problem description.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
);
}

View File

@ -0,0 +1,156 @@
"use client";
import { z } from "zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Difficulty } from "@/generated/client";
import { Switch } from "@/components/ui/switch";
import { getDifficultyColorClass } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNewProblemStore } from "@/app/(app)/dashboard/@admin/problemset/new/store";
import { problemSchema } from "@/components/features/dashboard/admin/problemset/new/schema";
export const newProblemMetadataSchema = problemSchema.pick({
displayId: true,
title: true,
difficulty: true,
published: true,
});
type NewProblemMetadataSchema = z.infer<typeof newProblemMetadataSchema>;
export default function NewProblemMetadataForm() {
const router = useRouter();
const { displayId, title, difficulty, published, setData } = useNewProblemStore();
const form = useForm<NewProblemMetadataSchema>({
resolver: zodResolver(newProblemMetadataSchema),
defaultValues: {
// displayId must be a number and cannot be an empty string ("")
// so set it to undefined here and convert it to "" in the Input component.
displayId: displayId || undefined,
title: title || "",
difficulty: difficulty || "EASY",
published: published || false,
},
});
const onSubmit = (data: NewProblemMetadataSchema) => {
setData(data);
router.push("/dashboard/problemset/new/description");
};
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="max-w-3xl mx-auto space-y-8 py-10"
>
<FormField
control={form.control}
name="displayId"
render={({ field }) => (
<FormItem>
<FormLabel>Display ID</FormLabel>
<FormControl>
<Input placeholder="e.g., 1001" {...field} value={field.value ?? ""} />
</FormControl>
<FormDescription>
Unique numeric identifier visible to users
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Problem Title</FormLabel>
<FormControl>
<Input placeholder="e.g., Two Sum" {...field} />
</FormControl>
<FormDescription>
Descriptive title summarizing the problem
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="difficulty"
render={({ field }) => (
<FormItem>
<FormLabel>Difficulty Level</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select difficulty level" />
</SelectTrigger>
</FormControl>
<SelectContent>
{Object.values(Difficulty).map((difficulty) => (
<SelectItem key={difficulty} value={difficulty}>
<span className={getDifficultyColorClass(difficulty)}>
{difficulty}
</span>
</SelectItem>
))}
</SelectContent>
</Select>
<FormDescription>
Categorize problem complexity for better filtering
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="published"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5 mr-2">
<FormLabel>Publish Status</FormLabel>
<FormDescription>
Make problem visible in public listings
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<Button type="submit">Next</Button>
</form>
</Form>
);
}

View File

@ -0,0 +1,107 @@
"use client";
import { z } from "zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNewProblemStore } from "@/app/(app)/dashboard/@admin/problemset/new/store";
import { problemSchema } from "@/components/features/dashboard/admin/problemset/new/schema";
import { newProblemMetadataSchema } from "@/components/features/dashboard/admin/problemset/new/components/metadata-form";
import { newProblemDescriptionSchema } from "@/components/features/dashboard/admin/problemset/new/components/description-form";
const newProblemSolutionSchema = problemSchema.pick({
solution: true,
});
type NewProblemSolutionSchema = z.infer<typeof newProblemSolutionSchema>;
export default function NewProblemSolutionForm() {
const {
hydrated,
displayId,
title,
difficulty,
published,
description,
solution,
} = useNewProblemStore();
const router = useRouter();
const form = useForm<NewProblemSolutionSchema>({
resolver: zodResolver(newProblemSolutionSchema),
defaultValues: {
solution: solution || "",
},
});
const onSubmit = (data: NewProblemSolutionSchema) => {
console.log({
...data,
displayId,
title,
difficulty,
published,
description,
});
};
useEffect(() => {
if (!hydrated) return;
try {
newProblemMetadataSchema.parse({
displayId,
title,
difficulty,
published,
});
} catch {
router.push("/dashboard/problemset/new/metadata");
}
try {
newProblemDescriptionSchema.parse({ description });
} catch {
router.push("/dashboard/problemset/new/description");
}
}, [hydrated, displayId, title, difficulty, published, description, router]);
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="max-w-3xl mx-auto space-y-8 py-10"
>
<FormField
control={form.control}
name="solution"
render={({ field }) => (
<FormItem>
<FormLabel>Solution</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
This is your problem solution.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Next</Button>
</form>
</Form>
);
}