From 5dc5f968d19fd3c3be09cbe8a41105c4a87a6ff3 Mon Sep 17 00:00:00 2001 From: Matti Nannt Date: Tue, 14 Feb 2023 14:30:34 +0100 Subject: [PATCH] Update custom form layout (#204) * add new layout for custom survey view * make filterNavigation work without schema --- apps/formbricks-com/pages/waitlist.tsx | 2 +- apps/web/src/components/EmptyPageFiller.tsx | 2 +- .../components/forms/custom/CustomPage.tsx | 65 ++++ .../components/forms/custom/CustomResults.tsx | 65 ++++ .../forms/custom/CustomTimeline.tsx | 188 ++++++++++++ .../forms/custom/FormOverviewPage.tsx | 278 ------------------ .../forms/custom/OverviewResults.tsx | 138 +++++++++ .../forms/custom/SetupInstructions.tsx | 135 +++++++++ .../forms/shared/FilterNavigation.tsx | 13 +- .../forms/shared/SubmissionCounter.tsx | 2 +- .../forms/submissions/SubmissionDisplay.tsx | 25 -- .../forms/submissions/SubmissionsPage.tsx | 176 ----------- .../components/forms/summary/SummaryPage.tsx | 144 --------- apps/web/src/lib/submissions.ts | 3 + .../forms/[formId]/custom/index.tsx | 11 +- .../forms/[formId]/custom/pipelines/index.tsx | 18 -- .../[formId]/custom/submissions/index.tsx | 16 - .../forms/[formId]/custom/summary/index.tsx | 16 - .../[organisationId]/forms/[formId]/index.tsx | 16 - package.json | 3 +- packages/ui/src/icons/ClockIcon.tsx | 19 ++ packages/ui/src/index.tsx | 1 + 22 files changed, 635 insertions(+), 701 deletions(-) create mode 100644 apps/web/src/components/forms/custom/CustomPage.tsx create mode 100644 apps/web/src/components/forms/custom/CustomResults.tsx create mode 100644 apps/web/src/components/forms/custom/CustomTimeline.tsx delete mode 100644 apps/web/src/components/forms/custom/FormOverviewPage.tsx create mode 100644 apps/web/src/components/forms/custom/OverviewResults.tsx create mode 100644 apps/web/src/components/forms/custom/SetupInstructions.tsx delete mode 100644 apps/web/src/components/forms/submissions/SubmissionDisplay.tsx delete mode 100644 apps/web/src/components/forms/submissions/SubmissionsPage.tsx delete mode 100644 apps/web/src/components/forms/summary/SummaryPage.tsx delete mode 100644 apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/pipelines/index.tsx delete mode 100644 apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/submissions/index.tsx delete mode 100644 apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/summary/index.tsx delete mode 100644 apps/web/src/pages/organisations/[organisationId]/forms/[formId]/index.tsx create mode 100644 packages/ui/src/icons/ClockIcon.tsx diff --git a/apps/formbricks-com/pages/waitlist.tsx b/apps/formbricks-com/pages/waitlist.tsx index afe0699193..5b61a0a851 100644 --- a/apps/formbricks-com/pages/waitlist.tsx +++ b/apps/formbricks-com/pages/waitlist.tsx @@ -57,7 +57,7 @@ const WaitlistPage = () => { formId={ process.env.NODE_ENV === "production" ? "cld37mt2i0000ld08p9q572bc" - : "cldonm4ra000019axa4oc440z" + : "cldufl8uh000019mzr7fdotyu" } onPageSubmit={({ page }) => plausible(`waitlistSubmitPage-${page.id}`)} onFinished={() => plausible("waitlistFinished")} diff --git a/apps/web/src/components/EmptyPageFiller.tsx b/apps/web/src/components/EmptyPageFiller.tsx index b752bc1c96..6fcf9d661d 100644 --- a/apps/web/src/components/EmptyPageFiller.tsx +++ b/apps/web/src/components/EmptyPageFiller.tsx @@ -4,7 +4,7 @@ import { Button } from "@formbricks/ui"; import React from "react"; interface Props { - children: React.ReactNode; + children?: React.ReactNode; onClick?: () => void; alertText: string; hintText: string; diff --git a/apps/web/src/components/forms/custom/CustomPage.tsx b/apps/web/src/components/forms/custom/CustomPage.tsx new file mode 100644 index 0000000000..5c3a6ca7eb --- /dev/null +++ b/apps/web/src/components/forms/custom/CustomPage.tsx @@ -0,0 +1,65 @@ +"use client"; + +import LoadingSpinner from "@/components/LoadingSpinner"; +import TabNavigation from "@/components/TabNavigation"; +import { useForm } from "@/lib/forms"; +import { + ChartPieIcon, + InformationCircleIcon, + RectangleStackIcon, + ShareIcon, +} from "@heroicons/react/20/solid"; +import { useRouter } from "next/router"; +import { useState } from "react"; +import PipelinesOverview from "../pipelines/PipelinesOverview"; +import OverviewResults from "./OverviewResults"; +import CustomResults from "./CustomResults"; +import SetupInstructions from "./SetupInstructions"; + +const tabs = [ + { name: "Results", icon: RectangleStackIcon }, + { name: "Overview", icon: ChartPieIcon }, + { name: "Data Pipelines", icon: ShareIcon }, + { name: "Setup Instructions", icon: InformationCircleIcon }, +]; + +export default function CustomPage() { + const router = useRouter(); + const [currentTab, setCurrentTab] = useState("Results"); + const { form, isLoadingForm, isErrorForm } = useForm( + router.query.formId?.toString(), + router.query.organisationId?.toString() + ); + + if (isLoadingForm) { + return ( +
+ +
+ ); + } + + if (isErrorForm) { + return
Error loading ressources. Maybe you don‘t have enough access rights.
; + } + + return ( +
+
+
+

{form.label}

+ +
+ {currentTab === "Results" ? ( + + ) : currentTab === "Overview" ? ( + + ) : currentTab === "Data Pipelines" ? ( + + ) : currentTab === "Setup Instructions" ? ( + + ) : null} +
+
+ ); +} diff --git a/apps/web/src/components/forms/custom/CustomResults.tsx b/apps/web/src/components/forms/custom/CustomResults.tsx new file mode 100644 index 0000000000..bb6272e134 --- /dev/null +++ b/apps/web/src/components/forms/custom/CustomResults.tsx @@ -0,0 +1,65 @@ +"use client"; + +import EmptyPageFiller from "@/components/EmptyPageFiller"; +import LoadingSpinner from "@/components/LoadingSpinner"; +import { useSubmissions } from "@/lib/submissions"; +import { InboxIcon } from "@heroicons/react/24/outline"; +import { useRouter } from "next/router"; +import { useState } from "react"; +import FilterNavigation from "../shared/FilterNavigation"; +import { SubmissionCounter } from "../shared/SubmissionCounter"; +import CustomTimeline from "./CustomTimeline"; + +export default function PMFResults() { + const router = useRouter(); + const { submissions, isLoadingSubmissions, isErrorSubmissions } = useSubmissions( + router.query.organisationId?.toString(), + router.query.formId?.toString() + ); + + const [filteredSubmissions, setFilteredSubmissions] = useState([]); + + if (isLoadingSubmissions) { + return ( +
+ +
+ ); + } + + if (isErrorSubmissions) { + return
Error loading ressources. Maybe you don‘t have enough access rights
; + } + return ( +
+
+
+
+
+ + +
+ + {/* Submission grid */} + +
+ {submissions.length === 0 ? ( + + + + ) : ( + + )} +
+
+
+
+
+ ); +} diff --git a/apps/web/src/components/forms/custom/CustomTimeline.tsx b/apps/web/src/components/forms/custom/CustomTimeline.tsx new file mode 100644 index 0000000000..1e76a33db5 --- /dev/null +++ b/apps/web/src/components/forms/custom/CustomTimeline.tsx @@ -0,0 +1,188 @@ +import EmptyPageFiller from "@/components/EmptyPageFiller"; +import LoadingSpinner from "@/components/LoadingSpinner"; +import { useForm } from "@/lib/forms"; +import { MergeWithSchema, persistSubmission, useSubmissions } from "@/lib/submissions"; +import { convertDateTimeString, parseUserAgent } from "@/lib/utils"; +import { Button, CheckMarkIcon, ClockIcon } from "@formbricks/ui"; +import { InboxIcon } from "@heroicons/react/24/outline"; +import clsx from "clsx"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { toast } from "react-toastify"; + +export default function PMFTimeline({ submissions }) { + const router = useRouter(); + + const { + submissions: allSubmissions, + mutateSubmissions, + isLoadingSubmissions, + isErrorSubmissions, + } = useSubmissions(router.query.organisationId?.toString(), router.query.formId?.toString()); + + const { form, isLoadingForm, isErrorForm } = useForm( + router.query.formId?.toString(), + router.query.organisationId?.toString() + ); + + const toggleArchiveSubmission = (submission) => { + const updatedSubmission = JSON.parse(JSON.stringify(submission)); + updatedSubmission.archived = !updatedSubmission.archived; + // save submission without customer + const submissionWoCustomer = { ...updatedSubmission }; + delete submissionWoCustomer.customer; + persistSubmission(submissionWoCustomer, router.query.organisationId?.toString()); + // update all submissions + const submissionIdx = allSubmissions.findIndex((s) => s.id === submission.id); + const updatedSubmissions = JSON.parse(JSON.stringify(allSubmissions)); + updatedSubmissions[submissionIdx] = updatedSubmission; + mutateSubmissions(updatedSubmissions, false); + if (updatedSubmission.archived) { + toast.success("Submission archived"); + } else { + toast.success("Submission restored"); + } + }; + + if (isLoadingForm || isLoadingSubmissions) return ; + + if (isErrorForm || isErrorSubmissions) { + return
Error loading ressources. Maybe you don‘t have enough access rights
; + } + + return ( +
+
    + {submissions.length === 0 ? ( + + + + ) : ( + <> + {submissions.map((submission, submissionIdx) => ( +
  • +
    + {submissionIdx !== submissions.length - 1 ? ( +
    +
  • + ))} + + )} +
+
+ ); +} diff --git a/apps/web/src/components/forms/custom/FormOverviewPage.tsx b/apps/web/src/components/forms/custom/FormOverviewPage.tsx deleted file mode 100644 index a19f9cb61b..0000000000 --- a/apps/web/src/components/forms/custom/FormOverviewPage.tsx +++ /dev/null @@ -1,278 +0,0 @@ -"use client"; - -import LoadingSpinner from "@/components/LoadingSpinner"; -import { useForm } from "@/lib/forms"; -import { useOrganisation } from "@/lib/organisations"; -import { Button } from "@formbricks/ui"; -import { UserIcon } from "@heroicons/react/20/solid"; -import clsx from "clsx"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import Prism from "prismjs"; -import { useEffect, useMemo, useState } from "react"; -import { AiFillApi } from "react-icons/ai"; -import { toast } from "react-toastify"; - -require("prismjs/components/prism-javascript"); - -const tabs = [ - { id: "overview", name: "Overview", icon: UserIcon }, - { id: "api", name: "API", icon: AiFillApi }, -]; - -export default function FormOverviewPage() { - const router = useRouter(); - const { form, isLoadingForm, isErrorForm } = useForm( - router.query.formId?.toString(), - router.query.organisationId?.toString() - ); - const { organisation, isLoadingOrganisation, isErrorOrganisation } = useOrganisation( - router.query.organisationId?.toString() - ); - const [activeTab, setActiveTab] = useState(tabs[0]); - - const capturePostUrl = useMemo(() => { - if (form) { - return `${window.location.protocol}//${window.location.host}/api/capture/forms/${form.id}/submissions`; - } - }, [form]); - - useEffect(() => { - if (!isLoadingForm) { - Prism.highlightAll(); - } - }, [isLoadingForm]); - - if (isLoadingForm || isLoadingOrganisation) { - return ( -
- -
- ); - } - - if (isErrorForm || isErrorOrganisation) { - return
Error loading ressources. Maybe you don‘t have enough access rights
; - } - return ( -
-
-

- {form.label} - - {organisation.name} - -

-
-
-
-
- - {/* Use an "onChange" listener to redirect the user to the selected tab URL. */} - -
-
-
- -
-
-
- - {activeTab.id === "overview" ? ( -
-
-
-
- -
- - - -
-
- -
- -
-
- - POST - - -
- - -
-
-
- -
-

How to get started

-
    -
  1. POST a submission to the capture endpoint.
  2. -
  3. - View submission under{" "} - - Submissions - {" "} - tab. -
  4. -
  5. - Get notified or pipe submission data to a different tool in the{" "} - - Pipelines - {" "} - tab. -
  6. -
  7. - For a summary of form data a schema is required. Learn all about schemas in our{" "} - - docs - - . -
  8. -
-
-
-
- ) : activeTab.id === "api" ? ( -
-
-
-
- -
-
- - POST - - -
- - -
-
-
-
-                    
-                      {`{
-"customerId": "user@example.com", /* optional */
-"data": {
-  "firstname": "John",
-  "lastname": "Doe",
-  "feedback": "I like the app very much"
-  }
-}`}
-                    
-                  
-
-
-
-

Quick Tips

-

Authentication

-

- Via the API you can send submissions directly to Formbricks HQ. The API doesn't need - any authentication and can also be called in the users browser. -

-

CustomerId

-

- You can pass along a customer ID to identify the respondent. This allows you to attribute - submissions of several surveys to the same respondent. -

-
-
-
- ) : null} -
-
- ); -} diff --git a/apps/web/src/components/forms/custom/OverviewResults.tsx b/apps/web/src/components/forms/custom/OverviewResults.tsx new file mode 100644 index 0000000000..a4684ea15c --- /dev/null +++ b/apps/web/src/components/forms/custom/OverviewResults.tsx @@ -0,0 +1,138 @@ +"use client"; + +import EmptyPageFiller from "@/components/EmptyPageFiller"; +import LoadingSpinner from "@/components/LoadingSpinner"; +import { useForm } from "@/lib/forms"; +import { useSubmissions } from "@/lib/submissions"; +import { capitalizeFirstLetter } from "@/lib/utils"; +import { Bar, Nps, Table } from "@formbricks/charts"; +import { RectangleGroupIcon } from "@heroicons/react/24/outline"; +import { useRouter } from "next/router"; +import { useState } from "react"; +import FilterNavigation from "../shared/FilterNavigation"; +import { SubmissionCounter } from "../shared/SubmissionCounter"; + +export default function OverviewResults() { + const router = useRouter(); + const { form, isLoadingForm, isErrorForm } = useForm( + router.query.formId?.toString(), + router.query.organisationId?.toString() + ); + const { submissions, isLoadingSubmissions, isErrorSubmissions } = useSubmissions( + router.query.organisationId?.toString(), + router.query.formId?.toString() + ); + const [filteredSubmissions, setFilteredSubmissions] = useState([]); + + if (isLoadingSubmissions || isLoadingForm) { + return ( +
+ +
+ ); + } + + if (isErrorSubmissions || isErrorForm) { + return
Error loading ressources. Maybe you don‘t have enough access rights
; + } + + return ( +
+
+
+
+
+ + +
+ + {/* Submission grid */} + +
+ {form && form.schema && Object.keys(form.schema).length > 0 ? ( + <> +
+ {form.schema.pages.map((page) => + page.elements + .filter((e) => + [ + "checkbox", + "email", + "number", + "nps", + "phone", + "radio", + "search", + "text", + "textarea", + "url", + ].includes(e.type) + ) + .map((elem) => ( +
+

+ {elem.label} + + {capitalizeFirstLetter(elem.type)} + +

+ {filteredSubmissions.filter((s) => elem.name in s.data).length === 0 ? ( + + ) : ( + <> + {["email", "number", "phone", "search", "text", "textarea", "url"].includes( + elem.type + ) ? ( +
+ + + ) : ["checkbox", "radio"].includes(elem.type) ? ( +
+ +
+ ) : ["nps"].includes(elem.type) ? ( +
+ +
+ ) : null} + + )} + + )) + )} + {} + + + ) : ( + + + + )} + + + + + + ); +} diff --git a/apps/web/src/components/forms/custom/SetupInstructions.tsx b/apps/web/src/components/forms/custom/SetupInstructions.tsx new file mode 100644 index 0000000000..1b6127098b --- /dev/null +++ b/apps/web/src/components/forms/custom/SetupInstructions.tsx @@ -0,0 +1,135 @@ +import { Button } from "@formbricks/ui"; +import { useRouter } from "next/router"; +import Prism from "prismjs"; +import { useEffect } from "react"; +import { toast } from "react-toastify"; + +export default function SetupInstructions({}) { + const router = useRouter(); + const formId = router.query.formId.toString(); + + useEffect(() => { + Prism.highlightAll(); + }, []); + + return ( +
+
+
+
+
+
+
+ +
+
+ + POST + + +
+ + +
+
+
+
+                    
+                      {`{
+"customer": {
+  "email": "user@example.com",
+},
+"data": {
+  "firstname": "John",
+  "lastname": "Doe",
+  "feedback": "I like the app very much"
+  }
+}`}
+                    
+                  
+
+
+
+

Quick Tips

+

Authentication

+

+ Via the API you can send submissions directly to Formbricks HQ. The API doesn't need + any authentication and can also be called in the users browser. +

+

customer

+

+ You can pass along a customer object to identify the respondent. This allows you to + attribute submissions of several surveys to the same respondent. This is optional. +

+
+
+
+
+
+
+ +
+ + + +
+
+

Custom Survey Docs

+

Get detailed instructions in our Docs:

+ +
+
+

Need help? Join Discord!

+

Got a question? We're happy to help:

+ +
+
+
+
+
+ ); +} diff --git a/apps/web/src/components/forms/shared/FilterNavigation.tsx b/apps/web/src/components/forms/shared/FilterNavigation.tsx index 5d7b075ed7..25e030488d 100644 --- a/apps/web/src/components/forms/shared/FilterNavigation.tsx +++ b/apps/web/src/components/forms/shared/FilterNavigation.tsx @@ -1,6 +1,8 @@ +import EmptyPageFiller from "@/components/EmptyPageFiller"; import LoadingSpinner from "@/components/LoadingSpinner"; import { useForm } from "@/lib/forms"; import { camelToTitle, filterUniqueById } from "@/lib/utils"; +import { RectangleGroupIcon } from "@heroicons/react/24/outline"; import clsx from "clsx"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; @@ -156,7 +158,7 @@ export default function FilterNavigation({ useEffect(() => { // build filters based on form schema - if (form && form.schema) { + if (form && form.schema && Object.keys(form.schema).length > 0) { const filters = []; for (const page of form.schema.pages) { for (const element of page.elements) { @@ -197,7 +199,7 @@ export default function FilterNavigation({ return
Error loading ressources. Maybe you don‘t have enough access rights
; } - return ( + return form.schema && Object.keys(form.schema).length > 0 ? (
{filters.map((filter) => (
@@ -239,5 +241,12 @@ export default function FilterNavigation({
))}
+ ) : ( + + + ); } diff --git a/apps/web/src/components/forms/shared/SubmissionCounter.tsx b/apps/web/src/components/forms/shared/SubmissionCounter.tsx index fb4024da38..f8e48ca91b 100644 --- a/apps/web/src/components/forms/shared/SubmissionCounter.tsx +++ b/apps/web/src/components/forms/shared/SubmissionCounter.tsx @@ -1,6 +1,6 @@ export function SubmissionCounter({ numFilteredSubmissions, numTotalSubmissions }) { return ( -
+
{numFilteredSubmissions} responses
diff --git a/apps/web/src/components/forms/submissions/SubmissionDisplay.tsx b/apps/web/src/components/forms/submissions/SubmissionDisplay.tsx deleted file mode 100644 index c6b3011118..0000000000 --- a/apps/web/src/components/forms/submissions/SubmissionDisplay.tsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; - -import { MergeWithSchema } from "@/lib/submissions"; -import clsx from "clsx"; - -export default function SubmissionDisplay({ schema, submission }) { - return ( -
-
    - {Object.entries(MergeWithSchema(submission.data, schema)).map(([key, value]) => ( -
  • -

    {key}

    -

    - {value.toString()} -

    -
  • - ))} -
-
- ); -} diff --git a/apps/web/src/components/forms/submissions/SubmissionsPage.tsx b/apps/web/src/components/forms/submissions/SubmissionsPage.tsx deleted file mode 100644 index 3da8c9ec49..0000000000 --- a/apps/web/src/components/forms/submissions/SubmissionsPage.tsx +++ /dev/null @@ -1,176 +0,0 @@ -"use client"; - -import SubmissionDisplay from "@/components/forms/submissions/SubmissionDisplay"; -import LoadingSpinner from "@/components/LoadingSpinner"; -import { useForm } from "@/lib/forms"; -import { deleteSubmission, useSubmissions } from "@/lib/submissions"; -import { convertDateTimeString } from "@/lib/utils"; -import { RadioGroup, Switch } from "@headlessui/react"; -import { TrashIcon } from "@heroicons/react/24/outline"; -import type { Submission } from "@prisma/client"; -import clsx from "clsx"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import { toast } from "react-toastify"; - -export default function SubmissionsPage() { - const router = useRouter(); - const [finishedOnly, setFinishedOnly] = useState(false); - const [filteredSubmissions, setFileredSubmission] = useState([]); - const { submissions, isLoadingSubmissions, mutateSubmissions, isErrorSubmissions } = useSubmissions( - router.query.organisationId?.toString(), - router.query.formId?.toString() - ); - const { form, isLoadingForm, isErrorForm } = useForm( - router.query.formId?.toString(), - router.query.organisationId?.toString() - ); - const [activeSubmission, setActiveSubmission] = useState(null); - - useEffect(() => { - if (submissions) { - if (finishedOnly) { - setFileredSubmission(submissions.filter((submission) => submission.finished)); - } else { - setFileredSubmission(submissions); - } - } - }, [finishedOnly, submissions]); - - const handleDelete = async (submission: Submission) => { - try { - await deleteSubmission( - router.query.organisationId?.toString(), - router.query.formId?.toString(), - submission.id - ); - - await mutateSubmissions(); - setActiveSubmission(null); - toast("Successfully deleted"); - } catch (error) { - toast(error); - } - }; - - useEffect(() => { - if (!isLoadingSubmissions && submissions.length > 0) { - setActiveSubmission(submissions[0]); - } - }, [isLoadingSubmissions, submissions]); - - if (isLoadingSubmissions || isLoadingForm) { - return ; - } - - if (isErrorForm || isErrorSubmissions) { - return
Error loading ressources. Maybe you don‘t have enough access rights
; - } - - return ( -
-
-
-
- {!activeSubmission ? ( - - ) : ( - <> -
-
-
-

- {convertDateTimeString(activeSubmission.createdAt.toString())} -

- -
-
-
-
- -
- - )} -
-
- -
-
- ); -} diff --git a/apps/web/src/components/forms/summary/SummaryPage.tsx b/apps/web/src/components/forms/summary/SummaryPage.tsx deleted file mode 100644 index 0dd54637dc..0000000000 --- a/apps/web/src/components/forms/summary/SummaryPage.tsx +++ /dev/null @@ -1,144 +0,0 @@ -"use client"; - -import AnalyticsCard from "@/components/AnalyticsCard"; -import LoadingSpinner from "@/components/LoadingSpinner"; -import { useForm } from "@/lib/forms"; -import { useSubmissions } from "@/lib/submissions"; -import { capitalizeFirstLetter } from "@/lib/utils"; -import { useOrganisation } from "@/lib/organisations"; -import { Bar, Nps, Table } from "@formbricks/charts"; -import { ExclamationTriangleIcon } from "@heroicons/react/20/solid"; -import Link from "next/link"; -import { useRouter } from "next/router"; - -export default function SummaryPage() { - const router = useRouter(); - const { form, isLoadingForm, isErrorForm } = useForm( - router.query.formId?.toString(), - router.query.organisationId?.toString() - ); - const { organisation, isLoadingOrganisation, isErrorOrganisation } = useOrganisation( - router.query.organisationId?.toString() - ); - const { submissions, isLoadingSubmissions } = useSubmissions( - router.query.organisationId?.toString(), - router.query.formId?.toString() - ); - - if (isLoadingForm || isLoadingOrganisation || isLoadingSubmissions) { - return ( -
- -
- ); - } - - if (isErrorForm || isErrorOrganisation) { - return
Error loading ressources. Maybe you don‘t have enough access rights
; - } - - return ( -
-
-

- Summary - {form.label} - - {organisation.name} - -

-
-
- -
-
- - {Object.keys(form.schema).length === 0 ? ( -
-
-
-
-
-

No schema detected for this form.

-
-

To summarize your data Formbricks HQ needs a schema of your form.

-
-
-
- - Setup schema - -
-
-
-
-
- ) : ( -
- {form.schema.pages.map((page) => - page.elements - .filter((e) => - [ - "checkbox", - "email", - "number", - "nps", - "phone", - "radio", - "search", - "text", - "textarea", - "url", - ].includes(e.type) - ) - .map((elem) => ( -
- {["email", "number", "phone", "search", "text", "textarea", "url"].includes(elem.type) ? ( -
-

- {elem.label} - - {capitalizeFirstLetter(elem.type)} - -

-
- - ) : ["checkbox", "radio"].includes(elem.type) ? ( -
-

- {elem.label} - - {capitalizeFirstLetter(elem.type)} - -

- -
- ) : ["nps"].includes(elem.type) ? ( -
-

- {elem.label} - - {capitalizeFirstLetter(elem.type)} - -

- -
- ) : null} - - )) - )} - {} - - )} - - ); -} diff --git a/apps/web/src/lib/submissions.ts b/apps/web/src/lib/submissions.ts index 77e9990711..69de07d89c 100644 --- a/apps/web/src/lib/submissions.ts +++ b/apps/web/src/lib/submissions.ts @@ -91,6 +91,9 @@ export const MergeWithSchema = (submissionData, schema) => { }; export const getOptionLabelMap = (schema) => { + if (!schema || !schema.pages) { + return {}; + } const optionLabelMap = {}; for (const page of schema.pages) { for (const elem of page.elements) { diff --git a/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/index.tsx b/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/index.tsx index 320609aff5..a4bef9181d 100644 --- a/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/index.tsx +++ b/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/index.tsx @@ -1,15 +1,14 @@ -import FormOverviewPage from "@/components/forms/custom/FormOverviewPage"; +"use client"; + +import CustomPage from "@/components/forms/custom/CustomPage"; import LayoutApp from "@/components/layout/LayoutApp"; -import LayoutWrapperForm from "@/components/layout/LayoutWrapperCustomForm"; import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation"; -export default function FormOverview({}) { +export default function OrganisationFormsPage({}) { return ( - - - + ); diff --git a/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/pipelines/index.tsx b/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/pipelines/index.tsx deleted file mode 100644 index e329de67c2..0000000000 --- a/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/pipelines/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import PipelinesPage from "@/components/forms/pipelines/PipelinesOverview"; -import LayoutApp from "@/components/layout/LayoutApp"; -import LayoutWrapperCustomForm from "@/components/layout/LayoutWrapperCustomForm"; -import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation"; - -export default function Pipeline({}) { - return ( - - - -
- -
-
-
-
- ); -} diff --git a/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/submissions/index.tsx b/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/submissions/index.tsx deleted file mode 100644 index 26dae13a72..0000000000 --- a/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/submissions/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import SubmissionsPage from "@/components/forms/submissions/SubmissionsPage"; -import LayoutApp from "@/components/layout/LayoutApp"; -import LayoutWrapperForm from "@/components/layout/LayoutWrapperCustomForm"; -import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation"; - -export default function Submissions({}) { - return ( - - - - - - - - ); -} diff --git a/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/summary/index.tsx b/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/summary/index.tsx deleted file mode 100644 index cdba93d60c..0000000000 --- a/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/custom/summary/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import SummaryPage from "@/components/forms/summary/SummaryPage"; -import LayoutApp from "@/components/layout/LayoutApp"; -import LayoutWrapperForm from "@/components/layout/LayoutWrapperCustomForm"; -import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation"; - -export default function Submissions({}) { - return ( - - - - - - - - ); -} diff --git a/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/index.tsx b/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/index.tsx deleted file mode 100644 index 320609aff5..0000000000 --- a/apps/web/src/pages/organisations/[organisationId]/forms/[formId]/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import FormOverviewPage from "@/components/forms/custom/FormOverviewPage"; -import LayoutApp from "@/components/layout/LayoutApp"; -import LayoutWrapperForm from "@/components/layout/LayoutWrapperCustomForm"; -import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation"; - -export default function FormOverview({}) { - return ( - - - - - - - - ); -} diff --git a/package.json b/package.json index ca3970f467..fe271458f5 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "format": "prettier --write \"**/*.{ts,tsx,md}\"", "generate": "turbo run generate", "lint": "turbo run lint", - "release": "turbo run build --filter=react^... && changeset publish" + "release": "turbo run build --filter=react^... && changeset publish", + "nuke": "rm -r node_modules; for d in **/node_modules; do echo $d; rm -r $d; done" }, "devDependencies": { "@changesets/cli": "^2.22.0", diff --git a/packages/ui/src/icons/ClockIcon.tsx b/packages/ui/src/icons/ClockIcon.tsx new file mode 100644 index 0000000000..42950e0b24 --- /dev/null +++ b/packages/ui/src/icons/ClockIcon.tsx @@ -0,0 +1,19 @@ +export function ClockIcon(props: any) { + return ( + + + + + + + + ); +} diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index c47b68ddba..6a97f30b30 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -11,6 +11,7 @@ export * from "./icons/FormIcon"; export * from "./icons/CodeFileIcon"; export * from "./icons/TabletTouchIcon"; export * from "./icons/PMFIcon"; +export * from "./icons/ClockIcon"; export * from "./icons/IdeaIcon"; export * from "./icons/AngryBirdRageIcon"; export * from "./icons/AngryBirdRage2Icon";