From 844c590d7c8f79bcf0e01e43fdcd36fdc40f04bb Mon Sep 17 00:00:00 2001 From: Timothy Date: Wed, 17 Aug 2022 16:41:22 +0200 Subject: [PATCH] Feature/#41 unpublish forms (#59) * #41: Display message for unpublished forms * #41: Add publish/unpublish GUI * #41: Revamp UI and add 'closed' parameter * change indigo color of access-switch to red * update live-form on republish, show error message to user in frontend when form unpublished Co-authored-by: Matthias Nannt --- components/builder/Builder.tsx | 21 ++- components/builder/SettingsModal.tsx | 138 ++++++++++++++++++ pages/api/forms/[id]/nocodeform/index.ts | 3 +- .../api/public/forms/[id]/nocodeform/index.ts | 1 + pages/f/[id].tsx | 32 +++- .../migration.sql | 2 + prisma/schema.prisma | 1 + 7 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 components/builder/SettingsModal.tsx create mode 100644 prisma/migrations/20220816150326_add_closed_property_to_nocode_form/migration.sql diff --git a/components/builder/Builder.tsx b/components/builder/Builder.tsx index c5611a4f53..6d9f9638b5 100644 --- a/components/builder/Builder.tsx +++ b/components/builder/Builder.tsx @@ -1,5 +1,6 @@ import EditorJS from "@editorjs/editorjs"; import { + CogIcon, DocumentAddIcon, EyeIcon, PaperAirplaneIcon, @@ -16,6 +17,7 @@ import SecondNavBar from "../layout/SecondNavBar"; import Loading from "../Loading"; import LoadingModal from "../LoadingModal"; import ShareModal from "./ShareModal"; +import SettingsModal from "./SettingsModal"; let Editor = dynamic(() => import("../editorjs/Editor"), { ssr: false, }); @@ -27,6 +29,7 @@ export default function Builder({ formId }) { const { noCodeForm, isLoadingNoCodeForm, mutateNoCodeForm } = useNoCodeForm(formId); const [openShareModal, setOpenShareModal] = useState(false); + const [openSettingsModal, setOpenSettingsModal] = useState(false); const [loading, setLoading] = useState(false); const addPage = () => { @@ -63,12 +66,17 @@ export default function Builder({ formId }) { setLoading(true); setTimeout(async () => { const newNoCodeForm = JSON.parse(JSON.stringify(noCodeForm)); + const firstPublish = newNoCodeForm.published ? false : true; newNoCodeForm.blocks = newNoCodeForm.blocksDraft; newNoCodeForm.published = true; await persistNoCodeForm(newNoCodeForm); mutateNoCodeForm(newNoCodeForm); setLoading(false); - toast("Your changes are now public 🎉"); + toast( + firstPublish + ? "Your form is now published 🎉" + : "Your changes are now published 🎉" + ); }, 500); }; @@ -100,6 +108,12 @@ export default function Builder({ formId }) { Icon: ShareIcon, label: "Share", }, + { + id: "settings", + onClick: () => setOpenSettingsModal(true), + Icon: CogIcon, + label: "Settings", + }, ]; if (isLoadingNoCodeForm || isLoadingForm) { @@ -129,6 +143,11 @@ export default function Builder({ formId }) { setOpen={setOpenShareModal} formId={formId} /> + ); diff --git a/components/builder/SettingsModal.tsx b/components/builder/SettingsModal.tsx new file mode 100644 index 0000000000..52b1503e45 --- /dev/null +++ b/components/builder/SettingsModal.tsx @@ -0,0 +1,138 @@ +/* This example requires Tailwind CSS v2.0+ */ +import { Dialog, Switch, Transition } from "@headlessui/react"; +import { Fragment, useState } from "react"; +import { TailSpin } from "react-loader-spinner"; +import { persistNoCodeForm, useNoCodeForm } from "../../lib/noCodeForm"; +import Loading from "../Loading"; +import { XIcon } from "@heroicons/react/outline"; +import { toast } from "react-toastify"; +import { classNames } from "../../lib/utils"; + +export default function SettingsModal({ open, setOpen, formId }) { + const { noCodeForm, isLoadingNoCodeForm, mutateNoCodeForm } = + useNoCodeForm(formId); + const [loading, setLoading] = useState(false); + + const toggleClose = async () => { + setLoading(true); + setTimeout(async () => { + const newNoCodeForm = JSON.parse(JSON.stringify(noCodeForm)); + newNoCodeForm.closed = !noCodeForm.closed; + await persistNoCodeForm(newNoCodeForm); + mutateNoCodeForm(newNoCodeForm); + setLoading(false); + toast( + newNoCodeForm.closed + ? "Your form is now closed for submissions " + : "Your form is now open for submissions 🎉" + ); + }, 500); + }; + + if (isLoadingNoCodeForm) { + return ; + } + + return ( + + + +
+ + +
+
+ + +
+ +
+
+
+

