diff --git a/apps/website/src/templates/contact.tsx b/apps/website/src/templates/contact.tsx index 09dbe50e5..5889f02d9 100644 --- a/apps/website/src/templates/contact.tsx +++ b/apps/website/src/templates/contact.tsx @@ -1,6 +1,6 @@ import { Contact } from '@custom/ui/routes/Contact'; import React from 'react'; -export default function ContentHubPage() { +export default function ContactPage() { return ; } diff --git a/packages/drupal/custom/custom.info.yml b/packages/drupal/custom/custom.info.yml index e0d6338fd..732a2f809 100644 --- a/packages/drupal/custom/custom.info.yml +++ b/packages/drupal/custom/custom.info.yml @@ -6,3 +6,4 @@ dependencies: - silverback_gatsby:silverback_gatsby - simple_oauth:simple_oauth - consumers:consumers + - drupal:serialization diff --git a/packages/drupal/custom/custom.services.yml b/packages/drupal/custom/custom.services.yml index 2f01322df..0ad082aad 100644 --- a/packages/drupal/custom/custom.services.yml +++ b/packages/drupal/custom/custom.services.yml @@ -9,8 +9,4 @@ services: custom.webform: class: Drupal\custom\Webform - arguments: ['@renderer', '@entity_type.manager'] - - custom.contact: - class: Drupal\custom\Contact - arguments: ['@custom.webform'] + arguments: ['@renderer', '@entity_type.manager', '@serializer'] diff --git a/packages/drupal/custom/src/Contact.php b/packages/drupal/custom/src/Contact.php deleted file mode 100644 index c0a4d7b26..000000000 --- a/packages/drupal/custom/src/Contact.php +++ /dev/null @@ -1,118 +0,0 @@ -webformService = $webformService; - } - - /** - * Creates a contact webform entry. - */ - public function create(DirectiveArguments $args): array { - try { - $submissionData = [ - 'name' => $args->args['contact']['name'], - 'email' => $args->args['contact']['email'], - 'subject' => $args->args['contact']['subject'], - 'message' => $args->args['contact']['message'], - ]; - $webformSubmission = $this->webformService->createSubmission('contact', $submissionData); - - // If we get an array from the createSubmission call, then it means there - // were errors during the insert / validate operation, so we just return - // them. - if (is_array($webformSubmission)) { - return [ - 'errors' => $this->formatErrors($webformSubmission), - 'contact' => NULL, - ]; - } - - // We successfully submitted the data. - if (is_object($webformSubmission) && $webformSubmission instanceof WebformSubmissionInterface) { - $submittedData = $webformSubmission->getData(); - return [ - 'errors' => NULL, - 'contact' => [ - 'id' => $webformSubmission->id(), - 'name' => $submittedData['name'], - 'email' => $submittedData['email'], - 'subject' => $submittedData['subject'], - 'message' => $submittedData['message'], - ], - ]; - } - } catch (\InvalidArgumentException $e) { - return [ - 'contact' => NULL, - 'errors' => [ - [ - 'message' => $e->getMessage(), - 'key' => 'invalid_webform' - ] - ] - ]; - } catch (\Exception $e) { - return [ - 'contact' => NULL, - 'errors' => [ - [ - 'message' => $e->getMessage(), - 'key' => 'invalid_input' - ] - ] - ]; - } - - // We should actually never get here... if we do, we don't know what - // happened. - return [ - 'contact' => NULL, - 'errors' => [ - [ - 'message' => 'Unknown error', - 'key' => 'unknown_error', - ] - ] - ]; - } - - /** - * Helper method to arrange a set of webform submission errors in a way that - * can be used by the MutationError graphl type. - */ - protected function formatErrors(array $webformSubmissionErrors) { - $formattedErrors = []; - foreach ($webformSubmissionErrors as $fieldName => $error) { - $formattedErrors[] = [ - 'message' => $error->__toString(), - 'key' => 'invalid_field_' . $fieldName, - 'field' => $fieldName, - ]; - } - return $formattedErrors; - } -} diff --git a/packages/drupal/custom/src/Webform.php b/packages/drupal/custom/src/Webform.php index 3cc44c7fa..541f367ae 100644 --- a/packages/drupal/custom/src/Webform.php +++ b/packages/drupal/custom/src/Webform.php @@ -8,6 +8,8 @@ use Drupal\graphql_directives\DirectiveArguments; use Drupal\webform\WebformSubmissionForm; use Drupal\webform\WebformSubmissionInterface; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\SerializerInterface; /** * Helper service for managing webforms with graphql. @@ -26,15 +28,24 @@ class Webform { */ protected EntityTypeManagerInterface $entityTypeManager; + /** + * The serializer serice. + * + * @var \Symfony\Component\Serializer\SerializerInterface + */ + protected Serializer $serializer; + /** * Webform constructor. */ public function __construct( RendererInterface $renderer, - EntityTypeManagerInterface $entityTypeManager + EntityTypeManagerInterface $entityTypeManager, + SerializerInterface $serializer ) { $this->entityTypeManager = $entityTypeManager; $this->renderer = $renderer; + $this->serializer = $serializer; } /** @@ -71,34 +82,102 @@ function () use ($args, $webformId, $webFormStorage) { } /** - * Creates a webform submission. - * - * @param string $webformId - * @param array $submissionData - * - * @return \Drupal\webform\WebformSubmissionInterface|array|null - * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException - * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + * Directive to create a webform submission. */ - public function createSubmission(string $webformId, array $submissionData) : WebformSubmissionInterface | array | null { - $webform = $this->entityTypeManager->getStorage('webform')->load($webformId); - if (!$webform) { - throw new \InvalidArgumentException('The webform could no be loaded.'); - } - $isOpen = WebformSubmissionForm::isOpen($webform); - if ($isOpen !== TRUE) { - throw new \Exception($isOpen); + public function createSubmission(DirectiveArguments $args) : array { + try { + $webformId = $args->args['webformId']; + $webform = $this->entityTypeManager->getStorage('webform') + ->load($webformId); + if (!$webform) { + throw new \InvalidArgumentException('The webform could no be loaded.'); + } + $isOpen = WebformSubmissionForm::isOpen($webform); + if ($isOpen !== TRUE) { + throw new \Exception($isOpen); + } + $submittedData = $args->args['submittedData']; + $values = [ + 'webform_id' => $webformId, + 'entity_type' => NULL, + 'entity_id' => NULL, + 'data' => $this->serializer->decode($submittedData, 'json'), + ]; + // The WebformSubmissionForm::submitFormValues() will return an array with + // errors, if there are validation errors, otherwise it will return a + // webform submission entity. + $webformSubmission = WebformSubmissionForm::submitFormValues($values); + + // If we get an array from the createSubmission call, then it means there + // were errors during the insert / validate operation, so we just return + // them. + if (is_array($webformSubmission)) { + return [ + 'errors' => $this->formatErrors($webformSubmission), + 'submission' => NULL, + ]; + } + + // We successfully submitted the data. + if (is_object($webformSubmission) && $webformSubmission instanceof WebformSubmissionInterface) { + $webformSubmissionData = $webformSubmission->getData(); + return [ + 'errors' => NULL, + 'submission' => $this->serializer->encode(array_merge([ + 'submissionId' => $webformSubmission->id(), + ], $webformSubmissionData), 'json'), + ]; + } + } catch (\InvalidArgumentException $e) { + return [ + 'submission' => NULL, + 'errors' => [ + [ + 'message' => $e->getMessage(), + 'key' => 'invalid_webform' + ] + ] + ]; + } catch (\Exception $e) { + return [ + 'submission' => NULL, + 'errors' => [ + [ + 'message' => $e->getMessage(), + 'key' => 'invalid_input' + ] + ] + ]; } - $values = [ - 'webform_id' => 'contact', - 'entity_type' => NULL, - 'entity_id' => NULL, - 'data' => $submissionData, + + // We should actually never get here... if we do, we don't know what + // happened. + return [ + 'submission' => NULL, + 'errors' => [ + [ + 'message' => 'Unknown error', + 'key' => 'unknown_error', + ] + ] ]; - // The WebformSubmissionForm::submitFormValues() will return an array with - // errors, if there are validation errors, otherwise it will return a - // webform submission entity. - return WebformSubmissionForm::submitFormValues($values); + + } + + /** + * Helper method to arrange a set of webform submission errors in a way that + * can be used by the MutationError graphl type. + */ + protected function formatErrors(array $webformSubmissionErrors) { + $formattedErrors = []; + foreach ($webformSubmissionErrors as $fieldName => $error) { + $formattedErrors[] = [ + 'message' => $error->__toString(), + 'key' => 'invalid_field_' . $fieldName, + 'field' => $fieldName, + ]; + } + return $formattedErrors; } } diff --git a/packages/schema/codegen.ts b/packages/schema/codegen.ts index 44f75b652..de805092a 100644 --- a/packages/schema/codegen.ts +++ b/packages/schema/codegen.ts @@ -8,6 +8,7 @@ const scalars = { Markup: '@amazeelabs/scalars#Markup', Url: '@amazeelabs/scalars#Url', ImageSource: '@amazeelabs/scalars#ImageSource', + JSONString: 'string', }; const common = { diff --git a/packages/schema/src/operations/ContactRequest.gql b/packages/schema/src/operations/ContactRequest.gql index 878063452..08c3d89f4 100644 --- a/packages/schema/src/operations/ContactRequest.gql +++ b/packages/schema/src/operations/ContactRequest.gql @@ -1,15 +1,10 @@ -mutation ContactRequest($contact: ContactInput) { - createContact(contact: $contact) { +mutation CreateSubmission($webformId: String!, $submittedData: JSONString!) { + createWebformSubmission(webformId: $webformId, submittedData: $submittedData) { errors { key field message } - contact { - name - email - message - subject - } + submission } } diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index cea80f342..7574b26a6 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -1,6 +1,7 @@ scalar Url @default @value(string: "") scalar Markup @default @value(string: "") scalar ImageSource @default @value(string: "") +scalar JSONString @default @value(string: "{}") """ implementation(drupal): custom.content_hub::query @@ -17,9 +18,9 @@ implementation(drupal): custom.webform::url directive @webformIdToUrl(id: String!) on FIELD_DEFINITION """ -implementation(drupal): custom.contact::create +implementation(drupal): custom.webform::createSubmission """ -directive @createContact(contact: ContactInput) on FIELD_DEFINITION +directive @createWebformSubmission(webformId: String!, submittedData: JSONString!) on FIELD_DEFINITION """ Retrieve the properties of an image. @@ -237,28 +238,13 @@ type Query { } type Mutation { - createContact(contact: ContactInput): ContactCreateResponse - @createContact(contact: "$contact") + createWebformSubmission(webformId: String!, submittedData: JSONString!): WebformSubmissionCreateResponse + @createWebformSubmission(webformId: "$webformId", submittedData: "$submittedData") } -type Contact { - id: String! - name: String - email: String - subject: String - message: String -} - -input ContactInput { - name: String - email: String - subject: String - message: String -} - -type ContactCreateResponse { +type WebformSubmissionCreateResponse { errors: [MutationError] - contact: Contact + submission: JSONString } type MutationError { diff --git a/packages/ui/src/components/Molecules/ContactForm.tsx b/packages/ui/src/components/Molecules/ContactForm.tsx index be4c3eeda..ce09588b0 100644 --- a/packages/ui/src/components/Molecules/ContactForm.tsx +++ b/packages/ui/src/components/Molecules/ContactForm.tsx @@ -1,4 +1,4 @@ -import { ContactRequestMutation } from '@custom/schema'; +import { CreateSubmissionMutation } from '@custom/schema'; import React from 'react'; import { useForm } from 'react-hook-form'; import { useIntl } from 'react-intl'; @@ -19,18 +19,18 @@ export function ContactForm() { type FormValue = z.infer; const { register, handleSubmit } = useForm(); - const { data, trigger, isMutating } = useMutation(ContactRequestMutation); + const { data, trigger, isMutating } = useMutation(CreateSubmissionMutation); const errorMessages = !isMutating && data && - data.createContact?.errors && - data.createContact.errors.length > 0 - ? data.createContact.errors.map((error) => { + data.createWebformSubmission?.errors && + data.createWebformSubmission.errors.length > 0 + ? data.createWebformSubmission.errors.map((error) => { return error?.message || ''; }) : null; const successMessages = - !isMutating && data && data.createContact?.contact + !isMutating && data && data.createWebformSubmission?.submission ? [ intl.formatMessage({ defaultMessage: 'The contact has been submitted.', @@ -48,7 +48,8 @@ export function ContactForm() { className="mt-5 sm:items-center" onSubmit={handleSubmit((values) => { trigger({ - contact: values, + webformId: 'contact', + submittedData: JSON.stringify(values), }); })} > diff --git a/tests/schema/specs/contact.spec.ts b/tests/schema/specs/contact.spec.ts index 4d4a91c30..d83d3569e 100644 --- a/tests/schema/specs/contact.spec.ts +++ b/tests/schema/specs/contact.spec.ts @@ -7,75 +7,47 @@ describe('create contact', () => { it('creates a new contact using a graphql mutation', async () => { const result = await fetch(gql` mutation { - createContact( - contact: { - name: "John Doe" - email: "john@doe.com" - message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pretium aliquam magna." - subject: "Lorem ipsum" - } + createWebformSubmission( + webformId: "contact", + submittedData: "{\\"name\\": \\"John Doe\\", \\"email\\": \\"john@doe.com\\", \\"message\\": \\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pretium aliquam magna.\\", \\"subject\\": \\"Lorem ipsum\\"}" ) { errors { key field message } - contact { - name - email - message - subject - } + submission } } `); - expect(result).toMatchInlineSnapshot(` - { - "data": { - "createContact": { - "contact": { - "email": "john@doe.com", - "message": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pretium aliquam magna.", - "name": "John Doe", - "subject": "Lorem ipsum", - }, - "errors": null, - }, - }, - } - `); + const submission = JSON.parse(result.data.createWebformSubmission.submission); + expect(submission.email).toEqual('john@doe.com'); + expect(submission.message).toEqual('Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pretium aliquam magna.'); + expect(submission.name).toEqual('John Doe'); + expect(submission.subject).toEqual('Lorem ipsum'); + expect(result.data.createWebformSubmission.errors).toBeNull(); }); it('tries to create a new contact with an invalid e-mail address', async () => { const result = await fetch(gql` mutation { - createContact( - contact: { - name: "Jane" - email: "invalid_email" - message: "Test message." - subject: "Test subject" - } + createWebformSubmission( + webformId: "contact", + submittedData: "{\\"name\\": \\"Jane\\",\\"email\\": \\"invalid_email\\",\\"message\\": \\"Test message.\\",\\"subject\\": \\"Test subject\\"}" ) { errors { key field message } - contact { - name - email - message - subject - } + submission } } `); expect(result).toMatchInlineSnapshot(` { "data": { - "createContact": { - "contact": null, + "createWebformSubmission": { "errors": [ { "field": "email", @@ -83,6 +55,7 @@ describe('create contact', () => { "message": "The email address invalid_email is not valid. Use the format user@example.com.", }, ], + "submission": null, }, }, }