update hq db schema, add form overview page

This commit is contained in:
Matthias Nannt
2022-11-29 15:34:55 +01:00
parent ec695df017
commit a977cc403b
17 changed files with 387 additions and 117 deletions

View File

@@ -20,6 +20,7 @@
"next": "^13.0.5",
"next-auth": "^4.17.0",
"nodemailer": "^6.8.0",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.6.0",

View File

@@ -1,16 +1,16 @@
"use client";
import AvatarPlaceholder from "@/images/avatar-placeholder.png";
import { Disclosure, Menu, Transition } from "@headlessui/react";
import { Bars3Icon, BellIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import { signOut, useSession } from "next-auth/react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { Fragment, useMemo } from "react";
import { Logo } from "../Logo";
import AvatarPlaceholder from "@/images/avatar-placeholder.png";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Fragment } from "react";
import LoadingSpinner from "../LoadingSpinner";
import clsx from "clsx";
import { Logo } from "../Logo";
export default function ProjectsLayout({ params, children }) {
const router = useRouter();

View File

@@ -13,7 +13,7 @@ export default function MeSettingsPage() {
const [openNewApiKeyModal, setOpenNewApiKeyModal] = useState(false);
return (
<div>
<div className="mx-auto mt-8 max-w-7xl px-4 sm:px-6 lg:px-8">
<header className="mb-8">
<h1 className="text-3xl font-bold leading-tight tracking-tight text-gray-900">Account Settings</h1>
</header>

View File

@@ -3,7 +3,7 @@
import EmptyPageFiller from "@/components/EmptyPageFiller";
import { deleteForm, useForms } from "@/lib/forms";
import { Menu, Transition } from "@headlessui/react";
import { CommandLineIcon, DocumentPlusIcon, PlusIcon, SquaresPlusIcon } from "@heroicons/react/24/outline";
import { DocumentPlusIcon, PlusIcon } from "@heroicons/react/24/outline";
import { EllipsisHorizontalIcon, TrashIcon } from "@heroicons/react/24/solid";
import clsx from "clsx";
import Link from "next/link";
@@ -11,7 +11,7 @@ import { Fragment, useState } from "react";
import NewFormModal from "./NewFormModal";
export default function FormsList({ teamId }) {
const { forms, mutateForms } = useForms(teamId);
const { forms, mutateForms, isLoadingForms } = useForms(teamId);
const [openNewFormModal, setOpenNewFormModal] = useState(false);
const newForm = async () => {

View File

@@ -0,0 +1,62 @@
"use client";
import { Disclosure } from "@headlessui/react";
import { BellIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
export default function FormLayout({ params, children }) {
const navigation = [
{ name: "Form", href: `/teams/${params.teamId}/forms/${params.formId}/`, current: true },
{ name: "Pipelines", href: `/teams/${params.teamId}/forms/${params.formId}/pipelines/`, current: false },
{ name: "Results", href: `/teams/${params.teamId}/forms/${params.formId}/results/`, current: false },
];
return (
<>
<Disclosure as="header" className="bg-white shadow">
{({ open }) => (
<>
<div className="px-2 sm:px-4 lg:divide-y lg:divide-gray-200 lg:px-8">
<nav className="hidden lg:flex lg:space-x-8 lg:py-2" aria-label="Global">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={clsx(
item.current
? "bg-gray-100 text-gray-900"
: "text-gray-900 hover:bg-gray-50 hover:text-gray-900",
"inline-flex items-center rounded-md py-2 px-3 text-sm font-medium"
)}
aria-current={item.current ? "page" : undefined}>
{item.name}
</a>
))}
</nav>
</div>
<Disclosure.Panel as="nav" className="lg:block" aria-label="Global">
<div className="space-y-1 px-2 pt-2 pb-3">
{navigation.map((item) => (
<Disclosure.Button
key={item.name}
as="a"
href={item.href}
className={clsx(
item.current
? "bg-gray-100 text-gray-900"
: "text-gray-900 hover:bg-gray-50 hover:text-gray-900",
"block rounded-md py-2 px-3 text-base font-medium"
)}
aria-current={item.current ? "page" : undefined}>
{item.name}
</Disclosure.Button>
))}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
{children}
</>
);
}

View File

@@ -3,10 +3,56 @@
import LoadingSpinner from "@/app/LoadingSpinner";
import { useForm } from "@/lib/forms";
import { useTeam } from "@/lib/teams";
import { Button } from "@formbricks/ui";
import { DocumentMagnifyingGlassIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import Link from "next/link";
import { useEffect, useMemo } from "react";
import { FaReact, FaVuejs } from "react-icons/fa";
import { toast } from "react-toastify";
import Prism from "prismjs";
require("prismjs/components/prism-javascript");
const getLibs = (formId: string) => [
{
id: "react",
name: "React",
href: `https://formbricks.com/docs/react-form-library/introduction`,
bgColor: "bg-brand-dark",
target: "_blank",
icon: FaReact,
},
{
id: "reactNative",
name: "React Native",
comingSoon: true,
href: "#",
disabled: true,
icon: FaReact,
},
{
id: "vue",
name: "Vue.js",
comingSoon: true,
href: "#",
disabled: true,
icon: FaVuejs,
},
];
export default function FormsPage({ params }) {
const { form, isLoadingForm, isErrorForm } = useForm(params.formId, params.teamId);
const { team, isLoadingTeam, isErrorTeam } = useTeam(params.teamId);
const libs = useMemo(() => {
if (form) {
return getLibs(form.id);
}
}, [form]);
useEffect(() => {
Prism.highlightAll();
}, []);
if (isLoadingForm || isLoadingTeam) {
return (
@@ -29,6 +75,131 @@ export default function FormsPage({ params }) {
</span>
</h1>
</header>
<div>
<div className="mx-auto mt-8">
<h1 className="text-ui-gray-dark text-3xl font-bold leading-tight">Connect your form</h1>
</div>
<div className="mt-4 mb-12">
<p className="text-ui-gray-dark">
To send all form submissions to this dashboard, update the form ID in the{" "}
<code className="javascript">{"<snoopForm>"}</code> component.
</p>
</div>
<div className="grid grid-cols-2 gap-10">
<div>
<label htmlFor="formId" className="text-ui-gray-dark block text-base">
Your form ID
</label>
<div className="mt-3">
<input
id="formId"
type="text"
className="text-md mb-3 w-full rounded-sm border-gray-300 shadow-sm disabled:bg-gray-100"
value={form.id}
disabled
/>
<Button
className="w-full justify-center"
onClick={() => {
navigator.clipboard.writeText(form.id);
toast("Copied form ID to clipboard");
}}>
copy
</Button>
</div>
</div>
<div className="rounded-md bg-black p-8 font-light text-gray-200">
<pre>
<code className="language-js whitespace-pre-wrap">
{`import { Form, Text, Email, Checkbox, Submit, sendToHQ } from "@formbricks/react";
import "@formbricks/react/styles.css";
export default function WaitlistForm() {
return (
<Form id="${form.id}" onSubmit={sendToHQ}>
<Text name="name" label="What's your name?" validation="required" />
<Email
name="email"
label="What's your email address?"
placeholder="you@example.com"
validation="required|email"
/>
<Checkbox
name="terms"
label="Terms & Conditions"
help="To use our service, please accept."
validation="accepted"
/>
<Submit label="Let's go!" />
</Form>
);
}`}
</code>
</pre>
</div>
</div>
<div className="mt-16">
<h2 className="text-ui-gray-dark text-xl font-bold">Code your form</h2>
<div className="mt-4 mb-12">
<p className="text-ui-gray-dark">
Build your form with the code library of your choice. Manage your data in this dashboard.
</p>
</div>
<ul role="list" className="mt-3 grid grid-cols-1 gap-5 sm:grid-cols-2 sm:gap-6">
{libs.map((lib) => (
<Link
key={lib.id}
href={lib.href}
className={clsx(
"col-span-1 flex rounded-md shadow-sm",
lib.disabled && "pointer-events-none"
)}
target={lib.target || ""}
rel="noreferrer">
<li
className={clsx(
lib.comingSoon ? "text-slate-500" : "text-slate-800 shadow-sm hover:text-black",
"col-span-1 flex w-full rounded-md"
)}>
<div
className={clsx(
lib.bgColor || "bg-slate-300",
"flex w-20 flex-shrink-0 items-center justify-center rounded-l-md text-sm font-medium text-white"
)}>
<lib.icon
className={clsx(lib.comingSoon ? "text-slate-100" : "stroke-1 text-white", "h-10 w-10")}
/>
</div>
<div
className={clsx(
lib.comingSoon ? "border-dashed" : "",
"flex flex-1 items-center justify-between truncate rounded-r-md bg-white"
)}>
<div className="inline-flex truncate px-4 py-6 text-lg">
<p className="font-light">{lib.name}</p>
{lib.comingSoon && (
<div className="ml-3 rounded bg-teal-100 p-1 px-3">
<p className="text-xs text-black">coming soon</p>
</div>
)}
</div>
</div>
</li>
</Link>
))}
</ul>
<div className="my-12 text-center font-light text-slate-500">
<p>
Your form is running? Go to{" "}
<Link href={`/forms/${form.id}/preview`} className="text-red underline">
Pipelines
</Link>
</p>
</div>
</div>
</div>
</div>
);
}

View File

@@ -6,7 +6,7 @@ import { useTeam } from "@/lib/teams";
import FormsList from "./FormsList";
export default function FormsPage({ params }) {
const { forms, isLoadingForms, isErrorForms } = useForms(params.teamId);
const { isLoadingForms, isErrorForms } = useForms(params.teamId);
const { team, isLoadingTeam, isErrorTeam } = useTeam(params.teamId);
if (isLoadingForms || isLoadingTeam) {

View File

@@ -2,14 +2,11 @@
import { Dialog, Transition } from "@headlessui/react";
import { Cog8ToothIcon, RectangleStackIcon, UsersIcon, XMarkIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Fragment, useMemo, useState } from "react";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
export default function Example({ children, params }) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const pathname = usePathname();
@@ -39,14 +36,6 @@ export default function Example({ children, params }) {
return (
<>
{/*
This example requires updating your template:
```
<html class="h-full bg-gray-50">
<body class="h-full overflow-hidden">
```
*/}
<div className="flex h-full">
{/* Narrow sidebar */}
<div className="bg-brand-dark hidden w-28 overflow-y-auto bg-gradient-to-r md:block">
@@ -56,7 +45,7 @@ export default function Example({ children, params }) {
<Link
key={item.name}
href={item.href}
className={classNames(
className={clsx(
item.current
? "bg-brand-light text-white"
: "hover:bg-brand-light text-teal-100 hover:text-white",
@@ -64,7 +53,7 @@ export default function Example({ children, params }) {
)}
aria-current={item.current ? "page" : undefined}>
<item.icon
className={classNames(
className={clsx(
item.current ? "text-white" : "text-teal-300 group-hover:text-white",
"h-6 w-6"
)}
@@ -133,7 +122,7 @@ export default function Example({ children, params }) {
<a
key={item.name}
href={item.href}
className={classNames(
className={clsx(
item.current
? "bg-brand-dark text-white"
: "hover:bg-brand-dark text-teal-100 hover:text-white",
@@ -141,7 +130,7 @@ export default function Example({ children, params }) {
)}
aria-current={item.current ? "page" : undefined}>
<item.icon
className={classNames(
className={clsx(
item.current ? "text-white" : "text-teal-300 group-hover:text-white",
"mr-3 h-6 w-6"
)}

View File

@@ -1,5 +1,6 @@
import "@/styles/globals.css";
import "@/styles/toastify.css";
import "@/styles/prism.css";
// include styles from the ui package
import SessionProvider from "./SessionProvider";

View File

@@ -1,7 +1,7 @@
import useSWR from "swr";
import { fetcher } from "./utils";
export const useForms = (teamId) => {
export const useForms = (teamId: string) => {
const { data, error, mutate } = useSWR(`/api/teams/${teamId}/forms`, fetcher);
return {
@@ -35,7 +35,7 @@ export const persistForm = async (form) => {
}
};
export const createForm = async (teamId, form = {}) => {
export const createForm = async (teamId: string, form = {}) => {
try {
const res = await fetch(`/api/teams/${teamId}/forms`, {
method: "POST",
@@ -49,7 +49,7 @@ export const createForm = async (teamId, form = {}) => {
}
};
export const deleteForm = async (teamId, formId) => {
export const deleteForm = async (teamId: string, formId: string) => {
try {
await fetch(`/api/teams/${teamId}/forms/${formId}`, {
method: "DELETE",

View File

@@ -9,15 +9,9 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
return res.status(401).json({ message: "Not authenticated" });
}
const teamId = parseInt(req.query.teamId.toString());
if (isNaN(teamId)) {
return res.status(400).json({ message: "Invalid teamId" });
}
const teamId = req.query.teamId.toString();
const formId = parseInt(req.query.formId.toString());
if (isNaN(formId)) {
return res.status(400).json({ message: "Invalid formId" });
}
const formId = req.query.formId.toString();
// check team permission
const membership = await prisma.membership.findUnique({

View File

@@ -9,10 +9,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
return res.status(401).json({ message: "Not authenticated" });
}
const teamId = parseInt(req.query.teamId.toString());
if (isNaN(teamId)) {
return res.status(400).json({ message: "Invalid teamId" });
}
const teamId = req.query.teamId.toString();
// check team permission
const membership = await prisma.membership.findUnique({

View File

@@ -1,8 +1,6 @@
import { getSessionOrUser, hashApiKey } from "@/lib/apiHelper";
import { getSessionOrUser } from "@/lib/apiHelper";
import { prisma } from "@formbricks/database";
import { randomBytes } from "crypto";
import type { NextApiRequest, NextApiResponse } from "next";
import { unstable_getServerSession } from "next-auth";
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
// Check Authentication
@@ -11,10 +9,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
return res.status(401).json({ message: "Not authenticated" });
}
const teamId = parseInt(req.query.teamId.toString());
if (isNaN(teamId)) {
return res.status(400).json({ message: "Invalid teamId" });
}
const teamId = req.query.teamId.toString();
// GET /api/teams[teamId]
// Get a specific team

View File

@@ -0,0 +1,90 @@
/* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+clike+javascript */
code[class*="language-"],
pre[class*="language-"] {
color: #f8f8f2;
background: 0 0;
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
.token.doctype,
.token.prolog {
color: #8292a2;
}
.token.punctuation {
color: #f8f8f2;
}
.token.namespace {
opacity: 0.7;
}
.token.constant,
.token.deleted,
.token.property,
.token.symbol,
.token.tag {
color: #f92672;
}
.token.boolean,
.token.number {
color: #ae81ff;
}
.token.attr-name,
.token.builtin,
.token.char,
.token.inserted,
.token.selector,
.token.string {
color: #a6e22e;
}
.language-css .token.string,
.style .token.string,
.token.entity,
.token.operator,
.token.url,
.token.variable {
color: #f8f8f2;
}
.token.atrule,
.token.attr-value,
.token.class-name,
.token.function {
color: #e6db74;
}
.token.keyword {
color: #66d9ef;
}
.token.important,
.token.regex {
color: #fd971f;
}
.token.bold,
.token.important {
font-weight: 700;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@@ -9,12 +9,12 @@ CREATE TYPE "IdentityProvider" AS ENUM ('EMAIL', 'GITHUB');
-- CreateTable
CREATE TABLE "Pipeline" (
"id" SERIAL NOT NULL,
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"name" TEXT NOT NULL,
"type" "PipelineType" NOT NULL,
"formId" INTEGER NOT NULL,
"formId" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"config" JSONB NOT NULL DEFAULT '{}',
@@ -26,7 +26,7 @@ CREATE TABLE "Customer" (
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"teamId" INTEGER NOT NULL,
"teamId" TEXT NOT NULL,
"data" JSONB NOT NULL DEFAULT '{}',
CONSTRAINT "Customer_pkey" PRIMARY KEY ("id","teamId")
@@ -34,11 +34,11 @@ CREATE TABLE "Customer" (
-- CreateTable
CREATE TABLE "Form" (
"id" SERIAL NOT NULL,
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"label" TEXT NOT NULL,
"teamId" INTEGER NOT NULL,
"teamId" TEXT NOT NULL,
"schema" JSONB NOT NULL DEFAULT '{}',
CONSTRAINT "Form_pkey" PRIMARY KEY ("id")
@@ -46,12 +46,12 @@ CREATE TABLE "Form" (
-- CreateTable
CREATE TABLE "Submission" (
"id" SERIAL NOT NULL,
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"formId" INTEGER NOT NULL,
"formId" TEXT NOT NULL,
"customerId" TEXT,
"teamId" INTEGER,
"teamId" TEXT,
"data" JSONB NOT NULL DEFAULT '{}',
CONSTRAINT "Submission_pkey" PRIMARY KEY ("id")
@@ -59,7 +59,7 @@ CREATE TABLE "Submission" (
-- CreateTable
CREATE TABLE "Team" (
"id" SERIAL NOT NULL,
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"name" TEXT NOT NULL,
@@ -69,8 +69,8 @@ CREATE TABLE "Team" (
-- CreateTable
CREATE TABLE "Membership" (
"teamId" INTEGER NOT NULL,
"userId" INTEGER NOT NULL,
"teamId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"accepted" BOOLEAN NOT NULL DEFAULT false,
"role" "MembershipRole" NOT NULL,
@@ -79,22 +79,22 @@ CREATE TABLE "Membership" (
-- CreateTable
CREATE TABLE "ApiKey" (
"id" SERIAL NOT NULL,
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastUsedAt" TIMESTAMP(3),
"label" TEXT,
"hashedKey" TEXT NOT NULL,
"userId" INTEGER NOT NULL,
"userId" TEXT NOT NULL,
CONSTRAINT "ApiKey_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Account" (
"id" SERIAL NOT NULL,
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"userId" INTEGER NOT NULL,
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
@@ -111,7 +111,7 @@ CREATE TABLE "Account" (
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"name" TEXT,

View File

@@ -18,13 +18,13 @@ enum PipelineType {
}
model Pipeline {
id Int @id @default(autoincrement())
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
type PipelineType
form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
formId Int
formId String
enabled Boolean @default(false)
config Json @default("{}")
}
@@ -34,7 +34,7 @@ model Customer {
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId Int
teamId String
submissions Submission[]
data Json @default("{}")
@@ -42,31 +42,31 @@ model Customer {
}
model Form {
id Int @id @default(autoincrement())
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
label String
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId Int
teamId String
schema Json @default("{}")
submissions Submission[]
pipelines Pipeline[]
}
model Submission {
id Int @id @default(autoincrement())
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
formId Int
formId String
customer Customer? @relation(fields: [customerId, teamId], references: [id, teamId])
customerId String?
teamId Int?
teamId String?
data Json @default("{}")
}
model Team {
id Int @id @default(autoincrement())
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
@@ -83,9 +83,9 @@ enum MembershipRole {
model Membership {
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId Int
teamId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
userId String
accepted Boolean @default(false)
role MembershipRole
@@ -93,13 +93,13 @@ model Membership {
}
model ApiKey {
id Int @id @unique @default(autoincrement())
id String @id @unique @default(cuid())
createdAt DateTime @default(now())
lastUsedAt DateTime?
label String?
hashedKey String @unique()
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
userId String
}
enum IdentityProvider {
@@ -108,11 +108,11 @@ enum IdentityProvider {
}
model Account {
id Int @id @default(autoincrement())
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
userId String
type String
provider String
providerAccountId String
@@ -128,7 +128,7 @@ model Account {
}
model User {
id Int @id @default(autoincrement())
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String?

44
pnpm-lock.yaml generated
View File

@@ -14,43 +14,6 @@ importers:
tsx: 3.9.0
turbo: 1.6.3
apps/examples:
specifiers:
'@formbricks/react': workspace:*
'@formbricks/tailwind-config': workspace:*
'@formbricks/tsconfig': workspace:*
'@formbricks/ui': workspace:*
'@heroicons/react': ^2.0.13
'@types/node': ^18.11.9
'@types/react': ^18.0.25
'@types/react-dom': ^18.0.8
autoprefixer: ^10.4.12
eslint-config-formbricks: workspace:*
next: latest
postcss: ^8.4.18
react: ^18.2.0
react-dom: ^18.2.0
tailwindcss: ^3.2.2
typescript: ^4.8.4
dependencies:
'@formbricks/react': link:../../packages/react
'@formbricks/ui': link:../../packages/ui
'@heroicons/react': 2.0.13_react@18.2.0
next: 13.0.5_biqbaboplfbrettd7655fr4n2y
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
devDependencies:
'@formbricks/tailwind-config': link:../../packages/tailwind-config
'@formbricks/tsconfig': link:../../packages/tsconfig
'@types/node': 18.11.9
'@types/react': 18.0.25
'@types/react-dom': 18.0.9
autoprefixer: 10.4.12_postcss@8.4.18
eslint-config-formbricks: link:../../packages/eslint-config-formbricks
postcss: 8.4.18
tailwindcss: 3.2.4_postcss@8.4.18
typescript: 4.9.3
apps/formbricks-com:
specifiers:
'@docsearch/react': ^3.3.0
@@ -137,6 +100,7 @@ importers:
next-auth: ^4.17.0
nodemailer: ^6.8.0
postcss: ^8.4.19
prismjs: ^1.29.0
react: ^18.2.0
react-dom: ^18.2.0
react-icons: ^4.6.0
@@ -156,6 +120,7 @@ importers:
next: 13.0.5_biqbaboplfbrettd7655fr4n2y
next-auth: 4.17.0_2xoejpawkzgot77rbv5mbik6ve
nodemailer: 6.8.0
prismjs: 1.29.0
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
react-icons: 4.6.0_react@18.2.0
@@ -12966,6 +12931,11 @@ packages:
engines: {node: '>=6'}
dev: false
/prismjs/1.29.0:
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
engines: {node: '>=6'}
dev: false
/process-nextick-args/2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: true