From dcf25a027d86ed4b64b661898187d475fbdc8ac7 Mon Sep 17 00:00:00 2001 From: HARUMI JANG <harumi.jang@acquia.com> Date: Tue, 13 Dec 2022 19:26:41 -0500 Subject: [PATCH 1/7] WIP: Added updated webform to client side --- .../components/withCustomStyles.tsx | 28 +++++++ examples/example-webform/package.json | 3 +- .../example-webform/pages/client-side.tsx | 81 ++++++++++++++++++- yarn.lock | 39 ++------- 4 files changed, 116 insertions(+), 35 deletions(-) create mode 100644 examples/example-webform/components/withCustomStyles.tsx diff --git a/examples/example-webform/components/withCustomStyles.tsx b/examples/example-webform/components/withCustomStyles.tsx new file mode 100644 index 000000000..61e3b6ba1 --- /dev/null +++ b/examples/example-webform/components/withCustomStyles.tsx @@ -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; diff --git a/examples/example-webform/package.json b/examples/example-webform/package.json index 7ca374b65..f29c75fe3 100644 --- a/examples/example-webform/package.json +++ b/examples/example-webform/package.json @@ -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-alpha1" }, "devDependencies": { "@babel/core": "^7.12.9", diff --git a/examples/example-webform/pages/client-side.tsx b/examples/example-webform/pages/client-side.tsx index fe30142b2..aa05c76e0 100644 --- a/examples/example-webform/pages/client-side.tsx +++ b/examples/example-webform/pages/client-side.tsx @@ -7,14 +7,25 @@ import { useForm } from "react-hook-form" import * as yup from "yup" import { yupResolver } from "@hookform/resolvers/yup" import { contactFormSchema } from "../validations/contact" - +import { + resolveWebformContent, + Webform, + components, +} from "nextjs-drupal-webform" +import { drupal } from "basic-starter/lib/drupal" type FormData = yup.TypeOf<typeof contactFormSchema> +import classNames from "classnames" +import withCustomStyles from "../components/withCustomStyles" interface WebformPageProps { teams: DrupalTaxonomyTerm[] + webformObject: object } -export default function WebformPage({ teams }: WebformPageProps) { +export default function WebformPage({ + teams, + webformObject, +}: WebformPageProps) { const [status, setStatus] = React.useState<"error" | "success">() const { register, handleSubmit, formState, reset } = useForm<FormData>({ resolver: yupResolver(contactFormSchema), @@ -45,6 +56,45 @@ export default function WebformPage({ teams }: WebformPageProps) { return setStatus("error") } + 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([ + "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", + ]), + } + return ( <> <Head> @@ -61,6 +111,32 @@ export default function WebformPage({ teams }: WebformPageProps) { </a>{" "} module to submit the form values directly to Drupal. </p> + {console.log("webformObject", webformObject)} + <Webform + id="contact" + data={webformObject} + // onSubmit={{}} + // customComponents={{ + // textfield: withCustomStyles( + // components.textfield, + // fieldProps, + // labelProps, + // wrapperProps + // ), + // textarea: withCustomStyles( + // components.textarea, + // fieldProps, + // labelProps, + // wrapperProps + // ), + // select: withCustomStyles( + // components.select, + // fieldProps, + // labelProps, + // wrapperProps + // ), + // }} + /> <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"> @@ -186,6 +262,7 @@ export async function getStaticProps(): Promise< // Load terms terms for the contact form. return { props: { + webformObject: await resolveWebformContent("contact", drupal), teams: await getResourceCollection("taxonomy_term--team"), }, } diff --git a/yarn.lock b/yarn.lock index 754f1b578..a6f713f6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3280,21 +3280,14 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== -"@types/react-dom@17.0.2": +"@types/react-dom@17.0.2", "@types/react-dom@^18.0.9": version "17.0.2" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.2.tgz#35654cf6c49ae162d5bc90843d5437dc38008d43" integrity sha512-Icd9KEgdnFfJs39KyRyr0jQ7EKhq8U6CcHRMGAS45fp5qgUvxL3ujUCfWFttUK2UErqZNj97t9gsVPNAqcwoCg== dependencies: "@types/react" "*" -"@types/react-dom@^18.0.9": - version "18.0.9" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.9.tgz#ffee5e4bfc2a2f8774b15496474f8e7fe8d0b504" - integrity sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@17.0.2", "@types/react@^17.0.0": +"@types/react@*", "@types/react@17.0.2", "@types/react@^17.0.0", "@types/react@^17.0.43", "@types/react@^18.0.26": version "17.0.2" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8" integrity sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA== @@ -3302,24 +3295,6 @@ "@types/prop-types" "*" csstype "^3.0.2" -"@types/react@^17.0.43": - version "17.0.52" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.52.tgz#10d8b907b5c563ac014a541f289ae8eaa9bf2e9b" - integrity sha512-vwk8QqVODi0VaZZpDXQCmEmiOuyjEFPY7Ttaw5vjM112LOq37yz1CDJGrRJwA1fYEq4Iitd5rnjd1yWAc/bT+A== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/react@^18.0.26": - version "18.0.26" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917" - integrity sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -3327,11 +3302,6 @@ dependencies: "@types/node" "*" -"@types/scheduler@*": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== - "@types/sinonjs__fake-timers@8.1.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" @@ -10114,6 +10084,11 @@ next@^12.2.3: "@next/swc-win32-ia32-msvc" "12.2.3" "@next/swc-win32-x64-msvc" "12.2.3" +nextjs-drupal-webform@^1.0.0-alpha1: + version "1.0.0-alpha1" + resolved "https://registry.yarnpkg.com/nextjs-drupal-webform/-/nextjs-drupal-webform-1.0.0-alpha1.tgz#f4106207907c7a001ed8367edebc4c80218dc949" + integrity sha512-2eyXvp5mavu9QFSmLwWttSuYEon5CcPn3fIxUelVwicgo00OrKZ/9kaCKuEDNeWfYY8HrSQGtal4ANiJ5U908g== + node-abi@^3.3.0: version "3.22.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.22.0.tgz#00b8250e86a0816576258227edbce7bbe0039362" From 59590d2a66bd75e88cc5b432711910c10c46b154 Mon Sep 17 00:00:00 2001 From: HARUMI JANG <harumi.jang@acquia.com> Date: Wed, 14 Dec 2022 10:51:42 -0500 Subject: [PATCH 2/7] Revert client-side file --- .../example-webform/pages/client-side.tsx | 81 +------------------ 1 file changed, 2 insertions(+), 79 deletions(-) diff --git a/examples/example-webform/pages/client-side.tsx b/examples/example-webform/pages/client-side.tsx index aa05c76e0..fe30142b2 100644 --- a/examples/example-webform/pages/client-side.tsx +++ b/examples/example-webform/pages/client-side.tsx @@ -7,25 +7,14 @@ import { useForm } from "react-hook-form" import * as yup from "yup" import { yupResolver } from "@hookform/resolvers/yup" import { contactFormSchema } from "../validations/contact" -import { - resolveWebformContent, - Webform, - components, -} from "nextjs-drupal-webform" -import { drupal } from "basic-starter/lib/drupal" + type FormData = yup.TypeOf<typeof contactFormSchema> -import classNames from "classnames" -import withCustomStyles from "../components/withCustomStyles" interface WebformPageProps { teams: DrupalTaxonomyTerm[] - webformObject: object } -export default function WebformPage({ - teams, - webformObject, -}: WebformPageProps) { +export default function WebformPage({ teams }: WebformPageProps) { const [status, setStatus] = React.useState<"error" | "success">() const { register, handleSubmit, formState, reset } = useForm<FormData>({ resolver: yupResolver(contactFormSchema), @@ -56,45 +45,6 @@ export default function WebformPage({ return setStatus("error") } - 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([ - "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", - ]), - } - return ( <> <Head> @@ -111,32 +61,6 @@ export default function WebformPage({ </a>{" "} module to submit the form values directly to Drupal. </p> - {console.log("webformObject", webformObject)} - <Webform - id="contact" - data={webformObject} - // onSubmit={{}} - // customComponents={{ - // textfield: withCustomStyles( - // components.textfield, - // fieldProps, - // labelProps, - // wrapperProps - // ), - // textarea: withCustomStyles( - // components.textarea, - // fieldProps, - // labelProps, - // wrapperProps - // ), - // select: withCustomStyles( - // components.select, - // fieldProps, - // labelProps, - // wrapperProps - // ), - // }} - /> <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"> @@ -262,7 +186,6 @@ export async function getStaticProps(): Promise< // Load terms terms for the contact form. return { props: { - webformObject: await resolveWebformContent("contact", drupal), teams: await getResourceCollection("taxonomy_term--team"), }, } From 7a9a211c2c03dd160844646683c659ebeedc02af Mon Sep 17 00:00:00 2001 From: HARUMI JANG <harumi.jang@acquia.com> Date: Wed, 14 Dec 2022 11:07:33 -0500 Subject: [PATCH 3/7] Add custom styling --- .../components/withCustomStyles.tsx | 6 +- examples/example-webform/package.json | 2 +- .../example-webform/pages/server-side.tsx | 300 +++++++++++------- yarn.lock | 15 +- 4 files changed, 207 insertions(+), 116 deletions(-) diff --git a/examples/example-webform/components/withCustomStyles.tsx b/examples/example-webform/components/withCustomStyles.tsx index 61e3b6ba1..d417e5e69 100644 --- a/examples/example-webform/components/withCustomStyles.tsx +++ b/examples/example-webform/components/withCustomStyles.tsx @@ -21,8 +21,8 @@ const withCustomStyles = ( ...wrapperProps, }} /> - ); + ) } -}; +} -export default withCustomStyles; +export default withCustomStyles diff --git a/examples/example-webform/package.json b/examples/example-webform/package.json index f29c75fe3..97853d73b 100644 --- a/examples/example-webform/package.json +++ b/examples/example-webform/package.json @@ -23,7 +23,7 @@ "react-dom": "^17.0.2", "react-hook-form": "^7.25.3", "yup": "^0.32.11", - "nextjs-drupal-webform": "^1.0.0-alpha1" + "nextjs-drupal-webform": "^1.0.0-alpha2" }, "devDependencies": { "@babel/core": "^7.12.9", diff --git a/examples/example-webform/pages/server-side.tsx b/examples/example-webform/pages/server-side.tsx index 28afe26f2..a32dd3bf1 100644 --- a/examples/example-webform/pages/server-side.tsx +++ b/examples/example-webform/pages/server-side.tsx @@ -8,18 +8,65 @@ import * as yup from "yup" import { yupResolver } from "@hookform/resolvers/yup" import { contactFormSchema } from "../validations/contact" +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" type FormData = yup.TypeOf<typeof contactFormSchema> interface WebformPageProps { teams: DrupalTaxonomyTerm[] + webform: WebformProps } -export default function WebformPage({ teams }: WebformPageProps) { +export default function WebformPage({ teams, webform }: WebformPageProps) { const [status, setStatus] = React.useState<"error" | "success">() const { register, handleSubmit, formState, reset } = useForm<FormData>({ resolver: yupResolver(contactFormSchema), }) + 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([ + "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", + ]), + } // This makes a POST to a custom API route. // The Drupal base URL and the webform_id are NOT exposed. @@ -58,113 +105,149 @@ export default function WebformPage({ teams }: WebformPageProps) { 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> - </div> + <Webform + id="contact" + data={webform} + 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, + buttonProps, + labelProps, + wrapperProps + ), + }} + /> + {/*<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>*/} + {/*</div>*/} <p> <Link href="/" passHref> <a>Go back</a> @@ -183,6 +266,7 @@ export async function getStaticProps(): Promise< return { props: { teams: await getResourceCollection("taxonomy_term--team"), + webform: await resolveWebformContent("contact", drupal), }, } } diff --git a/yarn.lock b/yarn.lock index a6f713f6d..dbda8cd81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4623,6 +4623,11 @@ classnames@^2.3.1: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== +classnames@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" + integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -10084,10 +10089,12 @@ next@^12.2.3: "@next/swc-win32-ia32-msvc" "12.2.3" "@next/swc-win32-x64-msvc" "12.2.3" -nextjs-drupal-webform@^1.0.0-alpha1: - version "1.0.0-alpha1" - resolved "https://registry.yarnpkg.com/nextjs-drupal-webform/-/nextjs-drupal-webform-1.0.0-alpha1.tgz#f4106207907c7a001ed8367edebc4c80218dc949" - integrity sha512-2eyXvp5mavu9QFSmLwWttSuYEon5CcPn3fIxUelVwicgo00OrKZ/9kaCKuEDNeWfYY8HrSQGtal4ANiJ5U908g== +nextjs-drupal-webform@^1.0.0-alpha2: + version "1.0.0-alpha2" + resolved "https://registry.yarnpkg.com/nextjs-drupal-webform/-/nextjs-drupal-webform-1.0.0-alpha2.tgz#03f494b2f8a585ecff3c4bc0f55daeba08aedab9" + integrity sha512-O+AszzKB1qRT0wFmrqthb1EvCz6rOxE+lbZq1ycj5eW2ftPjWOaui6icKWYVYUH+hkxTD3FYVQbMv7qukpuzEw== + dependencies: + classnames "^2.3.2" node-abi@^3.3.0: version "3.22.0" From 02445aa6457ac8246815dd78b472154faa5cfc62 Mon Sep 17 00:00:00 2001 From: HARUMI JANG <harumi.jang@acquia.com> Date: Thu, 15 Dec 2022 10:53:33 -0500 Subject: [PATCH 4/7] Pass in data-cy to submit button --- .../example-webform/pages/server-side.tsx | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/examples/example-webform/pages/server-side.tsx b/examples/example-webform/pages/server-side.tsx index a32dd3bf1..ff207b3a4 100644 --- a/examples/example-webform/pages/server-side.tsx +++ b/examples/example-webform/pages/server-side.tsx @@ -66,23 +66,24 @@ export default function WebformPage({ teams, webform }: WebformPageProps) { "shadow-sm", "hover:bg-black", ]), + "data-cy": "btn-submit", } // 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") - } + // 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") + // } return ( <> @@ -140,6 +141,7 @@ export default function WebformPage({ teams, webform }: WebformPageProps) { wrapperProps ), }} + // className="space-y-6" /> {/*<div className="w-full max-w-md p-6 space-y-4 border rounded-md shadow">*/} {/* {status === "error" ? (*/} From 13612b755d936c85302b9d5f723e7f066b2dbf89 Mon Sep 17 00:00:00 2001 From: HARUMI JANG <harumi.jang@acquia.com> Date: Wed, 21 Dec 2022 14:16:33 -0500 Subject: [PATCH 5/7] Update example and update text --- .../components/WebformButton.tsx | 17 ++ examples/example-webform/lib/drupal.ts | 17 ++ examples/example-webform/package.json | 2 +- examples/example-webform/pages/api/webform.ts | 10 + examples/example-webform/pages/index.tsx | 15 +- .../example-webform/pages/server-side.tsx | 232 ++++-------------- yarn.lock | 8 +- 7 files changed, 101 insertions(+), 200 deletions(-) create mode 100644 examples/example-webform/components/WebformButton.tsx create mode 100644 examples/example-webform/lib/drupal.ts create mode 100644 examples/example-webform/pages/api/webform.ts diff --git a/examples/example-webform/components/WebformButton.tsx b/examples/example-webform/components/WebformButton.tsx new file mode 100644 index 000000000..cb1050bd0 --- /dev/null +++ b/examples/example-webform/components/WebformButton.tsx @@ -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) diff --git a/examples/example-webform/lib/drupal.ts b/examples/example-webform/lib/drupal.ts new file mode 100644 index 000000000..cd653cc63 --- /dev/null +++ b/examples/example-webform/lib/drupal.ts @@ -0,0 +1,17 @@ +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", + }, + // @see https://github.com/vercel/next.js/discussions/32238 + // @see https://github.com/vercel/next.js/blob/d895a50abbc8f91726daa2d7ebc22c58f58aabbb/packages/next/server/api-utils/node.ts#L504 + forceIframeSameSiteCookie: process.env.NODE_ENV === "development", + } +) diff --git a/examples/example-webform/package.json b/examples/example-webform/package.json index 97853d73b..5cfeecc30 100644 --- a/examples/example-webform/package.json +++ b/examples/example-webform/package.json @@ -23,7 +23,7 @@ "react-dom": "^17.0.2", "react-hook-form": "^7.25.3", "yup": "^0.32.11", - "nextjs-drupal-webform": "^1.0.0-alpha2" + "nextjs-drupal-webform": "^1.0.0-beta1" }, "devDependencies": { "@babel/core": "^7.12.9", diff --git a/examples/example-webform/pages/api/webform.ts b/examples/example-webform/pages/api/webform.ts new file mode 100644 index 000000000..60889f251 --- /dev/null +++ b/examples/example-webform/pages/api/webform.ts @@ -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) +} diff --git a/examples/example-webform/pages/index.tsx b/examples/example-webform/pages/index.tsx index 661473675..743c4be72 100644 --- a/examples/example-webform/pages/index.tsx +++ b/examples/example-webform/pages/index.tsx @@ -1,5 +1,6 @@ import Head from "next/head" import Link from "next/link" +import * as React from "react" export default function IndexPage() { return ( @@ -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> diff --git a/examples/example-webform/pages/server-side.tsx b/examples/example-webform/pages/server-side.tsx index ff207b3a4..0e3185731 100644 --- a/examples/example-webform/pages/server-side.tsx +++ b/examples/example-webform/pages/server-side.tsx @@ -2,12 +2,6 @@ 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" import withCustomStyles from "../components/withCustomStyles" import { resolveWebformContent, @@ -17,19 +11,13 @@ import { } from "nextjs-drupal-webform" import { drupal } from "basic-starter/lib/drupal" import classNames from "classnames" - -type FormData = yup.TypeOf<typeof contactFormSchema> +import WebformButton from "../components/WebformButton" interface WebformPageProps { - teams: DrupalTaxonomyTerm[] webform: WebformProps } -export default function WebformPage({ teams, webform }: WebformPageProps) { - const [status, setStatus] = React.useState<"error" | "success">() - const { register, handleSubmit, formState, reset } = useForm<FormData>({ - resolver: yupResolver(contactFormSchema), - }) +export default function WebformPage({ webform }: WebformPageProps) { const labelProps = { className: classNames(["block", "text-sm", "font-medium", "text-gray-700"]), } @@ -54,8 +42,6 @@ export default function WebformPage({ teams, webform }: WebformPageProps) { } const buttonProps = { className: classNames([ - "flex", - "justify-center", "px-4", "py-2", "text-sm font-medium", @@ -69,22 +55,6 @@ export default function WebformPage({ teams, webform }: WebformPageProps) { "data-cy": "btn-submit", } - // 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") - // } - return ( <> <Head> @@ -95,161 +65,53 @@ export default function WebformPage({ teams, webform }: 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> - <Webform - id="contact" - data={webform} - 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, - buttonProps, - labelProps, - wrapperProps - ), - }} - // className="space-y-6" - /> - {/*<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>*/} - {/*</div>*/} + <div className="w-full max-w-md p-6 space-y-4 border rounded-md shadow"> + <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> <a>Go back</a> @@ -264,10 +126,8 @@ export default function WebformPage({ teams, webform }: 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), }, } diff --git a/yarn.lock b/yarn.lock index dbda8cd81..3610e9737 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10089,10 +10089,10 @@ next@^12.2.3: "@next/swc-win32-ia32-msvc" "12.2.3" "@next/swc-win32-x64-msvc" "12.2.3" -nextjs-drupal-webform@^1.0.0-alpha2: - version "1.0.0-alpha2" - resolved "https://registry.yarnpkg.com/nextjs-drupal-webform/-/nextjs-drupal-webform-1.0.0-alpha2.tgz#03f494b2f8a585ecff3c4bc0f55daeba08aedab9" - integrity sha512-O+AszzKB1qRT0wFmrqthb1EvCz6rOxE+lbZq1ycj5eW2ftPjWOaui6icKWYVYUH+hkxTD3FYVQbMv7qukpuzEw== +nextjs-drupal-webform@^1.0.0-beta1: + version "1.0.0-beta1" + resolved "https://registry.yarnpkg.com/nextjs-drupal-webform/-/nextjs-drupal-webform-1.0.0-beta1.tgz#fbb7989856fa0f559b599c3859c0922f4c5f7bba" + integrity sha512-qdV0LVZvBS9+VEI3IwGp43HkY2z6WuMxRWEuoVC9thqRxTWvspoCGCNYqpdm37IqN9uUgafQyzpQa6eI08ejSA== dependencies: classnames "^2.3.2" From 89afc9562f04070c2a0e1c9ceeabb4f39896793c Mon Sep 17 00:00:00 2001 From: HARUMI JANG <harumi.jang@acquia.com> Date: Wed, 21 Dec 2022 14:34:36 -0500 Subject: [PATCH 6/7] Update confirmation msg in test --- examples/example-webform/cypress/integration/webform.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example-webform/cypress/integration/webform.spec.tsx b/examples/example-webform/cypress/integration/webform.spec.tsx index 78b838bf3..47f6026ca 100644 --- a/examples/example-webform/cypress/integration/webform.spec.tsx +++ b/examples/example-webform/cypress/integration/webform.spec.tsx @@ -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") }) }) From da29f76ad035984a7e55c0d69b5a4e736e0be63c Mon Sep 17 00:00:00 2001 From: HARUMI JANG <harumi.jang@acquia.com> Date: Wed, 21 Dec 2022 14:46:51 -0500 Subject: [PATCH 7/7] remove option from DrupalClient --- examples/example-webform/lib/drupal.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/example-webform/lib/drupal.ts b/examples/example-webform/lib/drupal.ts index cd653cc63..a1e60835e 100644 --- a/examples/example-webform/lib/drupal.ts +++ b/examples/example-webform/lib/drupal.ts @@ -10,8 +10,5 @@ export const drupal = new DrupalClient( scope: "administrator developer site_builder content_administrator content_author content_editor user_administrator headless", }, - // @see https://github.com/vercel/next.js/discussions/32238 - // @see https://github.com/vercel/next.js/blob/d895a50abbc8f91726daa2d7ebc22c58f58aabbb/packages/next/server/api-utils/node.ts#L504 - forceIframeSameSiteCookie: process.env.NODE_ENV === "development", } )