add submit button, add form preview, autosave form in builder

This commit is contained in:
Matthias Nannt
2022-06-21 15:37:34 +09:00
parent ae5ae7b46c
commit 9dd2f557d2
11 changed files with 9307 additions and 1832 deletions

View File

@@ -5,6 +5,7 @@ import Loading from "../Loading";
import Page from "./Page";
import UsageIntro from "./UsageIntro";
import LoadingModal from "../LoadingModal";
import Link from "next/link";
export default function Builder({ formId }) {
const { noCodeForm, isLoadingNoCodeForm, mutateNoCodeForm } =
@@ -13,6 +14,13 @@ export default function Builder({ formId }) {
const [isInitialized, setIsInitialized] = useState(false);
const [isLoading, setIsLoading] = useState(false);
// autosave
useEffect(() => {
if (isInitialized) {
save();
}
}, [pagesDraft, isInitialized]);
const save = async () => {
setIsLoading(true);
const newNoCodeForm = JSON.parse(JSON.stringify(noCodeForm));
@@ -61,18 +69,17 @@ export default function Builder({ formId }) {
<div className="relative z-10 flex flex-shrink-0 h-16 border-b border-gray-200 shadow-inner bg-gray-50">
<div className="flex items-center justify-center flex-1 px-4">
<nav className="flex space-x-4" aria-label="resultModes">
<button
onClick={() => save()}
className="px-3 py-2 text-sm font-medium text-gray-600 border border-gray-800 rounded-md hover:text-gray-600"
>
Save
</button>
<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>
</nav>
</div>
</div>

View File

@@ -4,6 +4,7 @@ import EditorJS from "@editorjs/editorjs";
import DragDrop from "editorjs-drag-drop";
import Undo from "editorjs-undo";
import TextQuestion from "./tools/TextQuestion";
import SubmitButton from "./tools/SubmitButton";
const Editor = ({ id, autofocus = false, onChange, value }) => {
const [blocks, setBlocks] = useState([]);
@@ -43,7 +44,7 @@ const Editor = ({ id, autofocus = false, onChange, value }) => {
setBlocks(content.blocks);
},
autofocus: autofocus,
tools: { textQuestion: TextQuestion },
tools: { textQuestion: TextQuestion, submitButton: SubmitButton },
});
};

View File

@@ -0,0 +1,61 @@
import { API, BlockTool, BlockToolData, ToolConfig } from "@editorjs/editorjs";
import { ArrowRightIcon } from "@heroicons/react/solid";
import ReactDOM from "react-dom";
//styles imports in angular.json
interface TextQuestionData extends BlockToolData {
latexString: string;
}
export default class TextQuestion implements BlockTool {
label: string;
placeholder: string;
api: API;
static get toolbox(): { icon: string; title?: string } {
return {
icon: `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M18.1 15.3C18 15.4 17.8 15.5 17.7 15.6L15.3 16L17 19.6C17.2 20 17 20.4 16.6 20.6L13.8 21.9C13.7 22 13.6 22 13.5 22C13.2 22 12.9 21.8 12.8 21.6L11.2 18L9.3 19.5C9.2 19.6 9 19.7 8.8 19.7C8.4 19.7 8 19.4 8 18.9V7.5C8 7 8.3 6.7 8.8 6.7C9 6.7 9.2 6.8 9.3 6.9L18 14.3C18.3 14.5 18.4 15 18.1 15.3M6 12H4V4H20V12H18.4L20.6 13.9C21.4 13.6 21.9 12.9 21.9 12V4C21.9 2.9 21 2 19.9 2H4C2.9 2 2 2.9 2 4V12C2 13.1 2.9 14 4 14H6V12Z" />`,
title: "Submit Button",
};
}
constructor({
data,
}: {
api: API;
config?: ToolConfig;
data?: TextQuestionData;
}) {
this.label = data.label || "Submit";
this.placeholder = data.placeholder;
}
save(block: HTMLDivElement) {
console.log(
(block.firstElementChild.firstElementChild as HTMLInputElement).innerHTML
);
return {
label: (block.firstElementChild.firstElementChild as HTMLInputElement)
.innerHTML,
};
}
render(): HTMLElement {
const container = document.createElement("div");
const toolView = (
<div className="inline-flex items-center px-4 py-2 pb-3 text-sm font-medium text-white bg-gray-700 border border-transparent rounded-md shadow-sm hover:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<div
contentEditable
id="label"
defaultValue={this.label}
className="p-0 bg-transparent border-transparent ring-0 active:ring-0 focus:border-transparent focus:ring-0 focus:outline-none"
>
{this.label}
</div>
<ArrowRightIcon className="w-5 h-5 ml-2 -mr-1" aria-hidden="true" />
</div>
);
ReactDOM.render(toolView, container);
return container;
}
}

View File

@@ -30,9 +30,6 @@ export default class TextQuestion implements BlockTool {
}
save(block: HTMLDivElement) {
// console.log(block)
// ;(window as any).x = block
return {
label: (block.firstElementChild.firstElementChild as HTMLInputElement)
.value,
@@ -41,18 +38,12 @@ export default class TextQuestion implements BlockTool {
).value,
};
}
/* renderSettings(): HTMLElement {
renderSettings(): HTMLElement {
return document.createElement("div");
} */
}
render(): HTMLElement {
/* this.wrapperDiv.innerHTML = "";
this.latexDiv.innerHTML = "";
this.renderLatex();
this.wrapperDiv.append(this.latexDiv);
this.wrapperDiv.append(this.editTextfield);
return this.wrapperDiv; */
const container = document.createElement("div");
const toolView = (
<div className="pb-3">
@@ -60,12 +51,12 @@ export default class TextQuestion implements BlockTool {
type="text"
id="label"
defaultValue={this.label}
className="block p-0 text-sm font-medium text-gray-700 border-0 border-transparent ring-0 focus:ring-0"
className="block w-full p-0 text-base font-medium text-gray-700 border-0 border-transparent ring-0 focus:ring-0"
placeholder="Your Question"
/>
<input
type="text"
className="block w-full mt-1 text-gray-400 border-gray-300 rounded-md shadow-sm sm:text-sm placeholder:text-gray-300"
className="block w-full mt-1 text-gray-400 border-gray-300 rounded-md shadow-sm sm:text-base placeholder:text-gray-300"
placeholder="optional placeholder"
defaultValue={this.placeholder}
/>

View File

@@ -0,0 +1,62 @@
import { SnoopElement, SnoopForm, SnoopPage } from "@snoopforms/react";
import { useMemo } from "react";
import { useNoCodeForm } from "../../lib/noCodeForm";
import Loading from "../Loading";
export default function App({ id = "", formId, draft = false }) {
const { noCodeForm, isLoadingNoCodeForm, mutateNoCodeForm } =
useNoCodeForm(formId);
const pages = useMemo(() => {
if (!isLoadingNoCodeForm) {
return noCodeForm[draft ? "pagesDraft" : "pages"];
}
}, [draft, isLoadingNoCodeForm, noCodeForm]);
if (!pages) {
return <Loading />;
}
return (
<div className="w-full px-5 py-5">
<SnoopForm
key={id} // used to reset form
domain="localhost:3000"
protocol="http"
formId="kQ1L4BLH"
className="w-full max-w-3xl mx-auto space-y-6"
>
{pages.map((page) => (
<SnoopPage key={page.id} name={page.id}>
{page.blocks.map((block) =>
block.type === "paragraph" ? (
<p>{block.data.text}</p>
) : block.type === "textQuestion" ? (
<SnoopElement
type="text"
name={"name"}
label={block.data.label}
classNames={{
label: "mt-4 block text-sm font-medium text-gray-800",
element:
"flex-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300",
}}
required
/>
) : block.type === "submitButton" ? (
<SnoopElement
name="submit"
type="submit"
label={block.data.label}
classNames={{
button:
"flex justify-center px-4 py-2 mt-5 text-sm font-medium text-white border border-transparent rounded-md shadow-sm bg-red-600 hover:bg-red-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-600",
}}
/>
) : null
)}
</SnoopPage>
))}
</SnoopForm>
</div>
);
}

View File

@@ -0,0 +1,63 @@
import Head from "next/head";
import Link from "next/link";
import { ArrowLeftIcon, RefreshIcon } from "@heroicons/react/outline";
import { useSession, signIn } from "next-auth/react";
import Loading from "../Loading";
export default function LayoutShare({ formId, resetApp, 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>Form Preview</title>
</Head>
<div className="flex min-h-screen overflow-hidden bg-gray-50">
<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 border-gray-200 shadow-sm">
<div className="flex flex-1 px-4 sm:px-6">
<div className="flex items-center flex-1">
<Link href={`/forms/${formId}/form`}>
<a>
<ArrowLeftIcon className="w-6 h-6" aria-hidden="true" />
</a>
</Link>
</div>
<p className="flex items-center justify-center flex-1 text-gray-600">
Preview
</p>
<div className="flex items-center justify-end flex-1 space-x-2 text-right sm:ml-6 sm:space-x-4">
<button
type="button"
onClick={() => resetApp()}
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"
>
Restart
<RefreshIcon
className="w-5 h-5 ml-2 -mr-1"
aria-hidden="true"
/>
</button>
</div>
</div>
</div>
</header>
{/* Main content */}
{children}
</div>
</div>
</>
);
}

View File

@@ -1,4 +1,6 @@
/** @type {import('next').NextConfig} */
var path = require("path");
const nextConfig = {
reactStrictMode: false,
async redirects() {
@@ -10,6 +12,11 @@ const nextConfig = {
},
];
},
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
config.resolve.alias["react"] = path.resolve("./node_modules/react");
// Important: return the modified config
return config;
},
};
module.exports = nextConfig;

7279
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@
"@headlessui/react": "^1.6.1",
"@heroicons/react": "^1.0.6",
"@prisma/client": "^3.15.1",
"@snoopforms/react": "file:../snoopforms-react",
"babel-plugin-superjson-next": "^0.4.3",
"bcryptjs": "^2.4.3",
"date-fns": "^2.28.0",

View File

@@ -0,0 +1,44 @@
import { GetServerSideProps } from "next";
import { getSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useState } from "react";
import App from "../../../components/frontend/App";
import LayoutPreview from "../../../components/layout/LayoutPreview";
import Loading from "../../../components/Loading";
import { useForm } from "../../../lib/forms";
import { v4 as uuidv4 } from "uuid";
export default function Share({}) {
const router = useRouter();
const formId = router.query.id.toString();
const { form, isLoadingForm } = useForm(formId);
const [appId, setAppId] = useState(uuidv4());
const resetApp = () => {
setAppId(uuidv4());
};
if (isLoadingForm) {
return <Loading />;
}
if (form.formType !== "NOCODE") {
return (
<div>Preview is only avaiblable for Forms built with No-Code-Editor</div>
);
}
return (
<LayoutPreview formId={formId} resetApp={resetApp}>
<App id={appId} formId={formId} draft={true} />
</LayoutPreview>
);
}
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getSession({ req });
if (!session) {
res.statusCode = 403;
}
return { props: {} };
};

3581
yarn.lock

File diff suppressed because it is too large Load Diff