mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-25 18:48:58 -06:00
fix login error when database not reachable, add form & pipeline pages, add code/nocode option to onboarding
This commit is contained in:
@@ -7,12 +7,12 @@ import { DotsHorizontalIcon, TrashIcon } from "@heroicons/react/solid";
|
||||
import { classNames } from "../lib/utils";
|
||||
import { createForm, useForms } from "../lib/forms";
|
||||
|
||||
export default function FormList({}) {
|
||||
export default function FormList() {
|
||||
const { forms, mutateForms } = useForms();
|
||||
|
||||
const newForm = async () => {
|
||||
const form = await createForm();
|
||||
await Router.push(`/forms/${form.id}/results`);
|
||||
await Router.push(`/forms/${form.id}/form`);
|
||||
};
|
||||
|
||||
const deleteForm = async (form, formIdx) => {
|
||||
@@ -44,9 +44,9 @@ export default function FormList({}) {
|
||||
.map((form, formIdx) => (
|
||||
<li key={form.id} className="col-span-1 ">
|
||||
<div className="bg-white divide-y divide-gray-200 rounded-lg shadow">
|
||||
<Link href={`/forms/${form.id}/results`}>
|
||||
<Link href={`/forms/${form.id}/form`}>
|
||||
<a>
|
||||
<div className="px-4 py-5 sm:p-6">{form.title}</div>
|
||||
<div className="px-4 py-5 sm:p-6">{form.name}</div>
|
||||
</a>
|
||||
</Link>
|
||||
<div className="px-4 py-1 text-right sm:px-6">
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
/* This example requires Tailwind CSS v2.0+ */
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Dialog, RadioGroup, Transition } from "@headlessui/react";
|
||||
import { LightBulbIcon } from "@heroicons/react/outline";
|
||||
import { CheckCircleIcon } from "@heroicons/react/solid";
|
||||
import { Fragment, useState } from "react";
|
||||
import { persistForm, useForm } from "../../lib/forms";
|
||||
import { classNames } from "../../lib/utils";
|
||||
import Loading from "../Loading";
|
||||
|
||||
const formTypes = [
|
||||
{
|
||||
id: "NOCODE",
|
||||
title: "No-Code Builder",
|
||||
description:
|
||||
"Use our Notion-like form builder to build your form without a single line of code",
|
||||
},
|
||||
{
|
||||
id: "CODE",
|
||||
title: "Code",
|
||||
description: "Use our snoopReact library to code the form yourself\n",
|
||||
additionalDescription: "(VueJS and other framework support coming soon)",
|
||||
},
|
||||
];
|
||||
|
||||
type FormOnboardingModalProps = {
|
||||
open: boolean;
|
||||
setOpen: (o: boolean) => void;
|
||||
@@ -18,10 +35,16 @@ export default function FormOnboardingModal({
|
||||
}: FormOnboardingModalProps) {
|
||||
const { form, mutateForm, isLoadingForm } = useForm(formId);
|
||||
const [name, setName] = useState(form.name);
|
||||
const [formType, setFormType] = useState(formTypes[0]);
|
||||
|
||||
const submitForm = async (e) => {
|
||||
e.preventDefault();
|
||||
const updatedForm = { ...form, name, finishedOnboarding: true };
|
||||
const updatedForm = {
|
||||
...form,
|
||||
name,
|
||||
finishedOnboarding: true,
|
||||
formType: formType.id,
|
||||
};
|
||||
await persistForm(updatedForm);
|
||||
mutateForm(updatedForm);
|
||||
setOpen(false);
|
||||
@@ -50,7 +73,7 @@ export default function FormOnboardingModal({
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Dialog.Overlay className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" />
|
||||
<Dialog.Overlay className="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75 backdrop-blur" />
|
||||
</Transition.Child>
|
||||
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
@@ -71,7 +94,7 @@ export default function FormOnboardingModal({
|
||||
>
|
||||
<form
|
||||
onSubmit={(e) => submitForm(e)}
|
||||
className="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white rounded-lg shadow-xl sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6"
|
||||
className="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white rounded-lg shadow-xl sm:my-8 sm:align-middle sm:max-w-xl sm:w-full sm:p-6"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center justify-center w-12 h-12 mx-auto bg-red-100 rounded-full">
|
||||
@@ -89,7 +112,8 @@ export default function FormOnboardingModal({
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
Please choose a name and then we are ready to start.
|
||||
Please choose a name and a building approach and then we
|
||||
are ready to start.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,6 +138,70 @@ export default function FormOnboardingModal({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="my-5" />
|
||||
<RadioGroup value={formType} onChange={setFormType}>
|
||||
<RadioGroup.Label className="text-base font-medium text-gray-900">
|
||||
How would you like to build your form?
|
||||
</RadioGroup.Label>
|
||||
|
||||
<div className="grid grid-cols-1 mt-4 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
|
||||
{formTypes.map((formType) => (
|
||||
<RadioGroup.Option
|
||||
key={formType.id}
|
||||
value={formType}
|
||||
className={({ checked, active }) =>
|
||||
classNames(
|
||||
checked ? "border-transparent" : "border-gray-300",
|
||||
active ? "border-red-500 ring-2 ring-red-500" : "",
|
||||
"relative bg-white border rounded-lg shadow-sm p-4 flex cursor-pointer focus:outline-none"
|
||||
)
|
||||
}
|
||||
>
|
||||
{({ checked, active }) => (
|
||||
<>
|
||||
<span className="flex flex-1">
|
||||
<span className="flex flex-col">
|
||||
<RadioGroup.Label
|
||||
as="span"
|
||||
className="block text-sm font-medium text-gray-900"
|
||||
>
|
||||
{formType.title}
|
||||
</RadioGroup.Label>
|
||||
<RadioGroup.Description
|
||||
as="span"
|
||||
className="flex items-center mt-1 text-sm text-gray-500 whitespace-pre-wrap"
|
||||
>
|
||||
{formType.description}
|
||||
</RadioGroup.Description>
|
||||
<RadioGroup.Description
|
||||
as="span"
|
||||
className="flex items-center mt-1 text-xs text-gray-400 whitespace-pre-wrap"
|
||||
>
|
||||
{formType.additionalDescription}
|
||||
</RadioGroup.Description>
|
||||
</span>
|
||||
</span>
|
||||
<CheckCircleIcon
|
||||
className={classNames(
|
||||
!checked ? "invisible" : "",
|
||||
"h-5 w-5 text-red-600"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
className={classNames(
|
||||
active ? "border" : "border-2",
|
||||
checked ? "border-red-500" : "border-transparent",
|
||||
"absolute -inset-px rounded-lg pointer-events-none"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div className="mt-5 sm:mt-6">
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { TwitterPicker } from "react-color";
|
||||
|
||||
export default function ManageLayout({ survey, setSurvey, persistSurvey }) {
|
||||
const setSurveyAttribute = async (attribute, value) => {
|
||||
const updatedSurvey = { ...survey };
|
||||
updatedSurvey[attribute] = value;
|
||||
setSurvey(updatedSurvey);
|
||||
await persistSurvey(updatedSurvey);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-5 my-4 overflow-hidden overflow-y-auto bg-white rounded-lg shadow">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium leading-6 text-gray-900">
|
||||
Color Theme
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Choose the colors for your survey.
|
||||
</p>
|
||||
</div>
|
||||
<div className="my-6">
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Primary Color
|
||||
</label>
|
||||
<TwitterPicker
|
||||
color={survey.colorPrimary}
|
||||
onChangeComplete={(color) =>
|
||||
setSurveyAttribute("colorPrimary", color.hex)
|
||||
}
|
||||
triangle={"hide"}
|
||||
className="my-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import MenuProfile from "./MenuProfile";
|
||||
import { signIn, useSession } from "next-auth/react";
|
||||
import Loading from "../Loading";
|
||||
|
||||
export default function LayoutShare({ title, formId, children }) {
|
||||
export default function LayoutShare({ title, formId, currentStep, children }) {
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
if (status === "loading") {
|
||||
@@ -28,7 +28,7 @@ export default function LayoutShare({ title, formId, children }) {
|
||||
<div className="relative z-10 flex flex-shrink-0 h-16 bg-white border-b border-gray-200 shadow-sm">
|
||||
<div className="flex flex-1 px-4 sm:px-6">
|
||||
<MenuBreadcrumbs formId={formId} />
|
||||
<MenuSteps formId={formId} currentStep="results" />
|
||||
<MenuSteps formId={formId} currentStep={currentStep} />
|
||||
<div className="flex items-center justify-end flex-1 space-x-2 text-right sm:ml-6 sm:space-x-4">
|
||||
{/* Profile dropdown */}
|
||||
<MenuProfile />
|
||||
|
||||
@@ -12,6 +12,16 @@ export default function MenuSteps({ formId, currentStep }: MenuStepsProps) {
|
||||
const { form, isLoadingForm } = useForm(formId);
|
||||
const router = useRouter();
|
||||
const tabs = [
|
||||
{
|
||||
name: "Form",
|
||||
id: "form",
|
||||
href: `/forms/${form.id}/form`,
|
||||
},
|
||||
{
|
||||
name: "Pipelines",
|
||||
id: "pipelines",
|
||||
href: `/forms/${form.id}/pipelines`,
|
||||
},
|
||||
{
|
||||
name: "Results",
|
||||
id: "results",
|
||||
@@ -31,7 +41,7 @@ export default function MenuSteps({ formId, currentStep }: MenuStepsProps) {
|
||||
<select
|
||||
id="tabs"
|
||||
name="tabs"
|
||||
className="block w-full py-2 pl-3 pr-10 text-base border-gray-300 rounded-md focus:outline-none focus:ring-pink-500 focus:border-pink-500 sm:text-sm"
|
||||
className="block w-full py-2 pl-3 pr-10 text-base border-gray-300 rounded-md focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm"
|
||||
defaultValue={tabs.find((tab) => tab.id === currentStep).name}
|
||||
onChange={(e) => {
|
||||
const stepId = e.target.children[e.target.selectedIndex].id;
|
||||
@@ -52,7 +62,7 @@ export default function MenuSteps({ formId, currentStep }: MenuStepsProps) {
|
||||
<a
|
||||
className={classNames(
|
||||
tab.id === currentStep
|
||||
? "border-pink-500 text-pink-600"
|
||||
? "border-red-500 text-red-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
|
||||
"whitespace-nowrap py-5 px-1 border-b-2 font-medium text-sm"
|
||||
)}
|
||||
|
||||
@@ -27,12 +27,17 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
|
||||
},
|
||||
},
|
||||
async authorize(credentials, _req) {
|
||||
// Add logic here to look up the user from the credentials supplied
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
email: credentials?.email,
|
||||
},
|
||||
});
|
||||
let user;
|
||||
try {
|
||||
user = await prisma.user.findUnique({
|
||||
where: {
|
||||
email: credentials?.email,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw Error("Internal server error. Please try again later");
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User not found");
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function SignIn({ csrfToken }: props) {
|
||||
An error occurred when logging you in
|
||||
</h3>
|
||||
<div className="mt-2 text-sm text-red-700">
|
||||
<p className="space-y-1">{error}</p>
|
||||
<p className="space-y-1 whitespace-pre-wrap">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
46
pages/forms/[id]/form.tsx
Normal file
46
pages/forms/[id]/form.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { GetServerSideProps } from "next";
|
||||
import { getSession } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import FormOnboardingModal from "../../../components/build/FormOnboardingModal";
|
||||
import LayoutResults from "../../../components/layout/LayoutResults";
|
||||
import Loading from "../../../components/Loading";
|
||||
import { useForm } from "../../../lib/forms";
|
||||
|
||||
export default function FormPage() {
|
||||
const router = useRouter();
|
||||
const formId = router.query.id.toString();
|
||||
const { form, isLoadingForm } = useForm(router.query.id);
|
||||
const [openOnboardingModal, setOpenOnboardingModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (form && !form.finishedOnboarding) {
|
||||
setOpenOnboardingModal(true);
|
||||
}
|
||||
}, [isLoadingForm]);
|
||||
|
||||
if (isLoadingForm) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<LayoutResults title={form.title} formId={formId} currentStep="form">
|
||||
<div>Form</div>
|
||||
<FormOnboardingModal
|
||||
open={openOnboardingModal}
|
||||
setOpen={setOpenOnboardingModal}
|
||||
formId={formId}
|
||||
/>
|
||||
</LayoutResults>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
|
||||
const session = await getSession({ req });
|
||||
if (!session) {
|
||||
res.statusCode = 403;
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
32
pages/forms/[id]/pipelines.tsx
Normal file
32
pages/forms/[id]/pipelines.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { GetServerSideProps } from "next";
|
||||
import { getSession } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import LayoutResults from "../../../components/layout/LayoutResults";
|
||||
import Loading from "../../../components/Loading";
|
||||
import { useForm } from "../../../lib/forms";
|
||||
|
||||
export default function PipelinesPage() {
|
||||
const router = useRouter();
|
||||
const formId = router.query.id.toString();
|
||||
const { form, isLoadingForm } = useForm(router.query.id);
|
||||
|
||||
if (isLoadingForm) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<LayoutResults title={form.title} formId={formId} currentStep="pipelines">
|
||||
<div>Pipelines</div>
|
||||
</LayoutResults>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
|
||||
const session = await getSession({ req });
|
||||
if (!session) {
|
||||
res.statusCode = 403;
|
||||
}
|
||||
return { props: {} };
|
||||
};
|
||||
@@ -1,30 +1,19 @@
|
||||
import { GetServerSideProps } from "next";
|
||||
import { getSession } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import FormOnboardingModal from "../../../components/build/FormOnboardingModal";
|
||||
import LayoutResults from "../../../components/layout/LayoutResults";
|
||||
import Loading from "../../../components/Loading";
|
||||
import Submission from "../../../components/results/Submission";
|
||||
import { useForm } from "../../../lib/forms";
|
||||
import { useAnswerSessions } from "../../../lib/submissionSessions";
|
||||
|
||||
type ShareProps = {};
|
||||
|
||||
export default function Share({}: ShareProps) {
|
||||
export default function Share() {
|
||||
const router = useRouter();
|
||||
const formId = router.query.id.toString();
|
||||
const { form, isLoadingForm } = useForm(router.query.id);
|
||||
const { submissionSessions, isLoadingAnswerSessions } = useAnswerSessions(
|
||||
form?.id
|
||||
);
|
||||
const [openOnboardingModal, setOpenOnboardingModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (form && !form.finishedOnboarding) {
|
||||
setOpenOnboardingModal(true);
|
||||
}
|
||||
}, [isLoadingForm]);
|
||||
|
||||
if (isLoadingForm || isLoadingAnswerSessions) {
|
||||
return <Loading />;
|
||||
@@ -32,7 +21,7 @@ export default function Share({}: ShareProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LayoutResults title={form.title} formId={formId}>
|
||||
<LayoutResults title={form.title} formId={formId} currentStep="results">
|
||||
<div className="bg-white shadow sm:rounded-lg">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<h3 className="text-lg font-medium leading-6 text-gray-900">
|
||||
@@ -50,11 +39,6 @@ export default function Share({}: ShareProps) {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<FormOnboardingModal
|
||||
open={openOnboardingModal}
|
||||
setOpen={setOpenOnboardingModal}
|
||||
formId={formId}
|
||||
/>
|
||||
</LayoutResults>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user