mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-05-18 15:26:36 +00:00
feat(problemset): add metadata, description, and solution forms for new problem creation
This commit is contained in:
parent
ba676b3213
commit
424f24694b
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user