Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added example of Next.js Webform #398

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
17 changes: 17 additions & 0 deletions examples/example-webform/components/WebformButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { components } from "nextjs-drupal-webform"
import classNames from "classnames"

const buttonDecorator = (DecoratedComponent) => {
return function WebformCustomTable(props) {
const fieldProps = props.fieldProps ?? {}
const additionalClasses = []
if (props.element["#button_type"] === "primary") {
additionalClasses.push("bg-black hover:bg-black text-white")
}
fieldProps.className = classNames(fieldProps.className, additionalClasses)

return <DecoratedComponent {...props} fieldProps={fieldProps} />
}
}

export default buttonDecorator(components.button)
28 changes: 28 additions & 0 deletions examples/example-webform/components/withCustomStyles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const withCustomStyles = (
EnhancedComponent,
fieldProps = {},
labelProps = {},
wrapperProps = {}
) => {
return function WebformElementWithCustomStyles(props) {
return (
<EnhancedComponent
{...props}
labelProps={{
...(props.labelProps ?? {}),
...labelProps,
}}
fieldProps={{
...(props.fieldProps ?? {}),
...fieldProps,
}}
wrapperProps={{
...(props.wrapperProps ?? {}),
...wrapperProps,
}}
/>
)
}
}

export default withCustomStyles
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ context("server side webform", () => {
cy.get("[name=message]").type("This is my message.")
cy.get("[data-cy=btn-submit]").click()

cy.contains("Your message has been sent. Thank you.")
cy.contains("New submission added to Contact")
})
})

Expand Down
14 changes: 14 additions & 0 deletions examples/example-webform/lib/drupal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { DrupalClient } from "next-drupal"

export const drupal = new DrupalClient(
process.env.NEXT_PUBLIC_DRUPAL_BASE_URL,
{
previewSecret: process.env.DRUPAL_PREVIEW_SECRET,
auth: {
clientId: process.env.DRUPAL_CLIENT_ID,
clientSecret: process.env.DRUPAL_CLIENT_SECRET,
scope:
"administrator developer site_builder content_administrator content_author content_editor user_administrator headless",
},
}
)
3 changes: 2 additions & 1 deletion examples/example-webform/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-hook-form": "^7.25.3",
"yup": "^0.32.11"
"yup": "^0.32.11",
"nextjs-drupal-webform": "^1.0.0-beta1"
},
"devDependencies": {
"@babel/core": "^7.12.9",
Expand Down
10 changes: 10 additions & 0 deletions examples/example-webform/pages/api/webform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NextApiRequest, NextApiResponse } from "next"
import { drupal } from "../../lib/drupal"
import { WebformDefaultApiRoute } from "nextjs-drupal-webform"

export default async function handler(
request: NextApiRequest,
response: NextApiResponse
) {
return WebformDefaultApiRoute(request, response, drupal)
}
15 changes: 6 additions & 9 deletions examples/example-webform/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Head from "next/head"
import Link from "next/link"
import * as React from "react"

