From 8f0c2ce6421d282198ae1f4c53edc16bb4f95f24 Mon Sep 17 00:00:00 2001 From: Matthias Nannt Date: Tue, 14 Jun 2022 17:21:47 +0900 Subject: [PATCH] feat: add basic code form page and pipelines page --- components/FormList.tsx | 2 +- components/form/FormCode.tsx | 233 ++++++++++++++++++ .../{build => form}/FormOnboardingModal.tsx | 0 ...{LayoutResults.tsx => LayoutFormBasic.tsx} | 8 +- lib/submissionSessions.ts | 8 +- package.json | 2 + pages/forms/[id]/form.tsx | 43 ++-- pages/forms/[id]/index.tsx | 26 ++ pages/forms/[id]/pipelines.tsx | 141 ++++++++++- pages/forms/[id]/results.tsx | 19 +- .../migration.sql | 18 ++ prisma/schema.prisma | 17 +- tailwind.config.js | 2 +- yarn.lock | 46 ++++ 14 files changed, 530 insertions(+), 35 deletions(-) create mode 100644 components/form/FormCode.tsx rename components/{build => form}/FormOnboardingModal.tsx (100%) rename components/layout/{LayoutResults.tsx => LayoutFormBasic.tsx} (87%) create mode 100644 pages/forms/[id]/index.tsx rename prisma/migrations/{20220613140315_init => 20220614082103_init}/migration.sql (82%) diff --git a/components/FormList.tsx b/components/FormList.tsx index 6327df7e0b..aa3b436014 100644 --- a/components/FormList.tsx +++ b/components/FormList.tsx @@ -44,7 +44,7 @@ export default function FormList() { .map((form, formIdx) => (
  • - +
    {form.name}
    diff --git a/components/form/FormCode.tsx b/components/form/FormCode.tsx new file mode 100644 index 0000000000..23a9a7d307 --- /dev/null +++ b/components/form/FormCode.tsx @@ -0,0 +1,233 @@ +import { RadioGroup } from "@headlessui/react"; +import Link from "next/link"; +import { useState } from "react"; +import { FaReact, FaVuejs } from "react-icons/fa"; +import { classNames } from "../../lib/utils"; + +const libs = [ + { + id: "react", + name: "React", + href: "#", + bgColor: "bg-cyan-500", + ringColor: "ring-cyan-500", + icon: FaReact, + }, + { + id: "reactNative", + name: "React Native", + comingSoon: true, + href: "#", + members: 12, + bgColor: "bg-cyan-600", + ringColor: "ring-cyan-600", + icon: FaReact, + }, + { + id: "vue", + name: "Vue.js", + comingSoon: true, + href: "#", + members: 16, + bgColor: "bg-emerald-400", + ringColor: "ring-emerald-400", + icon: FaVuejs, + }, +]; + +export default function FormCode() { + const [selectedLib, setSelectedLib] = useState(null); + + return ( + <> +
    +
    +

    + Get started +

    +
    +
    +
    +

    + Welcome to your new form! To start using snoopHub with your + application you need to build a form using our libs for your preferred + programming language or framework. +

    +
    +
    + + + Choose your framework + + +
      + {libs.map((lib) => ( + + classNames( + checked ? `ring-2 ${lib.ringColor}` : "", + lib.comingSoon ? "opacity-50" : "", + "flex col-span-1 rounded-md shadow-sm" + ) + } + disabled={lib.comingSoon} + > + {({}) => ( +
    • +
      + +
      +
      +
      + + {lib.name} + + {lib.comingSoon && ( +

      + (coming soon) +

      + )} +
      +
      +
    • + )} +
      + ))} +
    +
    +
    +
    + {selectedLib?.id === "react" ? ( +
    +
    +
    +

    + + snoopReact + + + How to build your form + +

    +
    +
    +

    + Getting the snoopForms React Library up and running with Node + Package Manager: +

    +
    +                  
    +                    npm install --save @snoopforms/react
    +                  
    +                
    +

    Then build your form using our built-in components

    +
    +                  
    +                    {`import React from "react";
    +import { SnoopForm, SnoopElement, SnoopPage } from "@snoopforms/react";
    +
    +export default function Example({}) {
    +  return (
    +     {
    +        // do something with the data additional to sending to snoopForms
    +      }}
    +    >
    +      
    +        
    +      
    +      
    +        
    +        
    +      
    +      
    +        

    Thank you!

    +
    +
    + ); +}`} +
    +
    +

    + To read more about building your form with snoopReact, check + out our{" "} + + docs + + . +

    +
    +
    +
    +

    + Are you ready to go live and receive submissions? Go to{" "} + + Pipelines + {" "} + to pipe your submissions to other systems or go straight + to the{" "} + + Results + {" "} + to see how your form is used and keep track of your + submissions. +

    +
    +
    +
    +
    +
    +
    + ) : null} +
    + + ); +} diff --git a/components/build/FormOnboardingModal.tsx b/components/form/FormOnboardingModal.tsx similarity index 100% rename from components/build/FormOnboardingModal.tsx rename to components/form/FormOnboardingModal.tsx diff --git a/components/layout/LayoutResults.tsx b/components/layout/LayoutFormBasic.tsx similarity index 87% rename from components/layout/LayoutResults.tsx rename to components/layout/LayoutFormBasic.tsx index a481013d64..1bb3c5e2d4 100644 --- a/components/layout/LayoutResults.tsx +++ b/components/layout/LayoutFormBasic.tsx @@ -38,8 +38,12 @@ export default function LayoutShare({ title, formId, currentStep, children }) { {/* Main content */} -
    - {children} +
    +
    + {/* Replace with your content */} + {children} + {/* /End replace */} +
    diff --git a/lib/submissionSessions.ts b/lib/submissionSessions.ts index 9582fa09ef..e10ed8dc24 100644 --- a/lib/submissionSessions.ts +++ b/lib/submissionSessions.ts @@ -1,7 +1,7 @@ import useSWR from "swr"; import { fetcher } from "./utils"; -export const useAnswerSessions = (formId: string) => { +export const useSubmissionSessions = (formId: string) => { const { data, error, mutate } = useSWR( () => `/api/forms/${formId}/submissionSessions`, fetcher @@ -9,8 +9,8 @@ export const useAnswerSessions = (formId: string) => { return { submissionSessions: data, - isLoadingAnswerSessions: !error && !data, - isErrorAnswerSessions: error, - mutateAnswerSessions: mutate, + isLoadingSubmissionSessions: !error && !data, + isErrorSubmissionSessions: error, + mutateSubmissionSessions: mutate, }; }; diff --git a/package.json b/package.json index 87e82ce50b..798aa8f935 100644 --- a/package.json +++ b/package.json @@ -25,11 +25,13 @@ "react-contenteditable": "^3.3.6", "react-dom": "18.1.0", "react-feather": "^2.0.9", + "react-icons": "^4.4.0", "superjson": "^1.9.1", "swr": "^1.3.0" }, "devDependencies": { "@tailwindcss/forms": "^0.5.1", + "@tailwindcss/typography": "^0.5.2", "@types/bcryptjs": "^2.4.2", "@types/node": "17.0.32", "@types/react": "18.0.9", diff --git a/pages/forms/[id]/form.tsx b/pages/forms/[id]/form.tsx index eb785ae32d..64b9afba57 100644 --- a/pages/forms/[id]/form.tsx +++ b/pages/forms/[id]/form.tsx @@ -2,8 +2,9 @@ import { GetServerSideProps } from "next"; import { getSession } from "next-auth/react"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import FormOnboardingModal from "../../../components/build/FormOnboardingModal"; -import LayoutResults from "../../../components/layout/LayoutResults"; +import FormCode from "../../../components/form/FormCode"; +import FormOnboardingModal from "../../../components/form/FormOnboardingModal"; +import LayoutFormBasics from "../../../components/layout/LayoutFormBasic"; import Loading from "../../../components/Loading"; import { useForm } from "../../../lib/forms"; @@ -23,18 +24,32 @@ export default function FormPage() { return ; } - return ( - <> - -
    Form
    - -
    - - ); + if (form.formType === "NOCODE") { + return ( + <> + + + + + ); + } else { + return ( + <> + + + + + + ); + } } export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { diff --git a/pages/forms/[id]/index.tsx b/pages/forms/[id]/index.tsx new file mode 100644 index 0000000000..8f92dea363 --- /dev/null +++ b/pages/forms/[id]/index.tsx @@ -0,0 +1,26 @@ +import { useRouter } from "next/router"; +import { useEffect } from "react"; +import Loading from "../../../components/Loading"; +import { useSubmissionSessions } from "../../../lib/submissionSessions"; + +export default function FormIndex() { + const router = useRouter(); + console.log(router.query); + const formId = router.query.id; + const { submissionSessions, isLoadingSubmissionSessions } = + useSubmissionSessions(formId?.toString()); + + useEffect(() => { + if (!isLoadingSubmissionSessions) { + // redirect to /results if there is at least one submissionSession + if (submissionSessions.length > 0) { + router.push(`/forms/${formId}/results`); + } else { + // redirect to /form if there isn't one submissionSession + router.push(`/forms/${formId}/form`); + } + } + }, [isLoadingSubmissionSessions]); + + return ; +} diff --git a/pages/forms/[id]/pipelines.tsx b/pages/forms/[id]/pipelines.tsx index 297ba90bda..f22f88ff80 100644 --- a/pages/forms/[id]/pipelines.tsx +++ b/pages/forms/[id]/pipelines.tsx @@ -1,9 +1,55 @@ import { GetServerSideProps } from "next"; import { getSession } from "next-auth/react"; import { useRouter } from "next/router"; -import LayoutResults from "../../../components/layout/LayoutResults"; +import LayoutFormBasics from "../../../components/layout/LayoutFormBasic"; import Loading from "../../../components/Loading"; import { useForm } from "../../../lib/forms"; +import { BiPlug } from "react-icons/bi"; +import { SiZapier, SiAirtable, SiSlack } from "react-icons/si"; +import { FaCode, FaGoogle } from "react-icons/fa"; +import { classNames } from "../../../lib/utils"; + +const libs = [ + { + id: "webhook", + name: "Webhook", + href: "#", + bgColor: "bg-red-500", + icon: FaCode, + }, + { + id: "googleSheets", + name: "Google Sheets", + comingSoon: true, + href: "#", + bgColor: "bg-green-700", + icon: FaGoogle, + }, + { + id: "zapier", + name: "Zapier", + comingSoon: true, + href: "#", + bgColor: "bg-orange-500", + icon: SiZapier, + }, + { + id: "airtable", + name: "Airtable", + comingSoon: true, + href: "#", + bgColor: "bg-sky-400", + icon: SiAirtable, + }, + { + id: "slack", + name: "Slack", + comingSoon: true, + href: "#", + bgColor: "bg-purple-800", + icon: SiSlack, + }, +]; export default function PipelinesPage() { const router = useRouter(); @@ -16,9 +62,96 @@ export default function PipelinesPage() { return ( <> - -
    Pipelines
    -
    + +
    +
    +

    + Pipe your data +

    +
    +
    +
    +

    + snoopHub automatically stores your data and gives you and overview + of your submissions and form analytics. If you want to use your + submissions or form events in other systems you can set up pipelines + to let snoopHub sent the data to these applications as soon as it + arrives. +

    +
    +
    +
    + Active integrations +
    +
    + + +

    + You don't have any active pipelines +

    +

    + Choose a method from the available integrations and set up your + pipeline +

    +
    +
    +
    +
    +
    +

    + Available integrations +

    + + +
    +
    +
    +
    ); } diff --git a/pages/forms/[id]/results.tsx b/pages/forms/[id]/results.tsx index 7f1c68a0e1..360980f78b 100644 --- a/pages/forms/[id]/results.tsx +++ b/pages/forms/[id]/results.tsx @@ -1,27 +1,30 @@ import { GetServerSideProps } from "next"; import { getSession } from "next-auth/react"; import { useRouter } from "next/router"; -import LayoutResults from "../../../components/layout/LayoutResults"; +import LayoutFormBasics from "../../../components/layout/LayoutFormBasic"; import Loading from "../../../components/Loading"; import Submission from "../../../components/results/Submission"; import { useForm } from "../../../lib/forms"; -import { useAnswerSessions } from "../../../lib/submissionSessions"; +import { useSubmissionSessions } from "../../../lib/submissionSessions"; export default function Share() { const router = useRouter(); const formId = router.query.id.toString(); const { form, isLoadingForm } = useForm(router.query.id); - const { submissionSessions, isLoadingAnswerSessions } = useAnswerSessions( - form?.id - ); + const { submissionSessions, isLoadingSubmissionSessions } = + useSubmissionSessions(form?.id); - if (isLoadingForm || isLoadingAnswerSessions) { + if (isLoadingForm || isLoadingSubmissionSessions) { return ; } return ( <> - +

    @@ -39,7 +42,7 @@ export default function Share() { /> ))}

    - + ); } diff --git a/prisma/migrations/20220613140315_init/migration.sql b/prisma/migrations/20220614082103_init/migration.sql similarity index 82% rename from prisma/migrations/20220613140315_init/migration.sql rename to prisma/migrations/20220614082103_init/migration.sql index 166f4d3b51..4881278485 100644 --- a/prisma/migrations/20220613140315_init/migration.sql +++ b/prisma/migrations/20220614082103_init/migration.sql @@ -1,6 +1,9 @@ -- CreateEnum CREATE TYPE "FormType" AS ENUM ('CODE', 'NOCODE'); +-- CreateEnum +CREATE TYPE "PipelineType" AS ENUM ('WEBHOOK'); + -- CreateTable CREATE TABLE "Form" ( "id" TEXT NOT NULL, @@ -16,6 +19,18 @@ CREATE TABLE "Form" ( CONSTRAINT "Form_pkey" PRIMARY KEY ("id") ); +-- CreateTable +CREATE TABLE "Pipeline" ( + "id" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + "type" "PipelineType" NOT NULL, + "formId" TEXT NOT NULL, + "data" JSONB NOT NULL, + + CONSTRAINT "Pipeline_pkey" PRIMARY KEY ("id") +); + -- CreateTable CREATE TABLE "SubmissionSession" ( "id" TEXT NOT NULL, @@ -73,6 +88,9 @@ CREATE UNIQUE INDEX "verification_requests_token_key" ON "verification_requests" -- AddForeignKey ALTER TABLE "Form" ADD CONSTRAINT "Form_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; +-- AddForeignKey +ALTER TABLE "Pipeline" ADD CONSTRAINT "Pipeline_formId_fkey" FOREIGN KEY ("formId") REFERENCES "Form"("id") ON DELETE CASCADE ON UPDATE CASCADE; + -- AddForeignKey ALTER TABLE "SubmissionSession" ADD CONSTRAINT "SubmissionSession_formId_fkey" FOREIGN KEY ("formId") REFERENCES "Form"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4aa242aec4..ccb23c17c4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,6 +12,10 @@ enum FormType { NOCODE } +enum PipelineType { + WEBHOOK +} + model Form { id String @id createdAt DateTime @default(now()) @@ -24,6 +28,17 @@ model Form { finishedOnboarding Boolean @default(false) schema Json submissionSessions SubmissionSession[] + pipelines Pipeline[] +} + +model Pipeline { + id String @id @default(uuid()) + createdAt DateTime @default(now()) @map(name: "created_at") + updatedAt DateTime @updatedAt @map(name: "updated_at") + type PipelineType + form Form @relation(fields: [formId], references: [id], onDelete: Cascade) + formId String + data Json } model SubmissionSession { @@ -42,7 +57,7 @@ model SessionEvent { updatedAt DateTime @updatedAt @map(name: "updated_at") submissionSession SubmissionSession @relation(fields: [submissionSessionId], references: [id], onDelete: Cascade) submissionSessionId String - type String + type String data Json } diff --git a/tailwind.config.js b/tailwind.config.js index 17f37462f0..f658df2814 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -6,5 +6,5 @@ module.exports = { theme: { extend: {}, }, - plugins: [require("@tailwindcss/forms")], + plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")], }; diff --git a/yarn.lock b/yarn.lock index dee5184ba4..a6d736e5e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -220,6 +220,15 @@ dependencies: mini-svg-data-uri "^1.2.3" +"@tailwindcss/typography@^0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.2.tgz#24b069dab24d7a2467d01aca0dd432cb4b29f0ee" + integrity sha512-coq8DBABRPFcVhVIk6IbKyyHUt7YTEC/C992tatFB+yEx5WGBQrCgsSFjxHUr8AWXphWckadVJbominEduYBqw== + dependencies: + lodash.castarray "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.merge "^4.6.2" + "@tsconfig/node10@^1.0.7": version "1.0.8" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" @@ -1234,6 +1243,16 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +highlight.js@^10.5.0: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + +highlight.js@^11.5.1: + version "11.5.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.5.1.tgz#027c24e4509e2f4dcd00b4a6dda542ce0a1f7aea" + integrity sha512-LKzHqnxr4CrD2YsNoIf/o5nJ09j4yi/GcH5BnYz9UnVpZdS4ucMgvP61TDty5xJcFGRjnH4DpujkS9bHT3hq0Q== + hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -1477,6 +1496,16 @@ lodash-es@^4.17.15: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== +lodash.castarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115" + integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -1888,6 +1917,11 @@ prisma@^3.15.1: dependencies: "@prisma/engines" "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" +prismjs@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.28.0.tgz#0d8f561fa0f7cf6ebca901747828b149147044b6" + integrity sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw== + prop-types@^15.5.10, prop-types@^15.7.1, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -1966,6 +2000,18 @@ react-feather@^2.0.9: dependencies: prop-types "^15.7.2" +react-highlight@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/react-highlight/-/react-highlight-0.14.0.tgz#5aefa5518baa580f96b68d48129d7a5d2dc0c9ef" + integrity sha512-kWE+KXOXidS7SABhVopOgMnowbI3RAfeGZbnrduLNlWrYAED8sycL9l/Fvw3w0PFpIIawB7mRDnyhDcM/cIIGA== + dependencies: + highlight.js "^10.5.0" + +react-icons@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.4.0.tgz#a13a8a20c254854e1ec9aecef28a95cdf24ef703" + integrity sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg== + react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"