Files
payloadcms/examples/form-builder/next-app/components/Blocks/Form/index.tsx
Paul d053778bf2 chore: update form builder example (#6376)
Updates the form builder example
2024-05-15 21:22:54 -03:00

175 lines
4.8 KiB
TypeScript

'use client'
import type { Form as FormType } from '@payloadcms/plugin-form-builder/types'
import { useRouter } from 'next/navigation'
import React, { useCallback, useState } from 'react'
import { useForm } from 'react-hook-form'
import { Button } from '../../Button'
import { Gutter } from '../../Gutter'
import RichText from '../../RichText'
import { buildInitialFormState } from './buildInitialFormState'
import { fields } from './fields'
import classes from './index.module.scss'
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
form: FormType
introContent?: {
[k: string]: unknown
}[]
}
export const FormBlock: React.FC<
FormBlockType & {
id?: string
}
> = (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 },
getValues,
handleSubmit,
register,
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,
}))
// delay loading indicator by 1s
loadingTimerID = setTimeout(() => {
setIsLoading(true)
}, 1000)
try {
const req = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/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)
setError({
message: res.errors?.[0]?.message || 'Internal Server Error',
status: res.status,
})
return
}
setIsLoading(false)
setHasSubmitted(true)
if (confirmationType === 'redirect' && redirect) {
const { url } = redirect
const redirectUrl = url
if (redirectUrl) router.push(redirectUrl)
}
} catch (err) {
console.warn(err)
setIsLoading(false)
setError({
message: 'Something went wrong.',
})
}
}
void submitForm()
},
[router, formID, redirect, confirmationType],
)
return (
<Gutter>
<div
className={[classes.form, hasSubmitted && classes.hasSubmitted].filter(Boolean).join(' ')}
>
{enableIntro && introContent && !hasSubmitted && (
<RichText className={classes.intro} content={introContent} />
)}
{!isLoading && hasSubmitted && confirmationType === 'message' && (
<RichText className={classes.confirmationMessage} content={confirmationMessage} />
)}
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
{error && <div>{`${error.status || '500'}: ${error.message || ''}`}</div>}
{!hasSubmitted && (
<form id={formID} onSubmit={handleSubmit(onSubmit)}>
<div className={classes.fieldWrap}>
{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} />
</form>
)}
</div>
</Gutter>
)
}