From e41a7b0f4d7105bdaab77716a2e539172453c299 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 5 Mar 2024 10:49:27 +0200 Subject: [PATCH] feat(SLB-218): contact form and mutation example in FE --- apps/website/gatsby-node.mjs | 8 ++ apps/website/src/templates/contact.tsx | 6 + apps/website/src/utils/drupal-executor.ts | 63 +++++++--- .../schema/src/operations/ContactRequest.gql | 17 +++ .../Molecules/ContactForm.stories.ts | 9 ++ .../src/components/Molecules/ContactForm.tsx | 118 ++++++++++++++++++ .../ui/src/components/Organisms/Contact.tsx | 11 ++ packages/ui/src/components/Routes/Contact.tsx | 20 +++ packages/ui/src/utils/operation.ts | 69 +++++++--- tests/schema/specs/contact.spec.ts | 2 +- 10 files changed, 288 insertions(+), 35 deletions(-) create mode 100644 apps/website/src/templates/contact.tsx create mode 100644 packages/schema/src/operations/ContactRequest.gql create mode 100644 packages/ui/src/components/Molecules/ContactForm.stories.ts create mode 100644 packages/ui/src/components/Molecules/ContactForm.tsx create mode 100644 packages/ui/src/components/Organisms/Contact.tsx create mode 100644 packages/ui/src/components/Routes/Contact.tsx diff --git a/apps/website/gatsby-node.mjs b/apps/website/gatsby-node.mjs index aaaf21162..b3b3a79c1 100644 --- a/apps/website/gatsby-node.mjs +++ b/apps/website/gatsby-node.mjs @@ -102,6 +102,14 @@ export const createPages = async ({ actions }) => { }); }); + // Create a contact page in each language. + Object.values(Locale).forEach((locale) => { + actions.createPage({ + path: `/${locale}/contact`, + component: resolve(`./src/templates/contact.tsx`), + }); + }); + // Broken Gatsby links will attempt to load page-data.json files, which don't exist // and also should not be piped into the strangler function. Thats why they // are caught right here. diff --git a/apps/website/src/templates/contact.tsx b/apps/website/src/templates/contact.tsx new file mode 100644 index 000000000..77f8e781b --- /dev/null +++ b/apps/website/src/templates/contact.tsx @@ -0,0 +1,6 @@ +import { Contact } from '@custom/ui/routes/Contact'; +import React from 'react'; + +export default function ContentHubPage() { + return +} diff --git a/apps/website/src/utils/drupal-executor.ts b/apps/website/src/utils/drupal-executor.ts index a892e7a6e..0dfc1ad6d 100644 --- a/apps/website/src/utils/drupal-executor.ts +++ b/apps/website/src/utils/drupal-executor.ts @@ -9,23 +9,52 @@ export function drupalExecutor(endpoint: string, forward: boolean = true) { variables?: OperationVariables, ) { const url = new URL(endpoint, window.location.origin); - url.searchParams.set('queryId', id); - url.searchParams.set('variables', JSON.stringify(variables)); - const { data, errors } = await ( - await fetch(url, { - credentials: 'include', - headers: forward - ? { - 'SLB-Forwarded-Proto': window.location.protocol.slice(0, -1), - 'SLB-Forwarded-Host': window.location.hostname, - 'SLB-Forwarded-Port': window.location.port, - } - : {}, - }) - ).json(); - if (errors) { - throw errors; + if (variables && variables.graphqlOperationType === 'mutation') { + const { data, errors } = await ( + await fetch(url, { + method: 'POST', + credentials: 'include', + body: JSON.stringify({ + 'queryId': id, + 'variables': variables.variables, + }), + headers: forward + ? { + 'SLB-Forwarded-Proto': window.location.protocol.slice(0, -1), + 'SLB-Forwarded-Host': window.location.hostname, + 'SLB-Forwarded-Port': window.location.port, + "Content-Type": "application/json", + } + : { + "Content-Type": "application/json", + }, + }) + ).json(); + if (errors) { + throw errors; + } + return data; + } else { + url.searchParams.set('queryId', id); + if (variables?.variables) { + url.searchParams.set('variables', JSON.stringify(variables.variables)); + } + const { data, errors } = await ( + await fetch(url, { + credentials: 'include', + headers: forward + ? { + 'SLB-Forwarded-Proto': window.location.protocol.slice(0, -1), + 'SLB-Forwarded-Host': window.location.hostname, + 'SLB-Forwarded-Port': window.location.port, + } + : {}, + }) + ).json(); + if (errors) { + throw errors; + } + return data; } - return data; }; } diff --git a/packages/schema/src/operations/ContactRequest.gql b/packages/schema/src/operations/ContactRequest.gql new file mode 100644 index 000000000..8cf6c09e6 --- /dev/null +++ b/packages/schema/src/operations/ContactRequest.gql @@ -0,0 +1,17 @@ +mutation ContactRequest ( + $contact: ContactInput +){ + createContact(contact: $contact) { + errors { + key + field + message + } + contact { + name + email + message + subject + } + } +} \ No newline at end of file diff --git a/packages/ui/src/components/Molecules/ContactForm.stories.ts b/packages/ui/src/components/Molecules/ContactForm.stories.ts new file mode 100644 index 000000000..cb7511a9f --- /dev/null +++ b/packages/ui/src/components/Molecules/ContactForm.stories.ts @@ -0,0 +1,9 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { ContactForm as Component } from './ContactForm'; + +export default { + component: Component, +} satisfies Meta; + +export const Empty = {} satisfies StoryObj; diff --git a/packages/ui/src/components/Molecules/ContactForm.tsx b/packages/ui/src/components/Molecules/ContactForm.tsx new file mode 100644 index 000000000..e5e8f52d6 --- /dev/null +++ b/packages/ui/src/components/Molecules/ContactForm.tsx @@ -0,0 +1,118 @@ +import { ContactRequestMutation } from '@custom/schema'; +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { useIntl } from 'react-intl'; +import { z } from 'zod'; + +import { useMutation } from '../../utils/operation'; +import { Messages } from './Messages'; + +const formValueSchema = z.object({ + name: z.string(), + email: z.string(), + subject: z.string().optional(), + message: z.string(), +}); + +export function ContactForm() { + const intl = useIntl(); + type FormValue = z.infer; + const { register, handleSubmit } = useForm(); + + const { data, trigger, isMutating } = useMutation(ContactRequestMutation); + const errorMessages = (!isMutating && data && data.createContact?.errors && data.createContact.errors.length > 0 ) ? + data.createContact.errors.map(error => { + return error?.message || ''; + }) + : + null; + return ( +
+ { errorMessages ? : null} +
{ + // @todo: fix this + // @ts-ignore + trigger({ + contact: values, + }); + })} + > +
+ + +
+
+ + +
+
+ + +
+
+ +