From 80235ba5f885f38d460ae17ee0df56648221e857 Mon Sep 17 00:00:00 2001 From: Eva Decker Date: Fri, 6 Dec 2024 23:36:34 -0500 Subject: [PATCH] Locate form on its own route, improve edit experience --- src/components/quests/QuestForm/QuestForm.tsx | 93 +++++++++---------- src/routeTree.gen.ts | 29 ++++++ .../_home/quests.$questId.edit.tsx | 10 +- .../_home/quests.$questId.index.tsx | 2 +- .../_home_.quests.$questId.form.tsx | 57 ++++++++++++ .../admin_/quests.$questId.form.tsx | 23 +++-- 6 files changed, 152 insertions(+), 62 deletions(-) create mode 100644 src/routes/_authenticated/_home_.quests.$questId.form.tsx diff --git a/src/components/quests/QuestForm/QuestForm.tsx b/src/components/quests/QuestForm/QuestForm.tsx index 07b16f8..3b75390 100644 --- a/src/components/quests/QuestForm/QuestForm.tsx +++ b/src/components/quests/QuestForm/QuestForm.tsx @@ -1,61 +1,60 @@ import "survey-core/defaultV2.min.css"; -import { Button, Modal } from "@/components/common"; -import { api } from "@convex/_generated/api"; -import type { Doc } from "@convex/_generated/dataModel"; -import { useMutation } from "convex/react"; -import { useTheme } from "next-themes"; -import { useState } from "react"; -import type { CompleteEvent, SurveyModel } from "survey-core"; -import { - LayeredDarkPanelless, - LayeredLightPanelless, -} from "survey-core/themes"; -import { Model, Survey } from "survey-react-ui"; +import { Link } from "@/components/common"; +import type { Doc, Id } from "@convex/_generated/dataModel"; +import { Pencil, Plus } from "lucide-react"; +import { Heading } from "react-aria-components"; export type QuestFormProps = { quest: Doc<"quests">; + editable?: boolean; }; -export const QuestForm = ({ quest }: QuestFormProps) => { - const { resolvedTheme } = useTheme(); - const [isSurveyOpen, setIsSurveyOpen] = useState(false); - const saveData = useMutation(api.userFormData.set); +export const EditButton = ({ + isNew, + questId, +}: { isNew: boolean; questId: Id<"quests"> }) => { + const { buttonText, Icon } = isNew + ? { buttonText: "Create form", Icon: Plus } + : { buttonText: "Edit form", Icon: Pencil }; - if (!quest.formSchema) return null; - - const survey = new Model(quest.formSchema); - survey.applyTheme( - resolvedTheme === "dark" ? LayeredDarkPanelless : LayeredLightPanelless, + return ( + + {buttonText} + ); +}; - const handleSubmit = async (sender: SurveyModel, _options: CompleteEvent) => { - try { - // Validate JSON against schema and remove invalid values - sender.clearIncorrectValues(true); - for (const key in sender.data) { - const question = sender.getQuestionByName(key); - if (question) { - saveData({ - field: key, - value: question.value, - }); - } - } - setIsSurveyOpen(false); - } catch (err) { - console.error(err); - } - }; - - survey.onComplete.add(handleSubmit); +export const QuestForm = ({ quest, editable }: QuestFormProps) => { + if (!quest.formSchema && !editable) return null; return ( - <> - - - - - +
+
+ Answer questions +

+ We'll walk you through the steps to fill out your forms. +

+
+ {editable ? ( + + ) : ( + + Get Started + + )} +
); }; diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 9f9b1fd..f7727fa 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -30,6 +30,7 @@ import { Route as AuthenticatedAdminDocumentsDocumentIdImport } from './routes/_ import { Route as AuthenticatedAdminQuestsQuestIdIndexImport } from './routes/_authenticated/admin/quests.$questId.index' import { Route as AuthenticatedHomeQuestsQuestIdIndexImport } from './routes/_authenticated/_home/quests.$questId.index' import { Route as AuthenticatedAdminQuestsQuestIdFormImport } from './routes/_authenticated/admin_/quests.$questId.form' +import { Route as AuthenticatedHomeQuestsQuestIdFormImport } from './routes/_authenticated/_home_.quests.$questId.form' import { Route as AuthenticatedHomeQuestsQuestIdEditImport } from './routes/_authenticated/_home/quests.$questId.edit' // Create/Update Routes @@ -157,6 +158,13 @@ const AuthenticatedAdminQuestsQuestIdFormRoute = getParentRoute: () => AuthenticatedRoute, } as any) +const AuthenticatedHomeQuestsQuestIdFormRoute = + AuthenticatedHomeQuestsQuestIdFormImport.update({ + id: '/_home_/quests/$questId/form', + path: '/quests/$questId/form', + getParentRoute: () => AuthenticatedRoute, + } as any) + const AuthenticatedHomeQuestsQuestIdEditRoute = AuthenticatedHomeQuestsQuestIdEditImport.update({ id: '/quests/$questId/edit', @@ -287,6 +295,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedHomeQuestsQuestIdEditImport parentRoute: typeof AuthenticatedHomeImport } + '/_authenticated/_home_/quests/$questId/form': { + id: '/_authenticated/_home_/quests/$questId/form' + path: '/quests/$questId/form' + fullPath: '/quests/$questId/form' + preLoaderRoute: typeof AuthenticatedHomeQuestsQuestIdFormImport + parentRoute: typeof AuthenticatedImport + } '/_authenticated/admin_/quests/$questId/form': { id: '/_authenticated/admin_/quests/$questId/form' path: '/admin/quests/$questId/form' @@ -380,6 +395,7 @@ interface AuthenticatedRouteChildren { AuthenticatedSettingsRouteRoute: typeof AuthenticatedSettingsRouteRouteWithChildren AuthenticatedHomeRoute: typeof AuthenticatedHomeRouteWithChildren AuthenticatedBrowseIndexRoute: typeof AuthenticatedBrowseIndexRoute + AuthenticatedHomeQuestsQuestIdFormRoute: typeof AuthenticatedHomeQuestsQuestIdFormRoute AuthenticatedAdminQuestsQuestIdFormRoute: typeof AuthenticatedAdminQuestsQuestIdFormRoute } @@ -388,6 +404,8 @@ const AuthenticatedRouteChildren: AuthenticatedRouteChildren = { AuthenticatedSettingsRouteRoute: AuthenticatedSettingsRouteRouteWithChildren, AuthenticatedHomeRoute: AuthenticatedHomeRouteWithChildren, AuthenticatedBrowseIndexRoute: AuthenticatedBrowseIndexRoute, + AuthenticatedHomeQuestsQuestIdFormRoute: + AuthenticatedHomeQuestsQuestIdFormRoute, AuthenticatedAdminQuestsQuestIdFormRoute: AuthenticatedAdminQuestsQuestIdFormRoute, } @@ -424,6 +442,7 @@ export interface FileRoutesByFullPath { '/admin/documents': typeof AuthenticatedAdminDocumentsIndexRoute '/admin/quests': typeof AuthenticatedAdminQuestsIndexRoute '/quests/$questId/edit': typeof AuthenticatedHomeQuestsQuestIdEditRoute + '/quests/$questId/form': typeof AuthenticatedHomeQuestsQuestIdFormRoute '/admin/quests/$questId/form': typeof AuthenticatedAdminQuestsQuestIdFormRoute '/quests/$questId': typeof AuthenticatedHomeQuestsQuestIdIndexRoute '/admin/quests/$questId': typeof AuthenticatedAdminQuestsQuestIdIndexRoute @@ -443,6 +462,7 @@ export interface FileRoutesByTo { '/admin/documents': typeof AuthenticatedAdminDocumentsIndexRoute '/admin/quests': typeof AuthenticatedAdminQuestsIndexRoute '/quests/$questId/edit': typeof AuthenticatedHomeQuestsQuestIdEditRoute + '/quests/$questId/form': typeof AuthenticatedHomeQuestsQuestIdFormRoute '/admin/quests/$questId/form': typeof AuthenticatedAdminQuestsQuestIdFormRoute '/quests/$questId': typeof AuthenticatedHomeQuestsQuestIdIndexRoute '/admin/quests/$questId': typeof AuthenticatedAdminQuestsQuestIdIndexRoute @@ -467,6 +487,7 @@ export interface FileRoutesById { '/_authenticated/admin/documents/': typeof AuthenticatedAdminDocumentsIndexRoute '/_authenticated/admin/quests/': typeof AuthenticatedAdminQuestsIndexRoute '/_authenticated/_home/quests/$questId/edit': typeof AuthenticatedHomeQuestsQuestIdEditRoute + '/_authenticated/_home_/quests/$questId/form': typeof AuthenticatedHomeQuestsQuestIdFormRoute '/_authenticated/admin_/quests/$questId/form': typeof AuthenticatedAdminQuestsQuestIdFormRoute '/_authenticated/_home/quests/$questId/': typeof AuthenticatedHomeQuestsQuestIdIndexRoute '/_authenticated/admin/quests/$questId/': typeof AuthenticatedAdminQuestsQuestIdIndexRoute @@ -490,6 +511,7 @@ export interface FileRouteTypes { | '/admin/documents' | '/admin/quests' | '/quests/$questId/edit' + | '/quests/$questId/form' | '/admin/quests/$questId/form' | '/quests/$questId' | '/admin/quests/$questId' @@ -508,6 +530,7 @@ export interface FileRouteTypes { | '/admin/documents' | '/admin/quests' | '/quests/$questId/edit' + | '/quests/$questId/form' | '/admin/quests/$questId/form' | '/quests/$questId' | '/admin/quests/$questId' @@ -530,6 +553,7 @@ export interface FileRouteTypes { | '/_authenticated/admin/documents/' | '/_authenticated/admin/quests/' | '/_authenticated/_home/quests/$questId/edit' + | '/_authenticated/_home_/quests/$questId/form' | '/_authenticated/admin_/quests/$questId/form' | '/_authenticated/_home/quests/$questId/' | '/_authenticated/admin/quests/$questId/' @@ -567,6 +591,7 @@ export const routeTree = rootRoute "/_authenticated/settings", "/_authenticated/_home", "/_authenticated/browse/", + "/_authenticated/_home_/quests/$questId/form", "/_authenticated/admin_/quests/$questId/form" ] }, @@ -654,6 +679,10 @@ export const routeTree = rootRoute "filePath": "_authenticated/_home/quests.$questId.edit.tsx", "parent": "/_authenticated/_home" }, + "/_authenticated/_home_/quests/$questId/form": { + "filePath": "_authenticated/_home_.quests.$questId.form.tsx", + "parent": "/_authenticated" + }, "/_authenticated/admin_/quests/$questId/form": { "filePath": "_authenticated/admin_/quests.$questId.form.tsx", "parent": "/_authenticated" diff --git a/src/routes/_authenticated/_home/quests.$questId.edit.tsx b/src/routes/_authenticated/_home/quests.$questId.edit.tsx index 76ff460..18c6976 100644 --- a/src/routes/_authenticated/_home/quests.$questId.edit.tsx +++ b/src/routes/_authenticated/_home/quests.$questId.edit.tsx @@ -1,6 +1,11 @@ import { AppContent, PageHeader } from "@/components/app"; import { Badge, Empty, Link, RichText } from "@/components/common"; -import { QuestCosts, QuestTimeRequired, QuestUrls } from "@/components/quests"; +import { + QuestCosts, + QuestForm, + QuestTimeRequired, + QuestUrls, +} from "@/components/quests"; import { api } from "@convex/_generated/api"; import type { Id } from "@convex/_generated/dataModel"; import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router"; @@ -82,10 +87,11 @@ function QuestEditRoute() { Save -
+
+ diff --git a/src/routes/_authenticated/_home/quests.$questId.index.tsx b/src/routes/_authenticated/_home/quests.$questId.index.tsx index ac12427..c3100b5 100644 --- a/src/routes/_authenticated/_home/quests.$questId.index.tsx +++ b/src/routes/_authenticated/_home/quests.$questId.index.tsx @@ -100,7 +100,7 @@ function QuestDetailRoute() { -
+
diff --git a/src/routes/_authenticated/_home_.quests.$questId.form.tsx b/src/routes/_authenticated/_home_.quests.$questId.form.tsx new file mode 100644 index 0000000..47aa5ba --- /dev/null +++ b/src/routes/_authenticated/_home_.quests.$questId.form.tsx @@ -0,0 +1,57 @@ +import { api } from "@convex/_generated/api"; +import type { Id } from "@convex/_generated/dataModel"; +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { useMutation, useQuery } from "convex/react"; +import { useTheme } from "next-themes"; +import type { CompleteEvent, SurveyModel } from "survey-core"; +import { + LayeredDarkPanelless, + LayeredLightPanelless, +} from "survey-core/themes"; +import { Model, Survey } from "survey-react-ui"; + +export const Route = createFileRoute( + "/_authenticated/_home_/quests/$questId/form", +)({ + component: QuestFormRoute, +}); + +function QuestFormRoute() { + const navigate = useNavigate(); + const { questId } = Route.useParams(); + const { resolvedTheme } = useTheme(); + const quest = useQuery(api.quests.getById, { + questId: questId as Id<"quests">, + }); + + const saveData = useMutation(api.userFormData.set); + + const survey = new Model(quest?.formSchema); + survey.applyTheme( + resolvedTheme === "dark" ? LayeredDarkPanelless : LayeredLightPanelless, + ); + + const handleSubmit = async (sender: SurveyModel, _options: CompleteEvent) => { + try { + // Validate JSON against schema and remove invalid values + sender.clearIncorrectValues(true); + for (const key in sender.data) { + const question = sender.getQuestionByName(key); + if (question) { + saveData({ + field: key, + value: question.value, + }); + } + } + + navigate({ to: "/quests/$questId", params: { questId } }); + } catch (err) { + console.error(err); + } + }; + + survey.onComplete.add(handleSubmit); + + return ; +} diff --git a/src/routes/_authenticated/admin_/quests.$questId.form.tsx b/src/routes/_authenticated/admin_/quests.$questId.form.tsx index 7577cee..9be6e1a 100644 --- a/src/routes/_authenticated/admin_/quests.$questId.form.tsx +++ b/src/routes/_authenticated/admin_/quests.$questId.form.tsx @@ -1,10 +1,10 @@ import "survey-core/defaultV2.min.css"; import "survey-creator-core/survey-creator-core.min.css"; -import { Badge, Button, Link } from "@/components/common"; +import { Badge, Button } from "@/components/common"; import { api } from "@convex/_generated/api"; import type { Id } from "@convex/_generated/dataModel"; -import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { createFileRoute, useRouter } from "@tanstack/react-router"; import { useMutation, useQuery } from "convex/react"; import { CircleCheck, Pencil } from "lucide-react"; import { useEffect, useState } from "react"; @@ -19,11 +19,12 @@ export const Route = createFileRoute( function AdminQuestSurveyRoute() { const { questId } = Route.useParams(); - const navigate = useNavigate(); + const router = useRouter(); const [isPending, setIsPending] = useState(false); const quest = useQuery(api.quests.getById, { questId: questId as Id<"quests">, }); + const setFormSchema = useMutation(api.quests.setFormSchema); const creator = new SurveyCreator({ @@ -62,7 +63,7 @@ function AdminQuestSurveyRoute() { const handleSaveAndExit = () => { try { creator.saveSurvey(); - navigate({ to: "/admin/quests/$questId", params: { questId } }); + router.history.back(); toast.success("Saved form changes"); } catch (err) { console.error(err); @@ -79,16 +80,14 @@ function AdminQuestSurveyRoute() { {quest?.jurisdiction && {quest.jurisdiction}}
- router.history.back()} + size="small" + isDisabled={isPending} > Discard changes - +