mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-29 18:00:26 -06:00
figma frontend pt 1
This commit is contained in:
2
.vscode/settings.json
vendored
Normal file
2
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
@@ -2,13 +2,14 @@ import Link from "next/link";
|
||||
import Router from "next/router";
|
||||
import { Fragment } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { BsFilesAlt } from "react-icons/bs";
|
||||
|
||||
import {
|
||||
DotsHorizontalIcon,
|
||||
DocumentAddIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
} from "@heroicons/react/solid";
|
||||
TerminalIcon,
|
||||
ViewGridAddIcon,
|
||||
} from "@heroicons/react/outline";
|
||||
import EmptyPageFiller from "./layout/EmptyPageFiller";
|
||||
import { DotsHorizontalIcon, TrashIcon } from "@heroicons/react/solid";
|
||||
import { classNames } from "../lib/utils";
|
||||
import { createForm, useForms } from "../lib/forms";
|
||||
import Image from "next/image";
|
||||
@@ -34,52 +35,34 @@ export default function FormList() {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const isNoCode = false;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{forms &&
|
||||
(forms.length === 0 ? (
|
||||
<div className="mt-5 text-center">
|
||||
<Image
|
||||
src="/img/mascot-face-small.png"
|
||||
height={200}
|
||||
width={200}
|
||||
alt="snoopForms Mascot"
|
||||
/>
|
||||
<hr className="mb-8 -mt-2" />
|
||||
<h1 className="text-xl font-extrabold tracking-tight text-gray-900 sm:text-2xl md:text-3xl">
|
||||
<span className="block xl:inline">Welcome to snoopForms</span>{" "}
|
||||
</h1>
|
||||
<p className="max-w-md mx-auto mt-3 text-base text-gray-500 sm:text-lg md:mt-5 md:text-lg md:max-w-3xl">
|
||||
Spin up forms in minutes. Pipe your data exactly where you need
|
||||
it. Maximize your results with juicy analytics.
|
||||
</p>
|
||||
{/* <hr className="my-8" /> */}
|
||||
<div className="max-w-md p-8 mx-auto mt-8 rounded-lg shadow-inner bg-lightgray-200">
|
||||
<BsFilesAlt className="w-12 h-12 mx-auto text-gray-400" />
|
||||
<h3 className="mt-3 text-sm font-medium text-gray-900">
|
||||
You don't have any forms yet.
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
It's time to create your first!
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => newForm()}
|
||||
className="inline-flex items-center px-4 py-2 text-sm font-medium text-white border border-transparent rounded-md shadow-sm bg-snoopred-600 hover:bg-snoopred-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-snoopred-500"
|
||||
>
|
||||
<PlusIcon className="w-5 h-5 mr-2 -ml-1" aria-hidden="true" />
|
||||
New Form
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyPageFiller
|
||||
onClick={() => newForm()}
|
||||
alertText="You don't have any forms yet."
|
||||
hintText="Start by creating a form."
|
||||
buttonText="create form"
|
||||
borderStyles="border-4 border-dotted border-snoopred"
|
||||
icon="BsFilePlus"
|
||||
>
|
||||
<DocumentAddIcon className="w-24 h-24 mx-auto text-lightgray-700 stroke-thin" />
|
||||
</EmptyPageFiller>
|
||||
</div>
|
||||
) : (
|
||||
<ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
||||
<ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 place-content-stretch">
|
||||
<button onClick={() => newForm()}>
|
||||
<li className="col-span-1">
|
||||
<div className="overflow-hidden text-white rounded-lg shadow bg-snoopred">
|
||||
<div className="px-4 py-8 sm:p-10">+ New Form</div>
|
||||
<div className="overflow-hidden text-white font-light rounded-md shadow bg-snoopfade">
|
||||
<div className="px-4 py-8 sm:p-14">
|
||||
<PlusIcon className="w-14 h-14 mx-auto stroke-thin" />
|
||||
create form
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</button>
|
||||
@@ -87,71 +70,92 @@ export default function FormList() {
|
||||
.sort((a, b) => b.updatedAt - a.updatedAt)
|
||||
.map((form, formIdx) => (
|
||||
<li key={form.id} className="col-span-1 ">
|
||||
<div className="bg-white divide-y rounded-lg shadow divide-lightgray-200">
|
||||
<div className="flex flex-col justify-between bg-white rounded-md shadow h-full">
|
||||
<Link href={`/forms/${form.id}`}>
|
||||
<a>
|
||||
<div className="px-4 py-5 sm:p-6">{form.name}</div>
|
||||
<div className="px-4 py-5 sm:p-6 text-lg">
|
||||
{form.name}
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
<div className="px-4 py-1 text-right sm:px-6">
|
||||
<Menu
|
||||
as="div"
|
||||
className="relative inline-block text-left"
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div>
|
||||
<Menu.Button className="flex items-center p-2 -m-2 rounded-full text-darkgray-400 hover:text-darkgray-500-600 focus:outline-none">
|
||||
<span className="sr-only">Open options</span>
|
||||
<DotsHorizontalIcon
|
||||
className="w-5 h-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<div className="divide-y divide-lightgray-200 ">
|
||||
<div className="inline-flex text-sm ml-4 px-2 py-1 mb-2 rounded-sm bg-lightgray-400 text-darkgray-700">
|
||||
{isNoCode ? (
|
||||
<div className="flex">
|
||||
<ViewGridAddIcon className="w-4 h-4 mr-1 my-auto" />
|
||||
No-Code
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex">
|
||||
<TerminalIcon className="w-4 h-4 mr-1 my-auto" />
|
||||
Code
|
||||
</div>
|
||||
)}{" "}
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
static
|
||||
className="absolute left-0 w-56 mt-2 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
<div className="flex justify-between px-4 py-2 text-right sm:px-6">
|
||||
<p className="text-xs text-lightgray-900 ">
|
||||
0 responses
|
||||
</p>
|
||||
<Menu
|
||||
as="div"
|
||||
className="relative inline-block text-left"
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div>
|
||||
<Menu.Button className="flex items-center p-2 -m-2 rounded-full text-snoopred">
|
||||
<span className="sr-only">Open options</span>
|
||||
<DotsHorizontalIcon
|
||||
className="w-5 h-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
onClick={() =>
|
||||
deleteForm(form, formIdx)
|
||||
}
|
||||
className={classNames(
|
||||
active
|
||||
? "bg-lightgray-100 text-darkgray-700"
|
||||
: "text-darkgray-500",
|
||||
"flex px-4 py-2 text-sm w-full"
|
||||
)}
|
||||
>
|
||||
<TrashIcon
|
||||
className="w-5 h-5 mr-3 text-darkgray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>Delete Form</span>
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
<Menu.Items
|
||||
static
|
||||
className="absolute left-0 w-56 mt-2 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
>
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
onClick={() =>
|
||||
deleteForm(form, formIdx)
|
||||
}
|
||||
className={classNames(
|
||||
active
|
||||
? "bg-lightgray-100 text-darkgray-700"
|
||||
: "text-darkgray-500",
|
||||
"flex px-4 py-2 text-sm w-full"
|
||||
)}
|
||||
>
|
||||
<TrashIcon
|
||||
className="w-5 h-5 mr-3 text-darkgray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>Delete Form</span>
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
34
components/StandardButton.tsx
Normal file
34
components/StandardButton.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
fullwidth?: boolean;
|
||||
}
|
||||
|
||||
// button component, consuming props
|
||||
const Button: React.FC<Props> = ({
|
||||
children,
|
||||
onClick,
|
||||
disabled,
|
||||
fullwidth,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
className={
|
||||
`inline-flex items-center px-4 py-2 text-sm text-white border border-transparent rounded shadow-sm bg-snoopfade focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-snoopred-500 ` +
|
||||
(disabled ? " disabled" : "") +
|
||||
(fullwidth ? " w-full justify-center " : "")
|
||||
}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
@@ -1,24 +1,27 @@
|
||||
/* This example requires Tailwind CSS v2.0+ */
|
||||
import { Dialog, RadioGroup, Transition } from "@headlessui/react";
|
||||
import { CheckCircleIcon, LightBulbIcon } from "@heroicons/react/solid";
|
||||
import { CheckCircleIcon, XIcon } from "@heroicons/react/solid";
|
||||
import { useRouter } from "next/router";
|
||||
import { Fragment, useState } from "react";
|
||||
import { persistForm, useForm } from "../../lib/forms";
|
||||
import { createNoCodeForm } from "../../lib/noCodeForm";
|
||||
import { classNames } from "../../lib/utils";
|
||||
import Loading from "../Loading";
|
||||
import Button from "../StandardButton.tsx";
|
||||
import { BsPlus } from "react-icons/bs";
|
||||
|
||||
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.",
|
||||
"Use the Notion-like 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.",
|
||||
description:
|
||||
"Use the snoopReact library to code the form yourself and manage the data here.",
|
||||
additionalDescription: "",
|
||||
},
|
||||
];
|
||||
@@ -76,7 +79,7 @@ export default function FormOnboardingModal({
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Dialog.Overlay className="fixed inset-0 transition-opacity bg-darkgray-500 bg-opacity-10 backdrop-blur" />
|
||||
<Dialog.Overlay className="fixed inset-0 transition-opacity bg-darkgray-300 bg-opacity-10 backdrop-blur" />
|
||||
</Transition.Child>
|
||||
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
@@ -97,34 +100,26 @@ 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-xl 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-md shadow-xl sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center justify-center w-12 h-12 mx-auto rounded-full bg-snoopred-100">
|
||||
<LightBulbIcon
|
||||
className="w-6 h-6 text-snoopred"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3 text-center sm:mt-5">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-darkgray-700"
|
||||
>
|
||||
Welcome to your new form
|
||||
</Dialog.Title>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between">
|
||||
<h2 className="flex-none text-darkgray-700 text-xl font-bold pb-4">
|
||||
Create new form
|
||||
</h2>
|
||||
<XIcon className="flex-initial w-6 h-6 text-gray-200 stroke-1" />
|
||||
</div>
|
||||
<hr className="my-4" />
|
||||
<div>
|
||||
<label htmlFor="email" className="text-sm text-darkgray-500">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="text-sm font-light text-darkgray-700"
|
||||
>
|
||||
Name your form
|
||||
</label>
|
||||
<div className="mt-2">
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
className="block w-full p-2 mb-8 rounded-lg focus:ring-2 focus:ring-snoopred sm:text-sm"
|
||||
className="block w-full p-2 mb-8 rounded border-none focus:ring-2 focus:ring-snoopred sm:text-sm bg-gray-100 placeholder:font-extralight placeholder:text-darkgray-200"
|
||||
placeholder="e.g. Customer Research Survey"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
@@ -132,10 +127,10 @@ export default function FormOnboardingModal({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <hr className="my-5" /> */}
|
||||
|
||||
<RadioGroup value={formType} onChange={setFormType}>
|
||||
<RadioGroup.Label className="text-sm text-darkgray-500">
|
||||
How would you like to build your form?
|
||||
<RadioGroup.Label className="text-sm font-light text-darkgray-700">
|
||||
How do you build your form?
|
||||
</RadioGroup.Label>
|
||||
|
||||
<div className="grid grid-cols-1 mt-4 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
|
||||
@@ -149,7 +144,7 @@ export default function FormOnboardingModal({
|
||||
? "border-transparent"
|
||||
: "border-lightgray-300",
|
||||
active ? "border-snoopred ring-2 ring-snoopred" : "",
|
||||
"relative bg-white border rounded-lg shadow-sm p-4 flex cursor-pointer focus:outline-none"
|
||||
"relative bg-white border rounded shadow-sm p-4 flex cursor-pointer focus:outline-none"
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -159,13 +154,13 @@ export default function FormOnboardingModal({
|
||||
<span className="flex flex-col">
|
||||
<RadioGroup.Label
|
||||
as="span"
|
||||
className="block font-medium text-center text-md text-darkgray-900"
|
||||
className="block font-bold text-md text-darkgray-700"
|
||||
>
|
||||
{formType.title}
|
||||
</RadioGroup.Label>
|
||||
<RadioGroup.Description
|
||||
as="span"
|
||||
className="flex items-center mt-1 text-xs text-center whitespace-pre-wrap text-darkgray-500"
|
||||
className="flex items-center mt-1 text-xs whitespace-pre-wrap text-darkgray-500"
|
||||
>
|
||||
{formType.description}
|
||||
</RadioGroup.Description>
|
||||
@@ -173,18 +168,25 @@ export default function FormOnboardingModal({
|
||||
</span>
|
||||
<CheckCircleIcon
|
||||
className={classNames(
|
||||
!checked ? "invisible" : "",
|
||||
!checked ? "hidden" : "",
|
||||
"h-5 w-5 text-snoopred"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
checked ? "hidden" : "",
|
||||
"h-4 w-4 rounded-full border-2"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
className={classNames(
|
||||
active ? "border" : "border-2",
|
||||
checked
|
||||
? "border-snoopred"
|
||||
: "border-transparent",
|
||||
"absolute -inset-px rounded-lg pointer-events-none"
|
||||
"absolute -inset-px rounded pointer-events-none"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
@@ -195,12 +197,10 @@ export default function FormOnboardingModal({
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div className="mt-5 sm:mt-6">
|
||||
<button
|
||||
type="submit"
|
||||
className="inline-flex justify-center w-full px-4 py-2 text-base font-medium text-white border border-transparent rounded-md shadow-sm bg-snoopred hover:bg-snoopred-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-snoopred sm:text-sm"
|
||||
>
|
||||
Create form
|
||||
</button>
|
||||
<Button type="submit" buttonText="create form" fullwidth>
|
||||
create form
|
||||
<BsPlus className="w-6 h-6 ml-1"></BsPlus>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Transition.Child>
|
||||
|
||||
40
components/layout/EmptyPageFiller.tsx
Normal file
40
components/layout/EmptyPageFiller.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
import Button from "../StandardButton.tsx";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
onClick: () => void;
|
||||
alertText: string;
|
||||
hintText: string;
|
||||
buttonText: string;
|
||||
borderStyles: string;
|
||||
}
|
||||
|
||||
const EmptyPageFiller: React.FC<Props> = ({
|
||||
children,
|
||||
onClick,
|
||||
alertText,
|
||||
hintText,
|
||||
buttonText,
|
||||
borderStyles,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
`bg-white max-w-md p-8 mx-auto mt-8 rounded-lg ` + borderStyles
|
||||
}
|
||||
>
|
||||
{children}
|
||||
<h3 className="mt-5 text-base font-bold text-lightgray-700">
|
||||
{alertText}
|
||||
</h3>
|
||||
<p className="mt-1 text-xs font-light text-lightgray-700">{hintText}</p>
|
||||
<div className="mt-6">
|
||||
<Button onClick={onClick}>{buttonText}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyPageFiller;
|
||||
@@ -23,7 +23,7 @@ export default function Layout({ children }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-lightgray-100">
|
||||
<div className="min-h-screen bg-bggray">
|
||||
<Disclosure as="nav" className="bg-white shadow-sm">
|
||||
{({ open }) => (
|
||||
<>
|
||||
|
||||
@@ -90,3 +90,14 @@
|
||||
.cdx-block {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Recurring Tailwind Classes */
|
||||
|
||||
@layer components {
|
||||
.bg-snoopfade {
|
||||
@apply bg-gradient-to-br from-snoopred to-pink-500 hover:to-snoopred;
|
||||
}
|
||||
.text-snoopfade {
|
||||
@apply text-transparent bg-clip-text bg-gradient-to-br from-snoopred to-pink-500 hover:to-snoopred;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,24 @@ module.exports = {
|
||||
"./components/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
borderRadius: {
|
||||
none: "0",
|
||||
sm: "0.25rem", // 4px
|
||||
DEFAULT: "0.5rem", //8px
|
||||
DEFAULT: "8px",
|
||||
md: "1rem", // 16px
|
||||
lg: "1.5rem", // 24px
|
||||
xl: "2rem", // 32px
|
||||
"2xl": "2.5rem", // 40px
|
||||
"3xl": "3rem",
|
||||
"4xl": "3.5rem",
|
||||
full: "9999px",
|
||||
},
|
||||
extend: {
|
||||
strokeWidth: {
|
||||
thin: "0.5px",
|
||||
thinner: "0.25px",
|
||||
},
|
||||
colors: {
|
||||
snoopred: {
|
||||
DEFAULT: "#f53b57",
|
||||
@@ -175,6 +192,9 @@ module.exports = {
|
||||
800: "#e1b50c",
|
||||
900: "#d7ab02",
|
||||
},
|
||||
bggray: {
|
||||
DEFAULT: "#FAFAFB",
|
||||
},
|
||||
lightgray: {
|
||||
50: "#ffffff",
|
||||
100: "#faffff",
|
||||
|
||||
Reference in New Issue
Block a user