From 18fa039a8653035b7bb5851581cdec15175883fd Mon Sep 17 00:00:00 2001 From: Pranavsai Date: Fri, 19 Dec 2025 15:37:54 -0800 Subject: [PATCH 01/13] Built the form question editor component and a test page to try it out (looks pretty clean). Supports multiple question types with dynamic UI and uses Zod schemas. --- .../src/app/admin/forms/test-editor/page.tsx | 68 ++++ .../admin/forms/question-edit-card.tsx | 326 ++++++++++++++++++ apps/blade/src/lib/types/form.ts | 44 +++ 3 files changed, 438 insertions(+) create mode 100644 apps/blade/src/app/admin/forms/test-editor/page.tsx create mode 100644 apps/blade/src/components/admin/forms/question-edit-card.tsx create mode 100644 apps/blade/src/lib/types/form.ts diff --git a/apps/blade/src/app/admin/forms/test-editor/page.tsx b/apps/blade/src/app/admin/forms/test-editor/page.tsx new file mode 100644 index 000000000..90563c7fc --- /dev/null +++ b/apps/blade/src/app/admin/forms/test-editor/page.tsx @@ -0,0 +1,68 @@ +"use client"; + +import { useState } from "react"; +// Replaced uuid import with native crypto.randomUUID for simplicity and to avoid adding dependencies +// import { v4 as uuidv4 } from "uuid"; +const uuidv4 = () => crypto.randomUUID(); + +import { QuestionEditCard } from "~/components/admin/forms/question-edit-card"; +import type { FormQuestion } from "~/lib/types/form"; + +export default function TestEditorPage() { + const [question, setQuestion] = useState({ + id: uuidv4(), + title: "Untitled Question", + type: "multiple_choice", + required: false, + options: [ + { id: uuidv4(), value: "Option 1", isOther: false }, + ], + }); + + const handleUpdate = (updatedQuestion: FormQuestion) => { + console.log("Updated Question:", updatedQuestion); + setQuestion(updatedQuestion); + }; + + const handleDelete = (id: string) => { + console.log("Delete Question:", id); + alert("Delete clicked for id: " + id); + }; + + const handleDuplicate = (q: FormQuestion) => { + console.log("Duplicate Question:", q); + alert("Duplicate clicked"); + }; + + return ( +
+
+
+

Ripoff Google Form by KH test

+

+ Welcome to the awesome bootleg google form by KH mindblown emoji* +

+
+ +
+ +
+ +
+ + {/*
+

Current State (Debug)

+
+                    {JSON.stringify(question, null, 2)}
+                
+
*/} +
+ + ); +} diff --git a/apps/blade/src/components/admin/forms/question-edit-card.tsx b/apps/blade/src/components/admin/forms/question-edit-card.tsx new file mode 100644 index 000000000..bef6b0150 --- /dev/null +++ b/apps/blade/src/components/admin/forms/question-edit-card.tsx @@ -0,0 +1,326 @@ +"use client"; + +import * as React from "react"; +import { + AlignLeft, + Calendar, + CheckSquare, + ChevronDown, + Circle, + CircleDot, + Clock, + Copy, + MoreVertical, + Pilcrow, + Trash, + X, + GripHorizontal, +} from "lucide-react"; +const uuidv4 = () => crypto.randomUUID(); + +import { cn } from "@forge/ui"; +import { Button } from "@forge/ui/button"; +import { Card } from "@forge/ui/card"; +import { Input } from "@forge/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@forge/ui/select"; +import { Checkbox } from "@forge/ui/checkbox"; +import { Textarea } from "@forge/ui/textarea"; + +import { + FormQuestion, + QuestionOption, + QuestionType, +} from "~/lib/types/form"; + +interface QuestionEditCardProps { + question: FormQuestion; + isActive: boolean; + onUpdate: (updatedQuestion: FormQuestion) => void; + onDelete: (id: string) => void; + onDuplicate: (question: FormQuestion) => void; +} + +const QUESTION_TYPES: { + value: QuestionType; + label: string; + icon: React.ElementType; +}[] = [ + { value: "short_answer", label: "Short answer", icon: AlignLeft }, + { value: "paragraph", label: "Paragraph", icon: Pilcrow }, + { value: "multiple_choice", label: "Multiple choice", icon: CircleDot }, + { value: "checkboxes", label: "Checkboxes", icon: CheckSquare }, + { value: "dropdown", label: "Dropdown", icon: ChevronDown }, + { value: "date", label: "Date", icon: Calendar }, + { value: "time", label: "Time", icon: Clock }, + ]; + +export function QuestionEditCard({ + question, + isActive, + onUpdate, + onDelete, + onDuplicate, +}: QuestionEditCardProps) { + // -- Handlers -- + + const handleTitleChange = (e: React.ChangeEvent) => { + onUpdate({ ...question, title: e.target.value }); + }; + + const handleTypeChange = (newType: QuestionType) => { + let updatedQuestion = { ...question, type: newType }; + + if ( + ["multiple_choice", "checkboxes", "dropdown"].includes(newType) && + (!question.options || question.options.length === 0) + ) { + updatedQuestion.options = [{ id: uuidv4(), value: "Option 1", isOther: false }]; + } + + if (["short_answer", "paragraph", "date", "time"].includes(newType)) { + updatedQuestion.options = undefined; + } + + onUpdate(updatedQuestion); + }; + + const handleRequiredChange = (checked: boolean) => { + onUpdate({ ...question, required: checked }); + }; + + return ( + { + e.stopPropagation(); + }} + > + + + {/* Header */} +
+
+