+ Settings +

+
+

+ Access +

+
+ + + + Close form for new submissions? + + + Your form is currently{" "} + + {noCodeForm.closed ? "closed" : "open"} + {" "} + for submissions. + + + {loading ? ( + + ) : ( + toggleClose()} + className={classNames( + noCodeForm.closed ? "bg-red-600" : "bg-gray-200", + "relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" + )} + > + + )} + +
+
+
+
+
+
+
+
+ ); +} diff --git a/pages/api/forms/[id]/nocodeform/index.ts b/pages/api/forms/[id]/nocodeform/index.ts index 4c1f405c9a..e25bb2eee8 100644 --- a/pages/api/forms/[id]/nocodeform/index.ts +++ b/pages/api/forms/[id]/nocodeform/index.ts @@ -42,7 +42,7 @@ export default async function handle( // Required fields in body: - // Optional fields in body: title, published, finishedOnboarding, elements, elementsDraft else if (req.method === "POST") { - const { id, createdAt, blocks, blocksDraft, published } = req.body; + const { id, createdAt, blocks, blocksDraft, published, closed } = req.body; const data = { id, createdAt, @@ -50,6 +50,7 @@ export default async function handle( blocksDraft, formId, published, + closed, updatedAt: new Date(), }; // create or update record diff --git a/pages/api/public/forms/[id]/nocodeform/index.ts b/pages/api/public/forms/[id]/nocodeform/index.ts index f1ad75cbb4..2e17dca07b 100644 --- a/pages/api/public/forms/[id]/nocodeform/index.ts +++ b/pages/api/public/forms/[id]/nocodeform/index.ts @@ -17,6 +17,7 @@ export default async function handle( select: { id: true, published: true, + closed: true, blocks: true, }, }); diff --git a/pages/f/[id].tsx b/pages/f/[id].tsx index 797fd9a6d7..752915f568 100644 --- a/pages/f/[id].tsx +++ b/pages/f/[id].tsx @@ -4,14 +4,15 @@ import Loading from "../../components/Loading"; import MessagePage from "../../components/MessagePage"; import { useNoCodeFormPublic } from "../../lib/noCodeForm"; import { useRouter } from "next/router"; +import Image from "next/image"; -export default function Share({}) { +export default function NoCodeFormPublic() { const router = useRouter(); const formId = router.query.id.toString(); const { noCodeForm, isLoadingNoCodeForm, isErrorNoCodeForm } = useNoCodeFormPublic(formId); - if (isErrorNoCodeForm) { + if (isErrorNoCodeForm || !noCodeForm?.published) { return ( ); @@ -23,7 +24,32 @@ export default function Share({}) { return ( - + {noCodeForm.closed ? ( +
+
+
+
+ snoopForms logo +
+
+

+ Form closed! +

+

+ This form is closed for any further submissions. +

+
+
+
+
+ ) : ( + + )}
); } diff --git a/prisma/migrations/20220816150326_add_closed_property_to_nocode_form/migration.sql b/prisma/migrations/20220816150326_add_closed_property_to_nocode_form/migration.sql new file mode 100644 index 0000000000..78a8d94c40 --- /dev/null +++ b/prisma/migrations/20220816150326_add_closed_property_to_nocode_form/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "NoCodeForm" ADD COLUMN "closed" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5d6aad9857..083dbd2e80 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -39,6 +39,7 @@ model NoCodeForm { blocks Json @default("[]") blocksDraft Json @default("[]") published Boolean @default(false) + closed Boolean @default(false) } model Pipeline {