From bc1cf7b5c340834c74616d01f8314d2a83b6661b Mon Sep 17 00:00:00 2001 From: Colin Diem Date: Thu, 21 Apr 2022 17:52:22 -0400 Subject: [PATCH] Chapter 3, Improving the Contact Form. --- .../migration.sql | 8 ++ api/db/schema.prisma | 8 ++ api/src/graphql/contacts.sdl.js | 30 ++++++++ api/src/services/contacts/contacts.js | 17 +++++ .../services/contacts/contacts.scenarios.js | 6 ++ api/src/services/contacts/contacts.test.js | 54 +++++++++++++ web/src/pages/ContactPage/ContactPage.js | 75 ++++++++++++++++--- 7 files changed, 188 insertions(+), 10 deletions(-) create mode 100644 api/db/migrations/20220421213019_create_contact/migration.sql create mode 100644 api/src/graphql/contacts.sdl.js create mode 100644 api/src/services/contacts/contacts.js create mode 100644 api/src/services/contacts/contacts.scenarios.js create mode 100644 api/src/services/contacts/contacts.test.js diff --git a/api/db/migrations/20220421213019_create_contact/migration.sql b/api/db/migrations/20220421213019_create_contact/migration.sql new file mode 100644 index 0000000..8d7bd91 --- /dev/null +++ b/api/db/migrations/20220421213019_create_contact/migration.sql @@ -0,0 +1,8 @@ +-- CreateTable +CREATE TABLE "Contact" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "message" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/api/db/schema.prisma b/api/db/schema.prisma index 3e7e700..7c6c24a 100644 --- a/api/db/schema.prisma +++ b/api/db/schema.prisma @@ -14,3 +14,11 @@ model Post { body String createdAt DateTime @default(now()) } + +model Contact { + id Int @id @default(autoincrement()) + name String + email String + message String + createdAt DateTime @default(now()) +} diff --git a/api/src/graphql/contacts.sdl.js b/api/src/graphql/contacts.sdl.js new file mode 100644 index 0000000..d7b70e5 --- /dev/null +++ b/api/src/graphql/contacts.sdl.js @@ -0,0 +1,30 @@ +export const schema = gql` + type Contact { + id: Int! + name: String! + email: String! + message: String! + createdAt: DateTime! + } + + type Query { + contacts: [Contact!]! @requireAuth + contact(id: Int!): Contact @requireAuth + } + + input CreateContactInput { + name: String! + email: String! + message: String! + } + + input UpdateContactInput { + name: String + email: String + message: String + } + + type Mutation { + createContact(input: CreateContactInput!): Contact! @skipAuth + } +` diff --git a/api/src/services/contacts/contacts.js b/api/src/services/contacts/contacts.js new file mode 100644 index 0000000..a36cf89 --- /dev/null +++ b/api/src/services/contacts/contacts.js @@ -0,0 +1,17 @@ +import { db } from 'src/lib/db' + +export const contacts = () => { + return db.contact.findMany() +} + +export const contact = ({ id }) => { + return db.contact.findUnique({ + where: { id }, + }) +} + +export const createContact = ({ input }) => { + return db.contact.create({ + data: input, + }) +} diff --git a/api/src/services/contacts/contacts.scenarios.js b/api/src/services/contacts/contacts.scenarios.js new file mode 100644 index 0000000..64885cf --- /dev/null +++ b/api/src/services/contacts/contacts.scenarios.js @@ -0,0 +1,6 @@ +export const standard = defineScenario({ + contact: { + one: { data: { name: 'String', email: 'String', message: 'String' } }, + two: { data: { name: 'String', email: 'String', message: 'String' } }, + }, +}) diff --git a/api/src/services/contacts/contacts.test.js b/api/src/services/contacts/contacts.test.js new file mode 100644 index 0000000..814a53e --- /dev/null +++ b/api/src/services/contacts/contacts.test.js @@ -0,0 +1,54 @@ +import { + contacts, + contact, + createContact, + updateContact, + deleteContact, +} from './contacts' + +// Generated boilerplate tests do not account for all circumstances +// and can fail without adjustments, e.g. Float and DateTime types. +// Please refer to the RedwoodJS Testing Docs: +// https://redwoodjs.com/docs/testing#testing-services +// https://redwoodjs.com/docs/testing#jest-expect-type-considerations + +describe('contacts', () => { + scenario('returns all contacts', async (scenario) => { + const result = await contacts() + + expect(result.length).toEqual(Object.keys(scenario.contact).length) + }) + + scenario('returns a single contact', async (scenario) => { + const result = await contact({ id: scenario.contact.one.id }) + + expect(result).toEqual(scenario.contact.one) + }) + + scenario('creates a contact', async () => { + const result = await createContact({ + input: { name: 'String', email: 'String', message: 'String' }, + }) + + expect(result.name).toEqual('String') + expect(result.email).toEqual('String') + expect(result.message).toEqual('String') + }) + + scenario('updates a contact', async (scenario) => { + const original = await contact({ id: scenario.contact.one.id }) + const result = await updateContact({ + id: original.id, + input: { name: 'String2' }, + }) + + expect(result.name).toEqual('String2') + }) + + scenario('deletes a contact', async (scenario) => { + const original = await deleteContact({ id: scenario.contact.one.id }) + const result = await contact({ id: original.id }) + + expect(result).toEqual(null) + }) +}) diff --git a/web/src/pages/ContactPage/ContactPage.js b/web/src/pages/ContactPage/ContactPage.js index 0d784f9..be00375 100644 --- a/web/src/pages/ContactPage/ContactPage.js +++ b/web/src/pages/ContactPage/ContactPage.js @@ -1,21 +1,76 @@ -import { MetaTags } from '@redwoodjs/web' -import { Form, TextField, Submit, TextAreaField } from '@redwoodjs/forms' +import { MetaTags, useMutation } from '@redwoodjs/web' +import { toast, Toaster } from '@redwoodjs/web/toast' +import { + FieldError, + Form, + Label, + TextField, + Submit, + TextAreaField, +} from '@redwoodjs/forms' + +const CREATE_CONTACT = gql` + mutation CreateContactMutation($input: CreateContactInput!) { + createContact(input: $input) { + id + } + } +` const ContactPage = () => { + const [create, { loading, error }] = useMutation(CREATE_CONTACT, { + onCompleted: () => { + toast.success('Thank you for your submission!') + }, + }) + const onSubmit = (data) => { + create({ variables: { input: data } }) console.log(data) } return ( <> -
- - - - - - - Save + + + + + + + + + + + + + + + + Save )