update layout approach to have a unified look

This commit is contained in:
Matthias Nannt
2022-06-24 21:26:39 +09:00
parent 8648d0bb12
commit e760cdf29d
39 changed files with 1258 additions and 1312 deletions

View File

@@ -37,7 +37,7 @@ export default function FormList() {
return (
<>
<div>
<div className="h-full px-6 py-8 lg:px-8">
{forms &&
(forms.length === 0 ? (
<div className="mt-5 text-center">
@@ -53,7 +53,7 @@ export default function FormList() {
</EmptyPageFiller>
</div>
) : (
<ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 place-content-stretch">
<ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 place-content-stretch ">
<button onClick={() => newForm()}>
<li className="col-span-1">
<div className="overflow-hidden font-light text-white rounded-md shadow bg-snoopfade">
@@ -67,7 +67,7 @@ export default function FormList() {
{forms
.sort((a, b) => b.updatedAt - a.updatedAt)
.map((form, formIdx) => (
<li key={form.id} className="relative col-span-1 realative ">
<li key={form.id} className="relative col-span-1 ">
<div className="flex flex-col justify-between h-full bg-white rounded-md shadow">
<div className="px-4 py-5 text-lg sm:p-6">
{form.name}

View File

@@ -1,23 +1,23 @@
import {
DocumentAddIcon,
EyeIcon,
PaperAirplaneIcon,
ShareIcon,
} from "@heroicons/react/outline";
import { NoCodeForm } from "@prisma/client";
import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";
import { toast } from "react-toastify";
import { v4 as uuidv4 } from "uuid";
import { useForm } from "../../lib/forms";
import { persistNoCodeForm, useNoCodeForm } from "../../lib/noCodeForm";
import SecondNavBar from "../layout/SecondNavBar";
import Loading from "../Loading";
import Page from "./Page";
import ShareModal from "./ShareModal";
import SecondNavBar from "../layout/SecondNavBar";
import SecondNavBarItem from "../layout/SecondNavBarItem";
import {
DocumentAddIcon,
PlusIcon,
EyeIcon,
ShareIcon,
PaperAirplaneIcon,
} from "@heroicons/react/outline";
export default function Builder({ formId }) {
const router = useRouter();
const { form, isLoadingForm } = useForm(formId);
const { noCodeForm, isLoadingNoCodeForm, mutateNoCodeForm } =
useNoCodeForm(formId);
@@ -123,31 +123,39 @@ export default function Builder({ formId }) {
return <Loading />;
}
const noCodeSecondNavigation = [
{
id: "addPage",
onClick: () => addPage(),
Icon: DocumentAddIcon,
//Icon: PlusIcon
label: "Page",
},
{
id: "preview",
onClick: () => {
router.push(`/forms/${formId}/preview`);
},
Icon: EyeIcon,
label: "Preview",
},
{
id: "publish",
onClick: () => publishChanges(),
Icon: PaperAirplaneIcon,
label: "Publish",
},
{
id: "share",
onClick: () => setOpenShareModal(true),
Icon: ShareIcon,
label: "Share",
},
];
return (
<>
<SecondNavBar>
<SecondNavBarItem>
<PlusIcon className="w-8 h-8 mx-auto stroke-1" />
Element
</SecondNavBarItem>
<SecondNavBarItem onClick={() => addPage()}>
<DocumentAddIcon className="w-8 h-8 mx-auto stroke-1" />
Page
</SecondNavBarItem>
<SecondNavBarItem link href={`/forms/${formId}/preview`}>
<EyeIcon className="w-8 h-8 mx-auto stroke-1" />
Preview
</SecondNavBarItem>
<SecondNavBarItem onClick={() => publishChanges()}>
<PaperAirplaneIcon className="w-8 h-8 mx-auto stroke-1" />
Publish
</SecondNavBarItem>
<SecondNavBarItem onClick={() => setOpenShareModal(true)}>
<ShareIcon className="w-8 h-8 mx-auto stroke-1" />
Share
</SecondNavBarItem>
</SecondNavBar>
<SecondNavBar navItems={noCodeSecondNavigation} />
<div className="w-full bg-ui-gray-lighter">
<div className="flex justify-center w-full">
<div className="grid w-full grid-cols-1">

View File

@@ -29,10 +29,13 @@ export default function PageToolbar({
page.type === "thankyou"
? "bg-red-400 text-white hover:bg-red-500"
: "bg-white text-gray-400 hover:bg-gray-50",
"relative inline-flex items-center px-4 py-2 text-sm font-medium border border-gray-300 rounded-l-md focus:z-10 focus:outline-none focus:ring-1 focus:ring-red-500 focus:border-red-500"
"has-tooltip relative inline-flex items-center px-4 py-2 text-sm font-medium border border-gray-300 rounded-l-md focus:z-10 focus:outline-none focus:ring-1 focus:ring-red-500 focus:border-red-500"
)}
>
<span className="sr-only">Annotate</span>
<span className="sr-only">Thank You Page</span>
<span className="w-32 p-1 -mt-16 -ml-10 text-xs text-white bg-gray-600 rounded shadow-lg tooltip">
Is Thank You Page
</span>
<MdWavingHand className="w-4 h-4" aria-hidden="true" />
</button>
<button
@@ -42,9 +45,12 @@ export default function PageToolbar({
deletePageAction(pageIdx);
}
}}
className="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-400 bg-white border border-gray-300 rounded-r-md hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-red-500 focus:border-red-500"
className="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-400 bg-white border border-gray-300 has-tooltip rounded-r-md hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-red-500 focus:border-red-500"
>
<span className="sr-only">Delete</span>
<span className="w-24 p-1 -mt-16 -ml-8 text-xs text-white bg-gray-600 rounded shadow-lg tooltip">
Delete Page
</span>
<TrashIcon className="w-4 h-4" aria-hidden="true" />
</button>
</span>

View File

@@ -1,36 +0,0 @@
/* This example requires Tailwind CSS v2.0+ */
import { InformationCircleIcon } from "@heroicons/react/solid";
import { useState } from "react";
export default function UsageIntro() {
const [dismissed, setDismissed] = useState(false);
return (
!dismissed && (
<div className="p-4 border border-gray-700 rounded-md bg-ui-gray-light">
<div className="flex">
<div className="flex-shrink-0">
<InformationCircleIcon
className="w-5 h-5 text-blue-400"
aria-hidden="true"
/>
</div>
<div className="flex-1 ml-3 md:flex md:justify-between">
<p className="text-sm text-gray-700">
Welcome to the snoopForms No-Code Editor. Use &apos;tab&apos; to
add new blocks or change their options. You can also drag &apos;n
drop blocks to reorder them.
</p>
<p className="mt-3 text-sm md:mt-0 md:ml-6">
<a
onClick={() => setDismissed(true)}
className="font-medium text-gray-700 whitespace-nowrap hover:text-gray-600"
>
Dismiss
</a>
</p>
</div>
</div>
</div>
)
);
}

View File

@@ -1,17 +1,16 @@
import { DocumentSearchIcon } from "@heroicons/react/outline";
import Link from "next/link";
import { FaReact, FaVuejs } from "react-icons/fa";
import { DocumentSearchIcon, TerminalIcon } from "@heroicons/react/outline";
import { toast } from "react-toastify";
import { classNames } from "../../lib/utils";
import StandardButton from "../StandardButton";
import Link from "next/link";
import SecondNavBar from "../layout/SecondNavBar";
import SecondNavBarItem from "../layout/SecondNavBarItem";
export default function FormCode({ formId }) {
const libs = [
{
id: "react",
name: "React",
href: `forms/${formId}/react`,
href: `/forms/${formId}/form/react`,
bgColor: "bg-blue",
version: "v0.1",
icon: FaReact,
@@ -38,169 +37,153 @@ export default function FormCode({ formId }) {
href: "https://docs.snoopforms.com",
bgColor: "bg-ui-gray-dark",
icon: DocumentSearchIcon,
target: "_blank",
},
];
return (
<>
<SecondNavBar>
<SecondNavBarItem link href={`/forms/${formId}/form`}>
<TerminalIcon className="w-8 h-8 mx-auto stroke-1" />
formID
</SecondNavBarItem>
<SecondNavBarItem link href={`/forms/${formId}/react`}>
<FaReact className="w-8 h-8 mx-auto stroke-1" />
React
</SecondNavBarItem>
<SecondNavBarItem disabled>
<FaReact className="w-8 h-8 mx-auto stroke-1" />
React Native
</SecondNavBarItem>
<SecondNavBarItem disabled>
<FaVuejs className="w-8 h-8 mx-auto stroke-1" />
Vue
</SecondNavBarItem>
<SecondNavBarItem link outbound href="https://docs.snoopforms.com">
<DocumentSearchIcon className="w-8 h-8 mx-auto stroke-1" />
Docs
</SecondNavBarItem>
</SecondNavBar>
<header>
<div className="max-w-5xl">
<div className="mx-auto mt-8">
<h1 className="text-3xl font-bold leading-tight text-ui-gray-dark">
Connect your form
</h1>
<div className="mx-auto mt-8">
<h1 className="text-3xl font-bold leading-tight text-ui-gray-dark">
Connect your form
</h1>
</div>
<div className="mt-4 mb-12">
<p className="text-ui-gray-dark">
To send all form submissions to this dashboard, update the form ID in
the <code>{"<snoopForm>"}</code> component.
</p>
</div>
<div className="grid grid-cols-2 gap-10">
<div>
<label htmlFor="formId" className="block text-base text-ui-gray-dark">
Your form ID
</label>
<div className="mt-3">
<input
id="formId"
type="text"
className="w-full mb-3 border-gray-300 rounded-sm shadow-sm text-md disabled:bg-gray-100"
value={formId}
disabled
/>
<StandardButton
onClick={() => {
navigator.clipboard.writeText(formId);
toast("Copied form ID to clipboard");
}}
fullwidth
>
copy
</StandardButton>
</div>
</div>
</header>
<div className="max-w-5xl">
<div className="p-8 font-light text-gray-200 bg-black rounded-md">
<p>
<code>
{"<"}
<span className="text-yellow-200">SnoopForm</span>
{""}
</code>
</p>
<p>
<code>{`domain="${window?.location.host}"`}</code>
</p>
<p>
<code>{`protocol="${window?.location.protocol.replace(
":",
""
)}"`}</code>
</p>
<p>
<code>{`formId=${formId}`}</code>
</p>
<p>
<code>{">"}</code>
</p>
<p>
<code>
<span className="text-gray-600">{`{...}`}</span>
</code>
</p>
<code>
{"</"}
<span className="text-yellow-200">SnoopForm</span>
{">"}
</code>
</div>
</div>
<div className="mt-16">
<h2 className="text-xl font-bold text-ui-gray-dark">Code your form</h2>
<div className="mt-4 mb-12">
<p className="text-ui-gray-dark">
To send all form submissions to this dashboard, update the form ID
in the <code>{"<snoopForm>"}</code> component.
Build your form with the code library of your choice. Manage your
data in this dashboard.
</p>
</div>
<div className="grid grid-cols-2 gap-10">
<div>
<label
htmlFor="formId"
className="block text-base text-ui-gray-dark"
<ul
role="list"
className="grid grid-cols-1 gap-5 mt-3 sm:gap-6 sm:grid-cols-2"
>
{libs.map((lib) => (
<a
className="flex col-span-1 rounded-md shadow-sm"
key={lib.id}
href={lib.href}
target={lib.target || ""}
rel="noreferrer"
>
Your form ID
</label>
<div className="mt-3">
<input
id="formId"
type="text"
className="w-full mb-3 text-lg font-bold border-gray-300 rounded-sm shadow-sm disabled:bg-gray-100"
value={formId}
disabled
/>
<StandardButton
onClick={() => {
navigator.clipboard.writeText(formId);
}}
fullwidth
<li
className={classNames(
lib.comingSoon
? "text-ui-gray-medium"
: "shadow-sm text-ui-gray-dark hover:text-black",
"flex col-span-1 rounded-md w-full"
)}
>
copy
</StandardButton>
</div>
</div>
<div className="p-8 font-light text-gray-200 bg-black rounded-md">
<p>
<code>
{"<"}
<span className="text-yellow-200">snoopForm</span>
{""}
</code>
</p>
<p>
<code>{'domain="localhost:3000"'}</code>
</p>
<p>
<code>{'protocol="http"'}</code>
</p>
<p>
<code className="font-bold text-red">{'formId="luHwCdbz"'}</code>
</p>
<p>
<code>{"onSubmit={({ submission, schema }) =>{}/>"}</code>
</p>
</div>
</div>
<div className="mt-16">
<h2 className="text-xl font-bold text-ui-gray-dark">
Code your form
</h2>
<div className="mt-4 mb-12">
<p className="text-ui-gray-dark">
Build your form with the code library of your choice. Manage your
data in this dashboard.
</p>
</div>
<ul
role="list"
className="grid grid-cols-1 gap-5 mt-3 sm:gap-6 sm:grid-cols-2"
>
{libs.map((lib) => (
<a
className="flex col-span-1 rounded-md shadow-sm"
key={lib.id}
href={lib.href}
>
<li
<div
className={classNames(
lib.comingSoon
? "text-ui-gray-medium"
: "shadow-sm text-ui-gray-dark hover:text-black",
"flex col-span-1 rounded-md w-full"
lib.bgColor,
"flex-shrink-0 flex items-center justify-center w-20 text-white text-sm font-medium rounded-l-md"
)}
>
<div
<lib.icon
className={classNames(
lib.bgColor,
"flex-shrink-0 flex items-center justify-center w-20 text-white text-sm font-medium rounded-md"
lib.comingSoon
? "text-ui-gray-medium"
: "text-white stroke-1",
"w-10 h-10"
)}
>
<lib.icon
className={classNames(
lib.comingSoon
? "text-ui-gray-medium"
: "text-white stroke-1",
"w-12 h-12"
)}
/>
</div>
<div
className={classNames(
lib.comingSoon ? "border-dashed" : "",
"flex items-center justify-between flex-1 truncate bg-white rounded-r-md"
/>
</div>
<div
className={classNames(
lib.comingSoon ? "border-dashed" : "",
"flex items-center justify-between flex-1 truncate bg-white rounded-r-md"
)}
>
<div className="inline-flex px-4 py-6 text-lg truncate">
<p className="font-light">{lib.name}</p>
{lib.comingSoon && (
<div className="p-1 px-3 ml-3 bg-green-100 rounded">
<p className="text-xs text-black">coming soon</p>
</div>
)}
>
<div className="inline-flex px-4 py-8 text-lg truncate">
<p className="font-light">{lib.name}</p>
{lib.comingSoon && (
<div className="p-1 px-3 ml-3 bg-green-100 rounded">
<p className="text-xs text-black">coming soon</p>
</div>
)}
</div>
</div>
</li>
</a>
))}
</ul>
</div>
</li>
</a>
))}
</ul>
<div className="my-12 font-light text-center text-ui-gray-medium">
<p>
Your form is running? Go to{" "}
<Link href={`/forms/${formId}/preview`}>
<a className="underline text-red">Pipelines</a>
</Link>
</p>
</div>
<div className="my-12 font-light text-center text-ui-gray-medium">
<p>
Your form is running? Go to{" "}
<Link href={`/forms/${formId}/preview`}>
<a className="underline text-red">Pipelines</a>
</Link>
</p>
</div>
</div>
</>

View File

@@ -1,45 +0,0 @@
import React from "react";
import { QuestionMarkCircleIcon } from "@heroicons/react/outline";
import { classNames } from "../../lib/utils";
interface Props {
KPI: string | number;
typeText?: boolean;
label: string;
toolTipText: string;
trend: number;
}
const AnalyticsCard: React.FC<Props> = ({
KPI,
typeText = false,
label,
toolTipText,
trend,
}) => {
return (
<div className="grid content-between px-4 py-2 bg-white rounded-md shadow-md">
<div className="inline-flex items-center text-sm text-ui-gray-dark">
{label}{" "}
{toolTipText && (
<QuestionMarkCircleIcon className="w-4 h-4 ml-1 text-red hover:text-ui-gray-dark" />
)}
</div>
<div
className={classNames(
`font-bold leading-none flex justify-between items-end`,
typeText ? "text-3xl tracking-tight leading-10" : "text-7xl"
)}
>
{KPI}
{trend && (
<div className="flex items-center h-6 px-6 py-2 mb-2.5 text-sm font-light text-green-600 bg-green-200 rounded-full">
{trend} %
</div>
)}
</div>
</div>
);
};
export default AnalyticsCard;

View File

@@ -0,0 +1,73 @@
import { signIn, useSession } from "next-auth/react";
import Head from "next/head";
import { classNames } from "../../lib/utils";
import Loading from "../Loading";
import MenuBreadcrumbs from "./MenuBreadcrumbs";
import MenuProfile from "./MenuProfile";
import MenuSteps from "./MenuSteps";
import NewFormNavButton from "./NewFormNavButton";
interface BaseLayoutAuthorizedProps {
title: string;
breadcrumbs: any;
steps?: any;
currentStep?: string;
children: React.ReactNode;
limitHeightScreen?: boolean;
}
export default function BaseLayoutAuthorized({
title,
breadcrumbs,
steps,
currentStep,
children,
limitHeightScreen = false,
}: BaseLayoutAuthorizedProps) {
const { data: session, status } = useSession();
if (status === "loading") {
return <Loading />;
}
if (!session) {
signIn();
return <div>You need to be authenticated to view this page.</div>;
}
return (
<>
<Head>
<title>{title}</title>
</Head>
<div
className={classNames(
limitHeightScreen ? "h-screen" : "min-h-screen",
"flex bg-ui-gray-lighter h-full"
)}
>
<div className="flex flex-col flex-1 h-full">
<header className="w-full">
<div className="relative z-10 flex flex-shrink-0 h-16 bg-white border-b shadow-sm border-ui-gray-light">
<div className="flex justify-between flex-1">
<div className="inline-flex flex-1 gap-8">
<NewFormNavButton />
<MenuBreadcrumbs breadcrumbs={breadcrumbs} />
</div>
<div className="flex flex-1">
{steps && (
<MenuSteps steps={steps} currentStep={currentStep} />
)}
</div>
<div className="flex items-center justify-end flex-1 mr-4 space-x-2 text-right sm:ml-6 sm:space-x-4">
<MenuProfile />
</div>
</div>
</div>
</header>
{children}
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,9 @@
interface Props {
children?: React.ReactNode;
}
const FullWidth: React.FC<Props> = ({ children }) => {
return <main className="w-full h-full">{children}</main>;
};
export default FullWidth;

View File

@@ -1,136 +0,0 @@
/* This example requires Tailwind CSS v2.0+ */
import { Fragment } from "react";
import Image from "next/image";
import { Disclosure, Menu, Transition } from "@headlessui/react";
import { MenuIcon, XIcon } from "@heroicons/react/outline";
import { signIn, signOut, useSession } from "next-auth/react";
import Loading from "../Loading";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
export default function Layout({ children }) {
const { data: session, status } = useSession();
if (status === "loading") {
return <Loading />;
}
if (!session) {
signIn();
return <div>You need to be authenticated to view this page.</div>;
}
return (
<div className="min-h-screen bg-white">
<Disclosure as="nav" className="bg-white shadow-sm">
{({ open }) => (
<>
<div className="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex">
<div className="flex items-center flex-shrink-0 w-48">
<Image
src="/img/snoopforms-logo.svg"
alt="snoopForms logo"
width={300}
height={53}
/>
</div>
</div>
<div className="hidden sm:ml-6 sm:flex sm:items-center">
{/* Profile dropdown */}
<Menu as="div" className="relative ml-3">
{({ open }) => (
<>
<div>
<Menu.Button className="flex text-sm bg-white rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red">
<span className="sr-only">Open user menu</span>
<Image
width={32}
height={32}
src="/img/avatar-placeholder.png"
alt="profile"
className="rounded-full"
/>
</Menu.Button>
</div>
<Transition
show={open}
as={Fragment}
enter="transition ease-out duration-200"
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 right-0 w-48 py-1 mt-2 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<Menu.Item>
{({ active }) => (
<button
onClick={() => signOut()}
className={classNames(
active ? "bg-gray-100" : "",
"block px-4 py-2 text-sm text-ui-gray-dark hover:text-black w-full text-left"
)}
>
Sign Out
</button>
)}
</Menu.Item>
</Menu.Items>
</Transition>
</>
)}
</Menu>
</div>
<div className="flex items-center -mr-2 sm:hidden">
{/* Mobile menu button */}
<Disclosure.Button className="inline-flex items-center justify-center p-2 bg-white rounded-md text-ui-gray-dark hover:text-ui-gray-dark hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red">
<span className="sr-only">Open main menu</span>
{open ? (
<XIcon className="block w-6 h-6" aria-hidden="true" />
) : (
<MenuIcon className="block w-6 h-6" aria-hidden="true" />
)}
</Disclosure.Button>
</div>
</div>
</div>
<Disclosure.Panel className="sm:hidden">
<div className="pt-4 pb-3 border-t border-ui-gray-light">
<div className="flex items-center px-4">
<div className="flex-shrink-0">
<Image
className="w-10 h-10 rounded-full"
src="/img/avatar-placeholder.png"
alt="user avatar"
width={20}
height={20}
/>
</div>
</div>
<div className="mt-3 space-y-1">
<button
onClick={() => signOut()}
className="block px-4 py-2 text-base font-medium text-ui-gray-dark hover:text-ui-gray-dark hover:bg-gray-100"
>
Sign Out
</button>
</div>
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
<main>{children}</main>
</div>
);
}

View File

@@ -1,48 +0,0 @@
import Head from "next/head";
import MenuBreadcrumbs from "./MenuBreadcrumbs";
import MenuSteps from "./MenuSteps";
import MenuProfile from "./MenuProfile";
import { signIn, useSession } from "next-auth/react";
import Loading from "../Loading";
export default function LayoutShare({ title, formId, currentStep, children }) {
const { data: session, status } = useSession();
if (status === "loading") {
return <Loading />;
}
if (!session) {
signIn();
return <div>You need to be authenticated to view this page.</div>;
}
return (
<>
<Head>
<title>{title}</title>
</Head>
<div className="flex min-h-screen overflow-hidden bg-ui-gray-lighter">
<div className="flex flex-col flex-1 overflow-hidden">
<header className="w-full">
<div className="relative z-10 flex flex-shrink-0 h-16 bg-white border-b shadow-sm border-ui-gray-light">
<div className="flex flex-1 px-4 sm:px-6">
<MenuBreadcrumbs formId={formId} />
<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 />
</div>
</div>
</div>
</header>
{/* Main content */}
<main>
<div className="max-w-6xl mx-auto sm:px-6 lg:px-8">{children}</div>
</main>
</div>
</div>
</>
);
}

View File

@@ -1,51 +0,0 @@
import { signIn, useSession } from "next-auth/react";
import Head from "next/head";
import Loading from "../Loading";
import MenuBreadcrumbs from "./MenuBreadcrumbs";
import MenuProfile from "./MenuProfile";
import MenuSteps from "./MenuSteps";
export default function LayoutFormResults({
title,
formId,
currentStep,
children,
}) {
const { data: session, status } = useSession();
if (status === "loading") {
return <Loading />;
}
if (!session) {
signIn();
return <div>You need to be authenticated to view this page.</div>;
}
return (
<>
<Head>
<title>{title}</title>
</Head>
<div className="flex min-h-screen overflow-hidden bg-white">
<div className="flex flex-col flex-1 overflow-hidden">
<header className="w-full">
<div className="relative z-10 flex flex-shrink-0 h-16 bg-white border-b shadow-sm border-ui-gray-light">
<div className="flex flex-1 px-4 sm:px-6">
<MenuBreadcrumbs formId={formId} />
<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 />
</div>
</div>
</div>
</header>
{/* Main content */}
{children}
</div>
</div>
</>
);
}

View File

@@ -1,80 +0,0 @@
import { signIn, useSession } from "next-auth/react";
import Head from "next/head";
import { classNames } from "../../lib/utils";
import Loading from "../Loading";
import MenuBreadcrumbs from "./MenuBreadcrumbs";
import MenuProfile from "./MenuProfile";
import MenuSteps from "./MenuSteps";
export default function LayoutFormResults({
title,
formId,
resultMode,
setResultMode,
currentStep,
children,
}) {
const { data: session, status } = useSession();
if (status === "loading") {
return <Loading />;
}
if (!session) {
signIn();
return <div>You need to be authenticated to view this page.</div>;
}
const resultModes = [
{ name: "Summary", id: "summary", icon: "" },
{ name: "Responses", id: "responses", icon: "" },
{ name: "Analytics", id: "analytics", icon: "" },
];
return (
<>
<Head>
<title>{title}</title>
</Head>
<div className="flex min-h-screen overflow-hidden bg-ui-gray-lighter">
<div className="flex flex-col flex-1 overflow-hidden">
<header className="w-full">
<div className="relative z-10 flex flex-shrink-0 h-16 bg-white border-b shadow-sm border-ui-gray-light">
<div className="flex flex-1 px-4 sm:px-6">
<MenuBreadcrumbs formId={formId} />
<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 />
</div>
</div>
</div>
<div className="relative z-10 flex flex-shrink-0 h-16 border-b shadow-inner border-ui-gray-light bg-gray-50">
<div className="flex items-center justify-center flex-1 px-4">
<nav className="flex space-x-4" aria-label="resultModes">
{resultModes.map((mode) => (
<button
key={mode.name}
onClick={() => setResultMode(mode.id)}
className={classNames(
mode.id === resultMode
? "bg-ui-gray-light text-gray-800"
: "text-gray-600 hover:text-gray-800",
"px-3 py-2 font-medium text-sm rounded-md"
)}
aria-current={mode.id === resultMode ? "page" : undefined}
>
{mode.name}
</button>
))}
</nav>
</div>
</div>
</header>
{/* Main content */}
{children}
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,9 @@
interface Props {
children?: React.ReactNode;
}
const LimitedWidth: React.FC<Props> = ({ children }) => {
return <main className="h-full mx-auto max-w-7xl">{children}</main>;
};
export default LimitedWidth;

View File

@@ -1,38 +1,11 @@
import { HomeIcon, PlusIcon } from "@heroicons/react/outline";
import { HomeIcon } from "@heroicons/react/outline";
import Link from "next/link";
import { useForm } from "../../lib/forms";
import Router from "next/router";
import StandardButton from "../StandardButton";
import { createForm } from "../../lib/forms";
export default function MenuBreadcrumbs({ formId }) {
const newForm = async () => {
const form = await createForm();
await Router.push(`/forms/${form.id}/welcome`);
};
const { form, isLoadingForm } = useForm(formId);
const pages = [
{ name: "Forms", href: "/forms", current: false },
{ name: form.name, href: "#", current: true },
];
if (isLoadingForm) {
return <div />;
}
export default function MenuBreadcrumbs({ breadcrumbs }) {
return (
<div className="hidden sm:flex sm:flex-1">
<nav className="hidden lg:flex" aria-label="Breadcrumb">
<ol className="flex items-center space-x-4">
<li>
<div>
<StandardButton icon secondary onClick={() => newForm()}>
<PlusIcon className="w-6 h-6" />
</StandardButton>
</div>
</li>
<li>
<div>
<Link href="/forms/">
@@ -46,8 +19,8 @@ export default function MenuBreadcrumbs({ formId }) {
</Link>
</div>
</li>
{pages.map((page) => (
<li key={page.name}>
{breadcrumbs.map((crumb) => (
<li key={crumb.name}>
<div className="flex items-center">
<svg
className="flex-shrink-0 w-5 h-5 text-ui-gray-medium"
@@ -59,11 +32,10 @@ export default function MenuBreadcrumbs({ formId }) {
<path d="M5.555 17.776l8-16 .894.448-8 16-.894-.448z" />
</svg>
<a
href={page.href}
href={crumb.href}
className="ml-4 text-sm font-medium text-ui-gray-dark hover:text-ui-gray-dark"
aria-current={page.current ? "page" : undefined}
>
{page.name}
{crumb.name}
</a>
</div>
</li>

View File

@@ -12,13 +12,15 @@ export default function MenuProfile({}) {
<div className="inline-flex items-center ">
<Menu.Button className="flex ml-3 text-sm bg-white rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
<span className="sr-only">Open user menu</span>
<Image
className="w-8 h-8 rounded-full"
src="/img/avatar-placeholder.png"
alt="user avatar"
width={20}
height={20}
/>
<div className="w-8 h-8">
<Image
className="rounded-full"
src="/img/avatar-placeholder.png"
alt="user avatar"
width={50}
height={50}
/>
</div>
</Menu.Button>
</div>
<Transition

View File

@@ -1,74 +1,57 @@
import Link from "next/link";
import { useRouter } from "next/router";
import { useForm } from "../../lib/forms";
import { classNames } from "../../lib/utils";
interface Step {
id: string;
name: string;
href: string;
}
type MenuStepsProps = {
formId: string;
currentStep: "build" | "share" | "results";
steps: Step[];
currentStep: string;
};
export default function MenuSteps({ formId, currentStep }: MenuStepsProps) {
const { form, isLoadingForm } = useForm(formId);
export default function MenuSteps({ steps, currentStep }: MenuStepsProps) {
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",
href: `/forms/${form.id}/results`,
},
];
if (isLoadingForm) {
return <div />;
}
return (
<div className="flex items-center flex-1 justify-left sm:justify-center">
<div className="w-full sm:hidden">
<label htmlFor="tabs" className="sr-only">
<label htmlFor="steps" className="sr-only">
Select a view
</label>
<select
id="tabs"
name="tabs"
className="block w-full py-2 pl-3 pr-10 text-base border-ui-gray-medium rounded-md focus:outline-none focus:ring-red focus:border-red sm:text-sm"
defaultValue={tabs.find((tab) => tab.id === currentStep).name}
id="steps"
name="steps"
className="block w-full py-2 pl-3 pr-10 text-base rounded-md border-ui-gray-medium focus:outline-none focus:ring-red focus:border-red sm:text-sm"
defaultValue={steps.find((step) => step.id === currentStep).name}
onChange={(e) => {
const stepId = e.target.children[e.target.selectedIndex].id;
router.push(`/forms/${form.id}/${stepId}`);
router.push(steps.find((s) => s.id === stepId).href);
}}
>
{tabs.map((tab) => (
<option key={tab.name} id={tab.id}>
{tab.name}
{steps.map((step) => (
<option key={step.name} id={step.id}>
{step.name}
</option>
))}
</select>
</div>
<div className="hidden sm:block">
<nav className="flex -mb-px space-x-8" aria-label="Tabs">
{tabs.map((tab) => (
<Link key={tab.name} href={tab.href}>
<nav className="flex -mb-px space-x-8" aria-label="steps">
{steps.map((step) => (
<Link key={step.name} href={step.href}>
<a
className={classNames(
tab.id === currentStep
step.id === currentStep
? "border-red text-red"
: "border-transparent text-ui-gray-dark hover:text-ui-gray-dark hover:border-ui-gray-medium",
"whitespace-nowrap py-5 px-1 border-b-2 font-medium text-sm"
)}
aria-current={tab.id === currentStep ? "page" : undefined}
aria-current={step.id === currentStep ? "page" : undefined}
>
{tab.name}
{step.name}
</a>
</Link>
))}

View File

@@ -0,0 +1,28 @@
import { PlusIcon } from "@heroicons/react/outline";
import { useState } from "react";
import NewFormModal from "../form/NewFormModal";
export default function NewFormNavButton({}) {
const [openNewFormModal, setOpenNewFormModal] = useState(false);
return (
<>
<button
type="button"
className="items-center hidden text-sm border-r border-ui-gray-light sm:flex bg-ui-gray-lighter text-ui-gray-dark hover:text-white hover:bg-red-500"
onClick={() => setOpenNewFormModal(true)}
>
<nav className="hidden lg:flex" aria-label="Breadcrumb">
<ol className="flex items-center space-x-4">
<li>
<div className="inline-flex items-center px-6 py-2 text-sm font-medium leading-4 bg-transparent border border-transparent hover:text-white focus:outline-none">
<PlusIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
Start New Form
</div>
</li>
</ol>
</nav>
</button>
<NewFormModal open={openNewFormModal} setOpen={setOpenNewFormModal} />
</>
);
}

View File

@@ -1,16 +1,47 @@
import React from "react";
import { classNames } from "../../lib/utils";
interface NavItem {
id: string;
onClick: () => void;
Icon?: React.ElementType;
label?: string;
disabled?: boolean;
}
interface Props {
children: React.ReactNode;
navItems: NavItem[];
currentItemId?: string;
}
// button component, consuming props
const SecondNavBar: React.FC<Props> = ({ children }) => {
const SecondNavBar: React.FC<Props> = ({ navItems, currentItemId }) => {
return (
<div className="relative flex flex-shrink-0 h-16 py-2 border-b border-ui-gray-light bg-ui-gray-lighter">
<div className="relative flex flex-shrink-0 h-16 border-b border-ui-gray-light bg-ui-gray-lighter">
<div className="flex items-center justify-center flex-1 px-4 py-2">
<nav className="flex space-x-4" aria-label="resultModes">
{children}
<nav className="flex space-x-10" aria-label="resultModes">
{navItems.map((navItem) => (
<button
key={navItem.id}
className={classNames(
`h-16 text-xs`,
!navItem.disabled &&
(navItem.id === currentItemId
? "text-red border-b-2 border-red"
: "hover:border-b-2 hover:border-gray-300 text-ui-gray-dark hover:text-red bg-transparent"),
navItem.disabled
? "text-ui-gray-medium"
: "hover:border-b-2 hover:border-red text-ui-gray-dark hover:text-red"
)}
onClick={navItem.onClick}
disabled={navItem.disabled}
>
{navItem.Icon && (
<navItem.Icon className="w-6 h-6 mx-auto mb-1 stroke-1" />
)}
{navItem.label}
</button>
))}
</nav>
</div>
</div>

View File

@@ -1,58 +0,0 @@
import React from "react";
import { classNames } from "../../lib/utils";
import Link from "next/link";
interface Props {
children: React.ReactNode;
onClick?: () => void;
itemLabel?: string;
disabled?: boolean;
link?: boolean;
outbound?: boolean;
href?: string;
}
// button component, consuming props
const SecondNavBarItem: React.FC<Props> = ({
children,
onClick = () => {},
itemLabel,
disabled = false,
link = false,
outbound = false,
href,
...rest
}) => {
return (
<div>
{link ? (
<Link href={href}>
<a
target={outbound ? "_blank" : "_self"}
className="inline-flex p-2 text-xs bg-transparent rounded-sm hover:bg-ui-gray-light hover:cursor-pointer text-ui-gray-dark hover:text-red"
>
<span>{children}</span>
{itemLabel}
</a>
</Link>
) : (
<button
className={classNames(
`p-2 text-xs rounded-sm`,
disabled
? "text-ui-gray-medium"
: "bg-transparent hover:bg-ui-gray-light text-ui-gray-dark hover:text-red"
)}
onClick={onClick}
disabled={disabled}
{...rest}
>
<span>{children}</span>
{itemLabel}
</button>
)}
</div>
);
};
export default SecondNavBarItem;

View File

@@ -0,0 +1,75 @@
import { QuestionMarkCircleIcon } from "@heroicons/react/outline";
import { ArrowSmDownIcon, ArrowSmUpIcon } from "@heroicons/react/solid";
import React from "react";
import { classNames } from "../../lib/utils";
interface Props {
value: string | number;
label: string;
toolTipText: string;
trend?: number;
smallerText?: boolean;
}
const AnalyticsCard: React.FC<Props> = ({
value,
label,
toolTipText,
trend,
smallerText,
}) => {
return (
<div className="bg-white rounded-md shadow-md">
<div key={label} className="px-4 py-5 sm:p-6">
<dt className="inline-flex text-base font-normal text-gray-900 has-tooltip">
{label}{" "}
{toolTipText && (
<QuestionMarkCircleIcon className="w-4 h-4 ml-1 text-red hover:text-ui-gray-dark" />
)}
<span className="flex p-1 px-4 -mt-6 -ml-8 text-xs text-center text-white bg-gray-600 rounded shadow-lg grow tooltip">
{toolTipText}
</span>
</dt>
<dd className="flex items-baseline justify-between mt-1 md:block lg:flex">
<div
className={classNames(
smallerText ? "text-lg" : "text-xl",
"flex items-baseline text-xl font-semibold text-gray-800"
)}
>
{value}
</div>
{trend && (
<div
className={classNames(
trend >= 0
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800",
"inline-flex items-baseline px-2.5 py-0.5 rounded-full text-sm font-medium md:mt-2 lg:mt-0"
)}
>
{trend >= 0 ? (
<ArrowSmUpIcon
className="-ml-1 mr-0.5 flex-shrink-0 self-center h-5 w-5 text-green-500"
aria-hidden="true"
/>
) : (
<ArrowSmDownIcon
className="-ml-1 mr-0.5 flex-shrink-0 self-center h-5 w-5 text-red-500"
aria-hidden="true"
/>
)}
<span className="sr-only">
{trend >= 0 ? "Increased" : "Decreased"} by
</span>
{trend} %
</div>
)}
</dd>
</div>
</div>
);
};
export default AnalyticsCard;

View File

@@ -5,7 +5,7 @@ import {
useSubmissionSessions,
} from "../../lib/submissionSessions";
import { timeSince } from "../../lib/utils";
import AnalyticsCard from "../layout/AnalyticsCard";
import AnalyticsCard from "./AnalyticsCard";
export default function ResultsAnalytics({ formId }) {
const { submissionSessions, isLoadingSubmissionSessions } =
@@ -24,73 +24,71 @@ export default function ResultsAnalytics({ formId }) {
id: "uniqueUsers",
name: "Unique Users",
stat: analytics.uniqueUsers || "--",
toolTipText: "placeholder",
trend: 12,
toolTipText: "Tracked without cookies using fingerprinting technique",
trend: undefined,
},
{
id: "totalSubmissions",
name: "Total Submissions",
stat: analytics.totalSubmissions || "--",
trend: 10,
trend: undefined,
},
{
id: "lastSubmission",
name: "Last Submission",
stat: timeSince(analytics.lastSubmissionAt) || "--",
typeText: true,
smallerText: true,
},
];
}
}, [analytics]);
return (
<main>
<div className="max-w-5xl mx-auto sm:px-6 lg:px-8">
<h2 className="mt-8 text-xl font-bold text-ui-gray-dark">Analytics</h2>
<div>
{stats ? (
<dl className="grid grid-cols-1 gap-5 mt-8 sm:grid-cols-2 lg:grid-cols-3">
{stats.map((item) => (
<AnalyticsCard
key={item.id}
KPI={item.stat}
label={item.name}
toolTipText={item.toolTipText}
typeText={item.typeText}
trend={item.trend}
/>
))}
</dl>
) : null}
</div>
<div className="flex items-end">
<h2 className="mt-16 text-xl font-bold text-ui-gray-dark">
Optimize Form
</h2>
<div className="px-3 py-2 ml-2 text-xs text-green-800 rounded-sm bg-green-50">
<p>coming soon</p>
</div>
</div>
<div className="grid grid-cols-2 gap-10 mt-8">
<div className="relative p-8 bg-white rounded-md shadow-md h-60">
<Image
src="/../../img/drop-offs-v1.svg"
alt="drop-off"
objectFit="cover"
layout="fill"
className="rounded-md"
/>
</div>
<div className="relative p-8 bg-white rounded-md shadow-md h-60">
<Image
src="/../../img/a-b-test-v1.svg"
alt="drop-off"
objectFit="cover"
layout="fill"
className="rounded-md"
/>
</div>
<>
<h2 className="mt-8 text-xl font-bold text-ui-gray-dark">Analytics</h2>
<div>
{stats ? (
<dl className="grid grid-cols-1 gap-5 mt-8 sm:grid-cols-2 lg:grid-cols-3">
{stats.map((item) => (
<AnalyticsCard
key={item.id}
value={item.stat}
label={item.name}
toolTipText={item.toolTipText}
trend={item.trend}
smallerText={item.smallerText}
/>
))}
</dl>
) : null}
</div>
<div className="flex items-end">
<h2 className="mt-16 text-xl font-bold text-ui-gray-dark">
Optimize Form
</h2>
<div className="px-3 py-2 ml-2 text-xs text-green-800 rounded-sm bg-green-50">
<p>coming soon</p>
</div>
</div>
</main>
<div className="grid grid-cols-2 gap-10 mt-8">
<div className="p-5 bg-white rounded-md shadow-md">
<Image
src="/../../img/drop-offs-v1.svg"
alt="drop-off"
layout="responsive"
width={500}
height={273}
/>
</div>
<div className="p-5 bg-white rounded-md shadow-md">
<Image
src="/../../img/a-b-test-v1.svg"
alt="drop-off"
layout="responsive"
width={500}
height={273}
/>
</div>
</div>
</>
);
}

View File

@@ -35,7 +35,7 @@ export default function ResultsResponses({ formId }: ResultsResponseProps) {
}
return (
<div className="flex flex-col flex-1 w-full mx-auto overflow-visible max-w-screen">
<div className="flex flex-col flex-1 w-full h-full mx-auto overflow-visible max-w-screen">
<div className="relative z-0 flex flex-1 overflow-visible">
<main className="relative z-0 flex-1 overflow-y-auto focus:outline-none xl:order-last">
<div className="overflow-visible sm:rounded-lg">

View File

@@ -7,7 +7,7 @@ import {
} from "../../lib/submissionSessions";
import { SubmissionSummary } from "../../lib/types";
import { timeSince } from "../../lib/utils";
import AnalyticsCard from "../layout/AnalyticsCard";
import AnalyticsCard from "./AnalyticsCard";
import Loading from "../Loading";
import TextResults from "./summary/TextResults";
@@ -36,20 +36,20 @@ export default function ResultsSummary({ formId }) {
id: "uniqueUsers",
name: "Unique Users",
stat: analytics.uniqueUsers || "--",
toolTipText: "placeholder",
trend: 12,
toolTipText: "Tracked without cookies using fingerprinting technique",
trend: undefined,
},
{
id: "totalSubmissions",
name: "Total Submissions",
stat: analytics.totalSubmissions || "--",
trend: 10,
trend: undefined,
},
{
id: "lastSubmission",
name: "Last Submission",
stat: timeSince(analytics.lastSubmissionAt) || "--",
typeText: true,
smallerText: true,
},
];
}
@@ -60,25 +60,25 @@ export default function ResultsSummary({ formId }) {
}
return (
<main className="bg-gray-50">
<div className="max-w-5xl mx-auto sm:px-6 lg:px-8">
<h2 className="mt-8 text-xl font-bold text-ui-gray-dark">
Responses Overview
</h2>
<dl className="grid grid-cols-1 gap-5 mt-8 sm:grid-cols-2 lg:grid-cols-3">
{stats.map((item) => (
<AnalyticsCard
key={item.id}
KPI={item.stat}
label={item.name}
toolTipText={item.toolTipText}
typeText={item.typeText}
trend={item.trend}
/>
))}
</dl>
<div>
{summary.pages.map(
<>
<h2 className="mt-8 text-xl font-bold text-ui-gray-dark">
Responses Overview
</h2>
<dl className="grid grid-cols-1 gap-5 mt-8 sm:grid-cols-2 lg:grid-cols-3">
{stats.map((item) => (
<AnalyticsCard
key={item.id}
value={item.stat}
label={item.name}
toolTipText={item.toolTipText}
trend={item.trend}
smallerText={item.smallerText}
/>
))}
</dl>
<div>
{summary?.pages &&
summary.pages.map(
(page) =>
page.type === "form" && (
<div key={page.name}>
@@ -90,8 +90,7 @@ export default function ResultsSummary({ formId }) {
</div>
)
)}
</div>
</div>
</main>
</>
);
}

View File

@@ -0,0 +1,47 @@
import { DocumentSearchIcon, TerminalIcon } from "@heroicons/react/outline";
import { useRouter } from "next/router";
import { FaReact } from "react-icons/fa";
export const useCodeSecondNavigation = (formId) => {
const router = useRouter();
return [
{
id: "formId",
onClick: () => {
router.push(`/forms/${formId}/form`);
},
Icon: TerminalIcon,
label: "Form ID",
},
{
id: "react",
onClick: () => {
router.push(`/forms/${formId}/form/react`);
},
Icon: FaReact,
label: "React",
},
{
id: "reactNative",
onClick: () => {},
Icon: FaReact,
label: "React Native",
disabled: true,
},
{
id: "vueJs",
onClick: () => {},
Icon: TerminalIcon,
label: "VueJs",
disabled: true,
},
{
id: "docs",
onClick: () => {
window.open("https://docs.snoopforms.com", "_ blank");
},
Icon: DocumentSearchIcon,
label: "Docs",
},
];
};

View File

@@ -0,0 +1,17 @@
export const useFormMenuSteps = (formId) => [
{
id: "form",
name: "Form",
href: `/forms/${formId}/form`,
},
{
id: "pipelines",
name: "Pipelines",
href: `/forms/${formId}/pipelines`,
},
{
id: "results",
name: "Results",
href: `/forms/${formId}/results/summary`,
},
];

View File

@@ -0,0 +1,36 @@
import {
ChartBarIcon,
InboxIcon,
TrendingUpIcon,
} from "@heroicons/react/outline";
import { useRouter } from "next/router";
export const useFormResultsSecondNavigation = (formId) => {
const router = useRouter();
return [
{
id: "summary",
onClick: () => {
router.push(`/forms/${formId}/results/summary`);
},
Icon: ChartBarIcon,
label: "Summary",
},
{
id: "responses",
onClick: () => {
router.push(`/forms/${formId}/results/responses`);
},
Icon: InboxIcon,
label: "Responses",
},
{
id: "analytics",
onClick: () => {
router.push(`/forms/${formId}/results/analytics`);
},
Icon: TrendingUpIcon,
label: "Analytics",
},
];
};

View File

@@ -1,45 +0,0 @@
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import Builder from "../../../components/builder/Builder";
import FormCode from "../../../components/form/FormCode";
import LayoutFormBasics from "../../../components/layout/LayoutFormBasic";
import LayoutFormBuilder from "../../../components/layout/LayoutFormBuilder";
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);
if (isLoadingForm) {
return <Loading />;
}
if (form.formType === "NOCODE") {
return (
<>
<LayoutFormBuilder title={form.name} formId={formId} currentStep="form">
<Builder formId={formId} />
</LayoutFormBuilder>
</>
);
} else {
return (
<>
<LayoutFormBasics title={form.name} formId={formId} currentStep="form">
<FormCode formId={formId} />
</LayoutFormBasics>
</>
);
}
}
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getSession({ req });
if (!session) {
res.statusCode = 403;
}
return { props: {} };
};

View File

@@ -0,0 +1,67 @@
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import Builder from "../../../../components/builder/Builder";
import FormCode from "../../../../components/form/FormCode";
import BaseLayoutAuthorized from "../../../../components/layout/BaseLayoutAuthorized";
import FullWidth from "../../../../components/layout/FullWidth";
import LimitedWidth from "../../../../components/layout/LimitedWidth";
import SecondNavBar from "../../../../components/layout/SecondNavBar";
import Loading from "../../../../components/Loading";
import { useForm } from "../../../../lib/forms";
import { useCodeSecondNavigation } from "../../../../lib/navigation/formCodeSecondNavigation";
import { useFormMenuSteps } from "../../../../lib/navigation/formMenuSteps";
export default function FormPage() {
const router = useRouter();
const formId = router.query.id.toString();
const { form, isLoadingForm } = useForm(router.query.id);
const codeSecondNavigation = useCodeSecondNavigation(formId);
const formMenuSteps = useFormMenuSteps(formId);
if (isLoadingForm) {
return <Loading />;
}
const breadcrumbs = [{ name: form.name, href: "#", current: true }];
if (form.formType === "NOCODE") {
return (
<>
<BaseLayoutAuthorized
title={`${form.name} - snoopForms`}
breadcrumbs={breadcrumbs}
steps={formMenuSteps}
currentStep="form"
>
<FullWidth>
<Builder formId={formId} />
</FullWidth>
</BaseLayoutAuthorized>
</>
);
} else {
return (
<BaseLayoutAuthorized
title={`${form.name} - snoopForms`}
breadcrumbs={breadcrumbs}
steps={formMenuSteps}
currentStep="form"
>
<SecondNavBar navItems={codeSecondNavigation} currentItemId="formId" />
<LimitedWidth>
<FormCode formId={formId} />
</LimitedWidth>
</BaseLayoutAuthorized>
);
}
}
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getSession({ req });
if (!session) {
res.statusCode = 403;
}
return { props: {} };
};

View File

@@ -0,0 +1,134 @@
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import { FaDiscord } from "react-icons/fa";
import BaseLayoutAuthorized from "../../../../components/layout/BaseLayoutAuthorized";
import LimitedWidth from "../../../../components/layout/LimitedWidth";
import SecondNavBar from "../../../../components/layout/SecondNavBar";
import Loading from "../../../../components/Loading";
import { useForm } from "../../../../lib/forms";
import { useCodeSecondNavigation } from "../../../../lib/navigation/formCodeSecondNavigation";
export default function ReactPage() {
const router = useRouter();
const formId = router.query.id.toString();
const { form, isLoadingForm } = useForm(router.query.id);
const codeSecondNavigation = useCodeSecondNavigation(formId);
if (isLoadingForm) {
return <Loading />;
}
return (
<>
<BaseLayoutAuthorized
title={form.name}
breadcrumbs={[{ name: form.name, href: "#", current: true }]}
>
<SecondNavBar navItems={codeSecondNavigation} currentItemId="react" />
<LimitedWidth>
<header>
<div className="mx-auto mt-8 max-w-7xl">
<h1 className="text-3xl font-bold leading-tight text-ui-gray-dark">
Build your form with React
</h1>
</div>
</header>
<div className="my-4">
<p className="text-ui-gray-dark">
Use our pre-built components to build your form. Manage data in
this dashboard.
</p>
</div>
<div className="grid grid-cols-2 gap-10 mt-16">
<div>
<h2 className="text-xl font-bold text-ui-gray-dark">
1. Get started
</h2>
<p className="text-ui-gray-dark">
Install the snoopReact Library with Node Package Manager via
snoopforms/react.
</p>
</div>
<div className="p-8 font-light text-gray-200 bg-black rounded-md">
<code>{"npm install --save @snoopforms/react"}</code>
</div>
</div>
<div className="grid grid-cols-2 gap-10 mt-16">
<div>
<h2 className="text-xl font-bold text-ui-gray-dark">
2. Build the form
</h2>
<p className="text-ui-gray-dark">
Use the pre-built components snoopForm, snoopPage and
snoopElement to build exactly the form you want.
</p>
</div>
<div className="p-8 font-light text-gray-200 bg-black rounded-md">
<code className="whitespace-pre language-js">{`<SnoopForm
domain="localhost:3000"
protocol="http"
className="w-full space-y-6"
onSubmit={({ submission, schema })=>{}}>
<SnoopPage name="first">
<SnoopElement
type="text"
name={"name"}
label="Your name"
classNames={{
label: "your-label-class",
element: "your-input-class",}}
required/>
</SnoopPage>
<SnoopPage thankyou>
<h1>Thank you!</h1>
</SnoopPage>
</SnoopForm>`}</code>
</div>
</div>
<div className="grid grid-cols-2 gap-10 my-16">
<div>
<h2 className="text-xl font-bold text-ui-gray-dark">
Questions?
</h2>
<p className="pb-4 text-ui-gray-dark">
Find a more detailed explanation on how to go about build the
form and piping your data{" "}
<a
href="https://docs.snoopforms.com"
target="_blank"
rel="noreferrer"
className="underline text-red hover:no-underline"
>
in the docs.
</a>{" "}
Or join our Discord and ask us :)
</p>
</div>
<div className="flex items-center justify-center p-8 rounded-md bg-purple">
<a
className="inline-flex items-center justify-center px-4 py-2 bg-white rounded-sm hover:motion-safe:animate-bounce text-purple text-bold text-md"
href="https://discord.gg/kT4Aq7Kq"
target="_blank"
rel="noreferrer"
>
Join Discord <FaDiscord className="w-8 h-8 ml-2" />
</a>
</div>
</div>
</LimitedWidth>
</BaseLayoutAuthorized>
</>
);
}
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getSession({ req });
if (!session) {
res.statusCode = 403;
}
return { props: {} };
};

View File

@@ -13,7 +13,7 @@ export default function FormIndex() {
if (!isLoadingSubmissionSessions) {
// redirect to /results if there is at least one submissionSession
if (submissionSessions.length > 0) {
router.push(`/forms/${formId}/results`);
router.push(`/forms/${formId}/results/summary`);
} else {
// redirect to /form if there isn't one submissionSession
router.push(`/forms/${formId}/form`);

View File

@@ -1,27 +1,29 @@
import { CodeIcon, PuzzleIcon } from "@heroicons/react/outline";
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import LayoutFormBasics from "../../../components/layout/LayoutFormBasic";
import {
SiAirtable,
SiGoogle,
SiNotion,
SiSlack,
SiZapier,
} from "react-icons/si";
import BaseLayoutAuthorized from "../../../components/layout/BaseLayoutAuthorized";
import EmptyPageFiller from "../../../components/layout/EmptyPageFiller";
import LimitedWidth from "../../../components/layout/LimitedWidth";
import Loading from "../../../components/Loading";
import { useForm } from "../../../lib/forms";
import {
SiZapier,
SiAirtable,
SiSlack,
SiNotion,
SiGoogle,
} from "react-icons/si";
import { CodeIcon } from "@heroicons/react/outline";
import { useFormMenuSteps } from "../../../lib/navigation/formMenuSteps";
import { classNames } from "../../../lib/utils";
import EmptyPageFiller from "../../../components/layout/EmptyPageFiller";
import { PuzzleIcon } from "@heroicons/react/outline";
const libs = [
{
id: "webhook",
name: "Webhook",
href: "#",
bgColor: "bg-red-500",
comingSoon: true,
bgColor: "bg-ui-gray-light",
icon: CodeIcon,
},
{
@@ -70,6 +72,7 @@ export default function PipelinesPage() {
const router = useRouter();
const formId = router.query.id.toString();
const { form, isLoadingForm } = useForm(router.query.id);
const formMenuSteps = useFormMenuSteps(formId);
if (isLoadingForm) {
return <Loading />;
@@ -77,92 +80,97 @@ export default function PipelinesPage() {
return (
<>
<LayoutFormBasics
title={form.title}
formId={formId}
<BaseLayoutAuthorized
title={`${form.name} - snoopForms`}
breadcrumbs={[{ name: form.name, href: "#", current: true }]}
steps={formMenuSteps}
currentStep="pipelines"
>
<header>
<div className="mx-auto mt-8 max-w-7xl">
<h1 className="text-3xl font-bold leading-tight text-ui-gray-dark">
Data Pipelines
</h1>
<LimitedWidth>
<header>
<div className="mx-auto mt-8 max-w-7xl">
<h1 className="text-3xl font-bold leading-tight text-ui-gray-dark">
Data Pipelines
</h1>
</div>
</header>
<div className="my-4">
<p className="text-ui-gray-dark">
Pipe your data exactly where you need it. Add conditions for
variable data piping.
</p>
</div>
</header>
<div className="my-4">
<p className="text-ui-gray-dark">
Pipe your data exactly where you need it. Add conditions for
variable data piping.
</p>
</div>
<EmptyPageFiller
alertText="No active pipelines for 'form name'"
hintText="Setup a data pipeline below."
>
<PuzzleIcon className="w-24 h-24 mx-auto text-ui-gray-medium stroke-thin" />
</EmptyPageFiller>
<div>
<div className="mt-16">
<div>
<h2 className="text-xl font-bold text-ui-gray-dark">
Integrations
</h2>
<EmptyPageFiller
alertText={`No active pipelines for '${form.name}'`}
hintText="Setup a data pipeline below."
>
<PuzzleIcon className="w-24 h-24 mx-auto text-ui-gray-medium stroke-thin" />
</EmptyPageFiller>
<div>
<div className="mt-16">
<div>
<h2 className="text-xl font-bold text-ui-gray-dark">
Integrations
</h2>
<ul
role="list"
className="grid grid-cols-1 gap-5 mt-3 sm:gap-6 sm:grid-cols-3 lg:grid-cols-3"
>
{libs.map((lib) => (
<a
className="flex col-span-1 rounded-md shadow-sm"
key={lib.id}
>
<li
className={classNames(
lib.comingSoon
? "text-ui-gray-medium"
: "shadow-sm text-ui-gray-dark hover:text-black",
"flex col-span-1 rounded-md w-full"
)}
<ul
role="list"
className="grid grid-cols-1 gap-5 mt-3 sm:gap-6 sm:grid-cols-3 lg:grid-cols-3"
>
{libs.map((lib) => (
<a
className="flex col-span-1 rounded-md shadow-sm"
key={lib.id}
>
<div
<li
className={classNames(
lib.bgColor,
"flex-shrink-0 flex items-center justify-center w-20 text-white text-sm font-medium rounded-md"
lib.comingSoon
? "text-ui-gray-medium"
: "shadow-sm text-ui-gray-dark hover:text-black",
"flex col-span-1 rounded-md w-full"
)}
>
<lib.icon
<div
className={classNames(
lib.comingSoon
? "text-ui-gray-medium w-8 h-8"
: "text-white stroke-1 w-10 h-10",
""
)}
/>
</div>
<div
className={classNames(
lib.comingSoon ? "border-dashed" : "",
"flex items-center justify-between flex-1 truncate bg-white rounded-r-md"
)}
>
<div className="inline-flex px-4 py-8 text-sm truncate">
<p className="">{lib.name}</p>
{lib.comingSoon && (
<div className="p-1 px-3 ml-3 bg-green-100 rounded-sm">
<p className="text-xs text-black">coming soon</p>
</div>
lib.bgColor,
"flex-shrink-0 flex items-center justify-center w-20 text-white text-sm font-medium rounded-md"
)}
>
<lib.icon
className={classNames(
lib.comingSoon
? "text-ui-gray-medium w-8 h-8"
: "text-white stroke-1 w-10 h-10",
""
)}
/>
</div>
</div>
</li>
</a>
))}
</ul>
<div
className={classNames(
lib.comingSoon ? "border-dashed" : "",
"flex items-center justify-between flex-1 truncate bg-white rounded-r-md"
)}
>
<div className="inline-flex px-4 py-8 text-sm truncate">
<p className="">{lib.name}</p>
{lib.comingSoon && (
<div className="p-1 px-3 ml-3 bg-green-100 rounded-sm">
<p className="text-xs text-black">
coming soon
</p>
</div>
)}
</div>
</div>
</li>
</a>
))}
</ul>
</div>
</div>
</div>
</div>
</LayoutFormBasics>
</LimitedWidth>
</BaseLayoutAuthorized>
</>
);
}

View File

@@ -1,151 +0,0 @@
import { DocumentSearchIcon, TerminalIcon } from "@heroicons/react/outline";
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import { FaDiscord, FaReact, FaVuejs } from "react-icons/fa";
import LayoutFormBasics from "../../../components/layout/LayoutFormBasic";
import SecondNavBar from "../../../components/layout/SecondNavBar";
import SecondNavBarItem from "../../../components/layout/SecondNavBarItem";
import Loading from "../../../components/Loading";
import { useForm } from "../../../lib/forms";
export default function ReactPage() {
const router = useRouter();
const formId = router.query.id.toString();
const { form, isLoadingForm } = useForm(router.query.id);
if (isLoadingForm) {
return <Loading />;
}
return (
<>
<LayoutFormBasics
title={form.title}
formId={formId}
currentStep="pipelines"
>
<SecondNavBar>
<SecondNavBarItem link href={`/forms/${formId}/form`}>
<TerminalIcon className="w-8 h-8 mx-auto stroke-1" />
formID
</SecondNavBarItem>
<SecondNavBarItem link href={`/forms/${formId}/react`}>
<FaReact className="w-8 h-8 mx-auto stroke-1" />
React
</SecondNavBarItem>
<SecondNavBarItem disabled>
<FaReact className="w-8 h-8 mx-auto stroke-1" />
React Native
</SecondNavBarItem>
<SecondNavBarItem disabled>
<FaVuejs className="w-8 h-8 mx-auto stroke-1" />
Vue
</SecondNavBarItem>
<SecondNavBarItem link outbound href="https://docs.snoopforms.com">
<DocumentSearchIcon className="w-8 h-8 mx-auto stroke-1" />
Docs
</SecondNavBarItem>
</SecondNavBar>
<header>
<div className="mx-auto mt-8 max-w-7xl">
<h1 className="text-3xl font-bold leading-tight text-ui-gray-dark">
Build your form with React
</h1>
</div>
</header>
<div className="my-4">
<p className="text-ui-gray-dark">
Use our pre-built components to build your form. Manage data in this
dashboard.
</p>
</div>
<div className="grid grid-cols-2 gap-10 mt-16">
<div>
<h2 className="text-xl font-bold text-ui-gray-dark">
1. Get started
</h2>
<p className="text-ui-gray-dark">
Install the snoopReact Library with Node Package Manager via
snoopforms/react.
</p>
</div>
<div className="p-8 font-light text-gray-200 bg-black rounded-md">
<code>{"npm install --save @snoopforms/react"}</code>
</div>
</div>
<div className="grid grid-cols-2 gap-10 mt-16">
<div>
<h2 className="text-xl font-bold text-ui-gray-dark">
2. Build the form
</h2>
<p className="text-ui-gray-dark">
Use the pre-built components snoopForm, snoopPage and snoopElement
to build exactly the form you want.
</p>
</div>
<div className="p-8 font-light text-gray-200 bg-black rounded-md">
<code className="whitespace-pre language-js">{`<SnoopForm
domain="localhost:3000"
protocol="http"
className="w-full space-y-6"
onSubmit={({ submission, schema })=>{}}>
<SnoopPage name="first">
<SnoopElement
type="text"
name={"name"}
label="Your name"
classNames={{
label: "your-label-class",
element: "your-input-class",}}
required/>
</SnoopPage>
<SnoopPage thankyou>
<h1>Thank you!</h1>
</SnoopPage>
</SnoopForm>`}</code>
</div>
</div>
<div className="grid grid-cols-2 gap-10 my-16">
<div>
<h2 className="text-xl font-bold text-ui-gray-dark">Questions?</h2>
<p className="pb-4 text-ui-gray-dark">
Find a more detailed explanation on how to go about build the form
and piping your data{" "}
<a
href="https://docs.snoopforms.com"
target="_blank"
rel="noreferrer"
className="underline text-red hover:no-underline"
>
in the docs.
</a>{" "}
Or join our Discord and ask us :)
</p>
</div>
<div className="flex items-center justify-center p-8 rounded-md bg-purple">
<a
className="inline-flex items-center justify-center px-4 py-2 bg-white rounded-sm hover:motion-safe:animate-bounce text-purple text-bold text-md"
href="https://discord.gg/kT4Aq7Kq"
target="_blank"
rel="noreferrer"
>
Join Discord <FaDiscord className="w-8 h-8 ml-2" />
</a>
</div>
</div>
</LayoutFormBasics>
</>
);
}
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getSession({ req });
if (!session) {
res.statusCode = 403;
}
return { props: {} };
};

View File

@@ -1,45 +0,0 @@
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useState } from "react";
import LayoutFormResults from "../../../components/layout/LayoutFormResults";
import Loading from "../../../components/Loading";
import ResultsSummary from "../../../components/results/ResultsSummary";
import ResultsAnalytics from "../../../components/results/ResultsAnalytics";
import ResultsResponses from "../../../components/results/ResultsResponses";
import { useForm } from "../../../lib/forms";
export default function Share() {
const router = useRouter();
const formId = router.query.id.toString();
const { form, isLoadingForm } = useForm(router.query.id);
const [resultMode, setResultMode] = useState<string>("summary");
if (isLoadingForm) {
return <Loading />;
}
return (
<>
<LayoutFormResults
title={form.title}
formId={formId}
currentStep="results"
resultMode={resultMode}
setResultMode={setResultMode}
>
{resultMode === "summary" && <ResultsSummary formId={formId} />}
{resultMode === "responses" && <ResultsResponses formId={formId} />}
{resultMode === "analytics" && <ResultsAnalytics formId={formId} />}
</LayoutFormResults>
</>
);
}
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getSession({ req });
if (!session) {
res.statusCode = 403;
}
return { props: {} };
};

View File

@@ -0,0 +1,50 @@
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import BaseLayoutAuthorized from "../../../../components/layout/BaseLayoutAuthorized";
import LimitedWidth from "../../../../components/layout/LimitedWidth";
import SecondNavBar from "../../../../components/layout/SecondNavBar";
import Loading from "../../../../components/Loading";
import ResultsAnalytics from "../../../../components/results/ResultsAnalytics";
import { useForm } from "../../../../lib/forms";
import { useFormMenuSteps } from "../../../../lib/navigation/formMenuSteps";
import { useFormResultsSecondNavigation } from "../../../../lib/navigation/formResultsSecondNavigation";
export default function ResultsAnalyticsPage() {
const router = useRouter();
const formId = router.query.id.toString();
const { form, isLoadingForm } = useForm(router.query.id);
const formMenuSteps = useFormMenuSteps(formId);
const formResultsSecondNavigation = useFormResultsSecondNavigation(formId);
if (isLoadingForm) {
return <Loading />;
}
return (
<BaseLayoutAuthorized
title={`${form.name} - snoopForms`}
breadcrumbs={[{ name: form.name, href: "#", current: true }]}
steps={formMenuSteps}
currentStep="results"
limitHeightScreen={true}
>
<SecondNavBar
navItems={formResultsSecondNavigation}
currentItemId="analytics"
/>
<LimitedWidth>
<ResultsAnalytics formId={formId} />
</LimitedWidth>
</BaseLayoutAuthorized>
);
}
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getSession({ req });
if (!session) {
res.statusCode = 403;
}
return { props: {} };
};

View File

@@ -0,0 +1,50 @@
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import BaseLayoutAuthorized from "../../../../components/layout/BaseLayoutAuthorized";
import FullWidth from "../../../../components/layout/FullWidth";
import SecondNavBar from "../../../../components/layout/SecondNavBar";
import Loading from "../../../../components/Loading";
import ResultsResponses from "../../../../components/results/ResultsResponses";
import { useForm } from "../../../../lib/forms";
import { useFormMenuSteps } from "../../../../lib/navigation/formMenuSteps";
import { useFormResultsSecondNavigation } from "../../../../lib/navigation/formResultsSecondNavigation";
export default function ResultsResponsesPage() {
const router = useRouter();
const formId = router.query.id.toString();
const { form, isLoadingForm } = useForm(router.query.id);
const formMenuSteps = useFormMenuSteps(formId);
const formResultsSecondNavigation = useFormResultsSecondNavigation(formId);
if (isLoadingForm) {
return <Loading />;
}
return (
<BaseLayoutAuthorized
title={`${form.name} - snoopForms`}
breadcrumbs={[{ name: form.name, href: "#", current: true }]}
steps={formMenuSteps}
currentStep="results"
limitHeightScreen={true}
>
<SecondNavBar
navItems={formResultsSecondNavigation}
currentItemId="responses"
/>
<FullWidth>
<ResultsResponses formId={formId} />
</FullWidth>
</BaseLayoutAuthorized>
);
}
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getSession({ req });
if (!session) {
res.statusCode = 403;
}
return { props: {} };
};

View File

@@ -0,0 +1,50 @@
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import BaseLayoutAuthorized from "../../../../components/layout/BaseLayoutAuthorized";
import LimitedWidth from "../../../../components/layout/LimitedWidth";
import SecondNavBar from "../../../../components/layout/SecondNavBar";
import Loading from "../../../../components/Loading";
import ResultsSummary from "../../../../components/results/ResultsSummary";
import { useForm } from "../../../../lib/forms";
import { useFormMenuSteps } from "../../../../lib/navigation/formMenuSteps";
import { useFormResultsSecondNavigation } from "../../../../lib/navigation/formResultsSecondNavigation";
export default function ResultsSummaryPage() {
const router = useRouter();
const formId = router.query.id.toString();
const { form, isLoadingForm } = useForm(router.query.id);
const formMenuSteps = useFormMenuSteps(formId);
const formResultsSecondNavigation = useFormResultsSecondNavigation(formId);
if (isLoadingForm) {
return <Loading />;
}
return (
<BaseLayoutAuthorized
title={`${form.name} - snoopForms`}
breadcrumbs={[{ name: form.name, href: "#", current: true }]}
steps={formMenuSteps}
currentStep="results"
limitHeightScreen={true}
>
<SecondNavBar
navItems={formResultsSecondNavigation}
currentItemId="summary"
/>
<LimitedWidth>
<ResultsSummary formId={formId} />
</LimitedWidth>
</BaseLayoutAuthorized>
);
}
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getSession({ req });
if (!session) {
res.statusCode = 403;
}
return { props: {} };
};

View File

@@ -1,8 +1,8 @@
import LayoutBasic from "../../components/layout/LayoutBasic";
import FormList from "../../components/FormList";
import Head from "next/head";
import { useForms } from "../../lib/forms";
import BaseLayoutAuthorized from "../../components/layout/BaseLayoutAuthorized";
import LimitedWidth from "../../components/layout/LimitedWidth";
import Loading from "../../components/Loading";
import { useForms } from "../../lib/forms";
export default function Forms({}) {
const { isLoadingForms } = useForms();
@@ -12,16 +12,14 @@ export default function Forms({}) {
}
return (
<>
<Head>
<title>Your forms - snoopForms</title>
</Head>
<LayoutBasic>
<div className="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div className="px-4 py-8 sm:px-0">
<FormList />
</div>
</div>
</LayoutBasic>
<BaseLayoutAuthorized
title={"Forms - snoopForms"}
breadcrumbs={[{ name: "My Forms", href: "#", current: true }]}
>
<LimitedWidth>
<FormList />
</LimitedWidth>
</BaseLayoutAuthorized>
</>
);
}

View File

@@ -101,3 +101,11 @@
@apply text-transparent bg-clip-text bg-gradient-to-br from-red to-pink-500 hover:to-red;
}
}
.tooltip {
@apply absolute invisible;
}
.has-tooltip:hover .tooltip {
@apply z-50 visible;
}

View File

@@ -4,173 +4,6 @@ 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",
},
colors: {
red: {
DEFAULT: "#f53b57",
50: "#fff1f2",
100: "#ffe4e5",
200: "#fecdd2",
300: "#fea3ad",
400: "#fc7081",
500: "#f53b57",
600: "#e21c43",
700: "#bf1137",
800: "#a01136",
900: "#891235",
},
green: {
DEFAULT: "#0be881",
50: "#eefff6",
100: "#d7ffed",
200: "#b2ffda",
300: "#77febe",
400: "#35f39a",
500: "#0be881",
600: "#02b763",
700: "#068f50",
800: "#0b7042",
900: "#0b5c38",
},
pink: {
DEFAULT: "#ef5777",
50: "#fef2f2",
100: "#fee5e8",
200: "#fccfd6",
300: "#faa7b4",
400: "#f6768e",
500: "#ef5777",
600: "#da2453",
700: "#b81845",
800: "#9a1740",
900: "#84173d",
},
purple: {
DEFAULT: "#575fcf",
50: "#f2f3fc",
100: "#e2e4f7",
200: "#cbd0f2",
300: "#a8b2e8",
400: "#7e8bdc",
500: "#575fcf",
600: "#4d4cc4",
700: "#4842b3",
800: "#413a93",
900: "#373375",
},
blue: {
DEFAULT: "#4bcffa",
50: "#f0faff",
100: "#e0f4fe",
200: "#baeafd",
300: "#7cdbfd",
400: "#4bcffa",
500: "#0cb4eb",
600: "#0190c8",
700: "#0273a2",
800: "#066186",
900: "#0b506f",
},
teal: {
DEFAULT: "#34e7e4",
50: "#effefd",
100: "#c7fffa",
200: "#90fff6",
300: "#50f8ef",
400: "#34e7e4",
500: "#04c8c7",
600: "#009da1",
700: "#057d80",
800: "#0a6065",
900: "#0d5154",
},
amber: {
DEFAULT: "#ffc048",
50: "#fff9eb",
100: "#ffeec6",
200: "#ffda88",
300: "#ffc048",
400: "#ffa820",
500: "#f98407",
600: "#dd5f02",
700: "#b74006",
800: "#94300c",
900: "#7a290d",
},
orange: {
DEFAULT: "#ffa801",
50: "#fffdea",
100: "#fff6c5",
200: "#ffed85",
300: "#ffdc46",
400: "#ffca1b",
500: "#ffa801",
600: "#e27f00",
700: "#bb5702",
800: "#984308",
900: "#7c370b",
},
yellow: {
DEFAULT: "#ffdd59",
50: "#fffceb",
100: "#fff6c6",
200: "#ffeb88",
300: "#ffdd59",
400: "#ffc820",
500: "#f9a607",
600: "#dd7d02",
700: "#b75806",
800: "#94430c",
900: "#7a380d",
},
gray: {
DEFAULT: "#d2dae2",
50: "#f6f8f9",
100: "#eceff2",
200: "#d2dae2",
300: "#aebdcb",
400: "#8299ae",
500: "#627d95",
600: "#4e657b",
700: "#405164",
800: "#384654",
900: "#323d48",
},
"ui-gray": {
lighter: "#FAFAFB",
light: "#E5EAEF",
medium: "#B5BFC8",
dark: "#6B7177",
},
black: {
DEFAULT: "#1e272e",
50: "#f2f7f9",
100: "#dfebee",
200: "#c2d7df",
300: "#98bbc8",
400: "#6696aa",
500: "#4b7a8f",
600: "#416579",
700: "#395465",
800: "#354855",
900: "#1e272e",
},
white: {
DEFAULT: "#ffffff",
},
},
extend: {
strokeWidth: {
thin: "0.5px",
@@ -185,6 +18,173 @@ module.exports = {
text: {
KPI: "5rem",
},
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",
},
colors: {
red: {
DEFAULT: "#f53b57",
50: "#fff1f2",
100: "#ffe4e5",
200: "#fecdd2",
300: "#fea3ad",
400: "#fc7081",
500: "#f53b57",
600: "#e21c43",
700: "#bf1137",
800: "#a01136",
900: "#891235",
},
green: {
DEFAULT: "#0be881",
50: "#eefff6",
100: "#d7ffed",
200: "#b2ffda",
300: "#77febe",
400: "#35f39a",
500: "#0be881",
600: "#02b763",
700: "#068f50",
800: "#0b7042",
900: "#0b5c38",
},
pink: {
DEFAULT: "#ef5777",
50: "#fef2f2",
100: "#fee5e8",
200: "#fccfd6",
300: "#faa7b4",
400: "#f6768e",
500: "#ef5777",
600: "#da2453",
700: "#b81845",
800: "#9a1740",
900: "#84173d",
},
purple: {
DEFAULT: "#575fcf",
50: "#f2f3fc",
100: "#e2e4f7",
200: "#cbd0f2",
300: "#a8b2e8",
400: "#7e8bdc",
500: "#575fcf",
600: "#4d4cc4",
700: "#4842b3",
800: "#413a93",
900: "#373375",
},
blue: {
DEFAULT: "#4bcffa",
50: "#f0faff",
100: "#e0f4fe",
200: "#baeafd",
300: "#7cdbfd",
400: "#4bcffa",
500: "#0cb4eb",
600: "#0190c8",
700: "#0273a2",
800: "#066186",
900: "#0b506f",
},
teal: {
DEFAULT: "#34e7e4",
50: "#effefd",
100: "#c7fffa",
200: "#90fff6",
300: "#50f8ef",
400: "#34e7e4",
500: "#04c8c7",
600: "#009da1",
700: "#057d80",
800: "#0a6065",
900: "#0d5154",
},
amber: {
DEFAULT: "#ffc048",
50: "#fff9eb",
100: "#ffeec6",
200: "#ffda88",
300: "#ffc048",
400: "#ffa820",
500: "#f98407",
600: "#dd5f02",
700: "#b74006",
800: "#94300c",
900: "#7a290d",
},
orange: {
DEFAULT: "#ffa801",
50: "#fffdea",
100: "#fff6c5",
200: "#ffed85",
300: "#ffdc46",
400: "#ffca1b",
500: "#ffa801",
600: "#e27f00",
700: "#bb5702",
800: "#984308",
900: "#7c370b",
},
yellow: {
DEFAULT: "#ffdd59",
50: "#fffceb",
100: "#fff6c6",
200: "#ffeb88",
300: "#ffdd59",
400: "#ffc820",
500: "#f9a607",
600: "#dd7d02",
700: "#b75806",
800: "#94430c",
900: "#7a380d",
},
gray: {
DEFAULT: "#d2dae2",
50: "#f6f8f9",
100: "#eceff2",
200: "#d2dae2",
300: "#aebdcb",
400: "#8299ae",
500: "#627d95",
600: "#4e657b",
700: "#405164",
800: "#384654",
900: "#323d48",
},
"ui-gray": {
lighter: "#FAFAFB",
light: "#E5EAEF",
medium: "#B5BFC8",
dark: "#6B7177",
},
black: {
DEFAULT: "#1e272e",
50: "#f2f7f9",
100: "#dfebee",
200: "#c2d7df",
300: "#98bbc8",
400: "#6696aa",
500: "#4b7a8f",
600: "#416579",
700: "#395465",
800: "#354855",
900: "#1e272e",
},
white: {
DEFAULT: "#ffffff",
},
},
},
},
plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")],