add contact page
This commit is contained in:
@@ -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())
|
||||
+}
|
||||
```
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
105
api/prisma/migrations/20201011095227-create-contact/steps.json
Normal file
105
api/prisma/migrations/20201011095227-create-contact/steps.json
Normal file
@@ -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()"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,4 +3,5 @@
|
||||
20201009213512-create-posts
|
||||
20201011043647-create-parts
|
||||
20201011052155-add-code-to-part
|
||||
20201011082558-add-code-not-needed-upon-create
|
||||
20201011082558-add-code-not-needed-upon-create
|
||||
20201011095227-create-contact
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
29
api/src/graphql/contacts.sdl.js
Normal file
29
api/src/graphql/contacts.sdl.js
Normal file
@@ -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
|
||||
}
|
||||
`
|
||||
24
api/src/services/contacts/contacts.js
Normal file
24
api/src/services/contacts/contacts.js
Normal file
@@ -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 })
|
||||
}
|
||||
9
api/src/services/contacts/contacts.test.js
Normal file
9
api/src/services/contacts/contacts.test.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
import { contacts } from './contacts'
|
||||
*/
|
||||
|
||||
describe('contacts', () => {
|
||||
it('returns true', () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -12,6 +12,7 @@ import { Router, Route } from '@redwoodjs/router'
|
||||
const Routes = () => {
|
||||
return (
|
||||
<Router>
|
||||
<Route path="/contact" page={ContactPage} name="contact" />
|
||||
<Route path="/parts/new" page={NewPartPage} name="newPart" />
|
||||
<Route path="/parts/{id:Int}/edit" page={EditPartPage} name="editPart" />
|
||||
<Route path="/parts/{id:Int}" page={PartPage} name="part" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
<FatalErrorBoundary page={FatalErrorPage}>
|
||||
|
||||
@@ -15,6 +15,9 @@ const MainLayout = ({ children }) => {
|
||||
<li>
|
||||
<Link to={routes.parts()}>Parts</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={routes.contact()}>Contact</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
59
web/src/pages/ContactPage/ContactPage.js
Normal file
59
web/src/pages/ContactPage/ContactPage.js
Normal file
@@ -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 (
|
||||
<MainLayout>
|
||||
<Flash timeout={2000} />
|
||||
<Form onSubmit={onSubmit} validation={{ mode: 'onBlur' }} error={error} formMethods={formMethods}>
|
||||
<FormError
|
||||
error={error}
|
||||
wrapperStyle={{ color: 'red', backgroundColor: 'lavenderblush' }}
|
||||
/>
|
||||
<label htmlFor="name">Name</label>
|
||||
<TextField name="name" validation={{required: true}} errorClassName="error" />
|
||||
<FieldError name="name" className="error" />
|
||||
|
||||
<label htmlFor="email">Email</label>
|
||||
<TextField name="email" validation={{required: true}} errorClassName="error" />
|
||||
<FieldError name="email" className="error" />
|
||||
|
||||
<label htmlFor="message">Message</label>
|
||||
<TextAreaField name="message" validation={{required: true}} errorClassName="error" />
|
||||
<FieldError name="message" className="error" />
|
||||
|
||||
<Submit disabled={loading}>Save</Submit>
|
||||
</Form>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContactPage
|
||||
7
web/src/pages/ContactPage/ContactPage.stories.js
Normal file
7
web/src/pages/ContactPage/ContactPage.stories.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import ContactPage from './ContactPage'
|
||||
|
||||
export const generated = () => {
|
||||
return <ContactPage />
|
||||
}
|
||||
|
||||
export default { title: 'Pages/ContactPage' }
|
||||
11
web/src/pages/ContactPage/ContactPage.test.js
Normal file
11
web/src/pages/ContactPage/ContactPage.test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { render } from '@redwoodjs/testing'
|
||||
|
||||
import ContactPage from './ContactPage'
|
||||
|
||||
describe('ContactPage', () => {
|
||||
it('renders successfully', () => {
|
||||
expect(() => {
|
||||
render(<ContactPage />)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user