From 7badb387372e8aeec26d7ff18f733f9e8b5eca3d Mon Sep 17 00:00:00 2001 From: aahnik Date: Fri, 10 Jan 2025 08:30:57 +0530 Subject: [PATCH] Cleanup problem-editor and add logic to fetch problem for edit page, and handle submit to api --- app/[orgId]/problems/[id]/edit/page.tsx | 2 + app/[orgId]/problems/new/page.tsx | 2 + app/[orgId]/problems/page.tsx | 126 +++---- components/problem-editor.tsx | 425 +++++++++++++----------- 4 files changed, 271 insertions(+), 284 deletions(-) diff --git a/app/[orgId]/problems/[id]/edit/page.tsx b/app/[orgId]/problems/[id]/edit/page.tsx index e48509a..4cc30c3 100644 --- a/app/[orgId]/problems/[id]/edit/page.tsx +++ b/app/[orgId]/problems/[id]/edit/page.tsx @@ -1,3 +1,5 @@ +"use client"; + import { ProblemEditor } from "@/components/problem-editor"; export default function EditProblemPage() { diff --git a/app/[orgId]/problems/new/page.tsx b/app/[orgId]/problems/new/page.tsx index 58556e4..279303a 100644 --- a/app/[orgId]/problems/new/page.tsx +++ b/app/[orgId]/problems/new/page.tsx @@ -1,3 +1,5 @@ +"use client"; + import { ProblemEditor } from "@/components/problem-editor"; export default function NewProblemPage() { diff --git a/app/[orgId]/problems/page.tsx b/app/[orgId]/problems/page.tsx index 1ae3f22..02fb7e2 100644 --- a/app/[orgId]/problems/page.tsx +++ b/app/[orgId]/problems/page.tsx @@ -3,7 +3,10 @@ import { mockProblems, Problem } from "./mockProblems"; import { useToast } from "@/hooks/use-toast"; import { GenericListing, ColumnDef } from "@/mint/generic-listing"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useCallback } from "react"; +import { formatValidationErrors } from "@/utils/error"; +import { useRouter } from "next/navigation"; +import { MockAlert } from "@/components/mock-alert"; const columns: ColumnDef[] = [ { header: "Problem Code", accessorKey: "nameId", sortable: true }, @@ -18,118 +21,71 @@ export default function ProblemsPage({ params: { orgId: string }; }) { const [problems, setProblems] = useState([]); - const [selectedProblem, setSelectedProblem] = useState(null); - const [isEditorOpen, setIsEditorOpen] = useState(false); - + const [showMockAlert, setShowMockAlert] = useState(false); + const router = useRouter(); const { toast } = useToast(); - useEffect(() => { - const fetchProblems = async () => { - try { - const response = await fetch(`/api/orgs/${params.orgId}/problems`); - if (!response.ok) throw new Error("Failed to fetch problems"); - const data = await response.json(); - setProblems(data && data.length ? data : mockProblems); - } catch (error) { - console.error("Error fetching problems:", error); - setProblems(mockProblems); + const fetchProblems = useCallback(async () => { + try { + const response = await fetch(`/api/orgs/${params.orgId}/problems`); + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(formatValidationErrors(errorData)); } - }; - fetchProblems(); + const data = await response.json(); + setProblems(data); + setShowMockAlert(false); + } catch (error) { + console.error("Error fetching problems:", error); + setProblems(mockProblems); + setShowMockAlert(true); + } }, [params.orgId]); - const handleAdd = () => { - setSelectedProblem(null); - setIsEditorOpen(true); - }; - - // const handleEdit = (problem: Problem) => { - // setSelectedProblem(problem); - // setIsEditorOpen(true); - // }; + useEffect(() => { + fetchProblems(); + }, [fetchProblems]); const handleDelete = async (problem: Problem) => { try { const response = await fetch( - `/api/orgs/${params.orgId}/problems/${problem.id}`, + `/api/orgs/${params.orgId}/problems/${problem.nameId}`, { method: "DELETE", - }, + } ); - if (!response.ok) throw new Error("Failed to delete problem"); - setProblems((prev) => prev.filter((p) => p.id !== problem.id)); - } catch (error) { - console.error("Error deleting problem:", error); - } - }; - - /* - const handleSave = async (problem: Problem) => { - try { - const url = selectedProblem - ? `/api/orgs/${params.orgId}/problems/${selectedProblem.id}` - : `/api/orgs/${params.orgId}/problems`; - - const response = await fetch(url, { - method: selectedProblem ? "PATCH" : "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ ...problem, orgId: parseInt(params.orgId) }), - }); - if (!response.ok) throw new Error("Failed to save problem"); - const savedProblem = await response.json(); + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(formatValidationErrors(errorData)); + } - setProblems((prev) => { - if (selectedProblem) { - return prev.map((p) => - p.id === selectedProblem.id ? savedProblem : p, - ); - } - return [...prev, savedProblem]; + await fetchProblems(); + toast({ + title: "Success", + description: "Problem deleted successfully", }); + return Promise.resolve(); } catch (error) { - console.error("Error saving problem:", error); + console.error("Error deleting problem:", error); + return Promise.reject(error); } }; - */ - - // const handleSavelocal = (updatedProblem: Problem) => { - // setProblems( - // problems.map((p) => (p.id === updatedProblem.id ? updatedProblem : p)), - // ); - // toast({ - // title: "Success!", - // description: "Problem updated successfully", - // }); - // setIsEditorOpen(false); - // }; return ( <> + router.push(`/${params.orgId}/problems/new`)} + onEdit={(problem: Problem) => + router.push(`/${params.orgId}/problems/${problem.nameId}/edit`) + } onDelete={handleDelete} - allowDownload={true} - addPage="new" - editPathAttr="nameId" - rowClickAttr="nameId" - editPathSuffix="edit" /> - {/* - - - setIsEditorOpen(false)} - onSave={handleSavelocal} - /> - - */} ); } diff --git a/components/problem-editor.tsx b/components/problem-editor.tsx index cb73da0..fb122b5 100644 --- a/components/problem-editor.tsx +++ b/components/problem-editor.tsx @@ -1,12 +1,14 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Plus, X } from "lucide-react"; -import { useToast } from "@/components/ui/use-toast"; +import { useToast } from "@/hooks/use-toast"; +import { useRouter, useParams } from "next/navigation"; +import { formatValidationErrors } from "@/utils/error"; interface TestCase { input: string; @@ -15,54 +17,79 @@ interface TestCase { } interface Problem { - id?: string; - code: string; - title: string; - description: string; + nameId?: string; + code: string; // problem code (required) + name: string; // problem title (required) + statement: string; // problem description (required) allowedLanguages: string[]; testCases: TestCase[]; } -interface ProblemEditorProps { - problem?: Problem | null; - onSave?: (problem: Problem) => void; -} - -export function ProblemEditor({ problem, onSave }: ProblemEditorProps) { +export function ProblemEditor() { const { toast } = useToast(); + const router = useRouter(); + const params = useParams(); + const orgId = params.orgId as string; + const problemId = params.id as string; + const isEdit = !!problemId; const [currentProblem, setCurrentProblem] = useState({ - id: problem?.id?.toString() || "1", - code: problem?.code || "", - title: problem?.title || "", - description: problem?.description || "", - allowedLanguages: problem?.allowedLanguages || [ - "python", - "javascript", - "typescript", - ], - testCases: problem?.testCases || [ - { input: "", output: "", kind: "example" }, - ], + code: "", + name: "", + statement: "", + allowedLanguages: ["python", "javascript", "typescript"], + testCases: [{ input: "", output: "", kind: "example" }], }); + useEffect(() => { + if (isEdit) { + const fetchProblem = async () => { + try { + const response = await fetch( + `/api/orgs/${orgId}/problems/${problemId}`, + ); + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(formatValidationErrors(errorData)); + } + const data = await response.json(); + setCurrentProblem(data); + } catch (error) { + console.error("Error fetching problem:", error); + toast({ + variant: "destructive", + title: "Error", + description: + error instanceof Error + ? error.message + : "Failed to fetch problem", + }); + router.push(`/${orgId}/problems`); + } + }; + fetchProblem(); + console.log("From useEffect in problem editor", orgId, problemId); + } + }, [isEdit, orgId, problemId, router, toast]); + const updateProblemField = ( field: K, value: Problem[K], ) => { + setCurrentProblem((prev) => ({ ...prev, [field]: value })); + }; + + const addTestCase = (kind: "example" | "test") => { setCurrentProblem((prev) => ({ ...prev, - [field]: value, + testCases: [...prev.testCases, { input: "", output: "", kind }], })); }; - const addTestCase = () => { + const removeTestCase = (index: number) => { setCurrentProblem((prev) => ({ ...prev, - testCases: [ - ...prev.testCases, - { input: "", output: "", kind: "example" }, - ], + testCases: prev.testCases.filter((_, i) => i !== index), })); }; @@ -79,200 +106,200 @@ export function ProblemEditor({ problem, onSave }: ProblemEditorProps) { })); }; - const removeTestCase = (index: number) => { - if (currentProblem.testCases.length === 1) return; - setCurrentProblem((prev) => ({ - ...prev, - testCases: prev.testCases.filter((_, i) => i !== index), - })); - }; - - const handleSaveProblem = async () => { + const handleSubmit = async () => { try { - if (onSave) await onSave(currentProblem); + const url = isEdit + ? `/api/orgs/${orgId}/problems/${problemId}` + : `/api/orgs/${orgId}/problems`; + + const response = await fetch(url, { + method: isEdit ? "PUT" : "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + ...currentProblem, + orgId: parseInt(orgId), + }), + }); + + if (!response.ok) { + console.log("NOT RESPONSE OK in problems"); + const errorData = await response.json().catch(() => ({})); + console.log(errorData); + const errorMessage = + errorData.message || formatValidationErrors(errorData); + throw new Error(errorMessage); + } + toast({ title: "Success", - description: "Problem saved successfully", + description: `Problem ${isEdit ? "updated" : "created"} successfully`, }); + router.push(`/${orgId}/problems`); } catch (error) { console.error("Error saving problem:", error); toast({ - title: "Error", - description: "Failed to save problem", variant: "destructive", + title: "Error", + description: + error instanceof Error ? error.message : "Failed to save problem", }); } }; return ( -
-
- - - - Problem - - - Test Cases - - +
+
+

+ {isEdit ? "Edit Problem" : "Create New Problem"} +

+
+ + +
+
- -
-
- - updateProblemField("code", e.target.value)} - placeholder="Enter problem code (unique identifier for this problem)" - className="bg-muted border-border" - /> -
+
+
+ + updateProblemField("code", e.target.value)} + placeholder="Enter problem code" + /> +
-
- - updateProblemField("title", e.target.value)} - placeholder="Enter problem title" - className="bg-muted border-border" - /> -
+
+ + updateProblemField("name", e.target.value)} + placeholder="Enter problem name" + /> +
-
- -