mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-19 19:21:15 -05:00
frontend pt 3
This commit is contained in:
@@ -6,6 +6,9 @@ interface Props {
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
fullwidth?: boolean;
|
||||
small?: boolean;
|
||||
icon?: boolean;
|
||||
secondary?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -15,14 +18,20 @@ const StandardButton: React.FC<Props> = ({
|
||||
onClick = () => {},
|
||||
disabled = false,
|
||||
fullwidth = false,
|
||||
small = false,
|
||||
icon = false,
|
||||
secondary = false,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
className={classNames(
|
||||
`inline-flex items-center px-5 py-3 text-sm text-white rounded shadow-sm bg-snoopfade focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500`,
|
||||
`inline-flex items-center rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500`,
|
||||
disabled ? "disabled:opacity-50" : "",
|
||||
fullwidth ? " w-full justify-center " : ""
|
||||
fullwidth ? " w-full justify-center " : "",
|
||||
small ? "px-2.5 py-1.5 text-xs" : "px-5 py-3 text-sm",
|
||||
icon ? "px-1.5 py-1.5 text-xs" : "px-5 py-3 text-sm",
|
||||
secondary ? "bg-ui-gray-light text-red" : "bg-snoopfade text-white"
|
||||
)}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { NoCodeForm } from "@prisma/client";
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
@@ -8,6 +7,15 @@ import { persistNoCodeForm, useNoCodeForm } from "../../lib/noCodeForm";
|
||||
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 { form, isLoadingForm } = useForm(formId);
|
||||
@@ -117,35 +125,28 @@ export default function Builder({ formId }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative z-10 flex flex-shrink-0 h-16 shadow-inner bg-ui-gray-lighter">
|
||||
<div className="flex items-center justify-center flex-1 px-4">
|
||||
<nav className="flex space-x-4" aria-label="resultModes">
|
||||
<button
|
||||
onClick={() => addPage()}
|
||||
className="px-3 py-2 text-sm font-medium text-gray-600 border border-gray-800 rounded-md hover:text-gray-600"
|
||||
>
|
||||
Add Page
|
||||
</button>
|
||||
<Link href={`/forms/${formId}/preview`}>
|
||||
<a className="px-3 py-2 text-sm font-medium text-gray-600 border border-gray-800 rounded-md hover:text-gray-600">
|
||||
Preview Form
|
||||
</a>
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => publishChanges()}
|
||||
className="px-3 py-2 text-sm font-medium text-gray-600 border border-gray-800 rounded-md hover:text-gray-600"
|
||||
>
|
||||
Publish
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setOpenShareModal(true)}
|
||||
className="px-3 py-2 text-sm font-medium text-gray-600 border border-gray-800 rounded-md hover:text-gray-600"
|
||||
>
|
||||
Share
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div className="w-full bg-ui-gray-lighter">
|
||||
<div className="flex justify-center w-full">
|
||||
|
||||
@@ -2,44 +2,65 @@ import { FaReact, FaVuejs } from "react-icons/fa";
|
||||
import { DocumentSearchIcon } from "@heroicons/react/outline";
|
||||
import { classNames } from "../../lib/utils";
|
||||
import StandardButton from "../StandardButton";
|
||||
|
||||
const libs = [
|
||||
{
|
||||
id: "react",
|
||||
name: "React",
|
||||
href: "#",
|
||||
bgColor: "bg-snoopfade",
|
||||
version: "v0.1",
|
||||
icon: FaReact,
|
||||
},
|
||||
{
|
||||
id: "reactNative",
|
||||
name: "React Native",
|
||||
comingSoon: true,
|
||||
href: "#",
|
||||
bgColor: "bg-ui-gray-light",
|
||||
icon: FaReact,
|
||||
},
|
||||
{
|
||||
id: "vue",
|
||||
name: "Vue.js",
|
||||
comingSoon: true,
|
||||
href: "#",
|
||||
bgColor: "bg-ui-gray-light",
|
||||
icon: FaVuejs,
|
||||
},
|
||||
{
|
||||
id: "docs",
|
||||
name: "Docs",
|
||||
href: "#",
|
||||
bgColor: "bg-snoopfade",
|
||||
icon: DocumentSearchIcon,
|
||||
},
|
||||
];
|
||||
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`,
|
||||
bgColor: "bg-blue",
|
||||
version: "v0.1",
|
||||
icon: FaReact,
|
||||
},
|
||||
{
|
||||
id: "reactNative",
|
||||
name: "React Native",
|
||||
comingSoon: true,
|
||||
href: "#",
|
||||
bgColor: "bg-ui-gray-light",
|
||||
icon: FaReact,
|
||||
},
|
||||
{
|
||||
id: "vue",
|
||||
name: "Vue.js",
|
||||
comingSoon: true,
|
||||
href: "#",
|
||||
bgColor: "bg-ui-gray-light",
|
||||
icon: FaVuejs,
|
||||
},
|
||||
{
|
||||
id: "docs",
|
||||
name: "Docs",
|
||||
href: "https://docs.snoopforms.com",
|
||||
bgColor: "bg-ui-gray-dark",
|
||||
icon: DocumentSearchIcon,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecondNavBar>
|
||||
<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">
|
||||
@@ -53,7 +74,7 @@ export default function FormCode({ formId }) {
|
||||
<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 <strong>{"<snoopForm>"}</strong> component.
|
||||
in the <code>{"<snoopForm>"}</code> component.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-10">
|
||||
@@ -87,7 +108,7 @@ export default function FormCode({ formId }) {
|
||||
<p>
|
||||
<code>
|
||||
{"<"}
|
||||
<span className="text-yellow-200">SnoopForm</span>
|
||||
<span className="text-yellow-200">snoopForm</span>
|
||||
{""}
|
||||
</code>
|
||||
</p>
|
||||
@@ -120,7 +141,11 @@ export default function FormCode({ formId }) {
|
||||
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}>
|
||||
<a
|
||||
className="flex col-span-1 rounded-md shadow-sm"
|
||||
key={lib.id}
|
||||
href={lib.href}
|
||||
>
|
||||
<li
|
||||
className={classNames(
|
||||
lib.comingSoon
|
||||
@@ -163,12 +188,13 @@ export default function FormCode({ formId }) {
|
||||
</a>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="my-12 font-light text-center text-ui-gray-medium">
|
||||
<p>
|
||||
Your form is running? Go to{" "}
|
||||
<a href="#" className="underline text-red">
|
||||
Pipelines
|
||||
</a>
|
||||
<Link href={`/forms/${formId}/preview`}>
|
||||
<a className="underline text-red">Pipelines</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { HomeIcon } from "@heroicons/react/outline";
|
||||
import { HomeIcon, PlusIcon } 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 = [
|
||||
@@ -18,6 +26,13 @@ export default function MenuBreadcrumbs({ formId }) {
|
||||
<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/">
|
||||
|
||||
@@ -8,8 +8,8 @@ export default function MenuProfile({}) {
|
||||
<Menu as="div" className="relative flex-shrink-0">
|
||||
{({ 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-500">
|
||||
<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>
|
||||
<img
|
||||
className="w-8 h-8 rounded-full"
|
||||
|
||||
27
components/layout/SecondNavBar.tsx
Normal file
27
components/layout/SecondNavBar.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import { classNames } from "../../lib/utils";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// button component, consuming props
|
||||
const SecondNavBar: React.FC<Props> = ({
|
||||
children,
|
||||
onClick = () => {},
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<div className="relative z-10 flex flex-shrink-0 h-16 py-2 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>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecondNavBar;
|
||||
58
components/layout/SecondNavBarItem.tsx
Normal file
58
components/layout/SecondNavBarItem.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
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;
|
||||
148
pages/forms/[id]/react.tsx
Normal file
148
pages/forms/[id]/react.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import { GetServerSideProps } from "next";
|
||||
import { getSession } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import LayoutFormBasics from "../../../components/layout/LayoutFormBasic";
|
||||
import Loading from "../../../components/Loading";
|
||||
import StandardButton from "../../../components/StandardButton";
|
||||
import { useForm } from "../../../lib/forms";
|
||||
import { DocumentSearchIcon } from "@heroicons/react/outline";
|
||||
import Link from "next/link";
|
||||
import SecondNavBar from "../../../components/layout/SecondNavBar";
|
||||
import SecondNavBarItem from "../../../components/layout/SecondNavBarItem";
|
||||
import { FaReact, FaVuejs } from "react-icons/fa";
|
||||
import { FaDiscord } from "react-icons/fa";
|
||||
|
||||
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}/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>{`<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: {} };
|
||||
};
|
||||
Reference in New Issue
Block a user