diff --git a/api/prisma/migrations/20201011095227-create-contact/README.md b/api/prisma/migrations/20201011095227-create-contact/README.md new file mode 100644 index 0000000..5748d5e --- /dev/null +++ b/api/prisma/migrations/20201011095227-create-contact/README.md @@ -0,0 +1,50 @@ +# Migration `20201011095227-create-contact` + +This migration has been generated by Kurt Hutten at 10/11/2020, 8:52:27 PM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +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 +) +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20201011082558-add-code-not-needed-upon-create..20201011095227-create-contact +--- datamodel.dml ++++ datamodel.dml +@@ -1,9 +1,9 @@ + datasource DS { + // optionally set multiple providers + // example: provider = ["sqlite", "postgresql"] + provider = "sqlite" +- url = "***" ++ url = "***" + } + generator client { + provider = "prisma-client-js" +@@ -26,4 +26,12 @@ + createdAt DateTime @default(now()) + // userId + //likes, comments, reactions + } ++ ++model Contact { ++ id Int @id @default(autoincrement()) ++ name String ++ email String ++ message String ++ createdAt DateTime @default(now()) ++} +``` + + diff --git a/api/prisma/migrations/20201011095227-create-contact/schema.prisma b/api/prisma/migrations/20201011095227-create-contact/schema.prisma new file mode 100644 index 0000000..03189f2 --- /dev/null +++ b/api/prisma/migrations/20201011095227-create-contact/schema.prisma @@ -0,0 +1,37 @@ +datasource DS { + // optionally set multiple providers + // example: provider = ["sqlite", "postgresql"] + provider = "sqlite" + url = "***" +} + +generator client { + provider = "prisma-client-js" + binaryTargets = "native" +} + +model Post { + id Int @id @default(autoincrement()) + title String + body String + createdAt DateTime @default(now()) +} + +model Part { + id Int @id @default(autoincrement()) + title String + description String // markdown string + code String @default("// Welcome to Cascade Studio! Here are some useful functions:\n// Translate(), Rotate(), Scale(), Union(), Difference(), Intersection()\n// Box(), Sphere(), Cylinder(), Cone(), Text3D(), Polygon()\n// Offset(), Extrude(), RotatedExtrude(), Revolve(), Pipe(), Loft(),\n// FilletEdges(), ChamferEdges(),\n// Slider(), Button(), Checkbox()\nlet holeRadius = Slider(\"Radius\", 30 , 20 , 40);\nlet sphere = Sphere(50);\nlet cylinderZ = Cylinder(holeRadius, 200, true);\nlet cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));\nlet cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));\nTranslate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));\n\nTranslate([-25, 0, 40], Text3D(\"Hi!\"));\n// Don't forget to push imported or oc-defined shapes into sceneShapes to add them to the workspace!") + mainImage String // link to cloudinary + createdAt DateTime @default(now()) + // userId + //likes, comments, reactions +} + +model Contact { + id Int @id @default(autoincrement()) + name String + email String + message String + createdAt DateTime @default(now()) +} diff --git a/api/prisma/migrations/20201011095227-create-contact/steps.json b/api/prisma/migrations/20201011095227-create-contact/steps.json new file mode 100644 index 0000000..0ca4b72 --- /dev/null +++ b/api/prisma/migrations/20201011095227-create-contact/steps.json @@ -0,0 +1,105 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateModel", + "model": "Contact" + }, + { + "tag": "CreateField", + "model": "Contact", + "field": "id", + "type": "Int", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Contact", + "field": "id" + }, + "directive": "id" + } + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Contact", + "field": "id" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Contact", + "field": "id" + }, + "directive": "default" + }, + "argument": "", + "value": "autoincrement()" + }, + { + "tag": "CreateField", + "model": "Contact", + "field": "name", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Contact", + "field": "email", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Contact", + "field": "message", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Contact", + "field": "createdAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Contact", + "field": "createdAt" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Contact", + "field": "createdAt" + }, + "directive": "default" + }, + "argument": "", + "value": "now()" + } + ] +} \ No newline at end of file diff --git a/api/prisma/migrations/migrate.lock b/api/prisma/migrations/migrate.lock index 299515f..086c06b 100644 --- a/api/prisma/migrations/migrate.lock +++ b/api/prisma/migrations/migrate.lock @@ -3,4 +3,5 @@ 20201009213512-create-posts 20201011043647-create-parts 20201011052155-add-code-to-part -20201011082558-add-code-not-needed-upon-create \ No newline at end of file +20201011082558-add-code-not-needed-upon-create +20201011095227-create-contact \ No newline at end of file diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index 449c8ad..7f4deba 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -27,3 +27,11 @@ model Part { // userId //likes, comments, reactions } + +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..c4153b6 --- /dev/null +++ b/api/src/graphql/contacts.sdl.js @@ -0,0 +1,29 @@ +export const schema = gql` + type Contact { + id: Int! + name: String! + email: String! + message: String! + createdAt: DateTime! + } + + type Query { + contacts: [Contact!]! + } + + input CreateContactInput { + name: String! + email: String! + message: String! + } + + input UpdateContactInput { + name: String + email: String + message: String + } + + type Mutation { + createContact(input: CreateContactInput!): Contact + } +` diff --git a/api/src/services/contacts/contacts.js b/api/src/services/contacts/contacts.js new file mode 100644 index 0000000..3481975 --- /dev/null +++ b/api/src/services/contacts/contacts.js @@ -0,0 +1,24 @@ +import { UserInputError } from '@redwoodjs/api' + +import { db } from 'src/lib/db' + + +const validate = (input) => { + if (input.email && !input.email.match(/[^@]+@[^.]+\..+/)) { + throw new UserInputError("Can't create new contact", { + messages: { + email: ['is not formatted like an email address'], + }, + }) + } +} + +export const contacts = () => { + return db.contact.findMany() +} + +export const createContact = ({ input }) => { + + validate(input) + return db.contact.create({ data: input }) +} diff --git a/api/src/services/contacts/contacts.test.js b/api/src/services/contacts/contacts.test.js new file mode 100644 index 0000000..fbbbc1e --- /dev/null +++ b/api/src/services/contacts/contacts.test.js @@ -0,0 +1,9 @@ +/* +import { contacts } from './contacts' +*/ + +describe('contacts', () => { + it('returns true', () => { + expect(true).toBe(true) + }) +}) diff --git a/web/src/Routes.js b/web/src/Routes.js index 0245da0..189095b 100644 --- a/web/src/Routes.js +++ b/web/src/Routes.js @@ -12,6 +12,7 @@ import { Router, Route } from '@redwoodjs/router' const Routes = () => { return ( + diff --git a/web/src/index.css b/web/src/index.css index e69de29..d32ad66 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -0,0 +1,17 @@ + +button, input, label, textarea { + display: block; + outline: none; +} + +label { + margin-top: 1rem; +} + +.error { + color: red; +} + +input.error, textarea.error { + border: 1px solid red; +} diff --git a/web/src/index.js b/web/src/index.js index bc813e8..66c7d22 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -5,11 +5,11 @@ import FatalErrorPage from 'src/pages/FatalErrorPage' import Routes from 'src/Routes' import './scaffold.css' -import './index.css' import 'golden-layout/src/css/goldenlayout-base.css' import 'golden-layout/src/css/goldenlayout-dark-theme.css' import './cascade/css/main.css' import 'monaco-editor/min/vs/editor/editor.main.css' +import './index.css' ReactDOM.render( diff --git a/web/src/layouts/MainLayout/MainLayout.js b/web/src/layouts/MainLayout/MainLayout.js index 8412653..2782e2c 100644 --- a/web/src/layouts/MainLayout/MainLayout.js +++ b/web/src/layouts/MainLayout/MainLayout.js @@ -15,6 +15,9 @@ const MainLayout = ({ children }) => {
  • Parts
  • +
  • + Contact +
  • diff --git a/web/src/pages/ContactPage/ContactPage.js b/web/src/pages/ContactPage/ContactPage.js new file mode 100644 index 0000000..088f0b6 --- /dev/null +++ b/web/src/pages/ContactPage/ContactPage.js @@ -0,0 +1,59 @@ +import { Form, TextField, Submit, TextAreaField, FieldError, FormError } from '@redwoodjs/forms' +import { useMutation, Flash, useFlash } from '@redwoodjs/web' +import MainLayout from 'src/layouts/MainLayout' +import { useForm } from 'react-hook-form' + +const CREATE_CONTACT = gql` + mutation CreateContactMutation($input: CreateContactInput!) { + createContact(input: $input) { + id + } + } +` + +const ContactPage = () => { + const formMethods = useForm() + const { addMessage } = useFlash() + const [create, {loading, error}] = useMutation(CREATE_CONTACT, { + onCompleted: () => { + addMessage('Thank you for your submission!', { + style: { backgroundColor: 'green', color: 'white', padding: '1rem' } + }) + formMethods.reset() + }, + }) + const onSubmit = async (data) => { + try { + await create({ variables: { input: data } }) + } catch (error) { + console.log(error) + } + } + + return ( + + +
    + + + + + + + + + + + + + + Save + +
    + ) +} + +export default ContactPage diff --git a/web/src/pages/ContactPage/ContactPage.stories.js b/web/src/pages/ContactPage/ContactPage.stories.js new file mode 100644 index 0000000..0894c0b --- /dev/null +++ b/web/src/pages/ContactPage/ContactPage.stories.js @@ -0,0 +1,7 @@ +import ContactPage from './ContactPage' + +export const generated = () => { + return +} + +export default { title: 'Pages/ContactPage' } diff --git a/web/src/pages/ContactPage/ContactPage.test.js b/web/src/pages/ContactPage/ContactPage.test.js new file mode 100644 index 0000000..d31f442 --- /dev/null +++ b/web/src/pages/ContactPage/ContactPage.test.js @@ -0,0 +1,11 @@ +import { render } from '@redwoodjs/testing' + +import ContactPage from './ContactPage' + +describe('ContactPage', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +})