191 lines
5.2 KiB
TypeScript
191 lines
5.2 KiB
TypeScript
// @ts-nocheck
|
|
"use client";
|
|
|
|
import { useRouter } from "next/navigation";
|
|
import React, { useCallback, useState } from "react";
|
|
import { useForm } from "react-hook-form";
|
|
|
|
import { Form } from "@/payload-types";
|
|
import { Button } from "./Button";
|
|
import RichText from "./RichText";
|
|
import { buildInitialFormState } from "./buildInitialFormState";
|
|
import { fields } from "./fields";
|
|
import { toast } from "react-toastify";
|
|
|
|
export type Value = unknown;
|
|
|
|
export interface Property {
|
|
[key: string]: Value;
|
|
}
|
|
|
|
export interface Data {
|
|
[key: string]: Property | Property[] | Value;
|
|
}
|
|
|
|
export type FormBlockType = {
|
|
blockName?: string;
|
|
blockType?: "formBlock";
|
|
enableIntro?: boolean | null;
|
|
form: number | Form;
|
|
introContent?: {
|
|
root: {
|
|
type: string;
|
|
children: {
|
|
type: string;
|
|
version: number;
|
|
[k: string]: unknown;
|
|
}[];
|
|
direction: ("ltr" | "rtl") | null;
|
|
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
|
indent: number;
|
|
version: number;
|
|
};
|
|
[k: string]: unknown;
|
|
} | null;
|
|
};
|
|
|
|
export const FormBlock: React.FC<
|
|
FormBlockType & {
|
|
id?: string | null;
|
|
}
|
|
> = (props) => {
|
|
const {
|
|
enableIntro,
|
|
form: formFromProps,
|
|
form: { id: formID, confirmationMessage, confirmationType, redirect, submitButtonLabel } = {},
|
|
introContent,
|
|
} = props;
|
|
|
|
const formMethods = useForm({
|
|
defaultValues: buildInitialFormState(formFromProps.fields),
|
|
});
|
|
const {
|
|
control,
|
|
formState: { errors },
|
|
handleSubmit,
|
|
register,
|
|
reset,
|
|
setValue,
|
|
} = formMethods;
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [hasSubmitted, setHasSubmitted] = useState<boolean>();
|
|
const [error, setError] = useState<{ message: string; status?: string } | undefined>();
|
|
const router = useRouter();
|
|
|
|
const onSubmit = useCallback(
|
|
(data: Data) => {
|
|
let loadingTimerID: ReturnType<typeof setTimeout>;
|
|
|
|
const submitForm = async () => {
|
|
setError(undefined);
|
|
|
|
const dataToSend = Object.entries(data).map(([name, value]) => ({
|
|
field: name,
|
|
value,
|
|
}));
|
|
|
|
const toastId = toast.loading("Sending...");
|
|
|
|
// delay loading indicator by 1s
|
|
loadingTimerID = setTimeout(() => {
|
|
setIsLoading(true);
|
|
}, 250);
|
|
|
|
try {
|
|
const req = await fetch(`/api/form-submissions`, {
|
|
body: JSON.stringify({
|
|
form: formID,
|
|
submissionData: dataToSend,
|
|
}),
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
method: "POST",
|
|
});
|
|
|
|
const res = await req.json();
|
|
|
|
clearTimeout(loadingTimerID);
|
|
|
|
if (req.status >= 400) {
|
|
setIsLoading(false);
|
|
|
|
toast.error(res.errors?.[0]?.message || "Internal Server Error");
|
|
toast.dismiss(toastId);
|
|
setError({
|
|
message: res.errors?.[0]?.message || "Internal Server Error",
|
|
status: res.status,
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
toast.success(<RichText content={confirmationMessage} />);
|
|
toast.dismiss(toastId);
|
|
setIsLoading(false);
|
|
setHasSubmitted(true);
|
|
reset();
|
|
|
|
if (confirmationType === "redirect" && redirect) {
|
|
const { url } = redirect;
|
|
|
|
const redirectUrl = url;
|
|
|
|
if (redirectUrl) router.push(redirectUrl);
|
|
}
|
|
} catch (err) {
|
|
console.warn(err);
|
|
|
|
toast.error("Something went wrong");
|
|
toast.dismiss(toastId);
|
|
setIsLoading(false);
|
|
setError({
|
|
message: "Something went wrong",
|
|
});
|
|
}
|
|
};
|
|
|
|
void submitForm();
|
|
},
|
|
[router, formID, redirect, confirmationType]
|
|
);
|
|
|
|
return (
|
|
<div>
|
|
{!!formFromProps?.title && <h2>{formFromProps.title}</h2>}
|
|
<div className="mt-4">
|
|
{!!enableIntro && introContent && !hasSubmitted && <RichText content={introContent} />}
|
|
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
|
|
{error && <div className="text-red-500">{`${error.status || "500"}: ${error.message || ""}`}</div>}
|
|
|
|
<form id={formID} onSubmit={handleSubmit(onSubmit)} className="rd-form">
|
|
<div className="row space-y-4">
|
|
{formFromProps &&
|
|
formFromProps.fields &&
|
|
formFromProps.fields.map((field, index) => {
|
|
const Field: React.FC<any> = fields?.[field.blockType];
|
|
if (Field) {
|
|
return (
|
|
<React.Fragment key={index}>
|
|
<Field
|
|
form={formFromProps}
|
|
{...field}
|
|
{...formMethods}
|
|
control={control}
|
|
errors={errors}
|
|
register={register}
|
|
/>
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
return null;
|
|
})}
|
|
</div>
|
|
<Button appearance="primary" el="button" form={formID} label={submitButtonLabel} isLoading={isLoading} />
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|