export default function IndexPage() {
return (
Expand Down Expand Up @@ -33,19 +34,15 @@ export default function IndexPage() {
<a>See Example</a>
</Link>
</p>
<h2>Server Side (API Route)</h2>
<h2>Server Side</h2>
<p>
We submit the form values to a custom API route first. The API route
then submits the form to Drupal using the{" "}
<a href="https://www.drupal.org/project/webform_rest">
Webform REST
This example uses the{" "}
<a href="https://www.drupal.org/project/next_webform">
Next.js Webform
</a>{" "}
library to render and submit forms built using the Drupal Webform
module.
</p>
<p>
This is useful if we need to hide client IDs and secrets or our
Drupal implementation.
</p>
<p>
<Link href="/server-side" passHref>
<a>See Example</a>
Expand Down
234 changes: 90 additions & 144 deletions examples/example-webform/pages/server-side.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,57 @@ import * as React from "react"
import { GetStaticPropsResult } from "next"
import Head from "next/head"
import Link from "next/link"
import { getResourceCollection, DrupalTaxonomyTerm } from "next-drupal"
import { useForm } from "react-hook-form"
import * as yup from "yup"
import { yupResolver } from "@hookform/resolvers/yup"

import { contactFormSchema } from "../validations/contact"

type FormData = yup.TypeOf<typeof contactFormSchema>
import withCustomStyles from "../components/withCustomStyles"
import {
resolveWebformContent,
Webform,
components,
WebformProps,
} from "nextjs-drupal-webform"
import { drupal } from "basic-starter/lib/drupal"
import classNames from "classnames"
import WebformButton from "../components/WebformButton"

interface WebformPageProps {
teams: DrupalTaxonomyTerm[]
webform: WebformProps
}

export default function WebformPage({ teams }: WebformPageProps) {
const [status, setStatus] = React.useState<"error" | "success">()
const { register, handleSubmit, formState, reset } = useForm<FormData>({
resolver: yupResolver(contactFormSchema),
})

// This makes a POST to a custom API route.
// The Drupal base URL and the webform_id are NOT exposed.
async function onSubmit(data: FormData) {
const response = await fetch(`/api/contact`, {
method: "POST",
body: JSON.stringify(data),
})

if (response.ok) {
reset()
return setStatus("success")
}

return setStatus("error")
export default function WebformPage({ webform }: WebformPageProps) {
const labelProps = {
className: classNames(["block", "text-sm", "font-medium", "text-gray-700"]),
}
const fieldProps = {
className: classNames([
"relative",
"block",
"w-full px-3",
"py-2 mt-1 text-gray-900",
"placeholder-gray-500",
"border border-gray-300",
"rounded-md appearance-none",
"focus:outline-none",
"focus:ring-black",
"focus:border-black",
"focus:z-10",
"sm:text-sm",
]),
}
const wrapperProps = {
className: classNames(["space-y-3"]),
}
const buttonProps = {
className: classNames([
"px-4",
"py-2",
"text-sm font-medium",
"text-white",
"bg-black",
"border border-transparent",
"rounded-md",
"shadow-sm",
"hover:bg-black",
]),
"data-cy": "btn-submit",
}

return (
Expand All @@ -47,123 +65,52 @@ export default function WebformPage({ teams }: WebformPageProps) {
<h1>Next.js for Drupal</h1>
<h2>Webform Example - Server Side</h2>
<p>
We submit the form values to a custom API route first. The API route
then submits the form to Drupal using the{" "}
<a href="https://www.drupal.org/project/webform_rest">
Webform REST
This example uses the{" "}
<a href="https://www.drupal.org/project/next_webform">
Next.js Webform
</a>{" "}
library to render and submit forms built using the Drupal Webform
module.
</p>
<p>
This is useful if we need to hide client IDs and secrets or our
Drupal implementation.
</p>
<div className="w-full max-w-md p-6 space-y-4 border rounded-md shadow">
{status === "error" ? (
<div className="px-4 py-2 text-sm text-red-600 bg-red-100 border-red-200 rounded-md">
An error occured. Please try again.
</div>
) : null}
{status === "success" ? (
<div className="px-4 py-2 text-sm text-green-600 bg-green-100 border-green-200 rounded-md">
Your message has been sent. Thank you.
</div>
) : null}
{Object.values(formState.errors)?.length ? (
<div className="px-4 py-2 text-sm text-red-600 bg-red-100 border-red-200 rounded-md">
{Object.values(formState.errors).map((error, index) => (
<p key={index}>{error.message}</p>
))}
</div>
) : null}
<form className="space-y-6" onSubmit={handleSubmit(onSubmit)}>
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-700"
>
Name
</label>
<input
id="name"
name="name"
type="text"
className="relative block w-full px-3 py-2 mt-1 text-gray-900 placeholder-gray-500 border border-gray-300 rounded-md appearance-none focus:outline-none focus:ring-black focus:border-black focus:z-10 sm:text-sm"
{...register("name")}
/>
</div>
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700"
>
Email
</label>
<input
id="email"
name="email"
type="email"
className="relative block w-full px-3 py-2 mt-1 text-gray-900 placeholder-gray-500 border border-gray-300 rounded-md appearance-none focus:outline-none focus:ring-black focus:border-black focus:z-10 sm:text-sm"
{...register("email")}
/>
</div>
<div>
<label
htmlFor="team"
className="block text-sm font-medium text-gray-700"
>
Team
</label>
<select
id="team"
name="team"
className="relative block w-full px-3 py-2 mt-1 text-gray-900 placeholder-gray-500 border border-gray-300 rounded-md appearance-none focus:outline-none focus:ring-black focus:border-black focus:z-10 sm:text-sm"
{...register("team")}
>
<option value="">-- Select --</option>
{teams.map((team) => (
<option value={team.drupal_internal__tid} key={team.id}>
{team.name}
</option>
))}
</select>
</div>
<div>
<label
htmlFor="subject"
className="block text-sm font-medium text-gray-700"
>
Subject
</label>
<input
id="subject"
name="subject"
className="relative block w-full px-3 py-2 mt-1 text-gray-900 placeholder-gray-500 border border-gray-300 rounded-md appearance-none focus:outline-none focus:ring-black focus:border-black focus:z-10 sm:text-sm"
{...register("subject")}
/>
</div>
<div>
<label
htmlFor="message"
className="block text-sm font-medium text-gray-700"
>
Message
</label>
<textarea
id="message"
name="message"
className="relative block w-full h-32 px-3 py-2 mt-1 text-gray-900 placeholder-gray-500 border border-gray-300 rounded-md appearance-none focus:outline-none focus:ring-black focus:border-black focus:z-10 sm:text-sm"
{...register("message")}
></textarea>
</div>
<button
type="submit"
data-cy="btn-submit"
className="flex justify-center px-4 py-2 text-sm font-medium text-white bg-black border border-transparent rounded-md shadow-sm hover:bg-black"
>
Submit
</button>
</form>
<Webform
id="contact"
data={webform}
className="space-y-6"
customComponents={{
textfield: withCustomStyles(
components.textfield,
fieldProps,
labelProps,
wrapperProps
),
email: withCustomStyles(
components.email,
fieldProps,
labelProps,
wrapperProps
),
textarea: withCustomStyles(
components.textarea,
fieldProps,
labelProps,
wrapperProps
),
select: withCustomStyles(
components.select,
fieldProps,
labelProps,
wrapperProps
),
webform_actions: withCustomStyles(
components.webform_actions,
{},
{},
{ className: classNames("my-4", "space-x-4") }
),
button: withCustomStyles(WebformButton, buttonProps, {}, {}),
}}
/>
</div>
<p>
<Link href="/" passHref>
Expand All @@ -179,10 +126,9 @@ export default function WebformPage({ teams }: WebformPageProps) {
export async function getStaticProps(): Promise<
GetStaticPropsResult<WebformPageProps>
> {
// Load terms terms for the contact form.
return {
props: {
teams: await getResourceCollection("taxonomy_term--team"),
webform: await resolveWebformContent("contact", drupal),
},
}
}
Loading