mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-29 19:41:32 -05:00
Add Payment to Formbricks Cloud (#192)
* add enterprise license, add ee package * migrate database from workspaces to organisations * add payment api endpoints and billing pages * add stripe env variables to .env.example
This commit is contained in:
+7
-1
@@ -67,4 +67,10 @@ NEXT_PUBLIC_SENTRY_DSN=
|
||||
# Configure Github Login
|
||||
NEXT_PUBLIC_GITHUB_AUTH_ENABLED=0
|
||||
GITHUB_ID=
|
||||
GITHUB_SECRET=
|
||||
GITHUB_SECRET=
|
||||
|
||||
# Stripe Billing Variables
|
||||
NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID=
|
||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=
|
||||
STRIPE_SECRET_KEY=
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
@@ -1,6 +1,10 @@
|
||||
MIT License
|
||||
Copyright (c) 2022 Matthias Nannt, Johannes Dancker
|
||||
|
||||
Copyright (c) 2022 Matthias Nannt
|
||||
Portions of this software are licensed as follows:
|
||||
|
||||
- All content that resides under the "packages/ee/" directory of this repository, if that directory exists, is licensed under the license defined in "packages/ee/LICENSE".
|
||||
- All third party components incorporated into the Formbricks Software are licensed under the original license provided by the owner of the applicable component.
|
||||
- Content outside of the above mentioned directories or restrictions above is available under the "MIT" license as defined below.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@@ -3,12 +3,12 @@ import Image from "next/image";
|
||||
import { Callout } from "@/components/shared/Callout";
|
||||
import HeroAnimation from "@/components/shared/HeroAnimation.tsx";
|
||||
import MdxCTA from "@/components/shared/MdxCTA.tsx";
|
||||
import HeaderImage from "/images/SEO/Google Forms Open Source Alternative Comparison with Formbricks Open-source Online Form Builder.png";
|
||||
import WhyGoogle from "/images/SEO/GoogleForms GDPR compliant for EU company open source self-hosting alternative.png";
|
||||
import ControllerVsProcessor from "/images/SEO/Data Controller vs Data Processor Overview for open source forms and surveys.png";
|
||||
import LemonadeExample from "/images/SEO/Example Lemonade Radio Field with Image in React Library Open Source.PNG";
|
||||
import GoogleExample from "/images/SEO/Google Form Example Customize and make it comply with GDPR CCPA HIPAA open source alternative.png";
|
||||
import CrownGIF from "/images/SEO/who gets the crown of open source forms.gif";
|
||||
import HeaderImage from "@/images/SEO/Google Forms Open Source Alternative Comparison with Formbricks Open-source Online Form Builder.png";
|
||||
import WhyGoogle from "@/images/SEO/GoogleForms GDPR compliant for EU company open source self-hosting alternative.png";
|
||||
import ControllerVsProcessor from "@/images/SEO/Data Controller vs Data Processor Overview for open source forms and surveys.png";
|
||||
import LemonadeExample from "@/images/SEO/example-lemonade-radio-field-with-image-in-react-library-Oopen-source.png";
|
||||
import GoogleExample from "@/images/SEO/Google Form Example Customize and make it comply with GDPR CCPA HIPAA open source alternative.png";
|
||||
import CrownGIF from "@/images/SEO/who gets the crown of open source forms.gif";
|
||||
import HeaderAnimation from "@/components/shared/HeroAnimation.tsx";
|
||||
|
||||
export const meta = {
|
||||
|
||||
@@ -25,7 +25,6 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
var path = require("path");
|
||||
const { withSentryConfig } = require("@sentry/nextjs");
|
||||
|
||||
const withTM = require("next-transpile-modules")(["@formbricks/ee"]);
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
output: "standalone",
|
||||
@@ -55,6 +57,8 @@ const sentryWebpackPluginOptions = {
|
||||
silent: true, // Suppresses all logs
|
||||
};
|
||||
|
||||
const moduleExports = () => [withTM].reduce((acc, next) => next(acc), nextConfig);
|
||||
|
||||
module.exports = process.env.NEXT_PUBLIC_SENTRY_DSN
|
||||
? withSentryConfig(nextConfig, sentryWebpackPluginOptions)
|
||||
: nextConfig;
|
||||
? withSentryConfig(moduleExports, sentryWebpackPluginOptions)
|
||||
: moduleExports;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/charts": "workspace:*",
|
||||
"@formbricks/ee": "workspace:*",
|
||||
"@formbricks/react": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"@headlessui/react": "^1.7.8",
|
||||
@@ -22,6 +23,7 @@
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"next": "^13.1.6",
|
||||
"next-auth": "^4.19.0",
|
||||
"next-transpile-modules": "^10.0.0",
|
||||
"nodemailer": "^6.9.1",
|
||||
"platform": "^1.3.6",
|
||||
"prismjs": "^1.29.0",
|
||||
@@ -30,6 +32,7 @@
|
||||
"react-icons": "^4.7.1",
|
||||
"react-loader-spinner": "^5.3.4",
|
||||
"react-toastify": "^9.1.1",
|
||||
"stripe": "^11.8.0",
|
||||
"swr": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// This file configures the initialization of Sentry on the server.
|
||||
// The config you add here will be used whenever the server handles a request.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
|
||||
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1.0,
|
||||
// ...
|
||||
// Note: if you want to override the automatic release value, do not set a
|
||||
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
|
||||
// that it will also get attached to your source maps
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import PricingTable from "@formbricks/ee/billing/components/PricingTable";
|
||||
import Modal from "./Modal";
|
||||
|
||||
export default function UpgradeModal({ open, setOpen, organisationId }) {
|
||||
return (
|
||||
<Modal open={open} setOpen={setOpen}>
|
||||
<div className="my-6 sm:flex-auto">
|
||||
<h1 className="text-xl font-semibold text-gray-900">Upgrade to benefit from all features</h1>
|
||||
<p className="mt-2 text-sm text-gray-700">
|
||||
You do not currently have an active subscription. Upgrade to get access to all features and improve
|
||||
your user research.
|
||||
</p>
|
||||
</div>
|
||||
<div className="overflow-hidden rounded-lg">
|
||||
<PricingTable organisationId={organisationId} />
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import EmptyPageFiller from "@/components/EmptyPageFiller";
|
||||
import { useCustomers } from "@/lib/customers";
|
||||
import { useWorkspace } from "@/lib/workspaces";
|
||||
import { useOrganisation } from "@/lib/organisations";
|
||||
import { convertDateTimeString } from "@/lib/utils";
|
||||
import { UsersIcon } from "@heroicons/react/24/outline";
|
||||
import Link from "next/link";
|
||||
@@ -12,13 +12,13 @@ import { useRouter } from "next/router";
|
||||
export default function FormsPage() {
|
||||
const router = useRouter();
|
||||
const { customers, isLoadingCustomers, isErrorCustomers } = useCustomers(
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
const { workspace, isLoadingWorkspace, isErrorWorkspace } = useWorkspace(
|
||||
router.query.workspaceId?.toString()
|
||||
const { organisation, isLoadingOrganisation, isErrorOrganisation } = useOrganisation(
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
|
||||
if (isLoadingCustomers || isLoadingWorkspace) {
|
||||
if (isLoadingCustomers || isLoadingOrganisation) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
@@ -26,7 +26,7 @@ export default function FormsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorCustomers || isErrorWorkspace) {
|
||||
if (isErrorCustomers || isErrorOrganisation) {
|
||||
return <div>Error loading ressources. Maybe you don‘t have enough access rights.</div>;
|
||||
}
|
||||
return (
|
||||
@@ -35,7 +35,7 @@ export default function FormsPage() {
|
||||
<h1 className="text-3xl font-bold leading-tight tracking-tight text-gray-900">
|
||||
Customers
|
||||
<span className="text-brand-dark ml-4 inline-flex items-center rounded-md border border-teal-100 bg-teal-50 px-2.5 py-0.5 text-sm font-medium">
|
||||
{workspace.name}
|
||||
{organisation.name}
|
||||
</span>
|
||||
</h1>
|
||||
<p className="mt-4 text-slate-600">
|
||||
@@ -78,7 +78,8 @@ export default function FormsPage() {
|
||||
{customers.map((customer, customerIdx) => (
|
||||
<tr key={customer.email} className={customerIdx % 2 === 0 ? undefined : "bg-gray-50"}>
|
||||
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6">
|
||||
<Link href={`/workspaces/${router.query.workspaceId}/customers/${customer.email}`}>
|
||||
<Link
|
||||
href={`/organisations/${router.query.organisationId}/customers/${customer.email}`}>
|
||||
{customer.email}
|
||||
</Link>
|
||||
</td>
|
||||
@@ -90,7 +91,7 @@ export default function FormsPage() {
|
||||
</td>
|
||||
<td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
|
||||
<Link
|
||||
href={`/workspaces/${router.query.workspaceId}/customers/${customer.id}`}
|
||||
href={`/organisations/${router.query.organisationId}/customers/${customer.id}`}
|
||||
className="text-brand-dark hover:text-brand-light">
|
||||
View<span className="sr-only">, {customer.name}</span>
|
||||
</Link>
|
||||
|
||||
@@ -15,7 +15,7 @@ import EmptyPageFiller from "../EmptyPageFiller";
|
||||
export default function SingleCustomerPage() {
|
||||
const router = useRouter();
|
||||
const { customer, isLoadingCustomer, isErrorCustomer } = useCustomer(
|
||||
router.query.workspaceId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
router.query.customerId?.toString()
|
||||
);
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function SingleCustomerPage() {
|
||||
<main className="mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<Link
|
||||
className="inline-flex pt-5 text-sm text-gray-500"
|
||||
href={`/workspaces/${router.query.workspaceId}/customers/`}>
|
||||
href={`/organisations/${router.query.organisationId}/customers/`}>
|
||||
<BackIcon className="mr-2 h-5 w-5" />
|
||||
Back to customers overview
|
||||
</Link>
|
||||
|
||||
@@ -11,8 +11,8 @@ import { Fragment, useState } from "react";
|
||||
import NewFormModal from "@/components/forms/NewFormModal";
|
||||
import LoadingSpinner from "../LoadingSpinner";
|
||||
|
||||
export default function FormsList({ workspaceId }) {
|
||||
const { forms, mutateForms, isLoadingForms } = useForms(workspaceId);
|
||||
export default function FormsList({ organisationId }) {
|
||||
const { forms, mutateForms, isLoadingForms } = useForms(organisationId);
|
||||
const [openNewFormModal, setOpenNewFormModal] = useState(false);
|
||||
|
||||
const newForm = async () => {
|
||||
@@ -21,7 +21,7 @@ export default function FormsList({ workspaceId }) {
|
||||
|
||||
const deleteFormAction = async (form, formIdx) => {
|
||||
try {
|
||||
await deleteForm(workspaceId, form.id);
|
||||
await deleteForm(organisationId, form.id);
|
||||
// remove locally
|
||||
const updatedForms = JSON.parse(JSON.stringify(forms));
|
||||
updatedForms.splice(formIdx, 1);
|
||||
@@ -72,7 +72,7 @@ export default function FormsList({ workspaceId }) {
|
||||
<p className="line-clamp-3 text-lg">{form.label}</p>
|
||||
</div>
|
||||
<Link
|
||||
href={`/workspaces/${workspaceId}/forms/${form.id}/${form.type}/`}
|
||||
href={`/organisations/${organisationId}/forms/${form.id}/${form.type}/`}
|
||||
className="absolute h-full w-full"></Link>
|
||||
<div className="divide-y divide-slate-100 ">
|
||||
<div className="flex justify-between px-4 py-2 text-right sm:px-6">
|
||||
@@ -140,7 +140,7 @@ export default function FormsList({ workspaceId }) {
|
||||
</ul>
|
||||
))}
|
||||
</div>
|
||||
<NewFormModal open={openNewFormModal} setOpen={setOpenNewFormModal} workspaceId={workspaceId} />
|
||||
<NewFormModal open={openNewFormModal} setOpen={setOpenNewFormModal} organisationId={organisationId} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
import FormsList from "@/components/forms/FormsList";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import { useForms } from "@/lib/forms";
|
||||
import { useWorkspace } from "@/lib/workspaces";
|
||||
import { useOrganisation } from "@/lib/organisations";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export default function FormsPage({}) {
|
||||
const router = useRouter();
|
||||
const { isLoadingForms, isErrorForms } = useForms(router.query.workspaceId?.toString());
|
||||
const { workspace, isLoadingWorkspace, isErrorWorkspace } = useWorkspace(
|
||||
router.query.workspaceId?.toString()
|
||||
const { isLoadingForms, isErrorForms } = useForms(router.query.organisationId?.toString());
|
||||
const { organisation, isLoadingOrganisation, isErrorOrganisation } = useOrganisation(
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
|
||||
if (isLoadingForms || isLoadingWorkspace) {
|
||||
if (isLoadingForms || isLoadingOrganisation) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
@@ -21,7 +21,7 @@ export default function FormsPage({}) {
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorForms || isErrorWorkspace) {
|
||||
if (isErrorForms || isErrorOrganisation) {
|
||||
return <div>Error loading ressources. Maybe you don‘t have enough access rights</div>;
|
||||
}
|
||||
return (
|
||||
@@ -30,11 +30,11 @@ export default function FormsPage({}) {
|
||||
<h1 className="text-3xl font-bold leading-tight tracking-tight text-gray-900">
|
||||
Forms
|
||||
<span className="text-brand-dark ml-4 inline-flex items-center rounded-md border border-teal-100 bg-teal-50 px-2.5 py-0.5 text-sm font-medium">
|
||||
{workspace.name}
|
||||
{organisation.name}
|
||||
</span>
|
||||
</h1>
|
||||
</header>
|
||||
<FormsList workspaceId={router.query.workspaceId} />
|
||||
<FormsList organisationId={router.query.organisationId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,40 +7,62 @@ import { PMFIcon, FeedbackIcon, UserCommentIcon } from "@formbricks/ui";
|
||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||
import clsx from "clsx";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Fragment, useState } from "react";
|
||||
import { Fragment, useMemo, useState } from "react";
|
||||
import { BsPlus } from "react-icons/bs";
|
||||
import Link from "next/link";
|
||||
import LoadingSpinner from "../LoadingSpinner";
|
||||
import { useOrganisation } from "@/lib/organisations";
|
||||
import UpgradeModal from "../UpgradeModal";
|
||||
|
||||
type FormOnboardingModalProps = {
|
||||
open: boolean;
|
||||
setOpen: (v: boolean) => void;
|
||||
workspaceId: string;
|
||||
organisationId: string;
|
||||
};
|
||||
|
||||
const formTypes = [
|
||||
{
|
||||
id: "feedback",
|
||||
name: "Feedback Box",
|
||||
description: "A direct channel to feel the pulse of your users.",
|
||||
icon: FeedbackIcon,
|
||||
},
|
||||
{
|
||||
id: "custom",
|
||||
name: "Custom Survey",
|
||||
description: "Create and analyze your custom survey.",
|
||||
icon: UserCommentIcon,
|
||||
},
|
||||
{
|
||||
id: "pmf",
|
||||
name: "Product-Market Fit Survey",
|
||||
description: "Leverage the Superhuman PMF engine.",
|
||||
icon: PMFIcon,
|
||||
},
|
||||
];
|
||||
|
||||
export default function NewFormModal({ open, setOpen, workspaceId }: FormOnboardingModalProps) {
|
||||
export default function NewFormModal({ open, setOpen, organisationId }: FormOnboardingModalProps) {
|
||||
const router = useRouter();
|
||||
const [openUpgradeModal, setOpenUpgradeModal] = useState(false);
|
||||
const [label, setLabel] = useState("");
|
||||
const [formType, setFormType] = useState(formTypes[0].id);
|
||||
const [formType, setFormType] = useState("feedback");
|
||||
const { organisation, isLoadingOrganisation, isErrorOrganisation } = useOrganisation(organisationId);
|
||||
|
||||
const formTypes = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: "feedback",
|
||||
name: "Feedback Box",
|
||||
description: "A direct channel to feel the pulse of your users.",
|
||||
icon: FeedbackIcon,
|
||||
},
|
||||
{
|
||||
id: "custom",
|
||||
name: "Custom Survey",
|
||||
description: "Create and analyze your custom survey.",
|
||||
icon: UserCommentIcon,
|
||||
},
|
||||
{
|
||||
id: "pmf",
|
||||
name: "Product-Market Fit Survey",
|
||||
description: "Leverage the Superhuman PMF engine.",
|
||||
icon: PMFIcon,
|
||||
needsUpgrade: process.env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD && organisation?.plan === "free",
|
||||
},
|
||||
],
|
||||
[organisation]
|
||||
);
|
||||
|
||||
if (isLoadingOrganisation) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorOrganisation) {
|
||||
return <div>Error loading ressources. Maybe you don‘t have enough access rights</div>;
|
||||
}
|
||||
|
||||
const createFormAction = async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -194,129 +216,148 @@ export default function NewFormModal({ open, setOpen, workspaceId }: FormOnboard
|
||||
} else {
|
||||
throw new Error("Unknown form type");
|
||||
}
|
||||
const form = await createForm(workspaceId, formTemplate);
|
||||
router.push(`/workspaces/${workspaceId}/forms/${form.id}/${form.type}/`);
|
||||
const form = await createForm(organisationId, formTemplate);
|
||||
router.push(`/organisations/${organisationId}/forms/${form.id}/${form.type}/`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition.Root show={open} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={setOpen}>
|
||||
<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">
|
||||
<div className="fixed inset-0 bg-gray-500 bg-opacity-30 backdrop-blur-md transition-opacity" />
|
||||
</Transition.Child>
|
||||
<>
|
||||
<Transition.Root show={open} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={setOpen}>
|
||||
<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">
|
||||
<div className="fixed inset-0 bg-gray-500 bg-opacity-30 backdrop-blur-md transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<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">
|
||||
<Dialog.Panel className="relative transform rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
|
||||
<div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-0 focus:ring-offset-2"
|
||||
onClick={() => setOpen(false)}>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between">
|
||||
<h2 className="flex-none p-2 text-xl font-bold text-slate-800">Create new form</h2>
|
||||
</div>
|
||||
<form
|
||||
onSubmit={(e) => createFormAction(e)}
|
||||
className="inline-block w-full transform overflow-hidden p-2 text-left align-bottom transition-all sm:align-middle">
|
||||
<div>
|
||||
<label htmlFor="email" className="text-sm font-light text-slate-800">
|
||||
Name your form
|
||||
</label>
|
||||
<div className="mt-2">
|
||||
<input
|
||||
type="text"
|
||||
name="label"
|
||||
className="focus:border-brand focus:ring-brand block w-full rounded-md border-gray-300 shadow-sm sm:text-sm"
|
||||
placeholder="e.g. Feedback Box App"
|
||||
value={label}
|
||||
onChange={(e) => setLabel(e.target.value)}
|
||||
autoFocus
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<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">
|
||||
<Dialog.Panel className="relative transform rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
|
||||
<div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-0 focus:ring-offset-2"
|
||||
onClick={() => setOpen(false)}>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<hr className="my-6 text-gray-600" />
|
||||
<RadioGroup value={formType} onChange={setFormType}>
|
||||
<RadioGroup.Label className="text-sm font-light text-slate-800">
|
||||
Choose your form type
|
||||
</RadioGroup.Label>
|
||||
<div className="mt-3 space-y-4">
|
||||
{formTypes.map((formType) => (
|
||||
<RadioGroup.Option
|
||||
key={formType.name}
|
||||
value={formType.id}
|
||||
className={({ checked, active }) =>
|
||||
clsx(
|
||||
checked ? "border-transparent" : "border-gray-300",
|
||||
active ? "border-brand ring-brand ring-2" : "",
|
||||
"relative block cursor-pointer rounded-lg border bg-white px-6 py-4 shadow-sm focus:outline-none sm:flex"
|
||||
)
|
||||
}>
|
||||
{({ active, checked }) => (
|
||||
<>
|
||||
<RadioGroup.Description
|
||||
as="span"
|
||||
className="mt-2 mr-3 flex text-sm sm:mt-0 sm:flex-col sm:text-right">
|
||||
<formType.icon className="h-8 w-8" />
|
||||
</RadioGroup.Description>
|
||||
<span className="flex items-center">
|
||||
<span className="flex flex-col text-sm">
|
||||
<RadioGroup.Label as="span" className="font-medium text-gray-900">
|
||||
{formType.name}
|
||||
</RadioGroup.Label>
|
||||
<RadioGroup.Description as="span" className="text-gray-500">
|
||||
{formType.description}
|
||||
<div className="flex flex-row justify-between">
|
||||
<h2 className="flex-none p-2 text-xl font-bold text-slate-800">Create new form</h2>
|
||||
</div>
|
||||
<form
|
||||
onSubmit={(e) => createFormAction(e)}
|
||||
className="inline-block w-full transform overflow-hidden p-2 text-left align-bottom transition-all sm:align-middle">
|
||||
<div>
|
||||
<label htmlFor="email" className="text-sm font-light text-slate-800">
|
||||
Name your form
|
||||
</label>
|
||||
<div className="mt-2">
|
||||
<input
|
||||
type="text"
|
||||
name="label"
|
||||
className="focus:border-brand focus:ring-brand block w-full rounded-md border-gray-300 shadow-sm sm:text-sm"
|
||||
placeholder="e.g. Feedback Box App"
|
||||
value={label}
|
||||
onChange={(e) => setLabel(e.target.value)}
|
||||
autoFocus
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="my-6 text-gray-600" />
|
||||
<RadioGroup value={formType} onChange={setFormType}>
|
||||
<RadioGroup.Label className="text-sm font-light text-slate-800">
|
||||
Choose your form type
|
||||
</RadioGroup.Label>
|
||||
<div className="mt-3 space-y-4">
|
||||
{formTypes.map((formType) => (
|
||||
<div
|
||||
key={formType.name}
|
||||
onClick={() => {
|
||||
if (formType.needsUpgrade) {
|
||||
setOpenUpgradeModal(true);
|
||||
}
|
||||
}}>
|
||||
<RadioGroup.Option
|
||||
disabled={formType.needsUpgrade}
|
||||
value={formType.id}
|
||||
className={({ checked, active, disabled }) =>
|
||||
clsx(
|
||||
checked ? "border-transparent" : "border-gray-300",
|
||||
active ? "border-brand ring-brand ring-2" : "",
|
||||
disabled ? "bg-gray-100" : "bg-white",
|
||||
"relative block cursor-pointer rounded-lg border bg-white px-6 py-4 shadow-sm focus:outline-none sm:flex"
|
||||
)
|
||||
}>
|
||||
{({ active, checked }) => (
|
||||
<>
|
||||
<RadioGroup.Description
|
||||
as="span"
|
||||
className="mt-2 mr-3 flex text-sm sm:mt-0 sm:flex-col sm:text-right">
|
||||
<formType.icon className="h-8 w-8" />
|
||||
</RadioGroup.Description>
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex items-center">
|
||||
<span className="flex flex-col text-sm">
|
||||
<RadioGroup.Label as="span" className="font-medium text-gray-900">
|
||||
{formType.name}
|
||||
{formType.needsUpgrade && (
|
||||
<Link href={`/organisations/${organisation.id}/settings/billing`}>
|
||||
<span className="ml-2 inline-flex items-center rounded-full bg-teal-100 px-2.5 py-0.5 text-xs font-medium text-teal-800">
|
||||
Pro Feature
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
</RadioGroup.Label>
|
||||
<RadioGroup.Description as="span" className="text-gray-500">
|
||||
{formType.description}
|
||||
</RadioGroup.Description>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span
|
||||
className={clsx(
|
||||
active ? "border" : "border-2",
|
||||
checked ? "border-brand" : "border-transparent",
|
||||
"pointer-events-none absolute -inset-px rounded-lg"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
<span
|
||||
className={clsx(
|
||||
active ? "border" : "border-2",
|
||||
checked ? "border-brand" : "border-transparent",
|
||||
"pointer-events-none absolute -inset-px rounded-lg"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
||||
<div className="mt-5 sm:mt-6">
|
||||
<Button type="submit" className="w-full justify-center">
|
||||
create form
|
||||
<BsPlus className="ml-1 h-6 w-6"></BsPlus>
|
||||
</Button>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
||||
<div className="mt-5 sm:mt-6">
|
||||
<Button type="submit" className="w-full justify-center">
|
||||
create form
|
||||
<BsPlus className="ml-1 h-6 w-6"></BsPlus>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</form>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
<UpgradeModal open={openUpgradeModal} setOpen={setOpenUpgradeModal} organisationId={organisationId} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import { useForm } from "@/lib/forms";
|
||||
import { useWorkspace } from "@/lib/workspaces";
|
||||
import { useOrganisation } from "@/lib/organisations";
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { UserIcon } from "@heroicons/react/20/solid";
|
||||
import clsx from "clsx";
|
||||
@@ -24,10 +24,10 @@ export default function FormOverviewPage() {
|
||||
const router = useRouter();
|
||||
const { form, isLoadingForm, isErrorForm } = useForm(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
const { workspace, isLoadingWorkspace, isErrorWorkspace } = useWorkspace(
|
||||
router.query.workspaceId?.toString()
|
||||
const { organisation, isLoadingOrganisation, isErrorOrganisation } = useOrganisation(
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
const [activeTab, setActiveTab] = useState(tabs[0]);
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function FormOverviewPage() {
|
||||
}
|
||||
}, [isLoadingForm]);
|
||||
|
||||
if (isLoadingForm || isLoadingWorkspace) {
|
||||
if (isLoadingForm || isLoadingOrganisation) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
@@ -51,7 +51,7 @@ export default function FormOverviewPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorForm || isErrorWorkspace) {
|
||||
if (isErrorForm || isErrorOrganisation) {
|
||||
return <div>Error loading ressources. Maybe you don‘t have enough access rights</div>;
|
||||
}
|
||||
return (
|
||||
@@ -60,7 +60,7 @@ export default function FormOverviewPage() {
|
||||
<h1 className="text-3xl font-bold leading-tight tracking-tight text-gray-900">
|
||||
{form.label}
|
||||
<span className="text-brand-dark ml-4 inline-flex items-center rounded-md border border-teal-100 bg-teal-50 px-2.5 py-0.5 text-sm font-medium">
|
||||
{workspace.name}
|
||||
{organisation.name}
|
||||
</span>
|
||||
</h1>
|
||||
</header>
|
||||
@@ -179,7 +179,7 @@ export default function FormOverviewPage() {
|
||||
<li>
|
||||
View submission under{" "}
|
||||
<Link
|
||||
href={`/workspaces/${router.query.workspaceId}/forms/${router.query.formId}/submissions`}
|
||||
href={`/organisations/${router.query.organisationId}/forms/${router.query.formId}/submissions`}
|
||||
className="underline">
|
||||
Submissions
|
||||
</Link>{" "}
|
||||
@@ -188,7 +188,7 @@ export default function FormOverviewPage() {
|
||||
<li>
|
||||
Get notified or pipe submission data to a different tool in the{" "}
|
||||
<Link
|
||||
href={`/workspaces/${router.query.workspaceId}/forms/${router.query.formId}/pipelines`}
|
||||
href={`/organisations/${router.query.organisationId}/forms/${router.query.formId}/pipelines`}
|
||||
className="underline">
|
||||
Pipelines
|
||||
</Link>{" "}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function FeedbackPage() {
|
||||
const [currentTab, setCurrentTab] = useState("Results");
|
||||
const { form, isLoadingForm, isErrorForm } = useForm(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
|
||||
if (isLoadingForm) {
|
||||
|
||||
@@ -20,7 +20,7 @@ const subCategories = [
|
||||
export default function FeedbackResults() {
|
||||
const router = useRouter();
|
||||
const { submissions, isLoadingSubmissions, isErrorSubmissions } = useSubmissions(
|
||||
router.query.workspaceId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
router.query.formId?.toString()
|
||||
);
|
||||
const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false);
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function FeedbackTimeline({ submissions }) {
|
||||
const router = useRouter();
|
||||
|
||||
const { submissions: allSubmissions, mutateSubmissions } = useSubmissions(
|
||||
router.query.workspaceId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
router.query.formId?.toString()
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function FeedbackTimeline({ submissions }) {
|
||||
// save submission without customer
|
||||
const submissionWoCustomer = { ...updatedSubmission };
|
||||
delete submissionWoCustomer.customer;
|
||||
persistSubmission(submissionWoCustomer, router.query.workspaceId?.toString());
|
||||
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));
|
||||
@@ -107,7 +107,7 @@ export default function FeedbackTimeline({ submissions }) {
|
||||
{submission.customerEmail ? (
|
||||
<Link
|
||||
className="text-sm font-medium text-gray-700"
|
||||
href={`/workspaces/${router.query.workspaceId}/customers/${submission.customerEmail}`}>
|
||||
href={`/organisations/${router.query.organisationId}/customers/${submission.customerEmail}`}>
|
||||
{submission.customerEmail}
|
||||
</Link>
|
||||
) : (
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function AddPipelineModal({ open, setOpen }) {
|
||||
const [pipeline, setPipeline] = useState(getEmptyPipeline());
|
||||
const { pipelines, mutatePipelines } = usePipelines(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -43,7 +43,7 @@ export default function AddPipelineModal({ open, setOpen }) {
|
||||
e.preventDefault();
|
||||
const newPipeline = await createPipeline(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
pipeline
|
||||
);
|
||||
const newPipelines = JSON.parse(JSON.stringify(pipelines));
|
||||
|
||||
@@ -86,11 +86,11 @@ export default function PipelinesOverview({}) {
|
||||
const router = useRouter();
|
||||
const { form, isLoadingForm, isErrorForm } = useForm(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
const { pipelines, isLoadingPipelines, isErrorPipelines, mutatePipelines } = usePipelines(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
|
||||
const [openAddModal, setOpenAddModal] = useState(false);
|
||||
@@ -100,7 +100,7 @@ export default function PipelinesOverview({}) {
|
||||
const toggleEnabled = async (pipeline) => {
|
||||
const newPipeline = JSON.parse(JSON.stringify(pipeline));
|
||||
newPipeline.enabled = !newPipeline.enabled;
|
||||
await persistPipeline(router.query.formId, router.query.workspaceId, newPipeline);
|
||||
await persistPipeline(router.query.formId, router.query.organisationId, newPipeline);
|
||||
const pipelineIdx = pipelines.findIndex((p) => p.id === pipeline.id);
|
||||
if (pipelineIdx !== -1) {
|
||||
const newPipelines = JSON.parse(JSON.stringify(pipelines));
|
||||
@@ -115,7 +115,11 @@ export default function PipelinesOverview({}) {
|
||||
};
|
||||
|
||||
const deletePipelineAction = async (pipelineId) => {
|
||||
await deletePipeline(router.query.formId?.toString(), router.query.workspaceId?.toString(), pipelineId);
|
||||
await deletePipeline(
|
||||
router.query.formId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
pipelineId
|
||||
);
|
||||
const newPipelines = JSON.parse(JSON.stringify(pipelines));
|
||||
const pipelineIdx = newPipelines.findIndex((p) => p.id === pipelineId);
|
||||
if (pipelineIdx > -1) {
|
||||
|
||||
@@ -7,18 +7,18 @@ import PipelineSettings from "./PipelineSettings";
|
||||
export default function UpdatePipelineModal({ open, setOpen, pipelineId }) {
|
||||
const router = useRouter();
|
||||
const { pipeline, isLoadingPipeline, mutatePipeline } = usePipeline(
|
||||
router.query.workspaceId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
router.query.formId?.toString(),
|
||||
pipelineId
|
||||
);
|
||||
const { pipelines, mutatePipelines } = usePipelines(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
await persistPipeline(router.query.formId?.toString(), router.query.workspaceId?.toString(), pipeline);
|
||||
await persistPipeline(router.query.formId?.toString(), router.query.organisationId?.toString(), pipeline);
|
||||
const newPipelines = JSON.parse(JSON.stringify(pipelines));
|
||||
const pipelineIdx = pipelines.findIndex((p) => p.id === pipelineId);
|
||||
if (pipelineIdx > -1) {
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function PMFPage() {
|
||||
const [currentTab, setCurrentTab] = useState("Results");
|
||||
const { form, isLoadingForm, isErrorForm } = useForm(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
|
||||
if (isLoadingForm) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import PMFTimeline from "./PMFTimeline";
|
||||
export default function PMFResults() {
|
||||
const router = useRouter();
|
||||
const { submissions, isLoadingSubmissions, isErrorSubmissions } = useSubmissions(
|
||||
router.query.workspaceId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
router.query.formId?.toString()
|
||||
);
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@ export default function PMFTimeline({ submissions }) {
|
||||
mutateSubmissions,
|
||||
isLoadingSubmissions,
|
||||
isErrorSubmissions,
|
||||
} = useSubmissions(router.query.workspaceId?.toString(), router.query.formId?.toString());
|
||||
} = useSubmissions(router.query.organisationId?.toString(), router.query.formId?.toString());
|
||||
|
||||
const { form, isLoadingForm, isErrorForm } = useForm(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
|
||||
const toggleArchiveSubmission = (submission) => {
|
||||
@@ -31,7 +31,7 @@ export default function PMFTimeline({ submissions }) {
|
||||
// save submission without customer
|
||||
const submissionWoCustomer = { ...updatedSubmission };
|
||||
delete submissionWoCustomer.customer;
|
||||
persistSubmission(submissionWoCustomer, router.query.workspaceId?.toString());
|
||||
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));
|
||||
@@ -136,7 +136,7 @@ export default function PMFTimeline({ submissions }) {
|
||||
{submission.customerEmail ? (
|
||||
<Link
|
||||
className="text-sm font-medium text-gray-700"
|
||||
href={`/workspaces/${router.query.workspaceId}/customers/${submission.customerEmail}`}>
|
||||
href={`/organisations/${router.query.organisationId}/customers/${submission.customerEmail}`}>
|
||||
{submission.customerEmail}
|
||||
</Link>
|
||||
) : (
|
||||
|
||||
@@ -13,10 +13,10 @@ export default function SegmentResults() {
|
||||
const router = useRouter();
|
||||
const { form, isLoadingForm, isErrorForm } = useForm(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
const { submissions, isLoadingSubmissions, isErrorSubmissions } = useSubmissions(
|
||||
router.query.workspaceId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
router.query.formId?.toString()
|
||||
);
|
||||
const [filteredSubmissions, setFilteredSubmissions] = useState([]);
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function SegmentResults() {
|
||||
const router = useRouter();
|
||||
const [filteredSubmissions, setFilteredSubmissions] = useState([]);
|
||||
const { submissions, isLoadingSubmissions, isErrorSubmissions } = useSubmissions(
|
||||
router.query.workspaceId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
router.query.formId?.toString()
|
||||
);
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ interface Filter {
|
||||
|
||||
export default function FilterNavigation({ submissions, setFilteredSubmissions }) {
|
||||
const router = useRouter();
|
||||
const { formId, workspaceId } = router.query;
|
||||
const { formId, organisationId } = router.query;
|
||||
const [filters, setFilters] = useState<Filter[]>([]);
|
||||
|
||||
const { form, isLoadingForm, isErrorForm } = useForm(formId?.toString(), workspaceId?.toString());
|
||||
const { form, isLoadingForm, isErrorForm } = useForm(formId?.toString(), organisationId?.toString());
|
||||
|
||||
// filter submissions based on selected filters
|
||||
useEffect(() => {
|
||||
|
||||
@@ -18,12 +18,12 @@ export default function SubmissionsPage() {
|
||||
const [finishedOnly, setFinishedOnly] = useState(false);
|
||||
const [filteredSubmissions, setFileredSubmission] = useState([]);
|
||||
const { submissions, isLoadingSubmissions, mutateSubmissions, isErrorSubmissions } = useSubmissions(
|
||||
router.query.workspaceId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
router.query.formId?.toString()
|
||||
);
|
||||
const { form, isLoadingForm, isErrorForm } = useForm(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
const [activeSubmission, setActiveSubmission] = useState<Submission | null>(null);
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function SubmissionsPage() {
|
||||
const handleDelete = async (submission: Submission) => {
|
||||
try {
|
||||
await deleteSubmission(
|
||||
router.query.workspaceId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
router.query.formId?.toString(),
|
||||
submission.id
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import { useForm } from "@/lib/forms";
|
||||
import { useSubmissions } from "@/lib/submissions";
|
||||
import { capitalizeFirstLetter } from "@/lib/utils";
|
||||
import { useWorkspace } from "@/lib/workspaces";
|
||||
import { useOrganisation } from "@/lib/organisations";
|
||||
import { Bar, Nps, Table } from "@formbricks/charts";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/20/solid";
|
||||
import Link from "next/link";
|
||||
@@ -15,17 +15,17 @@ export default function SummaryPage() {
|
||||
const router = useRouter();
|
||||
const { form, isLoadingForm, isErrorForm } = useForm(
|
||||
router.query.formId?.toString(),
|
||||
router.query.workspaceId?.toString()
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
const { workspace, isLoadingWorkspace, isErrorWorkspace } = useWorkspace(
|
||||
router.query.workspaceId?.toString()
|
||||
const { organisation, isLoadingOrganisation, isErrorOrganisation } = useOrganisation(
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
const { submissions, isLoadingSubmissions } = useSubmissions(
|
||||
router.query.workspaceId?.toString(),
|
||||
router.query.organisationId?.toString(),
|
||||
router.query.formId?.toString()
|
||||
);
|
||||
|
||||
if (isLoadingForm || isLoadingWorkspace || isLoadingSubmissions) {
|
||||
if (isLoadingForm || isLoadingOrganisation || isLoadingSubmissions) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
@@ -33,7 +33,7 @@ export default function SummaryPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorForm || isErrorWorkspace) {
|
||||
if (isErrorForm || isErrorOrganisation) {
|
||||
return <div>Error loading ressources. Maybe you don‘t have enough access rights</div>;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function SummaryPage() {
|
||||
<h1 className="text-3xl font-bold leading-tight tracking-tight text-gray-900">
|
||||
Summary - {form.label}
|
||||
<span className="text-brand-dark ml-4 inline-flex items-center rounded-md border border-teal-100 bg-teal-50 px-2.5 py-0.5 text-sm font-medium">
|
||||
{workspace.name}
|
||||
{organisation.name}
|
||||
</span>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import AvatarPlaceholder from "@/images/avatar-placeholder.png";
|
||||
import { useMemberships } from "@/lib/memberships";
|
||||
import { Disclosure, Menu, Transition } from "@headlessui/react";
|
||||
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import clsx from "clsx";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Fragment } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import { Logo } from "../Logo";
|
||||
import Head from "next/head";
|
||||
|
||||
export default function LayoutApp({ children }) {
|
||||
const userNavigation = [
|
||||
{
|
||||
name: "Settings",
|
||||
onClick: () => {
|
||||
router.push("/me/settings");
|
||||
},
|
||||
},
|
||||
{ name: "Sign out", onClick: () => signOut() },
|
||||
];
|
||||
|
||||
const router = useRouter();
|
||||
const { data: session, status } = useSession();
|
||||
const { memberships, isLoadingMemberships, isErrorMemberships } = useMemberships();
|
||||
|
||||
const userNavigation = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: "Settings",
|
||||
href: "/me/settings",
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
if (status === "loading") {
|
||||
return (
|
||||
@@ -41,6 +43,18 @@ export default function LayoutApp({ children }) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
if (isLoadingMemberships) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center p-8">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorMemberships) {
|
||||
return <div>Error loading ressources. Maybe you don‘t have enough access rights</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
@@ -90,21 +104,65 @@ export default function LayoutApp({ children }) {
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95">
|
||||
<Menu.Items className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
{userNavigation.map((item) => (
|
||||
<Menu.Item key={item.name}>
|
||||
<Menu.Items className="absolute right-0 z-10 mt-2 w-48 origin-top-right divide-y divide-gray-100 rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-sm">Signed in as</p>
|
||||
<p className="truncate text-sm font-medium text-gray-900">{session.user.name}</p>
|
||||
</div>
|
||||
<div className="py-1">
|
||||
{userNavigation.map((item) => (
|
||||
<Menu.Item key={item.name}>
|
||||
{({ active }) => (
|
||||
<Link
|
||||
href={item.href}
|
||||
className={clsx(
|
||||
active ? "bg-gray-100" : "",
|
||||
"flex justify-start px-4 py-2 text-sm text-gray-700"
|
||||
)}>
|
||||
{item.name}
|
||||
</Link>
|
||||
)}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</div>
|
||||
{process.env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD === "1" &&
|
||||
memberships.map((membership) => (
|
||||
<>
|
||||
<div className="px-4 py-3">
|
||||
<p className="truncate text-sm font-medium text-gray-900">
|
||||
{membership.organisation.name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<Link
|
||||
href={`/organisations/${membership.organisation.id}/settings/billing`}
|
||||
className={clsx(
|
||||
active ? "bg-gray-100" : "",
|
||||
"flex justify-start px-4 py-2 text-sm text-gray-700"
|
||||
)}>
|
||||
Billing
|
||||
</Link>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
onClick={item.onClick}
|
||||
onClick={() => signOut()}
|
||||
className={clsx(
|
||||
active ? "bg-gray-100" : "",
|
||||
"flex w-full justify-start px-4 py-2 text-sm text-gray-700"
|
||||
)}>
|
||||
{item.name}
|
||||
Sign out
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
@@ -149,7 +207,7 @@ export default function LayoutApp({ children }) {
|
||||
<Disclosure.Button
|
||||
key={item.name}
|
||||
as="a"
|
||||
onClick={item.onClick}
|
||||
href={item.href}
|
||||
className="block px-4 py-2 text-base font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800">
|
||||
{item.name}
|
||||
</Disclosure.Button>
|
||||
|
||||
@@ -14,22 +14,22 @@ export default function LayoutWrapperCustomForm({ children }) {
|
||||
() => [
|
||||
{
|
||||
name: "Form",
|
||||
href: `/workspaces/${router.query.workspaceId}/forms/${router.query.formId}/custom/`,
|
||||
href: `/organisations/${router.query.organisationId}/forms/${router.query.formId}/custom/`,
|
||||
current: pathname.endsWith("custom") || pathname.endsWith("custom/"),
|
||||
},
|
||||
{
|
||||
name: "Pipelines",
|
||||
href: `/workspaces/${router.query.workspaceId}/forms/${router.query.formId}/custom/pipelines/`,
|
||||
href: `/organisations/${router.query.organisationId}/forms/${router.query.formId}/custom/pipelines/`,
|
||||
current: pathname.includes("pipelines"),
|
||||
},
|
||||
{
|
||||
name: "Summary",
|
||||
href: `/workspaces/${router.query.workspaceId}/forms/${router.query.formId}/custom/summary/`,
|
||||
href: `/organisations/${router.query.organisationId}/forms/${router.query.formId}/custom/summary/`,
|
||||
current: pathname.includes("summary"),
|
||||
},
|
||||
{
|
||||
name: "Submissions",
|
||||
href: `/workspaces/${router.query.workspaceId}/forms/${router.query.formId}/custom/submissions/`,
|
||||
href: `/organisations/${router.query.organisationId}/forms/${router.query.formId}/custom/submissions/`,
|
||||
current: pathname.includes("submissions"),
|
||||
},
|
||||
],
|
||||
|
||||
+4
-4
@@ -10,7 +10,7 @@ import { usePathname } from "next/navigation";
|
||||
import { useRouter } from "next/router";
|
||||
import { Fragment, useMemo, useState } from "react";
|
||||
|
||||
export default function LayoutWrapperWorkspace({ children }) {
|
||||
export default function LayoutWrapperOrganisation({ children }) {
|
||||
const router = useRouter();
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
@@ -18,19 +18,19 @@ export default function LayoutWrapperWorkspace({ children }) {
|
||||
() => [
|
||||
{
|
||||
name: "Forms",
|
||||
href: `/workspaces/${router.query.workspaceId}/forms`,
|
||||
href: `/organisations/${router.query.organisationId}/forms`,
|
||||
icon: FormIcon,
|
||||
current: pathname.includes("/form"),
|
||||
},
|
||||
{
|
||||
name: "Customers",
|
||||
href: `/workspaces/${router.query.workspaceId}/customers`,
|
||||
href: `/organisations/${router.query.organisationId}/customers`,
|
||||
icon: CustomersIcon,
|
||||
current: pathname.includes("/customers"),
|
||||
},
|
||||
/* {
|
||||
name: "Settings",
|
||||
href: `/workspaces/${router.query.workspaceId}/settings`,
|
||||
href: `/organisations/${router.query.organisationId}/settings`,
|
||||
icon: Cog8ToothIcon,
|
||||
current: pathname.includes("/settings"),
|
||||
}, */
|
||||
@@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import { useOrganisation } from "@/lib/organisations";
|
||||
import PricingTable from "@formbricks/ee/billing/components/PricingTable";
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const router = useRouter();
|
||||
const [loadingCustomerPortal, setLoadingCustomerPortal] = useState(false);
|
||||
const { organisation, isLoadingOrganisation, isErrorOrganisation } = useOrganisation(
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
|
||||
const openCustomerPortal = async () => {
|
||||
setLoadingCustomerPortal(true);
|
||||
const res = await fetch("/api/billing/create-customer-portal-session", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
stripeCustomerId: organisation.stripeCustomerId,
|
||||
returnUrl: `${window.location}`,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
setLoadingCustomerPortal(false);
|
||||
alert("Error loading billing portal");
|
||||
}
|
||||
const { sessionUrl } = await res.json();
|
||||
router.push(sessionUrl);
|
||||
};
|
||||
|
||||
if (isLoadingOrganisation) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorOrganisation) {
|
||||
return <div>Error loading ressources. Maybe you don‘t have enough access rights</div>;
|
||||
}
|
||||
return (
|
||||
<div className="mx-auto py-8 sm:px-6 lg:px-8">
|
||||
<header className="mb-8">
|
||||
<h1 className="text-3xl font-bold leading-tight tracking-tight text-gray-900">
|
||||
Billing
|
||||
<span className="text-brand-dark ml-4 inline-flex items-center rounded-md border border-teal-100 bg-teal-50 px-2.5 py-0.5 text-sm font-medium">
|
||||
{organisation.name}
|
||||
</span>
|
||||
</h1>
|
||||
</header>
|
||||
{organisation.plan === "free" ? (
|
||||
<>
|
||||
<div className="my-6 sm:flex-auto">
|
||||
<h1 className="text-xl font-semibold text-gray-900">Upgrade to benefit from all features</h1>
|
||||
<p className="mt-2 text-sm text-gray-700">
|
||||
You do not currently have an active subscription. Upgrade to get access to all features and
|
||||
improve your user research.
|
||||
</p>
|
||||
</div>
|
||||
<div className="overflow-hidden rounded-lg">
|
||||
<PricingTable organisationId={organisation.id} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="my-6 sm:flex-auto">
|
||||
<h1 className="text-xl font-semibold text-gray-900">View and manage your billing details</h1>
|
||||
<p className="mt-2 text-sm text-gray-700">
|
||||
View and edit your billing details, as well as cancel your subscription.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => openCustomerPortal()} loading={loadingCustomerPortal}>
|
||||
Billing Portal
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import { useWorkspace } from "@/lib/workspaces";
|
||||
import { useOrganisation } from "@/lib/organisations";
|
||||
import { InformationCircleIcon } from "@heroicons/react/20/solid";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const router = useRouter();
|
||||
const { workspace, isLoadingWorkspace, isErrorWorkspace } = useWorkspace(
|
||||
router.query.workspaceId?.toString()
|
||||
const { organisation, isLoadingOrganisation, isErrorOrganisation } = useOrganisation(
|
||||
router.query.organisationId?.toString()
|
||||
);
|
||||
|
||||
if (isLoadingWorkspace) {
|
||||
if (isLoadingOrganisation) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
@@ -19,7 +19,7 @@ export default function SettingsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (isErrorWorkspace) {
|
||||
if (isErrorOrganisation) {
|
||||
return <div>Error loading ressources. Maybe you don‘t have enough access rights</div>;
|
||||
}
|
||||
return (
|
||||
@@ -28,7 +28,7 @@ export default function SettingsPage() {
|
||||
<h1 className="text-3xl font-bold leading-tight tracking-tight text-gray-900">
|
||||
Settings
|
||||
<span className="text-brand-dark ml-4 inline-flex items-center rounded-md border border-teal-100 bg-teal-50 px-2.5 py-0.5 text-sm font-medium">
|
||||
{workspace.name}
|
||||
{organisation.name}
|
||||
</span>
|
||||
</h1>
|
||||
</header>
|
||||
@@ -39,7 +39,7 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p className="text-sm text-teal-700">
|
||||
Workspace Management Settings coming to Formbricks HQ soon.
|
||||
Organisation Management Settings coming to Formbricks HQ soon.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createHash } from "crypto";
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { unstable_getServerSession } from "next-auth";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
export const hashApiKey = (key: string): string => createHash("sha256").update(key).digest("hex");
|
||||
|
||||
@@ -29,7 +29,7 @@ export const hasOwnership = async (model, session, id) => {
|
||||
|
||||
export const getSessionOrUser = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
// check for session (browser usage)
|
||||
let session: any = await unstable_getServerSession(req, res, authOptions);
|
||||
let session: any = await getServerSession(req, res, authOptions);
|
||||
if (session && "user" in session) return session.user;
|
||||
// check for api key
|
||||
if (req.headers["x-api-key"]) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import useSWR from "swr";
|
||||
import { fetcher } from "./utils";
|
||||
|
||||
export const useCustomers = (workspaceId: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/workspaces/${workspaceId}/customers`, fetcher);
|
||||
export const useCustomers = (organisationId: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/organisations/${organisationId}/customers`, fetcher);
|
||||
|
||||
return {
|
||||
customers: data,
|
||||
@@ -12,8 +12,11 @@ export const useCustomers = (workspaceId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useCustomer = (workspaceId: string, customerId: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/workspaces/${workspaceId}/customers/${customerId}`, fetcher);
|
||||
export const useCustomer = (organisationId: string, customerId: string) => {
|
||||
const { data, error, mutate } = useSWR(
|
||||
`/api/organisations/${organisationId}/customers/${customerId}`,
|
||||
fetcher
|
||||
);
|
||||
|
||||
return {
|
||||
customer: data,
|
||||
@@ -23,9 +26,9 @@ export const useCustomer = (workspaceId: string, customerId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteCustomer = async (id: string, workspaceId: string) => {
|
||||
export const deleteCustomer = async (id: string, organisationId: string) => {
|
||||
try {
|
||||
await fetch(`/api/workspaces/${workspaceId}/customers/${id}`, {
|
||||
await fetch(`/api/organisations/${organisationId}/customers/${id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -45,7 +45,7 @@ export const sendVerificationEmail = async (user) => {
|
||||
The link is valid for one day. If it has expired please request a new token here:<br/>
|
||||
<a href="${verificationRequestLink}">${verificationRequestLink}</a><br/>
|
||||
<br/>
|
||||
Your Formbricks Workspace`,
|
||||
Your Formbricks Organisation`,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -66,7 +66,7 @@ export const sendForgotPasswordEmail = async (user) => {
|
||||
<br/>
|
||||
Your password won't change until you access the link above and create a new one.<br/>
|
||||
<br/>
|
||||
Your Formbricks Workspace`,
|
||||
Your Formbricks Organisation`,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -76,14 +76,14 @@ export const sendPasswordResetNotifyEmail = async (user) => {
|
||||
subject: "Your Formbricks password has been changed",
|
||||
html: `We're contacting you to notify you that your password has been changed.<br/>
|
||||
<br/>
|
||||
Your Formbricks Workspace`,
|
||||
Your Formbricks Organisation`,
|
||||
});
|
||||
};
|
||||
|
||||
export const sendSubmissionEmail = async (
|
||||
email: string,
|
||||
event: "created" | "updated" | "finished",
|
||||
workspaceId,
|
||||
organisationId,
|
||||
formId,
|
||||
formLabel: string,
|
||||
schema: any,
|
||||
@@ -120,7 +120,7 @@ export const sendSubmissionEmail = async (
|
||||
|
||||
Click <a href="${
|
||||
process.env.NEXTAUTH_URL
|
||||
}/workspaces/${workspaceId}/forms/${formId}/feedback">here</a> to see the submission.
|
||||
}/organisations/${organisationId}/forms/${formId}/feedback">here</a> to see the submission.
|
||||
${submission.customerEmail ? "<hr/>You can reply to this email to contact the user directly." : ""}`,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import useSWR from "swr";
|
||||
import { fetcher } from "./utils";
|
||||
|
||||
export const useForms = (workspaceId: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/workspaces/${workspaceId}/forms`, fetcher);
|
||||
export const useForms = (organisationId: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/organisations/${organisationId}/forms`, fetcher);
|
||||
|
||||
return {
|
||||
forms: data,
|
||||
@@ -12,8 +12,8 @@ export const useForms = (workspaceId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useForm = (id: string, workspaceId: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/workspaces/${workspaceId}/forms/${id}`, fetcher);
|
||||
export const useForm = (id: string, organisationId: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/organisations/${organisationId}/forms/${id}`, fetcher);
|
||||
|
||||
return {
|
||||
form: data,
|
||||
@@ -25,7 +25,7 @@ export const useForm = (id: string, workspaceId: string) => {
|
||||
|
||||
export const persistForm = async (form) => {
|
||||
try {
|
||||
await fetch(`/api/workspaces/${form.workspaceId}/forms/${form.id}/`, {
|
||||
await fetch(`/api/organisations/${form.organisationId}/forms/${form.id}/`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(form),
|
||||
@@ -35,9 +35,9 @@ export const persistForm = async (form) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const createForm = async (workspaceId: string, form = {}) => {
|
||||
export const createForm = async (organisationId: string, form = {}) => {
|
||||
try {
|
||||
const res = await fetch(`/api/workspaces/${workspaceId}/forms`, {
|
||||
const res = await fetch(`/api/organisations/${organisationId}/forms`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(form),
|
||||
@@ -49,9 +49,9 @@ export const createForm = async (workspaceId: string, form = {}) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteForm = async (workspaceId: string, formId: string) => {
|
||||
export const deleteForm = async (organisationId: string, formId: string) => {
|
||||
try {
|
||||
await fetch(`/api/workspaces/${workspaceId}/forms/${formId}`, {
|
||||
await fetch(`/api/organisations/${organisationId}/forms/${formId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import useSWR from "swr";
|
||||
import { fetcher } from "./utils";
|
||||
|
||||
export const useOrganisation = (id: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/organisations/${id}/`, fetcher);
|
||||
|
||||
return {
|
||||
organisation: data,
|
||||
isLoadingOrganisation: !error && !data,
|
||||
isErrorOrganisation: error,
|
||||
mutateOrganisation: mutate,
|
||||
};
|
||||
};
|
||||
@@ -2,8 +2,11 @@ import useSWR from "swr";
|
||||
|
||||
import { fetcher } from "./utils";
|
||||
|
||||
export const usePipelines = (formId: string, workspaceId: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/workspaces/${workspaceId}/forms/${formId}/pipelines`, fetcher);
|
||||
export const usePipelines = (formId: string, organisationId: string) => {
|
||||
const { data, error, mutate } = useSWR(
|
||||
`/api/organisations/${organisationId}/forms/${formId}/pipelines`,
|
||||
fetcher
|
||||
);
|
||||
|
||||
return {
|
||||
pipelines: data,
|
||||
@@ -13,9 +16,9 @@ export const usePipelines = (formId: string, workspaceId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const usePipeline = (workspaceId: string, formId: string, pipelineId: string) => {
|
||||
export const usePipeline = (organisationId: string, formId: string, pipelineId: string) => {
|
||||
const { data, error, mutate } = useSWR(
|
||||
`/api/workspaces/${workspaceId}/forms/${formId}/pipelines/${pipelineId}`,
|
||||
`/api/organisations/${organisationId}/forms/${formId}/pipelines/${pipelineId}`,
|
||||
fetcher
|
||||
);
|
||||
|
||||
@@ -27,9 +30,9 @@ export const usePipeline = (workspaceId: string, formId: string, pipelineId: str
|
||||
};
|
||||
};
|
||||
|
||||
export const persistPipeline = async (formId, workspaceId, pipeline) => {
|
||||
export const persistPipeline = async (formId, organisationId, pipeline) => {
|
||||
try {
|
||||
await fetch(`/api/workspaces/${workspaceId}/forms/${formId}/pipelines/${pipeline.id}/`, {
|
||||
await fetch(`/api/organisations/${organisationId}/forms/${formId}/pipelines/${pipeline.id}/`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(pipeline),
|
||||
@@ -39,9 +42,9 @@ export const persistPipeline = async (formId, workspaceId, pipeline) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const createPipeline = async (formId: string, workspaceId: string, pipeline = {}) => {
|
||||
export const createPipeline = async (formId: string, organisationId: string, pipeline = {}) => {
|
||||
try {
|
||||
const res = await fetch(`/api/workspaces/${workspaceId}/forms/${formId}/pipelines`, {
|
||||
const res = await fetch(`/api/organisations/${organisationId}/forms/${formId}/pipelines`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(pipeline),
|
||||
@@ -53,9 +56,9 @@ export const createPipeline = async (formId: string, workspaceId: string, pipeli
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePipeline = async (formId: string, workspaceId: string, pipelineId: string) => {
|
||||
export const deletePipeline = async (formId: string, organisationId: string, pipelineId: string) => {
|
||||
try {
|
||||
await fetch(`/api/workspaces/${workspaceId}/forms/${formId}/pipelines/${pipelineId}`, {
|
||||
await fetch(`/api/organisations/${organisationId}/forms/${formId}/pipelines/${pipelineId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -66,7 +66,7 @@ async function handleEmailNotification(triggeredEvents, pipeline, form, submissi
|
||||
await sendSubmissionEmail(
|
||||
email,
|
||||
"created",
|
||||
form.workspaceId,
|
||||
form.organisationId,
|
||||
form.id,
|
||||
form.label,
|
||||
form.schema,
|
||||
@@ -77,7 +77,7 @@ async function handleEmailNotification(triggeredEvents, pipeline, form, submissi
|
||||
await sendSubmissionEmail(
|
||||
email,
|
||||
"updated",
|
||||
form.workspaceId,
|
||||
form.organisationId,
|
||||
form.id,
|
||||
form.label,
|
||||
form.schema,
|
||||
@@ -88,7 +88,7 @@ async function handleEmailNotification(triggeredEvents, pipeline, form, submissi
|
||||
await sendSubmissionEmail(
|
||||
email,
|
||||
"finished",
|
||||
form.workspaceId,
|
||||
form.organisationId,
|
||||
form.id,
|
||||
form.label,
|
||||
form.schema,
|
||||
@@ -108,7 +108,7 @@ async function handleSlackNotification(triggeredEvents, pipeline, form, submissi
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `Someone just filled out your form "${form.label}". <${process.env.NEXTAUTH_URL}/workspaces/${form.workspaceId}/forms/${form.id}/feedback|View in Formbricks>`,
|
||||
text: `Someone just filled out your form "${form.label}". <${process.env.NEXTAUTH_URL}/organisations/${form.organisationId}/forms/${form.id}/feedback|View in Formbricks>`,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -132,7 +132,7 @@ async function handleSlackNotification(triggeredEvents, pipeline, form, submissi
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `Someone just updated a submission in your form "${form.label}". <${process.env.NEXTAUTH_URL}/workspaces/${form.workspaceId}/forms/${form.id}/feedback|View in Formbricks>`,
|
||||
text: `Someone just updated a submission in your form "${form.label}". <${process.env.NEXTAUTH_URL}/organisations/${form.organisationId}/forms/${form.id}/feedback|View in Formbricks>`,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -156,7 +156,7 @@ async function handleSlackNotification(triggeredEvents, pipeline, form, submissi
|
||||
type: "section",
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `Someone just finished your form "${form.label}". <${process.env.NEXTAUTH_URL}/workspaces/${form.workspaceId}/forms/${form.id}/feedback|View in Formbricks>`,
|
||||
text: `Someone just finished your form "${form.label}". <${process.env.NEXTAUTH_URL}/organisations/${form.organisationId}/forms/${form.id}/feedback|View in Formbricks>`,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import useSWR from "swr";
|
||||
import { fetcher } from "@/lib/utils";
|
||||
|
||||
export const useSubmissions = (workspaceId: string, formId: string) => {
|
||||
export const useSubmissions = (organisationId: string, formId: string) => {
|
||||
const { data, error, mutate } = useSWR(
|
||||
`/api/workspaces/${workspaceId}/forms/${formId}/submissions`,
|
||||
`/api/organisations/${organisationId}/forms/${formId}/submissions`,
|
||||
fetcher
|
||||
);
|
||||
|
||||
@@ -15,9 +15,9 @@ export const useSubmissions = (workspaceId: string, formId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteSubmission = async (workspaceId: string, formId: string, submissionId: string) => {
|
||||
export const deleteSubmission = async (organisationId: string, formId: string, submissionId: string) => {
|
||||
try {
|
||||
await fetch(`/api/workspaces/${workspaceId}/forms/${formId}/submissions/${submissionId}`, {
|
||||
await fetch(`/api/organisations/${organisationId}/forms/${formId}/submissions/${submissionId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -26,13 +26,16 @@ export const deleteSubmission = async (workspaceId: string, formId: string, subm
|
||||
}
|
||||
};
|
||||
|
||||
export const persistSubmission = async (submission, workspaceId) => {
|
||||
export const persistSubmission = async (submission, organisationId) => {
|
||||
try {
|
||||
await fetch(`/api/workspaces/${workspaceId}/forms/${submission.formId}/submissions/${submission.id}/`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(submission),
|
||||
});
|
||||
await fetch(
|
||||
`/api/organisations/${organisationId}/forms/${submission.formId}/submissions/${submission.id}/`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(submission),
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import useSWR from "swr";
|
||||
import { fetcher } from "./utils";
|
||||
|
||||
export const useWorkspace = (id: string) => {
|
||||
const { data, error, mutate } = useSWR(`/api/workspaces/${id}/`, fetcher);
|
||||
|
||||
return {
|
||||
workspace: data,
|
||||
isLoadingWorkspace: !error && !data,
|
||||
isErrorWorkspace: error,
|
||||
mutateWorkspace: mutate,
|
||||
};
|
||||
};
|
||||
@@ -226,14 +226,14 @@ export const authOptions: NextAuthOptions = {
|
||||
accounts: {
|
||||
create: [{ ...account }],
|
||||
},
|
||||
workspaces: {
|
||||
organisations: {
|
||||
create: [
|
||||
{
|
||||
accepted: true,
|
||||
role: "owner",
|
||||
workspace: {
|
||||
organisation: {
|
||||
create: {
|
||||
name: `${user.name}'s Workspace`,
|
||||
name: `${user.name}'s Organisation`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "@formbricks/ee/billing/api/create-customer-portal-session";
|
||||
@@ -0,0 +1 @@
|
||||
export { config, default } from "@formbricks/ee/billing/api/stripe-webhook";
|
||||
@@ -60,14 +60,14 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
event.data.customer = {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
email_workspaceId: {
|
||||
email_organisationId: {
|
||||
email: submission.customer.email,
|
||||
workspaceId: form.workspaceId,
|
||||
organisationId: form.organisationId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
email: customerEmail,
|
||||
workspace: { connect: { id: form.workspaceId } },
|
||||
organisation: { connect: { id: form.organisationId } },
|
||||
data: customerData,
|
||||
},
|
||||
},
|
||||
@@ -86,12 +86,12 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
await runPipelines(pipelineEvents, form, submission, submissionResult);
|
||||
// tracking
|
||||
if (submission.finished) {
|
||||
capturePosthogEvent(form.workspaceId, "submission finished", {
|
||||
capturePosthogEvent(form.organisationId, "submission finished", {
|
||||
formId,
|
||||
});
|
||||
captureTelemetry("submission finished");
|
||||
} else {
|
||||
capturePosthogEvent(form.workspaceId, "submission updated", {
|
||||
capturePosthogEvent(form.organisationId, "submission updated", {
|
||||
formId,
|
||||
});
|
||||
captureTelemetry("submission updated");
|
||||
|
||||
@@ -48,14 +48,14 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
event.data.customer = {
|
||||
connectOrCreate: {
|
||||
where: {
|
||||
email_workspaceId: {
|
||||
email_organisationId: {
|
||||
email: submission.customer.email,
|
||||
workspaceId: form.workspaceId,
|
||||
organisationId: form.organisationId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
email: customerEmail,
|
||||
workspace: { connect: { id: form.workspaceId } },
|
||||
organisation: { connect: { id: form.organisationId } },
|
||||
data: customerData,
|
||||
},
|
||||
},
|
||||
@@ -70,7 +70,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
const submissionResult = await prisma.submission.create(event);
|
||||
await runPipelines(pipelineEvents, form, submission, submissionResult);
|
||||
// tracking
|
||||
capturePosthogEvent(form.workspaceId, "submission received", {
|
||||
capturePosthogEvent(form.organisationId, "submission received", {
|
||||
formId,
|
||||
});
|
||||
captureTelemetry("submission received");
|
||||
|
||||
@@ -9,15 +9,15 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
// GET /api/workspaces
|
||||
// Get all of my workspaces
|
||||
// GET /api/organisations
|
||||
// Get all of my organisations
|
||||
if (req.method === "GET") {
|
||||
const memberships = await prisma.membership.findMany({
|
||||
where: {
|
||||
user: { email: session.email },
|
||||
},
|
||||
include: {
|
||||
workspace: true,
|
||||
organisation: true,
|
||||
},
|
||||
});
|
||||
return res.json(memberships);
|
||||
|
||||
+15
-15
@@ -9,33 +9,33 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const workspaceId = req.query.workspaceId.toString();
|
||||
const organisationId = req.query.organisationId.toString();
|
||||
|
||||
const customerEmail = req.query.customerEmail.toString();
|
||||
|
||||
// check workspace permission
|
||||
// check organisation permission
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_workspaceId: {
|
||||
userId_organisationId: {
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (membership === null) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ message: "You don't have access to this workspace or this workspace doesn't exist" });
|
||||
.json({ message: "You don't have access to this organisation or this organisation doesn't exist" });
|
||||
}
|
||||
|
||||
// GET /api/workspaces[workspaceId]/customers/[customerEmail]
|
||||
// Get a specific workspace
|
||||
// GET /api/organisations[organisationId]/customers/[customerEmail]
|
||||
// Get a specific organisation
|
||||
if (req.method === "GET") {
|
||||
const customer = await prisma.customer.findUnique({
|
||||
where: {
|
||||
email_workspaceId: {
|
||||
email_organisationId: {
|
||||
email: customerEmail,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
@@ -46,15 +46,15 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.json(customer);
|
||||
}
|
||||
|
||||
// POST /api/workspaces[workspaceId]/customer/[customerEmail]
|
||||
// POST /api/organisations[organisationId]/customer/[customerEmail]
|
||||
// Replace a specific customer
|
||||
else if (req.method === "POST") {
|
||||
const data = { ...req.body, updatedAt: new Date() };
|
||||
const prismaRes = await prisma.customer.update({
|
||||
where: {
|
||||
email_workspaceId: {
|
||||
email_organisationId: {
|
||||
email: customerEmail,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
data,
|
||||
@@ -62,14 +62,14 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.json(prismaRes);
|
||||
}
|
||||
|
||||
// Delete /api/workspaces[workspaceId]/customer/[customerEmail]
|
||||
// Delete /api/organisations[organisationId]/customer/[customerEmail]
|
||||
// Deletes a single customer
|
||||
else if (req.method === "DELETE") {
|
||||
const prismaRes = await prisma.customer.delete({
|
||||
where: {
|
||||
email_workspaceId: {
|
||||
email_organisationId: {
|
||||
email: customerEmail,
|
||||
workspaceId: workspaceId,
|
||||
organisationId: organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
+9
-9
@@ -9,30 +9,30 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const workspaceId = req.query.workspaceId.toString();
|
||||
const organisationId = req.query.organisationId.toString();
|
||||
|
||||
// check workspace permission
|
||||
// check organisation permission
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_workspaceId: {
|
||||
userId_organisationId: {
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (membership === null) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ message: "You don't have access to this workspace or this workspace doesn't exist" });
|
||||
.json({ message: "You don't have access to this organisation or this organisation doesn't exist" });
|
||||
}
|
||||
|
||||
// GET /api/workspaces[workspaceId]/customers
|
||||
// Get all customers of a specific workspace
|
||||
// GET /api/organisations[organisationId]/customers
|
||||
// Get all customers of a specific organisation
|
||||
if (req.method === "GET") {
|
||||
const forms = await prisma.customer.findMany({
|
||||
where: {
|
||||
workspace: {
|
||||
id: workspaceId,
|
||||
organisation: {
|
||||
id: organisationId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
+11
-11
@@ -10,39 +10,39 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const workspaceId = req.query.workspaceId.toString();
|
||||
const organisationId = req.query.organisationId.toString();
|
||||
|
||||
const formId = req.query.formId.toString();
|
||||
|
||||
// check workspace permission
|
||||
// check organisation permission
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_workspaceId: {
|
||||
userId_organisationId: {
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (membership === null) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ message: "You don't have access to this workspace or this workspace doesn't exist" });
|
||||
.json({ message: "You don't have access to this organisation or this organisation doesn't exist" });
|
||||
}
|
||||
|
||||
// GET /api/workspaces[workspaceId]/forms/[formId]
|
||||
// Get a specific workspace
|
||||
// GET /api/organisations[organisationId]/forms/[formId]
|
||||
// Get a specific organisation
|
||||
if (req.method === "GET") {
|
||||
const forms = await prisma.form.findFirst({
|
||||
where: {
|
||||
id: formId,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
return res.json(forms);
|
||||
}
|
||||
|
||||
// POST /api/workspaces[workspaceId]/forms/[formId]
|
||||
// POST /api/organisations[organisationId]/forms/[formId]
|
||||
// Replace a specific form
|
||||
else if (req.method === "POST") {
|
||||
const data = { ...req.body, updatedAt: new Date() };
|
||||
@@ -53,13 +53,13 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.json(prismaRes);
|
||||
}
|
||||
|
||||
// Delete /api/workspaces[workspaceId]/forms/[formId]
|
||||
// Delete /api/organisations[organisationId]/forms/[formId]
|
||||
// Deletes a single form
|
||||
else if (req.method === "DELETE") {
|
||||
const prismaRes = await prisma.form.delete({
|
||||
where: { id: formId },
|
||||
});
|
||||
capturePosthogEvent(workspaceId, "form created", {
|
||||
capturePosthogEvent(organisationId, "form created", {
|
||||
formId,
|
||||
});
|
||||
return res.json(prismaRes);
|
||||
+9
-9
@@ -10,26 +10,26 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const workspaceId = req.query.workspaceId.toString();
|
||||
const organisationId = req.query.organisationId.toString();
|
||||
const formId = req.query.formId.toString();
|
||||
const pipelineId = req.query.pipelineId.toString();
|
||||
|
||||
// check workspace permission
|
||||
// check organisation permission
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_workspaceId: {
|
||||
userId_organisationId: {
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (membership === null) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ message: "You don't have access to this workspace or this workspace doesn't exist" });
|
||||
.json({ message: "You don't have access to this organisation or this organisation doesn't exist" });
|
||||
}
|
||||
|
||||
// GET /api/workspaces[workspaceId]/forms/[formId]/pipelines/[pipelineId]
|
||||
// GET /api/organisations[organisationId]/forms/[formId]/pipelines/[pipelineId]
|
||||
// Get a specific pipeline
|
||||
if (req.method === "GET") {
|
||||
const pipeline = await prisma.pipeline.findFirst({
|
||||
@@ -42,7 +42,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.json(pipeline);
|
||||
}
|
||||
|
||||
// POST /api/workspaces[workspaceId]/forms/[formId]/pipelines/[pipelineId]
|
||||
// POST /api/organisations[organisationId]/forms/[formId]/pipelines/[pipelineId]
|
||||
// Replace a specific pipeline
|
||||
else if (req.method === "POST") {
|
||||
const data = { ...req.body, updatedAt: new Date() };
|
||||
@@ -53,13 +53,13 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.json(prismaRes);
|
||||
}
|
||||
|
||||
// Delete /api/workspaces[workspaceId]/forms/[formId]/pipelines/[pipelineId]
|
||||
// Delete /api/organisations[organisationId]/forms/[formId]/pipelines/[pipelineId]
|
||||
// Deletes a single form
|
||||
else if (req.method === "DELETE") {
|
||||
const prismaRes = await prisma.pipeline.delete({
|
||||
where: { id: pipelineId },
|
||||
});
|
||||
capturePosthogEvent(workspaceId, "pipeline deleted", {
|
||||
capturePosthogEvent(organisationId, "pipeline deleted", {
|
||||
formId,
|
||||
pipelineId,
|
||||
});
|
||||
+9
-9
@@ -10,25 +10,25 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const workspaceId = req.query.workspaceId.toString();
|
||||
const organisationId = req.query.organisationId.toString();
|
||||
const formId = req.query.formId.toString();
|
||||
|
||||
// check workspace permission
|
||||
// check organisation permission
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_workspaceId: {
|
||||
userId_organisationId: {
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (membership === null) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ message: "You don't have access to this workspace or this workspace doesn't exist" });
|
||||
.json({ message: "You don't have access to this organisation or this organisation doesn't exist" });
|
||||
}
|
||||
|
||||
// GET /api/workspaces[workspaceId]/forms/[formId]/pipelines
|
||||
// GET /api/organisations[organisationId]/forms/[formId]/pipelines
|
||||
// Get pipelines
|
||||
if (req.method === "GET") {
|
||||
// get submission
|
||||
@@ -36,7 +36,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
where: {
|
||||
form: {
|
||||
id: formId,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -44,7 +44,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.json(pipelines);
|
||||
}
|
||||
|
||||
// POST /api/workspaces[workspaceId]/forms/[formId]/pipelines
|
||||
// POST /api/organisations[organisationId]/forms/[formId]/pipelines
|
||||
// Create a new pipeline
|
||||
// Required fields in body: name, type
|
||||
// Optional fields in body: enabled, config
|
||||
@@ -58,7 +58,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
form: { connect: { id: formId } },
|
||||
},
|
||||
});
|
||||
capturePosthogEvent(workspaceId, "pipeline created", {
|
||||
capturePosthogEvent(organisationId, "pipeline created", {
|
||||
formId,
|
||||
pipelineId: result.id,
|
||||
});
|
||||
+8
-8
@@ -9,26 +9,26 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const workspaceId = req.query.workspaceId.toString();
|
||||
const organisationId = req.query.organisationId.toString();
|
||||
const formId = req.query.formId.toString();
|
||||
const submissionId = req.query.submissionId.toString();
|
||||
|
||||
// check workspace permission
|
||||
// check organisation permission
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_workspaceId: {
|
||||
userId_organisationId: {
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (membership === null) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ message: "You don't have access to this workspace or this workspace doesn't exist" });
|
||||
.json({ message: "You don't have access to this organisation or this organisation doesn't exist" });
|
||||
}
|
||||
|
||||
// GET /api/workspaces[workspaceId]/forms/[formId]/submissions/[submissionId]
|
||||
// GET /api/organisations[organisationId]/forms/[formId]/submissions/[submissionId]
|
||||
// Get a specific submission
|
||||
if (req.method === "GET") {
|
||||
const submission = await prisma.submission.findFirst({
|
||||
@@ -41,7 +41,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.json(submission);
|
||||
}
|
||||
|
||||
// POST /api/workspaces[workspaceId]/forms/[formId]/submissions/[submissionId]
|
||||
// POST /api/organisations[organisationId]/forms/[formId]/submissions/[submissionId]
|
||||
// Replace a specific submission
|
||||
else if (req.method === "POST") {
|
||||
const data = { ...req.body, updatedAt: new Date() };
|
||||
@@ -52,7 +52,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.json(prismaRes);
|
||||
}
|
||||
|
||||
// Delete /api/workspaces[workspaceId]/forms/[formId]/submissions/[submissionId]
|
||||
// Delete /api/organisations[organisationId]/forms/[formId]/submissions/[submissionId]
|
||||
// Deletes a single form
|
||||
else if (req.method === "DELETE") {
|
||||
const prismaRes = await prisma.submission.delete({
|
||||
+6
-6
@@ -9,25 +9,25 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const workspaceId = req.query.workspaceId.toString();
|
||||
const organisationId = req.query.organisationId.toString();
|
||||
const formId = req.query.formId.toString();
|
||||
|
||||
// check workspace permission
|
||||
// check organisation permission
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_workspaceId: {
|
||||
userId_organisationId: {
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (membership === null) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ message: "You don't have access to this workspace or this workspace doesn't exist" });
|
||||
.json({ message: "You don't have access to this organisation or this organisation doesn't exist" });
|
||||
}
|
||||
|
||||
// GET /api/workspaces[workspaceId]/forms/[formId]/submissions
|
||||
// GET /api/organisations[organisationId]/forms/[formId]/submissions
|
||||
// Get submissions
|
||||
if (req.method === "GET") {
|
||||
// get submission
|
||||
+12
-12
@@ -10,30 +10,30 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const workspaceId = req.query.workspaceId.toString();
|
||||
const organisationId = req.query.organisationId.toString();
|
||||
|
||||
// check workspace permission
|
||||
// check organisation permission
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_workspaceId: {
|
||||
userId_organisationId: {
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (membership === null) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ message: "You don't have access to this workspace or this workspace doesn't exist" });
|
||||
.json({ message: "You don't have access to this organisation or this organisation doesn't exist" });
|
||||
}
|
||||
|
||||
// GET /api/workspaces[workspaceId]/forms
|
||||
// Get a specific workspace
|
||||
// GET /api/organisations[organisationId]/forms
|
||||
// Get a specific organisation
|
||||
if (req.method === "GET") {
|
||||
const forms = await prisma.form.findMany({
|
||||
where: {
|
||||
workspace: {
|
||||
id: workspaceId,
|
||||
organisation: {
|
||||
id: organisationId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
@@ -46,7 +46,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.json(forms);
|
||||
}
|
||||
|
||||
// POST /api/workspaces[workspaceId]/forms
|
||||
// POST /api/organisations[organisationId]/forms
|
||||
// Create a new form
|
||||
// Required fields in body: -
|
||||
// Optional fields in body: label, schema
|
||||
@@ -57,10 +57,10 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
const result = await prisma.form.create({
|
||||
data: {
|
||||
...form,
|
||||
workspace: { connect: { id: workspaceId } },
|
||||
organisation: { connect: { id: organisationId } },
|
||||
},
|
||||
});
|
||||
capturePosthogEvent(workspaceId, "form created", {
|
||||
capturePosthogEvent(organisationId, "form created", {
|
||||
formId: result.id,
|
||||
});
|
||||
res.json(result);
|
||||
+9
-9
@@ -9,31 +9,31 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
const workspaceId = req.query.workspaceId.toString();
|
||||
const organisationId = req.query.organisationId.toString();
|
||||
|
||||
// GET /api/workspaces[workspaceId]
|
||||
// Get a specific workspace
|
||||
// GET /api/organisations[organisationId]
|
||||
// Get a specific organisation
|
||||
if (req.method === "GET") {
|
||||
// check if membership exists
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_workspaceId: {
|
||||
userId_organisationId: {
|
||||
userId: user.id,
|
||||
workspaceId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (membership === null) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ message: "You don't have access to this workspace or this workspace doesn't exist" });
|
||||
.json({ message: "You don't have access to this organisation or this organisation doesn't exist" });
|
||||
}
|
||||
const workspace = await prisma.workspace.findUnique({
|
||||
const organisation = await prisma.organisation.findUnique({
|
||||
where: {
|
||||
id: workspaceId,
|
||||
id: organisationId,
|
||||
},
|
||||
});
|
||||
return res.json(workspace);
|
||||
return res.json(organisation);
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
@@ -21,14 +21,14 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
const userData = await prisma.user.create({
|
||||
data: {
|
||||
...user,
|
||||
workspaces: {
|
||||
organisations: {
|
||||
create: [
|
||||
{
|
||||
accepted: true,
|
||||
role: "owner",
|
||||
workspace: {
|
||||
organisation: {
|
||||
create: {
|
||||
name: `${user.name}'s Workspace`,
|
||||
name: `${user.name}'s Organisation`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import { Button, Confetti } from "@formbricks/ui";
|
||||
|
||||
export default function Billing({}) {
|
||||
if (process.env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD !== "1") {
|
||||
return <div>Not available</div>;
|
||||
}
|
||||
return (
|
||||
<LayoutApp>
|
||||
<Confetti />
|
||||
<div className="mx-auto max-w-sm py-8 sm:px-6 lg:px-8">
|
||||
<div className="my-6 sm:flex-auto">
|
||||
<h1 className="text-center text-xl font-semibold text-gray-900">Upgrade successful</h1>
|
||||
<p className="mt-2 text-center text-sm text-gray-700">
|
||||
Thanks a lot for upgrading your formbricks subscription. You can now access all features and
|
||||
improve your user research.
|
||||
</p>
|
||||
</div>
|
||||
<Button className="w-full justify-center" href="/">
|
||||
Got to my forms
|
||||
</Button>
|
||||
</div>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
@@ -14,8 +14,8 @@ export default function ProjectsPage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (session && memberships && memberships.length > 0) {
|
||||
const workspaceId = memberships[0].workspaceId;
|
||||
router.push(`/workspaces/${workspaceId}/forms`);
|
||||
const organisationId = memberships[0].organisationId;
|
||||
router.push(`/organisations/${organisationId}/forms`);
|
||||
}
|
||||
if (!session) {
|
||||
router.push(`/auth/signin?callbackUrl=${encodeURIComponent(window.location.href)}`);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import ProfileSettingsPage from "@/components/me/ProfileSettingsPage";
|
||||
|
||||
export default function WorkspaceFormsPage({}) {
|
||||
export default function OrganisationFormsPage({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<ProfileSettingsPage />
|
||||
|
||||
+3
-3
@@ -2,14 +2,14 @@
|
||||
|
||||
import SingleCustomerPage from "@/components/customers/SingleCustomerPage";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperWorkspace from "@/components/layout/LayoutWrapperWorkspace";
|
||||
import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation";
|
||||
|
||||
export default function Customers({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperWorkspace>
|
||||
<LayoutWrapperOrganisation>
|
||||
<SingleCustomerPage />
|
||||
</LayoutWrapperWorkspace>
|
||||
</LayoutWrapperOrganisation>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
+3
-3
@@ -2,14 +2,14 @@
|
||||
|
||||
import CustomersPage from "@/components/customers/CustomersPage";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperWorkspace from "@/components/layout/LayoutWrapperWorkspace";
|
||||
import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation";
|
||||
|
||||
export default function Customers({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperWorkspace>
|
||||
<LayoutWrapperOrganisation>
|
||||
<CustomersPage />
|
||||
</LayoutWrapperWorkspace>
|
||||
</LayoutWrapperOrganisation>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
+3
-3
@@ -1,16 +1,16 @@
|
||||
import FormOverviewPage from "@/components/forms/custom/FormOverviewPage";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperForm from "@/components/layout/LayoutWrapperCustomForm";
|
||||
import LayoutWrapperWorkspace from "@/components/layout/LayoutWrapperWorkspace";
|
||||
import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation";
|
||||
|
||||
export default function FormOverview({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperWorkspace>
|
||||
<LayoutWrapperOrganisation>
|
||||
<LayoutWrapperForm>
|
||||
<FormOverviewPage />
|
||||
</LayoutWrapperForm>
|
||||
</LayoutWrapperWorkspace>
|
||||
</LayoutWrapperOrganisation>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
+3
-3
@@ -1,18 +1,18 @@
|
||||
import PipelinesPage from "@/components/forms/pipelines/PipelinesOverview";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperCustomForm from "@/components/layout/LayoutWrapperCustomForm";
|
||||
import LayoutWrapperWorkspace from "@/components/layout/LayoutWrapperWorkspace";
|
||||
import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation";
|
||||
|
||||
export default function Pipeline({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperWorkspace>
|
||||
<LayoutWrapperOrganisation>
|
||||
<LayoutWrapperCustomForm>
|
||||
<div className="p-5">
|
||||
<PipelinesPage />
|
||||
</div>
|
||||
</LayoutWrapperCustomForm>
|
||||
</LayoutWrapperWorkspace>
|
||||
</LayoutWrapperOrganisation>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
+3
-3
@@ -1,16 +1,16 @@
|
||||
import SubmissionsPage from "@/components/forms/submissions/SubmissionsPage";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperForm from "@/components/layout/LayoutWrapperCustomForm";
|
||||
import LayoutWrapperWorkspace from "@/components/layout/LayoutWrapperWorkspace";
|
||||
import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation";
|
||||
|
||||
export default function Submissions({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperWorkspace>
|
||||
<LayoutWrapperOrganisation>
|
||||
<LayoutWrapperForm>
|
||||
<SubmissionsPage />
|
||||
</LayoutWrapperForm>
|
||||
</LayoutWrapperWorkspace>
|
||||
</LayoutWrapperOrganisation>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
+3
-3
@@ -1,16 +1,16 @@
|
||||
import SummaryPage from "@/components/forms/summary/SummaryPage";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperForm from "@/components/layout/LayoutWrapperCustomForm";
|
||||
import LayoutWrapperWorkspace from "@/components/layout/LayoutWrapperWorkspace";
|
||||
import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation";
|
||||
|
||||
export default function Submissions({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperWorkspace>
|
||||
<LayoutWrapperOrganisation>
|
||||
<LayoutWrapperForm>
|
||||
<SummaryPage />
|
||||
</LayoutWrapperForm>
|
||||
</LayoutWrapperWorkspace>
|
||||
</LayoutWrapperOrganisation>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
+4
-4
@@ -2,14 +2,14 @@
|
||||
|
||||
import FeedbackPage from "@/components/forms/feedback/FeedbackPage";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperWorkspace from "@/components/layout/LayoutWrapperWorkspace";
|
||||
import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation";
|
||||
|
||||
export default function WorkspaceFormsPage({}) {
|
||||
export default function OrganisationFormsPage({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperWorkspace>
|
||||
<LayoutWrapperOrganisation>
|
||||
<FeedbackPage />
|
||||
</LayoutWrapperWorkspace>
|
||||
</LayoutWrapperOrganisation>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
+3
-3
@@ -1,16 +1,16 @@
|
||||
import FormOverviewPage from "@/components/forms/custom/FormOverviewPage";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperForm from "@/components/layout/LayoutWrapperCustomForm";
|
||||
import LayoutWrapperWorkspace from "@/components/layout/LayoutWrapperWorkspace";
|
||||
import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation";
|
||||
|
||||
export default function FormOverview({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperWorkspace>
|
||||
<LayoutWrapperOrganisation>
|
||||
<LayoutWrapperForm>
|
||||
<FormOverviewPage />
|
||||
</LayoutWrapperForm>
|
||||
</LayoutWrapperWorkspace>
|
||||
</LayoutWrapperOrganisation>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import PMFPage from "@/components/forms/pmf/PMFPage";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation";
|
||||
|
||||
export default function OrganisationFormsPage({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperOrganisation>
|
||||
<PMFPage />
|
||||
</LayoutWrapperOrganisation>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import FormsPage from "@/components/forms/FormsPage";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation";
|
||||
|
||||
export default function OrganisationFormsPage({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperOrganisation>
|
||||
<FormsPage />
|
||||
</LayoutWrapperOrganisation>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import BillingPage from "@/components/settings/BillingPage";
|
||||
|
||||
export default function Billing({}) {
|
||||
if (process.env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD !== "1") {
|
||||
return <div>Not available</div>;
|
||||
}
|
||||
return (
|
||||
<LayoutApp>
|
||||
<BillingPage />
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
+4
-4
@@ -1,15 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperWorkspace from "@/components/layout/LayoutWrapperWorkspace";
|
||||
import LayoutWrapperOrganisation from "@/components/layout/LayoutWrapperOrganisation";
|
||||
import SettingsPage from "@/components/settings/SettingsPage";
|
||||
|
||||
export default function WorkspaceFormsPage({}) {
|
||||
export default function OrganisationFormsPage({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperWorkspace>
|
||||
<LayoutWrapperOrganisation>
|
||||
<SettingsPage />
|
||||
</LayoutWrapperWorkspace>
|
||||
</LayoutWrapperOrganisation>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import PMFPage from "@/components/forms/pmf/PMFPage";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperWorkspace from "@/components/layout/LayoutWrapperWorkspace";
|
||||
|
||||
export default function WorkspaceFormsPage({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperWorkspace>
|
||||
<PMFPage />
|
||||
</LayoutWrapperWorkspace>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import FormsPage from "@/components/forms/FormsPage";
|
||||
import LayoutApp from "@/components/layout/LayoutApp";
|
||||
import LayoutWrapperWorkspace from "@/components/layout/LayoutWrapperWorkspace";
|
||||
|
||||
export default function WorkspaceFormsPage({}) {
|
||||
return (
|
||||
<LayoutApp>
|
||||
<LayoutWrapperWorkspace>
|
||||
<FormsPage />
|
||||
</LayoutWrapperWorkspace>
|
||||
</LayoutApp>
|
||||
);
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The primary key for the `Customer` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- The primary key for the `Membership` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- Added the required column `organisationId` to the `Customer` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `organisationId` to the `Form` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `organisationId` to the `Membership` table without a default value. This is not possible if the table is not empty.
|
||||
*/
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Customer" DROP CONSTRAINT "Customer_workspaceId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Form" DROP CONSTRAINT "Form_workspaceId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Membership" DROP CONSTRAINT "Membership_workspaceId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Submission" DROP CONSTRAINT "Submission_customerEmail_customerWorkspaceId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Customer" DROP CONSTRAINT "Customer_pkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Membership" DROP CONSTRAINT "Membership_pkey";
|
||||
|
||||
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Customer"
|
||||
RENAME COLUMN "workspaceId" TO "organisationId";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Form"
|
||||
RENAME COLUMN "workspaceId" TO "organisationId";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Membership"
|
||||
RENAME COLUMN "workspaceId" TO "organisationId";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Submission"
|
||||
RENAME COLUMN "customerWorkspaceId" TO "customerOrganisationId";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Workspace" RENAME TO "Organisation";
|
||||
|
||||
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_pkey" PRIMARY KEY ("email", "organisationId");
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Membership" ADD CONSTRAINT "Membership_pkey" PRIMARY KEY ("userId", "organisationId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Customer" ADD CONSTRAINT "Customer_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "Organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Form" ADD CONSTRAINT "Form_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "Organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Submission" ADD CONSTRAINT "Submission_customerEmail_customerOrganisationId_fkey" FOREIGN KEY ("customerEmail", "customerOrganisationId") REFERENCES "Customer"("email", "organisationId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Membership" ADD CONSTRAINT "Membership_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "Organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Organisation" RENAME CONSTRAINT "Workspace_pkey" TO "Organisation_pkey";
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Plan" AS ENUM ('free', 'pro');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Organisation" ADD COLUMN "plan" "Plan" NOT NULL DEFAULT 'free',
|
||||
ADD COLUMN "stripeCustomerId" TEXT;
|
||||
@@ -37,15 +37,15 @@ model Pipeline {
|
||||
}
|
||||
|
||||
model Customer {
|
||||
email String
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String
|
||||
submissions Submission[]
|
||||
data Json @default("{}")
|
||||
email String
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
organisationId String
|
||||
submissions Submission[]
|
||||
data Json @default("{}")
|
||||
|
||||
@@id([email, workspaceId])
|
||||
@@id([email, organisationId])
|
||||
}
|
||||
|
||||
enum FormType {
|
||||
@@ -55,41 +55,48 @@ enum FormType {
|
||||
}
|
||||
|
||||
model Form {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
type FormType
|
||||
label String
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String
|
||||
schema Json @default("{}")
|
||||
submissions Submission[]
|
||||
pipelines Pipeline[]
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
type FormType
|
||||
label String
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
organisationId String
|
||||
schema Json @default("{}")
|
||||
submissions Submission[]
|
||||
pipelines Pipeline[]
|
||||
}
|
||||
|
||||
model Submission {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
finished Boolean @default(false)
|
||||
archived Boolean @default(false)
|
||||
form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||
formId String
|
||||
customer Customer? @relation(fields: [customerEmail, customerWorkspaceId], references: [email, workspaceId])
|
||||
customerEmail String?
|
||||
customerWorkspaceId String?
|
||||
data Json @default("{}")
|
||||
meta Json @default("{}")
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
finished Boolean @default(false)
|
||||
archived Boolean @default(false)
|
||||
form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||
formId String
|
||||
customer Customer? @relation(fields: [customerEmail, customerOrganisationId], references: [email, organisationId])
|
||||
customerEmail String?
|
||||
customerOrganisationId String?
|
||||
data Json @default("{}")
|
||||
meta Json @default("{}")
|
||||
}
|
||||
|
||||
model Workspace {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
name String
|
||||
members Membership[]
|
||||
forms Form[]
|
||||
customers Customer[]
|
||||
enum Plan {
|
||||
free
|
||||
pro
|
||||
}
|
||||
|
||||
model Organisation {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
name String
|
||||
members Membership[]
|
||||
forms Form[]
|
||||
customers Customer[]
|
||||
plan Plan @default(free)
|
||||
stripeCustomerId String?
|
||||
}
|
||||
|
||||
enum MembershipRole {
|
||||
@@ -99,14 +106,14 @@ enum MembershipRole {
|
||||
}
|
||||
|
||||
model Membership {
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
accepted Boolean @default(false)
|
||||
role MembershipRole
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
organisationId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
accepted Boolean @default(false)
|
||||
role MembershipRole
|
||||
|
||||
@@id([userId, workspaceId])
|
||||
@@id([userId, organisationId])
|
||||
}
|
||||
|
||||
model ApiKey {
|
||||
@@ -154,7 +161,7 @@ model User {
|
||||
password String?
|
||||
identityProvider IdentityProvider @default(email)
|
||||
identityProviderAccountId String?
|
||||
workspaces Membership[]
|
||||
organisations Membership[]
|
||||
accounts Account[]
|
||||
apiKeys ApiKey[]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
The Formbricks.com Enterprise Edition (EE) license (the “EE License”)
|
||||
Copyright (c) 2020-present Formbricks.com
|
||||
|
||||
With regard to the Formbricks.com Software:
|
||||
|
||||
This software and associated documentation files (the "Software") may only be
|
||||
used in production, if you (and any entity that you represent) have agreed to,
|
||||
and are in compliance with, the Formbricks.com Subscription Terms of Service, available
|
||||
at https://formbricks.com/terms (the “Enterprise Terms”), or other
|
||||
agreement governing the use of the Software, as agreed by you and Formbricks.com,
|
||||
and otherwise have a valid Formbricks.com Enterprise license for the
|
||||
correct number of user seats. Subject to the foregoing sentence, you are free to
|
||||
modify this Software and publish patches to the Software. You agree that Formbricks.com
|
||||
and/or its licensors (as applicable) retain all right, title and interest in and
|
||||
to all such modifications and/or patches, and all such modifications and/or
|
||||
patches may only be used, copied, modified, displayed, distributed, or otherwise
|
||||
exploited with a valid PostHog Enterprise license for the correct
|
||||
number of user seats. Notwithstanding the foregoing, you may copy and modify
|
||||
the Software for development and testing purposes, without requiring a
|
||||
subscription. You agree that Formbricks.com and/or its licensors (as applicable) retain
|
||||
all right, title and interest in and to all such modifications. You are not
|
||||
granted any other rights beyond what is expressly stated herein. Subject to the
|
||||
foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
|
||||
and/or sell the Software.
|
||||
|
||||
This Enterprise License applies only to the part of this Software that is not distributed under
|
||||
the MIT license. Any part of this Software distributed under the MIT license or which
|
||||
is served client-side as an image, font, cascading stylesheet (CSS), file which produces
|
||||
or is compiled, arranged, augmented, or combined into client-side JavaScript, in whole or
|
||||
in part, is copyrighted under the MIT license. The full text of this Enterprise License shall
|
||||
be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
For all third party components incorporated into the Formbricks.com Software, those
|
||||
components are licensed under the original license provided by the owner of the
|
||||
applicable component.
|
||||
@@ -0,0 +1,26 @@
|
||||
//import { buffer } from "micro";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import Stripe from "stripe";
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
// https://github.com/stripe/stripe-node#configuration
|
||||
apiVersion: "2022-11-15",
|
||||
});
|
||||
|
||||
const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method === "POST") {
|
||||
const { stripeCustomerId, returnUrl } = req.body;
|
||||
// Authenticate your user.
|
||||
const session = await stripe.billingPortal.sessions.create({
|
||||
customer: stripeCustomerId,
|
||||
return_url: returnUrl,
|
||||
});
|
||||
|
||||
res.json({ sessionUrl: session.url });
|
||||
} else {
|
||||
res.setHeader("Allow", "POST");
|
||||
res.status(405).end("Method Not Allowed");
|
||||
}
|
||||
};
|
||||
|
||||
export default webhookHandler;
|
||||
@@ -0,0 +1,75 @@
|
||||
//import { buffer } from "micro";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { prisma } from "@formbricks/database";
|
||||
|
||||
import Stripe from "stripe";
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
// https://github.com/stripe/stripe-node#configuration
|
||||
apiVersion: "2022-11-15",
|
||||
});
|
||||
|
||||
const webhookSecret: string = process.env.STRIPE_WEBHOOK_SECRET!;
|
||||
|
||||
// Stripe requires the raw body to construct the event.
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
async function buffer(readable: any) {
|
||||
const chunks = [];
|
||||
for await (const chunk of readable) {
|
||||
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
||||
}
|
||||
return Buffer.concat(chunks);
|
||||
}
|
||||
|
||||
const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method === "POST") {
|
||||
const buf = await buffer(req);
|
||||
const sig = req.headers["stripe-signature"]!;
|
||||
|
||||
let event: Stripe.Event;
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(buf.toString(), sig, webhookSecret);
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
||||
// On error, log and return the error message.
|
||||
if (err! instanceof Error) console.log(err);
|
||||
console.log(`Error message: ${errorMessage}`);
|
||||
res.status(400).send(`Webhook Error: ${errorMessage}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cast event data to Stripe object.
|
||||
if (event.type === "checkout.session.completed") {
|
||||
const checkoutSession = event.data.object as Stripe.Checkout.Session;
|
||||
const organisationId = checkoutSession.client_reference_id;
|
||||
if (!organisationId) {
|
||||
console.error("No organisationId found in checkout session");
|
||||
return res.json({ message: "skipping, no organisationId found" });
|
||||
}
|
||||
const stripeCustomerId = checkoutSession.customer as string;
|
||||
const plan = "pro";
|
||||
await prisma.organisation.update({
|
||||
where: { id: organisationId },
|
||||
data: {
|
||||
stripeCustomerId,
|
||||
plan,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.warn(`🤷♀️ Unhandled event type: ${event.type}`);
|
||||
}
|
||||
|
||||
// Return a response to acknowledge receipt of the event.
|
||||
res.json({ received: true });
|
||||
} else {
|
||||
res.setHeader("Allow", "POST");
|
||||
res.status(405).end("Method Not Allowed");
|
||||
}
|
||||
};
|
||||
|
||||
export default webhookHandler;
|
||||
@@ -0,0 +1,27 @@
|
||||
import Script from "next/script";
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
["stripe-pricing-table"]: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function BillingPage({ organisationId }: { organisationId: string }) {
|
||||
if (!process.env.NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID || !process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY) {
|
||||
return <div>Stripe environment variables not set</div>;
|
||||
}
|
||||
|
||||
console.log(organisationId);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script async src="https://js.stripe.com/v3/pricing-table.js" />
|
||||
<stripe-pricing-table
|
||||
pricing-table-id={process.env.NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID}
|
||||
publishable-key={process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY}
|
||||
client-reference-id={organisationId}></stripe-pricing-table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@formbricks/ee",
|
||||
"sideEffects": false,
|
||||
"description": "Formbricks Enterprise Features",
|
||||
"authors": "Formbricks",
|
||||
"version": "1.0.0",
|
||||
"main": "index.ts",
|
||||
"devDependencies": {
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.8",
|
||||
"concurrently": "^7.5.0",
|
||||
"eslint": "^8.27.0",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"postcss": "^8.4.19",
|
||||
"react": "^18.2.0",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formbricks/database": "workspace:*",
|
||||
"next": "^13.1.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@formbricks/tsconfig/react-library.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["/*"]
|
||||
},
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
||||
@@ -35,6 +35,8 @@
|
||||
"dependencies": {
|
||||
"clsx": "^1.2.1",
|
||||
"next": "^13.0.2",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-use": "^17.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { useWindowSize } from "react-use";
|
||||
import ReactConfetti from "react-confetti";
|
||||
|
||||
export function Confetti({ colors = ["#00C4B8", "#eee"] }: { colors?: string[] }) {
|
||||
const { width, height } = useWindowSize();
|
||||
return <ReactConfetti width={width} height={height} colors={colors} numberOfPieces={400} recycle={false} />;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./Button";
|
||||
export * from "./Confetti";
|
||||
|
||||
/* Icons */
|
||||
export * from "./icons/BackIcon";
|
||||
|
||||
Generated
+266
-30
@@ -12,7 +12,7 @@ importers:
|
||||
'@changesets/cli': 2.25.0
|
||||
prettier: 2.8.3
|
||||
tsx: 3.9.0
|
||||
turbo: 1.7.0
|
||||
turbo: 1.7.2
|
||||
|
||||
apps/demo:
|
||||
specifiers:
|
||||
@@ -183,6 +183,7 @@ importers:
|
||||
specifiers:
|
||||
'@formbricks/charts': workspace:*
|
||||
'@formbricks/database': workspace:*
|
||||
'@formbricks/ee': workspace:*
|
||||
'@formbricks/react': workspace:*
|
||||
'@formbricks/tailwind-config': workspace:*
|
||||
'@formbricks/tsconfig': workspace:*
|
||||
@@ -203,6 +204,7 @@ importers:
|
||||
jsonwebtoken: ^9.0.0
|
||||
next: ^13.1.6
|
||||
next-auth: ^4.19.0
|
||||
next-transpile-modules: ^10.0.0
|
||||
nodemailer: ^6.9.1
|
||||
platform: ^1.3.6
|
||||
postcss: ^8.4.21
|
||||
@@ -212,10 +214,12 @@ importers:
|
||||
react-icons: ^4.7.1
|
||||
react-loader-spinner: ^5.3.4
|
||||
react-toastify: ^9.1.1
|
||||
stripe: ^11.8.0
|
||||
swr: ^2.0.3
|
||||
typescript: ^4.9.4
|
||||
dependencies:
|
||||
'@formbricks/charts': link:../../packages/charts
|
||||
'@formbricks/ee': link:../../packages/ee
|
||||
'@formbricks/react': link:../../packages/react
|
||||
'@formbricks/ui': link:../../packages/ui
|
||||
'@headlessui/react': 1.7.8_biqbaboplfbrettd7655fr4n2y
|
||||
@@ -228,6 +232,7 @@ importers:
|
||||
jsonwebtoken: 9.0.0
|
||||
next: 13.1.6_biqbaboplfbrettd7655fr4n2y
|
||||
next-auth: 4.19.0_phzvn2nhb6zk7o4ds4z4gvqlei
|
||||
next-transpile-modules: 10.0.0
|
||||
nodemailer: 6.9.1
|
||||
platform: 1.3.6
|
||||
prismjs: 1.29.0
|
||||
@@ -236,6 +241,7 @@ importers:
|
||||
react-icons: 4.7.1_react@18.2.0
|
||||
react-loader-spinner: 5.3.4_biqbaboplfbrettd7655fr4n2y
|
||||
react-toastify: 9.1.1_biqbaboplfbrettd7655fr4n2y
|
||||
stripe: 11.8.0
|
||||
swr: 2.0.3_react@18.2.0
|
||||
devDependencies:
|
||||
'@formbricks/database': link:../../packages/database
|
||||
@@ -304,6 +310,33 @@ importers:
|
||||
tsx: 3.12.1
|
||||
typescript: 4.8.4
|
||||
|
||||
packages/ee:
|
||||
specifiers:
|
||||
'@formbricks/database': workspace:*
|
||||
'@formbricks/tsconfig': workspace:*
|
||||
'@types/react': ^18.0.25
|
||||
'@types/react-dom': ^18.0.8
|
||||
concurrently: ^7.5.0
|
||||
eslint: ^8.27.0
|
||||
eslint-config-formbricks: workspace:*
|
||||
next: ^13.1.6
|
||||
postcss: ^8.4.19
|
||||
react: ^18.2.0
|
||||
typescript: ^4.8.4
|
||||
dependencies:
|
||||
'@formbricks/database': link:../database
|
||||
next: 13.1.6_biqbaboplfbrettd7655fr4n2y
|
||||
devDependencies:
|
||||
'@formbricks/tsconfig': link:../tsconfig
|
||||
'@types/react': 18.0.27
|
||||
'@types/react-dom': 18.0.10
|
||||
concurrently: 7.6.0
|
||||
eslint: 8.33.0
|
||||
eslint-config-formbricks: link:../eslint-config-formbricks
|
||||
postcss: 8.4.21
|
||||
react: 18.2.0
|
||||
typescript: 4.9.4
|
||||
|
||||
packages/eslint-config-formbricks:
|
||||
specifiers:
|
||||
eslint: ^8.26.0
|
||||
@@ -425,13 +458,17 @@ importers:
|
||||
next: ^13.0.2
|
||||
postcss: ^8.4.19
|
||||
react: ^18.2.0
|
||||
react-confetti: ^6.1.0
|
||||
react-dom: ^18.2.0
|
||||
react-use: ^17.4.0
|
||||
tsup: ^6.4.0
|
||||
typescript: ^4.8.4
|
||||
dependencies:
|
||||
clsx: 1.2.1
|
||||
next: 13.0.5_biqbaboplfbrettd7655fr4n2y
|
||||
react-confetti: 6.1.0_react@18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-use: 17.4.0_biqbaboplfbrettd7655fr4n2y
|
||||
devDependencies:
|
||||
'@formbricks/tsconfig': link:../tsconfig
|
||||
'@types/react': 18.0.25
|
||||
@@ -712,6 +749,20 @@ packages:
|
||||
lru-cache: 5.1.1
|
||||
semver: 6.3.0
|
||||
|
||||
/@babel/helper-compilation-targets/7.20.7_@babel+core@7.20.5:
|
||||
resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
dependencies:
|
||||
'@babel/compat-data': 7.20.5
|
||||
'@babel/core': 7.20.5
|
||||
'@babel/helper-validator-option': 7.18.6
|
||||
browserslist: 4.21.4
|
||||
lru-cache: 5.1.1
|
||||
semver: 6.3.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-create-class-features-plugin/7.20.5_@babel+core@7.20.12:
|
||||
resolution: {integrity: sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -2521,7 +2572,7 @@ packages:
|
||||
'@babel/helper-module-imports': 7.18.6
|
||||
'@babel/helper-plugin-utils': 7.20.2
|
||||
'@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.20.5
|
||||
'@babel/types': 7.20.5
|
||||
'@babel/types': 7.20.7
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-transform-react-pure-annotations/7.18.6_@babel+core@7.20.12:
|
||||
@@ -2840,7 +2891,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/compat-data': 7.20.5
|
||||
'@babel/core': 7.20.5
|
||||
'@babel/helper-compilation-targets': 7.20.0_@babel+core@7.20.5
|
||||
'@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.5
|
||||
'@babel/helper-plugin-utils': 7.20.2
|
||||
'@babel/helper-validator-option': 7.18.6
|
||||
'@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6_@babel+core@7.20.5
|
||||
@@ -2908,7 +2959,7 @@ packages:
|
||||
'@babel/plugin-transform-unicode-escapes': 7.18.10_@babel+core@7.20.5
|
||||
'@babel/plugin-transform-unicode-regex': 7.18.6_@babel+core@7.20.5
|
||||
'@babel/preset-modules': 0.1.5_@babel+core@7.20.5
|
||||
'@babel/types': 7.20.5
|
||||
'@babel/types': 7.20.7
|
||||
babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.20.5
|
||||
babel-plugin-polyfill-corejs3: 0.6.0_@babel+core@7.20.5
|
||||
babel-plugin-polyfill-regenerator: 0.4.1_@babel+core@7.20.5
|
||||
@@ -6129,6 +6180,10 @@ packages:
|
||||
'@types/istanbul-lib-report': 3.0.0
|
||||
dev: true
|
||||
|
||||
/@types/js-cookie/2.2.7:
|
||||
resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==}
|
||||
dev: false
|
||||
|
||||
/@types/json-schema/7.0.11:
|
||||
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
||||
|
||||
@@ -6617,6 +6672,10 @@ packages:
|
||||
'@xtuc/long': 4.2.2
|
||||
dev: true
|
||||
|
||||
/@xobotyi/scrollbar-width/1.9.5:
|
||||
resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==}
|
||||
dev: false
|
||||
|
||||
/@xtuc/ieee754/1.2.0:
|
||||
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
|
||||
|
||||
@@ -8532,6 +8591,12 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/copy-to-clipboard/3.3.3:
|
||||
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
|
||||
dependencies:
|
||||
toggle-selection: 1.0.6
|
||||
dev: false
|
||||
|
||||
/core-js-compat/3.26.1:
|
||||
resolution: {integrity: sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==}
|
||||
dependencies:
|
||||
@@ -8713,6 +8778,12 @@ packages:
|
||||
postcss: 8.4.21
|
||||
dev: true
|
||||
|
||||
/css-in-js-utils/3.1.0:
|
||||
resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==}
|
||||
dependencies:
|
||||
hyphenate-style-name: 1.0.4
|
||||
dev: false
|
||||
|
||||
/css-loader/3.6.0_webpack@4.46.0:
|
||||
resolution: {integrity: sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==}
|
||||
engines: {node: '>= 8.9.0'}
|
||||
@@ -8759,7 +8830,6 @@ packages:
|
||||
dependencies:
|
||||
mdn-data: 2.0.14
|
||||
source-map: 0.6.1
|
||||
dev: true
|
||||
|
||||
/css-unit-converter/1.1.2:
|
||||
resolution: {integrity: sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==}
|
||||
@@ -9472,7 +9542,6 @@ packages:
|
||||
resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==}
|
||||
dependencies:
|
||||
stackframe: 1.3.4
|
||||
dev: true
|
||||
|
||||
/es-abstract/1.20.4:
|
||||
resolution: {integrity: sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==}
|
||||
@@ -10559,6 +10628,14 @@ packages:
|
||||
/fast-levenshtein/2.0.6:
|
||||
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
||||
|
||||
/fast-loops/1.1.3:
|
||||
resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==}
|
||||
dev: false
|
||||
|
||||
/fast-shallow-equal/1.0.0:
|
||||
resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==}
|
||||
dev: false
|
||||
|
||||
/fast-url-parser/1.1.3:
|
||||
resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==}
|
||||
dependencies:
|
||||
@@ -10569,6 +10646,10 @@ packages:
|
||||
resolution: {integrity: sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw==}
|
||||
dev: true
|
||||
|
||||
/fastest-stable-stringify/2.0.2:
|
||||
resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==}
|
||||
dev: false
|
||||
|
||||
/fastq/1.13.0:
|
||||
resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
|
||||
dependencies:
|
||||
@@ -11727,6 +11808,10 @@ packages:
|
||||
engines: {node: '>=10.17.0'}
|
||||
dev: true
|
||||
|
||||
/hyphenate-style-name/1.0.4:
|
||||
resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==}
|
||||
dev: false
|
||||
|
||||
/iconv-lite/0.4.24:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -11852,6 +11937,13 @@ packages:
|
||||
/inline-style-parser/0.1.1:
|
||||
resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==}
|
||||
|
||||
/inline-style-prefixer/6.0.4:
|
||||
resolution: {integrity: sha512-FwXmZC2zbeeS7NzGjJ6pAiqRhXR0ugUShSNb6GApMl6da0/XGc4MOJsoWAywia52EEWbXNSy0pzkwz/+Y+swSg==}
|
||||
dependencies:
|
||||
css-in-js-utils: 3.1.0
|
||||
fast-loops: 1.1.3
|
||||
dev: false
|
||||
|
||||
/inquirer/7.3.3:
|
||||
resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
@@ -12542,6 +12634,10 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/js-cookie/2.2.1:
|
||||
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
|
||||
dev: false
|
||||
|
||||
/js-sdsl/4.2.0:
|
||||
resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==}
|
||||
|
||||
@@ -13323,7 +13419,6 @@ packages:
|
||||
|
||||
/mdn-data/2.0.14:
|
||||
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
|
||||
dev: true
|
||||
|
||||
/mdurl/1.0.1:
|
||||
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
|
||||
@@ -14164,6 +14259,24 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/nano-css/5.3.5_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-vSB9X12bbNu4ALBu7nigJgRViZ6ja3OU7CeuiV1zMIbXOdmkLahgtPmh3GBOlDxbKY0CitqlPdOReGlBLSp+yg==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
css-tree: 1.1.3
|
||||
csstype: 3.1.1
|
||||
fastest-stable-stringify: 2.0.2
|
||||
inline-style-prefixer: 6.0.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
rtl-css-js: 1.16.1
|
||||
sourcemap-codec: 1.4.8
|
||||
stacktrace-js: 2.0.2
|
||||
stylis: 4.1.3
|
||||
dev: false
|
||||
|
||||
/nanoid/3.3.4:
|
||||
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@@ -14264,6 +14377,13 @@ packages:
|
||||
next: 13.1.6_biqbaboplfbrettd7655fr4n2y
|
||||
dev: false
|
||||
|
||||
/next-transpile-modules/10.0.0:
|
||||
resolution: {integrity: sha512-FyeJ++Lm2Fq31gbThiRCrJlYpIY9QaI7A3TjuhQLzOix8ChQrvn5ny4MhfIthS5cy6+uK1AhDRvxVdW17y3Xdw==}
|
||||
deprecated: All features of next-transpile-modules are now natively built-in Next.js 13.1. Please use Next's transpilePackages option :)
|
||||
dependencies:
|
||||
enhanced-resolve: 5.12.0
|
||||
dev: false
|
||||
|
||||
/next/13.0.5_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-awpc3DkphyKydwCotcBnuKwh6hMqkT5xdiBK4OatJtOZurDPBYLP62jtM2be/4OunpmwIbsS0Eyv+ZGU97ciEg==}
|
||||
engines: {node: '>=14.6.0'}
|
||||
@@ -16051,7 +16171,6 @@ packages:
|
||||
engines: {node: '>=0.6'}
|
||||
dependencies:
|
||||
side-channel: 1.0.4
|
||||
dev: true
|
||||
|
||||
/querystring-es3/0.2.1:
|
||||
resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==}
|
||||
@@ -16132,6 +16251,16 @@ packages:
|
||||
minimist: 1.2.7
|
||||
strip-json-comments: 2.0.1
|
||||
|
||||
/react-confetti/6.1.0_react@18.2.0:
|
||||
resolution: {integrity: sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==}
|
||||
engines: {node: '>=10.18'}
|
||||
peerDependencies:
|
||||
react: ^16.3.0 || ^17.0.1 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
tween-functions: 1.2.0
|
||||
dev: false
|
||||
|
||||
/react-docgen-typescript/2.2.2_typescript@4.9.4:
|
||||
resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
|
||||
peerDependencies:
|
||||
@@ -16336,6 +16465,40 @@ packages:
|
||||
react-lifecycles-compat: 3.0.4
|
||||
dev: false
|
||||
|
||||
/react-universal-interface/0.6.2_react@18.2.0+tslib@2.4.1:
|
||||
resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
tslib: '*'
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/react-use/17.4.0_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-TgbNTCA33Wl7xzIJegn1HndB4qTS9u03QUwyNycUnXaweZkE4Kq2SB+Yoxx8qbshkZGYBDvUXbXWRUmQDcZZ/Q==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@types/js-cookie': 2.2.7
|
||||
'@xobotyi/scrollbar-width': 1.9.5
|
||||
copy-to-clipboard: 3.3.3
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-shallow-equal: 1.0.0
|
||||
js-cookie: 2.2.1
|
||||
nano-css: 5.3.5_biqbaboplfbrettd7655fr4n2y
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-universal-interface: 0.6.2_react@18.2.0+tslib@2.4.1
|
||||
resize-observer-polyfill: 1.5.1
|
||||
screenfull: 5.2.0
|
||||
set-harmonic-interval: 1.0.1
|
||||
throttle-debounce: 3.0.1
|
||||
ts-easing: 0.2.0
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/react/18.2.0:
|
||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -16773,6 +16936,10 @@ packages:
|
||||
/require-main-filename/2.0.0:
|
||||
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
||||
|
||||
/resize-observer-polyfill/1.5.1:
|
||||
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
|
||||
dev: false
|
||||
|
||||
/resolve-from/4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -16982,6 +17149,12 @@ packages:
|
||||
engines: {node: 6.* || >= 7.*}
|
||||
dev: true
|
||||
|
||||
/rtl-css-js/1.16.1:
|
||||
resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.6
|
||||
dev: false
|
||||
|
||||
/run-async/2.4.1:
|
||||
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
@@ -17106,6 +17279,11 @@ packages:
|
||||
ajv: 6.12.6
|
||||
ajv-keywords: 3.5.2_ajv@6.12.6
|
||||
|
||||
/screenfull/5.2.0:
|
||||
resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/semver-diff/2.1.0:
|
||||
resolution: {integrity: sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -17236,6 +17414,11 @@ packages:
|
||||
/set-blocking/2.0.0:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
|
||||
/set-harmonic-interval/1.0.1:
|
||||
resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==}
|
||||
engines: {node: '>=6.9'}
|
||||
dev: false
|
||||
|
||||
/set-value/2.0.1:
|
||||
resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -17453,6 +17636,11 @@ packages:
|
||||
deprecated: See https://github.com/lydell/source-map-url#deprecated
|
||||
dev: true
|
||||
|
||||
/source-map/0.5.6:
|
||||
resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/source-map/0.5.7:
|
||||
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -17476,7 +17664,6 @@ packages:
|
||||
/sourcemap-codec/1.4.8:
|
||||
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
|
||||
deprecated: Please use @jridgewell/sourcemap-codec instead
|
||||
dev: true
|
||||
|
||||
/space-separated-tokens/1.1.5:
|
||||
resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==}
|
||||
@@ -17552,9 +17739,29 @@ packages:
|
||||
deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
|
||||
dev: true
|
||||
|
||||
/stack-generator/2.0.10:
|
||||
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
|
||||
dependencies:
|
||||
stackframe: 1.3.4
|
||||
dev: false
|
||||
|
||||
/stackframe/1.3.4:
|
||||
resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==}
|
||||
dev: true
|
||||
|
||||
/stacktrace-gps/3.1.2:
|
||||
resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==}
|
||||
dependencies:
|
||||
source-map: 0.5.6
|
||||
stackframe: 1.3.4
|
||||
dev: false
|
||||
|
||||
/stacktrace-js/2.0.2:
|
||||
resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==}
|
||||
dependencies:
|
||||
error-stack-parser: 2.1.4
|
||||
stack-generator: 2.0.10
|
||||
stacktrace-gps: 3.1.2
|
||||
dev: false
|
||||
|
||||
/state-toggle/1.0.3:
|
||||
resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==}
|
||||
@@ -17807,6 +18014,14 @@ packages:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/stripe/11.8.0:
|
||||
resolution: {integrity: sha512-aGwrJDqYzpjQj0ejt7oN7BE7kUjZFxhUz/gDeyDCS7CBpZhDb26Eb6z9sS8KdbsbmuS8rkkn2lBY4koK7L1ZCw==}
|
||||
engines: {node: '>=12.*'}
|
||||
dependencies:
|
||||
'@types/node': 18.11.18
|
||||
qs: 6.11.0
|
||||
dev: false
|
||||
|
||||
/style-inject/0.3.0:
|
||||
resolution: {integrity: sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==}
|
||||
dev: true
|
||||
@@ -17918,6 +18133,10 @@ packages:
|
||||
postcss-selector-parser: 6.0.11
|
||||
dev: true
|
||||
|
||||
/stylis/4.1.3:
|
||||
resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==}
|
||||
dev: false
|
||||
|
||||
/sucrase/3.29.0:
|
||||
resolution: {integrity: sha512-bZPAuGA5SdFHuzqIhTAqt9fvNEo9rESqXIG3oiKdF8K4UmkQxC4KlNL3lVyAErXp+mPvUqZ5l13qx6TrDIGf3A==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -18274,6 +18493,11 @@ packages:
|
||||
any-promise: 1.3.0
|
||||
dev: true
|
||||
|
||||
/throttle-debounce/3.0.1:
|
||||
resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/through/2.3.8:
|
||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
dev: false
|
||||
@@ -18364,6 +18588,10 @@ packages:
|
||||
safe-regex: 1.1.0
|
||||
dev: true
|
||||
|
||||
/toggle-selection/1.0.6:
|
||||
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
|
||||
dev: false
|
||||
|
||||
/toidentifier/1.0.1:
|
||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||
engines: {node: '>=0.6'}
|
||||
@@ -18419,6 +18647,10 @@ packages:
|
||||
engines: {node: '>=6.10'}
|
||||
dev: true
|
||||
|
||||
/ts-easing/0.2.0:
|
||||
resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==}
|
||||
dev: false
|
||||
|
||||
/ts-interface-checker/0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
dev: true
|
||||
@@ -18662,67 +18894,71 @@ packages:
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/turbo-darwin-64/1.7.0:
|
||||
resolution: {integrity: sha512-hSGAueSf5Ko8J67mpqjpt9FsP6ePn1nMcl7IVPoJq5dHsgX3anCP/BPlexJ502bNK+87DDyhQhJ/LPSJXKrSYQ==}
|
||||
/turbo-darwin-64/1.7.2:
|
||||
resolution: {integrity: sha512-Sml3WR8MSu80W+gS8SnoKNImcDOlIX7zlvezzds65mW11yGniIFfZ18aKWGOm92Nj2SvXCQ2+UmyGghbFaHNmQ==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-darwin-arm64/1.7.0:
|
||||
resolution: {integrity: sha512-BLLOW5W6VZxk5+0ZOj5AO1qjM0P5isIgjbEuyAl8lHZ4s9antUbY4CtFrspT32XxPTYoDl4UjviPMcSsbcl3WQ==}
|
||||
/turbo-darwin-arm64/1.7.2:
|
||||
resolution: {integrity: sha512-JnlgGLScboUJGJxvmSsF+5xkImEDTMPg2FHzX4n8AMB9az9ZlPQAMtc+xu4p6Xp9eaykKiV2RG81YS3H0fxDLA==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-linux-64/1.7.0:
|
||||
resolution: {integrity: sha512-aw2qxmfZa+kT87SB3GNUoFimqEPzTlzlRqhPgHuAAT6Uf0JHnmebPt4K+ZPtDNl5yfVmtB05bhHPqw+5QV97Yg==}
|
||||
/turbo-linux-64/1.7.2:
|
||||
resolution: {integrity: sha512-vbLJw6ovG+lpiPqxniscBjljKJ2jbsHuKp8uK4j/wqgp68wAVKeAZW77GGDAUgDb88XH6Kvhh2hcizL+iWduww==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-linux-arm64/1.7.0:
|
||||
resolution: {integrity: sha512-AJEx2jX+zO5fQtJpO3r6uhTabj4oSA5ZhB7zTs/rwu/XqoydsvStA4X8NDW4poTbOjF7DcSHizqwi04tSMzpJw==}
|
||||
/turbo-linux-arm64/1.7.2:
|
||||
resolution: {integrity: sha512-zLnuS8WdHonKL74KqOopOH/leBOWumlVGF8/8hldbDPq0mwY+6myRR5/5LdveB51rkG4UJh/sQ94xV67tjBoyw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-windows-64/1.7.0:
|
||||
resolution: {integrity: sha512-ewj7PPv2uxqv0r31hgnBa3E5qwUu7eyVRP5M1gB/TJXfSHduU79gbxpKCyxIZv2fL/N2/3U7EPOQPSZxBAoljA==}
|
||||
/turbo-windows-64/1.7.2:
|
||||
resolution: {integrity: sha512-oE5PMoXjmR09okvVzteFb6FjA6yo+nMsacsgKH2yLNq4sOrVo9tG98JkRurOv5+L6ZQ3yGXPxWHiqeH7hLkAVQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-windows-arm64/1.7.0:
|
||||
resolution: {integrity: sha512-LzjOUzveWkvTD0jP8DBMYiAnYemmydsvqxdSmsUapHHJkl6wKZIOQNSO7pxsy+9XM/1/+0f9Y9F9ZNl5lePTEA==}
|
||||
/turbo-windows-arm64/1.7.2:
|
||||
resolution: {integrity: sha512-mdTUJk23acRv5qxA/yEstYhM1VFenVE3FDrssxGRFq7S80smtCGK1xUd4BEDDzDlVXOqBohmM5jRh9516rcjPQ==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo/1.7.0:
|
||||
resolution: {integrity: sha512-cwympNwQNnQZ/TffBd8yT0i0O10Cf/hlxccCYgUcwhcGEb9rDjE5thDbHoHw1hlJQUF/5ua7ERJe7Zr0lNE/ww==}
|
||||
/turbo/1.7.2:
|
||||
resolution: {integrity: sha512-YR/x3GZEx0C1RV6Yvuw/HB1Ixx3upM6ZTTa4WqKz9WtLWN8u2g+u2h5KpG5YtjCS3wl/8zVXgHf2WiMK6KIghg==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
optionalDependencies:
|
||||
turbo-darwin-64: 1.7.0
|
||||
turbo-darwin-arm64: 1.7.0
|
||||
turbo-linux-64: 1.7.0
|
||||
turbo-linux-arm64: 1.7.0
|
||||
turbo-windows-64: 1.7.0
|
||||
turbo-windows-arm64: 1.7.0
|
||||
turbo-darwin-64: 1.7.2
|
||||
turbo-darwin-arm64: 1.7.2
|
||||
turbo-linux-64: 1.7.2
|
||||
turbo-linux-arm64: 1.7.2
|
||||
turbo-windows-64: 1.7.2
|
||||
turbo-windows-arm64: 1.7.2
|
||||
dev: true
|
||||
|
||||
/tween-functions/1.2.0:
|
||||
resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==}
|
||||
dev: false
|
||||
|
||||
/type-check/0.3.2:
|
||||
resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
@@ -10,10 +10,13 @@
|
||||
"MAIL_FROM",
|
||||
"NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED",
|
||||
"NEXT_PUBLIC_GITHUB_AUTH_ENABLED",
|
||||
"NEXT_PUBLIC_IS_FORMBRICKS_CLOUD",
|
||||
"NEXT_PUBLIC_PASSWORD_RESET_DISABLED",
|
||||
"NEXT_PUBLIC_PRIVACY_URL",
|
||||
"NEXT_PUBLIC_SENTRY_DSN",
|
||||
"NEXT_PUBLIC_SIGNUP_DISABLED",
|
||||
"NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID",
|
||||
"NEXT_PUBLIC_STRIPE_PUBLIC_KEY",
|
||||
"NEXT_PUBLIC_TERMS_URL",
|
||||
"NEXTAUTH_SECRET",
|
||||
"NEXTAUTH_URL",
|
||||
|
||||
Reference in New Issue
Block a user