mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-23 13:48:58 -05:00
fix build issues, fix deployment issues in Dockerfile and docker-compose, move new form modal to formList
This commit is contained in:
+4
-2
@@ -11,7 +11,9 @@ FROM node:16-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
RUN yarn prisma:generate && yarn build && yarn install --production --ignore-scripts --prefer-offline
|
||||
RUN yarn prisma generate
|
||||
RUN yarn tsc prisma/seed.ts
|
||||
RUN yarn build && yarn install --production --ignore-scripts --prefer-offline
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM node:16-alpine AS runner
|
||||
@@ -23,7 +25,7 @@ RUN addgroup -g 1001 -S nodejs
|
||||
RUN adduser -S nextjs -u 1001
|
||||
|
||||
COPY --from=builder /app/next.config.js ./
|
||||
# COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
|
||||
@@ -91,7 +91,7 @@ yarn dev
|
||||
|
||||
## Deployment
|
||||
|
||||
The easiest way to deploy snoopForms yourself on your own machine is using Docker. This requires Docker and docker-compose on your system to work.
|
||||
The easiest way to deploy snoopForms yourself on your own machine is using Docker. This requires Docker and the docker compose plugin on your system to work.
|
||||
|
||||
Clone the repository:
|
||||
|
||||
@@ -109,11 +109,11 @@ cp .env.example .env && nano .env
|
||||
|
||||
```
|
||||
|
||||
Start the docker-compose process to build and spin up the snoopForms container as well as the postgres database.
|
||||
Start the docker compose process to build and spin up the snoopForms container as well as the postgres database.
|
||||
|
||||
```
|
||||
|
||||
docker-compose up -d
|
||||
docker compose up -d
|
||||
|
||||
```
|
||||
|
||||
|
||||
+133
-121
@@ -8,17 +8,18 @@ import {
|
||||
import { DotsHorizontalIcon, TrashIcon } from "@heroicons/react/solid";
|
||||
import Link from "next/link";
|
||||
import Router from "next/router";
|
||||
import { Fragment } from "react";
|
||||
import { Fragment, useState } from "react";
|
||||
import { createForm, useForms } from "../lib/forms";
|
||||
import { classNames } from "../lib/utils";
|
||||
import NewFormModal from "./form/NewFormModal";
|
||||
import EmptyPageFiller from "./layout/EmptyPageFiller";
|
||||
|
||||
export default function FormList() {
|
||||
const { forms, mutateForms } = useForms();
|
||||
const [openNewFormModal, setOpenNewFormModal] = useState(false);
|
||||
|
||||
const newForm = async () => {
|
||||
const form = await createForm();
|
||||
await Router.push(`/forms/${form.id}/welcome`);
|
||||
setOpenNewFormModal(true);
|
||||
};
|
||||
|
||||
const deleteForm = async (form, formIdx) => {
|
||||
@@ -35,127 +36,138 @@ export default function FormList() {
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
console.log(JSON.stringify(forms, null, 2));
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{forms &&
|
||||
(forms.length === 0 ? (
|
||||
<div className="mt-5 text-center">
|
||||
<EmptyPageFiller
|
||||
onClick={() => newForm()}
|
||||
alertText="You don't have any forms yet."
|
||||
hintText="Start by creating a form."
|
||||
buttonText="create form"
|
||||
borderStyles="border-4 border-dotted border-red"
|
||||
button={true}
|
||||
>
|
||||
<DocumentAddIcon className="w-24 h-24 mx-auto text-ui-gray-medium stroke-thin" />
|
||||
</EmptyPageFiller>
|
||||
</div>
|
||||
) : (
|
||||
<ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 place-content-stretch">
|
||||
<button onClick={() => newForm()}>
|
||||
<li className="col-span-1">
|
||||
<div className="overflow-hidden font-light text-white rounded-md shadow bg-snoopfade">
|
||||
<div className="px-4 py-8 sm:p-14">
|
||||
<PlusIcon className="mx-auto w-14 h-14 stroke-thin" />
|
||||
create form
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</button>
|
||||
{forms
|
||||
.sort((a, b) => b.updatedAt - a.updatedAt)
|
||||
.map((form, formIdx) => (
|
||||
<li key={form.id} className="col-span-1 realative ">
|
||||
<div className="flex flex-col justify-between h-full bg-white rounded-md shadow">
|
||||
<div className="px-4 py-5 text-lg sm:p-6">{form.name}</div>
|
||||
<Link href={`/forms/${form.id}`}>
|
||||
<a className="absolute w-full h-full" />
|
||||
</Link>
|
||||
<div className="divide-y divide-ui-gray-light ">
|
||||
<div className="inline-flex px-2 py-1 mb-2 ml-4 text-sm rounded-sm bg-ui-gray-light text-ui-gray-dark">
|
||||
{form.formType == "NOCODE" ? (
|
||||
<div className="flex">
|
||||
<ViewGridAddIcon className="w-4 h-4 my-auto mr-1" />
|
||||
No-Code
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex">
|
||||
<TerminalIcon className="w-4 h-4 my-auto mr-1" />
|
||||
Code
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between px-4 py-2 text-right sm:px-6">
|
||||
<p className="text-xs text-ui-gray-medium ">
|
||||
{form.submissionSessions?.length} responses
|
||||
</p>
|
||||
<Menu
|
||||
as="div"
|
||||
className="relative inline-block text-left"
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div>
|
||||
<Menu.Button className="flex items-center p-2 -m-2 rounded-full text-red">
|
||||
<span className="sr-only">Open options</span>
|
||||
<DotsHorizontalIcon
|
||||
className="w-5 h-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
static
|
||||
className="absolute left-0 w-56 px-1 mt-2 origin-top-right bg-white rounded-sm shadow-lg"
|
||||
>
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
onClick={() =>
|
||||
deleteForm(form, formIdx)
|
||||
}
|
||||
className={classNames(
|
||||
active
|
||||
? "bg-ui-gray-light rounded-sm text-ui-black"
|
||||
: "text-ui-gray-dark",
|
||||
"flex px-4 py-2 text-sm w-full"
|
||||
)}
|
||||
>
|
||||
<TrashIcon
|
||||
className="w-5 h-5 mr-3 text-ui-gray-dark"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>Delete Form</span>
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
<>
|
||||
<div>
|
||||
{forms &&
|
||||
(forms.length === 0 ? (
|
||||
<div className="mt-5 text-center">
|
||||
<EmptyPageFiller
|
||||
onClick={() => newForm()}
|
||||
alertText="You don't have any forms yet."
|
||||
hintText="Start by creating a form."
|
||||
buttonText="create form"
|
||||
borderStyles="border-4 border-dotted border-red"
|
||||
button={true}
|
||||
>
|
||||
<DocumentAddIcon className="w-24 h-24 mx-auto text-ui-gray-medium stroke-thin" />
|
||||
</EmptyPageFiller>
|
||||
</div>
|
||||
) : (
|
||||
<ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 place-content-stretch">
|
||||
<button onClick={() => newForm()}>
|
||||
<li className="col-span-1">
|
||||
<div className="overflow-hidden font-light text-white rounded-md shadow bg-snoopfade">
|
||||
<div className="px-4 py-8 sm:p-14">
|
||||
<PlusIcon className="mx-auto w-14 h-14 stroke-thin" />
|
||||
create form
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
))}
|
||||
</div>
|
||||
</button>
|
||||
{forms
|
||||
.sort((a, b) => b.updatedAt - a.updatedAt)
|
||||
.map((form, formIdx) => (
|
||||
<li key={form.id} className="relative col-span-1 realative ">
|
||||
<div className="flex flex-col justify-between h-full bg-white rounded-md shadow">
|
||||
<div className="px-4 py-5 text-lg sm:p-6">
|
||||
{form.name}
|
||||
</div>
|
||||
<Link href={`/forms/${form.id}`}>
|
||||
<a className="absolute w-full h-full" />
|
||||
</Link>
|
||||
<div className="divide-y divide-ui-gray-light ">
|
||||
<div className="inline-flex px-2 py-1 mb-2 ml-4 text-sm rounded-sm bg-ui-gray-light text-ui-gray-dark">
|
||||
{form.formType == "NOCODE" ? (
|
||||
<div className="flex">
|
||||
<ViewGridAddIcon className="w-4 h-4 my-auto mr-1" />
|
||||
No-Code
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex">
|
||||
<TerminalIcon className="w-4 h-4 my-auto mr-1" />
|
||||
Code
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between px-4 py-2 text-right sm:px-6">
|
||||
<p className="text-xs text-ui-gray-medium ">
|
||||
{form._count?.submissionSessions} responses
|
||||
</p>
|
||||
<Menu
|
||||
as="div"
|
||||
className="relative inline-block text-left"
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div>
|
||||
<Menu.Button className="flex items-center p-2 -m-2 rounded-full text-red">
|
||||
<span className="sr-only">
|
||||
Open options
|
||||
</span>
|
||||
<DotsHorizontalIcon
|
||||
className="w-5 h-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
static
|
||||
className="absolute left-0 w-56 px-1 mt-2 origin-top-right bg-white rounded-sm shadow-lg"
|
||||
>
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
onClick={() =>
|
||||
deleteForm(form, formIdx)
|
||||
}
|
||||
className={classNames(
|
||||
active
|
||||
? "bg-ui-gray-light rounded-sm text-ui-black"
|
||||
: "text-ui-gray-dark",
|
||||
"flex px-4 py-2 text-sm w-full"
|
||||
)}
|
||||
>
|
||||
<TrashIcon
|
||||
className="w-5 h-5 mr-3 text-ui-gray-dark"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>Delete Form</span>
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
))}
|
||||
</div>
|
||||
<NewFormModal open={openNewFormModal} setOpen={setOpenNewFormModal} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,6 +123,10 @@ export default function Builder({ formId }) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
{
|
||||
console.log("loaded nocode");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecondNavBar>
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
/* This example requires Tailwind CSS v2.0+ */
|
||||
import { Dialog, RadioGroup, Transition } from "@headlessui/react";
|
||||
import { CheckCircleIcon, XIcon } from "@heroicons/react/solid";
|
||||
import { useRouter } from "next/router";
|
||||
import { Fragment, useState } from "react";
|
||||
import { persistForm, useForm } from "../../lib/forms";
|
||||
import { createNoCodeForm } from "../../lib/noCodeForm";
|
||||
import { classNames } from "../../lib/utils";
|
||||
import Loading from "../Loading";
|
||||
import StandardButton from "../StandardButton";
|
||||
import { BsPlus } from "react-icons/bs";
|
||||
|
||||
const formTypes = [
|
||||
{
|
||||
id: "NOCODE",
|
||||
title: "No-Code Builder",
|
||||
description:
|
||||
"Use the Notion-like builder to build your form without a single line of code.",
|
||||
},
|
||||
{
|
||||
id: "CODE",
|
||||
title: "Code",
|
||||
description:
|
||||
"Use the snoopReact library to code the form yourself and manage the data here.",
|
||||
additionalDescription: "",
|
||||
},
|
||||
];
|
||||
|
||||
type FormOnboardingModalProps = {
|
||||
open: boolean;
|
||||
formId: string;
|
||||
};
|
||||
|
||||
export default function FormOnboardingModal({
|
||||
open,
|
||||
formId,
|
||||
}: FormOnboardingModalProps) {
|
||||
const router = useRouter();
|
||||
const { form, mutateForm, isLoadingForm } = useForm(formId);
|
||||
const [name, setName] = useState(form.name);
|
||||
const [formType, setFormType] = useState(formTypes[0]);
|
||||
|
||||
const submitForm = async (e) => {
|
||||
e.preventDefault();
|
||||
const updatedForm = {
|
||||
...form,
|
||||
name,
|
||||
finishedOnboarding: true,
|
||||
formType: formType.id,
|
||||
};
|
||||
await persistForm(updatedForm);
|
||||
mutateForm(updatedForm);
|
||||
if (updatedForm.formType === "NOCODE") {
|
||||
await createNoCodeForm(formId);
|
||||
}
|
||||
router.push(`/forms/${formId}/form`);
|
||||
};
|
||||
|
||||
if (isLoadingForm) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition.Root show={open} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
static
|
||||
className="fixed inset-0 z-10 overflow-y-auto"
|
||||
open={open}
|
||||
onClose={() => {}}
|
||||
>
|
||||
<div className="flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Dialog.Overlay className="fixed inset-0 transition-opacity bg-ui-gray-medium bg-opacity-10 backdrop-blur" />
|
||||
</Transition.Child>
|
||||
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
<span
|
||||
className="hidden sm:inline-block sm:align-middle sm:h-screen"
|
||||
aria-hidden="true"
|
||||
>
|
||||
​
|
||||
</span>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<form
|
||||
onSubmit={(e) => submitForm(e)}
|
||||
className="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white rounded-md shadow-xl sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6"
|
||||
>
|
||||
<div className="flex flex-row justify-between">
|
||||
<h2 className="flex-none pb-4 text-xl font-bold text-ui-gray-dark">
|
||||
Create new form
|
||||
</h2>
|
||||
<XIcon className="flex-initial w-6 h-6 stroke-1 text-ui-gray-light" />
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="text-sm font-light text-ui-gray-dark"
|
||||
>
|
||||
Name your form
|
||||
</label>
|
||||
<div className="mt-2">
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
className="block w-full p-2 mb-8 border-none rounded bg-ui-gray-light focus:ring-2 focus:ring-red sm:text-sm placeholder:font-extralight placeholder:text-ui-gray-medium"
|
||||
placeholder="e.g. Customer Research Survey"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RadioGroup value={formType} onChange={setFormType}>
|
||||
<RadioGroup.Label className="text-sm font-light text-ui-gray-dark">
|
||||
How do you build your form?
|
||||
</RadioGroup.Label>
|
||||
|
||||
<div className="grid grid-cols-1 mt-4 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
|
||||
{formTypes.map((formType) => (
|
||||
<RadioGroup.Option
|
||||
key={formType.id}
|
||||
value={formType}
|
||||
className={({ checked, active }) =>
|
||||
classNames(
|
||||
checked ? "border-transparent" : "",
|
||||
active
|
||||
? "border-red ring-2 ring-red"
|
||||
: "bg-ui-gray-lighter",
|
||||
"relative bg-white border rounded shadow-sm p-4 flex cursor-pointer focus:outline-none"
|
||||
)
|
||||
}
|
||||
>
|
||||
{({ checked, active }) => (
|
||||
<>
|
||||
<span className="flex flex-1">
|
||||
<span className="flex flex-col">
|
||||
<RadioGroup.Label
|
||||
as="span"
|
||||
className="block font-bold text-md text-ui-gray-dark"
|
||||
>
|
||||
{formType.title}
|
||||
</RadioGroup.Label>
|
||||
<RadioGroup.Description
|
||||
as="span"
|
||||
className="flex items-center mt-1 text-xs whitespace-pre-wrap text-ui-gray-dark"
|
||||
>
|
||||
{formType.description}
|
||||
</RadioGroup.Description>
|
||||
</span>
|
||||
</span>
|
||||
<CheckCircleIcon
|
||||
className={classNames(
|
||||
!checked ? "hidden" : "",
|
||||
"h-5 w-5 text-red"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
checked ? "hidden" : "",
|
||||
"h-4 w-4 rounded-full border-2 border-ui-gray-light"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
className={classNames(
|
||||
active ? "border" : "border-2",
|
||||
checked ? "border-red" : "border-transparent",
|
||||
"absolute -inset-px rounded pointer-events-none"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div className="mt-5 sm:mt-6">
|
||||
<StandardButton fullwidth type="submit">
|
||||
create form
|
||||
<BsPlus className="w-6 h-6 ml-1"></BsPlus>
|
||||
</StandardButton>
|
||||
</div>
|
||||
</form>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
}
|
||||
@@ -23,7 +23,7 @@ export default function Layout({ children }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-bggray">
|
||||
<div className="min-h-screen bg-white">
|
||||
<Disclosure as="nav" className="bg-white shadow-sm">
|
||||
{({ open }) => (
|
||||
<>
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
import React from "react";
|
||||
import { classNames } from "../../lib/utils";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// button component, consuming props
|
||||
const SecondNavBar: React.FC<Props> = ({
|
||||
children,
|
||||
onClick = () => {},
|
||||
...rest
|
||||
}) => {
|
||||
const SecondNavBar: React.FC<Props> = ({ children }) => {
|
||||
return (
|
||||
<div className="relative z-10 flex flex-shrink-0 h-16 py-2 bg-ui-gray-lighter">
|
||||
<div className="flex items-center justify-center flex-1 px-4 py-2">
|
||||
|
||||
@@ -5,11 +5,11 @@ import Link from "next/link";
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
itemLabel: string;
|
||||
itemLabel?: string;
|
||||
disabled?: boolean;
|
||||
link?: boolean;
|
||||
outbound?: boolean;
|
||||
href: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
// button component, consuming props
|
||||
|
||||
+6
-1
@@ -9,7 +9,12 @@ services:
|
||||
|
||||
snoopforms:
|
||||
build: .
|
||||
command: [sh, -c, "yarn prisma:migrate && yarn start"]
|
||||
command:
|
||||
[
|
||||
sh,
|
||||
-c,
|
||||
"yarn prisma migrate deploy && yarn prisma db seed && yarn start",
|
||||
]
|
||||
depends_on:
|
||||
- postgres
|
||||
ports:
|
||||
|
||||
+2
-2
@@ -35,12 +35,12 @@ export const persistForm = async (form) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const createForm = async () => {
|
||||
export const createForm = async (form = {}) => {
|
||||
try {
|
||||
const res = await fetch(`/api/forms`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({}),
|
||||
body: JSON.stringify(form),
|
||||
});
|
||||
return await res.json();
|
||||
} catch (error) {
|
||||
|
||||
+2
-1
@@ -6,7 +6,8 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"seed:prod": "node prisma/seed.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@editorjs/editorjs": "^2.24.3",
|
||||
|
||||
@@ -24,9 +24,9 @@ export default async function handle(
|
||||
owner: {
|
||||
select: { name: true },
|
||||
},
|
||||
submissionSessions: {
|
||||
select: { id: true },
|
||||
}
|
||||
_count: {
|
||||
select: { submissionSessions: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
res.json(formData);
|
||||
|
||||
@@ -17,11 +17,6 @@ export default function FormPage() {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!form.finishedOnboarding) {
|
||||
router.push(`/forms/${formId}/welcome`);
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
if (form.formType === "NOCODE") {
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { DocumentSearchIcon } from "@heroicons/react/outline";
|
||||
import { GetServerSideProps } from "next";
|
||||
import { getSession } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import { FaDiscord, FaReact, FaVuejs } from "react-icons/fa";
|
||||
import LayoutFormBasics from "../../../components/layout/LayoutFormBasic";
|
||||
import Loading from "../../../components/Loading";
|
||||
import StandardButton from "../../../components/StandardButton";
|
||||
import { useForm } from "../../../lib/forms";
|
||||
import { DocumentSearchIcon } from "@heroicons/react/outline";
|
||||
import Link from "next/link";
|
||||
import SecondNavBar from "../../../components/layout/SecondNavBar";
|
||||
import SecondNavBarItem from "../../../components/layout/SecondNavBarItem";
|
||||
import { FaReact, FaVuejs } from "react-icons/fa";
|
||||
import { FaDiscord } from "react-icons/fa";
|
||||
import Loading from "../../../components/Loading";
|
||||
import { useForm } from "../../../lib/forms";
|
||||
|
||||
export default function ReactPage() {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { GetServerSideProps } from "next";
|
||||
import { getSession } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import FormOnboardingModal from "../../../components/form/FormOnboardingModal";
|
||||
import LayoutFormBasics from "../../../components/layout/LayoutFormBasic";
|
||||
import Loading from "../../../components/Loading";
|
||||
import { useForm } from "../../../lib/forms";
|
||||
|
||||
export default function WelcomePage() {
|
||||
const router = useRouter();
|
||||
const formId = router.query.id.toString();
|
||||
const { form, isLoadingForm } = useForm(router.query.id);
|
||||
|
||||
if (isLoadingForm) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!form.finishedOnboarding) {
|
||||
return (
|
||||
<LayoutFormBasics title={form.title} formId={formId} currentStep="form">
|
||||
<FormOnboardingModal open={true} formId={formId} />
|
||||
</LayoutFormBasics>
|
||||
);
|
||||
} else {
|
||||
router.push(`/forms/${formId}`);
|
||||
return <Loading />;
|
||||
}
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
|
||||
const session = await getSession({ req });
|
||||
if (!session) {
|
||||
res.statusCode = 403;
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
@@ -1,116 +0,0 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "FormType" AS ENUM ('CODE', 'NOCODE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PipelineType" AS ENUM ('WEBHOOK');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Form" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"ownerId" INTEGER NOT NULL,
|
||||
"formType" "FormType" NOT NULL DEFAULT E'NOCODE',
|
||||
"name" TEXT NOT NULL,
|
||||
"published" BOOLEAN NOT NULL DEFAULT false,
|
||||
"finishedOnboarding" BOOLEAN NOT NULL DEFAULT false,
|
||||
"schema" JSONB NOT NULL,
|
||||
|
||||
CONSTRAINT "Form_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NoCodeForm" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"formId" TEXT NOT NULL,
|
||||
"pages" JSONB NOT NULL DEFAULT '[]',
|
||||
"pagesDraft" JSONB NOT NULL DEFAULT '[]',
|
||||
|
||||
CONSTRAINT "NoCodeForm_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,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"formId" TEXT NOT NULL,
|
||||
"userFingerprint" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "SubmissionSession_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SessionEvent" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"submissionSessionId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"data" JSONB NOT NULL,
|
||||
|
||||
CONSTRAINT "SessionEvent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT,
|
||||
"email_verified" TIMESTAMP(3),
|
||||
"password" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "verification_requests" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"identifier" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expires" TIMESTAMP(3) NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "verification_requests_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "NoCodeForm_formId_key" ON "NoCodeForm"("formId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "verification_requests_token_key" ON "verification_requests"("token");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Form" ADD CONSTRAINT "Form_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "NoCodeForm" ADD CONSTRAINT "NoCodeForm_formId_fkey" FOREIGN KEY ("formId") REFERENCES "Form"("id") ON DELETE CASCADE 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;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SessionEvent" ADD CONSTRAINT "SessionEvent_submissionSessionId_fkey" FOREIGN KEY ("submissionSessionId") REFERENCES "SubmissionSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -25,7 +25,6 @@ model Form {
|
||||
formType FormType @default(NOCODE)
|
||||
name String
|
||||
published Boolean @default(false)
|
||||
finishedOnboarding Boolean @default(false)
|
||||
schema Json
|
||||
submissionSessions SubmissionSession[]
|
||||
pipelines Pipeline[]
|
||||
|
||||
+9
-5
@@ -9,7 +9,7 @@ async function main() {
|
||||
const passwordHash = await hash(process.env.ADMIN_PASSWORD, 12);
|
||||
|
||||
if (typeof passwordHash === "string") {
|
||||
const userData: Prisma.UserCreateInput[] = [
|
||||
const users: Prisma.UserCreateInput[] = [
|
||||
{
|
||||
name: "Admin",
|
||||
email: process.env.ADMIN_EMAIL,
|
||||
@@ -17,11 +17,15 @@ async function main() {
|
||||
},
|
||||
];
|
||||
|
||||
for (const u of userData) {
|
||||
const user = await prisma.user.create({
|
||||
data: u,
|
||||
for (const user of users) {
|
||||
const userRes = await prisma.user.upsert({
|
||||
where: {
|
||||
email: user.email,
|
||||
},
|
||||
update: {},
|
||||
create: user,
|
||||
});
|
||||
console.log(`Created user with id: ${user.id}`);
|
||||
console.log(`Created user with id: ${userRes.id}`);
|
||||
}
|
||||
console.log(`Seeding finished.`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user