diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index fe7eed2..0bad771 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -82,6 +82,7 @@ Below are Namesake's core dependencies. The links below each lead to docs. | ----------------------------------------------------------------------------------- | -------------------------------------------------- | | [Convex](https://docs.convex.dev/) | Type-safe database, file storage, realtime updates | | [Convex Auth](https://labs.convex.dev/auth) | User authentication | +| [SurveyJS](https://surveyjs.io/documentation) | Form building, validation, and display | | [TanStack Router](https://tanstack.com/router/latest/docs/framework/react/overview) | File-based routing | | [React](https://react.dev/reference/react) | Front-end web framework | | [React Aria](https://react-spectrum.adobe.com/react-aria) | Accessible components and design system | diff --git a/src/routes/_authenticated/admin/documents.$documentId.tsx b/src/routes/_authenticated/admin/documents.$documentId.tsx index 7557797..77b60c1 100644 --- a/src/routes/_authenticated/admin/documents.$documentId.tsx +++ b/src/routes/_authenticated/admin/documents.$documentId.tsx @@ -1,27 +1,27 @@ -import { PageHeader } from '@/components/app' -import { Badge, Link } from '@/components/common' -import { api } from '@convex/_generated/api' -import type { Id } from '@convex/_generated/dataModel' -import { createFileRoute } from '@tanstack/react-router' -import { useQuery } from 'convex/react' +import { PageHeader } from "@/components/app"; +import { Badge, Link } from "@/components/common"; +import { api } from "@convex/_generated/api"; +import type { Id } from "@convex/_generated/dataModel"; +import { createFileRoute } from "@tanstack/react-router"; +import { useQuery } from "convex/react"; export const Route = createFileRoute( - '/_authenticated/admin/documents/$documentId', + "/_authenticated/admin/documents/$documentId", )({ component: AdminDocumentDetailRoute, -}) +}); function AdminDocumentDetailRoute() { - const { documentId } = Route.useParams() + const { documentId } = Route.useParams(); const document = useQuery(api.documents.getById, { - documentId: documentId as Id<'documents'>, - }) + documentId: documentId as Id<"documents">, + }); const fileUrl = useQuery(api.documents.getURL, { - documentId: documentId as Id<'documents'>, - }) + documentId: documentId as Id<"documents">, + }); - if (document === undefined) return - if (document === null) return 'Document not found' + if (document === undefined) return; + if (document === null) return "Document not found"; return (
@@ -36,10 +36,10 @@ function AdminDocumentDetailRoute() { > Go to quest @@ -52,5 +52,5 @@ function AdminDocumentDetailRoute() { /> )}
- ) + ); } diff --git a/src/routes/_authenticated/admin/documents.index.tsx b/src/routes/_authenticated/admin/documents.index.tsx index 154814f..d5f3990 100644 --- a/src/routes/_authenticated/admin/documents.index.tsx +++ b/src/routes/_authenticated/admin/documents.index.tsx @@ -1,4 +1,4 @@ -import { PageHeader } from '@/components/app' +import { PageHeader } from "@/components/app"; import { Badge, Button, @@ -21,75 +21,75 @@ import { TableHeader, TableRow, TextField, -} from '@/components/common' -import { api } from '@convex/_generated/api' -import type { DataModel, Id } from '@convex/_generated/dataModel' -import { JURISDICTIONS, type Jurisdiction } from '@convex/constants' -import { createFileRoute } from '@tanstack/react-router' -import { useMutation, useQuery } from 'convex/react' -import { Ellipsis, FileText, Plus } from 'lucide-react' -import { useState } from 'react' +} from "@/components/common"; +import { api } from "@convex/_generated/api"; +import type { DataModel, Id } from "@convex/_generated/dataModel"; +import { JURISDICTIONS, type Jurisdiction } from "@convex/constants"; +import { createFileRoute } from "@tanstack/react-router"; +import { useMutation, useQuery } from "convex/react"; +import { Ellipsis, FileText, Plus } from "lucide-react"; +import { useState } from "react"; -export const Route = createFileRoute('/_authenticated/admin/documents/')({ +export const Route = createFileRoute("/_authenticated/admin/documents/")({ component: DocumentsRoute, -}) +}); const NewDocumentModal = ({ isOpen, onOpenChange, onSubmit, }: { - isOpen: boolean - onOpenChange: (isOpen: boolean) => void - onSubmit: () => void + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; + onSubmit: () => void; }) => { - const generateUploadUrl = useMutation(api.documents.generateUploadUrl) - const uploadPDF = useMutation(api.documents.upload) - const createDocument = useMutation(api.documents.create) - const quests = useQuery(api.quests.getAllActive) - const [isSubmitting, setIsSubmitting] = useState(false) - const [file, setFile] = useState(null) - const [title, setTitle] = useState('') - const [code, setCode] = useState('') - const [jurisdiction, setJurisdiction] = useState(null) - const [questId, setQuestId] = useState | null>(null) + const generateUploadUrl = useMutation(api.documents.generateUploadUrl); + const uploadPDF = useMutation(api.documents.upload); + const createDocument = useMutation(api.documents.create); + const quests = useQuery(api.quests.getAllActive); + const [isSubmitting, setIsSubmitting] = useState(false); + const [file, setFile] = useState(null); + const [title, setTitle] = useState(""); + const [code, setCode] = useState(""); + const [jurisdiction, setJurisdiction] = useState(null); + const [questId, setQuestId] = useState | null>(null); const clearForm = () => { - setFile(null) - setTitle('') - setCode('') - setJurisdiction(null) - setQuestId(null) - } + setFile(null); + setTitle(""); + setCode(""); + setJurisdiction(null); + setQuestId(null); + }; const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() + e.preventDefault(); - if (jurisdiction === null) throw new Error('Jurisdiction is required') - if (file === null) throw new Error('File is required') - if (questId === null) throw new Error('Quest is required') + if (jurisdiction === null) throw new Error("Jurisdiction is required"); + if (file === null) throw new Error("File is required"); + if (questId === null) throw new Error("Quest is required"); - setIsSubmitting(true) + setIsSubmitting(true); const documentId = await createDocument({ title, jurisdiction, code, questId, - }) + }); - const postUrl = await generateUploadUrl() + const postUrl = await generateUploadUrl(); const result = await fetch(postUrl, { - method: 'POST', - headers: { 'Content-Type': file.type }, + method: "POST", + headers: { "Content-Type": file.type }, body: file, - }) - const { storageId } = await result.json() + }); + const { storageId } = await result.json(); - await uploadPDF({ documentId, storageId }) + await uploadPDF({ documentId, storageId }); - clearForm() - onSubmit() - } + clearForm(); + onSubmit(); + }; return ( setQuestId(key as Id<'quests'>)} + onSelectionChange={(key) => setQuestId(key as Id<"quests">)} isRequired className="w-full" > {quests?.map((quest) => { const textValue = `${quest.title}${ - quest.jurisdiction ? ` (${quest.jurisdiction})` : '' - }` + quest.jurisdiction ? ` (${quest.jurisdiction})` : "" + }`; return ( - {quest.title}{' '} + {quest.title}{" "} {quest.jurisdiction && {quest.jurisdiction}} - ) + ); })} { - if (e === null) return - const files = Array.from(e) - setFile(files[0]) + if (e === null) return; + const files = Array.from(e); + setFile(files[0]); }} > @@ -167,25 +167,25 @@ const NewDocumentModal = ({ - ) -} + ); +}; const DocumentTableRow = ({ document, }: { - document: DataModel['documents']['document'] + document: DataModel["documents"]["document"]; }) => { - const formUrl = useQuery(api.documents.getURL, { documentId: document._id }) - const softDelete = useMutation(api.documents.softDelete) - const undelete = useMutation(api.documents.undoSoftDelete) - const deleteForever = useMutation(api.documents.deleteForever) + const formUrl = useQuery(api.documents.getURL, { documentId: document._id }); + const softDelete = useMutation(api.documents.softDelete); + const undelete = useMutation(api.documents.undoSoftDelete); + const deleteForever = useMutation(api.documents.deleteForever); return ( @@ -240,12 +240,12 @@ const DocumentTableRow = ({ - ) -} + ); +}; function DocumentsRoute() { - const [isNewFormModalOpen, setIsNewFormModalOpen] = useState(false) - const documents = useQuery(api.documents.getAll) + const [isNewFormModalOpen, setIsNewFormModalOpen] = useState(false); + const documents = useQuery(api.documents.getAll); return (
@@ -272,7 +272,7 @@ function DocumentsRoute() { title="No documents" icon={FileText} button={{ - children: 'New Document', + children: "New Document", onPress: () => setIsNewFormModalOpen(true), }} /> @@ -289,5 +289,5 @@ function DocumentsRoute() { onSubmit={() => setIsNewFormModalOpen(false)} />
- ) + ); } diff --git a/src/routes/_authenticated/admin/quests.index.tsx b/src/routes/_authenticated/admin/quests.index.tsx index 4a6794b..6706acb 100644 --- a/src/routes/_authenticated/admin/quests.index.tsx +++ b/src/routes/_authenticated/admin/quests.index.tsx @@ -1,4 +1,4 @@ -import { PageHeader } from '@/components/app' +import { PageHeader } from "@/components/app"; import { Badge, Button, @@ -19,64 +19,64 @@ import { TableHeader, TableRow, TextField, -} from '@/components/common' -import { api } from '@convex/_generated/api' -import type { DataModel } from '@convex/_generated/dataModel' +} from "@/components/common"; +import { api } from "@convex/_generated/api"; +import type { DataModel } from "@convex/_generated/dataModel"; import { CATEGORIES, type Category, JURISDICTIONS, type Jurisdiction, -} from '@convex/constants' -import { createFileRoute, useNavigate } from '@tanstack/react-router' -import { useMutation, useQuery } from 'convex/react' -import { CircleHelp, Ellipsis, Milestone, Plus } from 'lucide-react' -import { useState } from 'react' -import { toast } from 'sonner' +} from "@convex/constants"; +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { useMutation, useQuery } from "convex/react"; +import { CircleHelp, Ellipsis, Milestone, Plus } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; -export const Route = createFileRoute('/_authenticated/admin/quests/')({ +export const Route = createFileRoute("/_authenticated/admin/quests/")({ component: QuestsRoute, -}) +}); const NewQuestModal = ({ isOpen, onOpenChange, onSubmit, }: { - isOpen: boolean - onOpenChange: (isOpen: boolean) => void - onSubmit: () => void + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; + onSubmit: () => void; }) => { - const create = useMutation(api.quests.create) - const [title, setTitle] = useState('') - const [category, setCategory] = useState(null) - const [jurisdiction, setJurisdiction] = useState(null) - const navigate = useNavigate() + const create = useMutation(api.quests.create); + const [title, setTitle] = useState(""); + const [category, setCategory] = useState(null); + const [jurisdiction, setJurisdiction] = useState(null); + const navigate = useNavigate(); const clearForm = () => { - setTitle('') - setCategory(null) - setJurisdiction(null) - } + setTitle(""); + setCategory(null); + setJurisdiction(null); + }; const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() + e.preventDefault(); const questId = await create({ title, category: category ?? undefined, jurisdiction: jurisdiction ?? undefined, - }) + }); if (questId) { - toast(`Created quest: ${title}`) - clearForm() - onSubmit() + toast(`Created quest: ${title}`); + clearForm(); + onSubmit(); navigate({ - to: '/admin/quests/$questId', + to: "/admin/quests/$questId", params: { questId }, - }) + }); } - } + }; return ( @@ -99,12 +99,12 @@ const NewQuestModal = ({ isRequired > {Object.entries(CATEGORIES).map(([key, { label, icon }]) => { - const Icon = icon ?? CircleHelp + const Icon = icon ?? CircleHelp; return ( {label} - ) + ); })}