diff options
| author | Wes Bos <wesbos@gmail.com> | 2018-04-12 16:31:01 -0400 |
|---|---|---|
| committer | Wes Bos <wesbos@gmail.com> | 2018-04-12 16:31:01 -0400 |
| commit | 97b6de6aa437f7a913a33b9e6aa18a919faf2caf (patch) | |
| tree | 431e12a535baf1d2ad59601ee7c5e8084ae7a263 | |
| parent | d22616d5a675f5a381c741784333151255713b45 (diff) | |
a whole bunch of things that should be in their own commits
26 files changed, 451 insertions, 245 deletions
diff --git a/backend/database/datamodel.graphql b/backend/database/datamodel.graphql index 43ae403..9465524 100644 --- a/backend/database/datamodel.graphql +++ b/backend/database/datamodel.graphql @@ -5,7 +5,6 @@ enum Permission { ITEMUPDATE ITEMDELETE PERMISSIONUPDATE - NUKE } type User { diff --git a/backend/database/prisma.yml b/backend/database/prisma.yml index 8f4cbe1..9782b52 100644 --- a/backend/database/prisma.yml +++ b/backend/database/prisma.yml @@ -11,10 +11,6 @@ disableAuth: true # the file path pointing to your data model datamodel: datamodel.graphql -# uncomment the following two lines to seed your service with initial data -# seed: -# import: seed.graphql - # cluster: ${env:PRISMA_CLUSTER} # cluster: local diff --git a/backend/database/seed.graphql b/backend/database/seed.graphql deleted file mode 100644 index 5779328..0000000 --- a/backend/database/seed.graphql +++ /dev/null @@ -1,24 +0,0 @@ -mutation { - createUser(data: { - email: "developer@example.com" - password: "$2a$10$hACwQ5/HQI6FhbIISOUVeusy3sKyUDhSq36fF5d/54aAdiygJPFzm" # plaintext password: "nooneknows" - name: "Sarah" - posts: { - create: [{ - title: "Hello World" - text: "This is my first blog post ever!" - isPublished: true - }, { - title: "My Second Post" - text: "My first post was good, but this one is better!" - isPublished: true - }, { - title: "Solving World Hunger" - text: "This is a draft..." - isPublished: false - }] - } - }) { - id - } -}
\ No newline at end of file diff --git a/backend/src/index.js b/backend/src/index.js index 8cf48fc..9f759ef 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -30,9 +30,3 @@ server.express.use(async (req, res, next) => { server.start({ port: 4444 }, deets => { console.log(`Server is running on http://localhost:${deets.port}`); }); - -// overwrite console.log -const chalk = require('chalk'); - -// global.console.l = (...butta) => console.log(chalk.bold.yellow(...butta)); -global.console.l = console.log; diff --git a/backend/src/mail.js b/backend/src/mail.js index 3159fe2..fc4a47b 100644 --- a/backend/src/mail.js +++ b/backend/src/mail.js @@ -9,4 +9,20 @@ const transport = nodemailer.createTransport({ }, }); -module.exports = transport; +const makeANiceEmail = text => ` + <div className="email" style=" + border:1px solid black; + padding: 20px; + font-family: sans-serif; + line-height: 2; + font-size: 20px; + "> + <h2>Hello There</h2> + <p>${text}</p> + + <p>😘 Wes Bos</p> + </div> +`; + +exports.transport = transport; +exports.makeANiceEmail = makeANiceEmail; diff --git a/backend/src/resolvers/Mutation.js b/backend/src/resolvers/Mutation.js index daaa7de..5886595 100644 --- a/backend/src/resolvers/Mutation.js +++ b/backend/src/resolvers/Mutation.js @@ -1,13 +1,11 @@ const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); -const { getUserId, Context, hasPermission } = require('../utils'); +const { hasPermission } = require('../utils'); const { randomBytes } = require('crypto'); const { promisify } = require('util'); const mail = require('../mail'); const stripe = require('../stripe'); -const wait = amount => new Promise(resolve => setTimeout(resolve, amount)); - const mutations = { // Signup Mutations async signup(parent, args, ctx, info) { @@ -29,7 +27,6 @@ const mutations = { async signin(parent, { email, password }, ctx, info) { const user = await ctx.db.query.user({ where: { email } }); - console.log(user); if (!user) { throw new Error(`No such user found for email: ${email}`); } @@ -44,7 +41,7 @@ const mutations = { }; }, - // Creation of Post Mutations + // Create An Item async createItem(parent, args, ctx, info) { if (!ctx.request.userId) { throw new Error('You must be logged in to create an item'); @@ -63,7 +60,6 @@ const mutations = { }, info ); - console.log(item); return item; }, @@ -85,19 +81,23 @@ const mutations = { async updateItem(parent, args, ctx, info) { const user = ctx.request.user; const item = await ctx.db.query.item({ where: { id: args.id } }, `{ user { id } }`); - if (item.user.id !== user.id || !user.permissions.includes('ADMIN')) { + + if (item.user.id !== user.id || !hasPermission(user, ['ADMIN'])) { throw new Error('You are not allowed to update that item!'); } const updates = { ...args }; // remove the ID because you can't update that delete updates.id; - return ctx.db.mutation.updateItem({ - where: { id: args.id }, - data: { - ...updates, + return ctx.db.mutation.updateItem( + { + where: { id: args.id }, + data: { + ...updates, + }, }, - }); + info + ); }, // Send password request @@ -117,14 +117,17 @@ const mutations = { data: { resetToken, resetTokenExpiry }, }); - console.log(res); // 3. Send them their token via email - const mailRes = await mail.sendMail({ + const mailRes = await mail.transport.sendMail({ from: 'wesbos@gmail.com', to: user.email, subject: 'Your password reset token', // TODO: don't hardcore localhost here - html: `Here is your reset link: http://localhost:3000/reset?resetToken=${resetToken}`, + html: mail.makeANiceEmail( + `Your password reset link is here! \n\n<a href="${ctx.request.protocol}://${ctx.request.get( + 'host' + )}/reset?resetToken=${resetToken}">Click Here to reset</a>s` + ), }); console.log(mailRes); return res.updateUser; @@ -175,8 +178,8 @@ const mutations = { Add to cart */ async addToCart(parent, args, ctx, info) { - console.l('Add to cart called'); - const userId = getUserId(ctx); + const userId = ctx.request.userId; + if (!userId) { throw new Error('You must be signed in to add to cart!'); } @@ -190,7 +193,6 @@ const mutations = { }); if (existingCartItem) { - console.log('Existing'); return ctx.db.mutation.updateCartItem( { where: { id: existingCartItem.id }, @@ -220,40 +222,46 @@ const mutations = { // delete that cart item async removeFromCart(parent, args, ctx, info) { - // TODO: add userId to where - return ctx.db.mutation.deleteCartItem({ - where: { id: args.id }, - }); + return ctx.db.mutation.deleteManyCartItems( + { + where: { + id: args.id, + user: { + id: ctx.request.userId, + }, + }, + }, + info + ); }, async createOrder(parent, args, ctx, info) { - const userId = getUserId(ctx); + const userId = ctx.request.userId; const user = await ctx.db.query.user( { where: { id: userId } }, // TODO - can we just pass info here? '{ id, name, email, cart { id, quantity, item { title, price, id, description, image } }}' ); // 1. Recalculate the total for the price - const amount = user.cart.reduce((tally, cartItem) => tally + cartItem.item.price * cartItem.quantity, 0); - // TODO Error Handling - // 2.1 Create a Stripe Customer - const customer = await stripe.customers.create({ - email: user.email, - }); - // 2.3 Charge the stripe token + const amount = user.cart.reduce( + (tally, cartItem) => tally + cartItem.item.price * cartItem.quantity, + 0 + ); + // 2. Create a stripe charge const charge = await stripe.charges.create({ amount, currency: 'usd', source: args.token, }); + // 3. convert the items they want to OrderItems const orderItems = user.cart.map(cartItem => { const orderItem = { quantity: cartItem.quantity, // copy all the item details so it's there forever ...cartItem.item, item: { - // realtionship to the Item incase we need it + // relationship to the Item incase we need it connect: { id: cartItem.item.id }, }, user: { connect: { id: user.id } }, @@ -263,7 +271,7 @@ const mutations = { return orderItem; }); - // Create the Order + // 4. Create the Order const order = await ctx.db.mutation.createOrder({ data: { total: charge.amount, @@ -279,8 +287,8 @@ const mutations = { }, }, }); - console.log('Gonna delete some items'); - // 6. Clean up, clear the users cart adn send back { user, order } + + // 5. Clean up, clear the users cart and send back { user, order } // Delete the users current cart items const cartItemIds = user.cart.map(cartItem => cartItem.id); await ctx.db.mutation.deleteManyCartItems({ @@ -289,21 +297,24 @@ const mutations = { }, }); - // 5. Send the order back to the client + // 6. Send the order back to the client return order; - // 4. TODO: Send an email with their order }, + async updateUser(parent, args, ctx, info) { - const userId = getUserId(ctx); - const updatedUser = await ctx.db.mutation.updateUser({ - data: args, - where: { id: userId }, - }); + const userId = ctx.request.userId; + const updatedUser = await ctx.db.mutation.updateUser( + { + data: args, + where: { id: userId }, + }, + info + ); return updatedUser; }, async updatePermissions(parent, args, ctx, info) { - const userId = getUserId(ctx); + const userId = ctx.request.userId; const currentUser = await ctx.db.query.user({ where: { id: userId } }, info); if (!currentUser) throw new Error('You Must be logged in to updat permissions!'); hasPermission(currentUser, ['ADMIN', 'PERMISSIONUPDATE']); diff --git a/backend/src/resolvers/Query.js b/backend/src/resolvers/Query.js index 22dcd2a..276f7fa 100644 --- a/backend/src/resolvers/Query.js +++ b/backend/src/resolvers/Query.js @@ -1,26 +1,52 @@ -const { getUserId, Context, checkForUserId } = require('../utils'); +const { hasPermission } = require('../utils'); + const { forwardTo } = require('prisma-binding'); const Query = { items(parent, args, ctx, info) { - console.log('ITEMS!'); - // check auth return ctx.db.query.items({ ...args }, info); }, itemsConnection: forwardTo('db'), // TODO: Make sure they own this order before looking it up - order: forwardTo('db'), + // order: forwardTo('db'), + async order(parent, args, ctx, info) { + // 1. make sure they are signed in + if (!ctx.request.userId) { + throw new Error('You Must be signed in to view an order'); + } + + // 2. Create the query + const where = { + id: args.id, + user: { + id: ctx.request.userId, + }, + }; + // 3. Fire off the query + const [order] = await ctx.db.query.orders({ where }, info); + + // 4. Check that they are allowed to view the order + if (order.user.id !== ctx.request.userId || hasPermission(ctx.request.user, ['ADMIN'])) { + throw new Error("You don't have permission"); + } + // 5. If everything checks out, return the order + return order; + }, me(parent, args, ctx, info) { const Authorization = ctx.request.get('Authorization'); if (!Authorization || Authorization === 'null') { - console.log('Authorization is null'); - return null; + return null; // don't error out, just return nothing } - const id = getUserId(ctx); - return ctx.db.query.user({ where: { id } }, info); + + return ctx.db.query.user( + { + where: { id: ctx.request.userId }, + }, + info + ); }, async orders(parent, args, ctx, info) { @@ -37,12 +63,6 @@ const Query = { info ); }, - - async users(parent, args, ctx, info) { - // TODO Permissions - const userId = getUserId(ctx); - return ctx.db.query.users({}, info); - }, }; module.exports = Query; diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql index 6613004..4318d2a 100644 --- a/backend/src/schema.graphql +++ b/backend/src/schema.graphql @@ -1,4 +1,4 @@ -# import Permission, Order, OrderItem, CartItem, ItemWhereInput, ItemOrderByInput, allItems, Item, ItemCreateInput, ItemOrderByInput, ItemWhereInput, Query.order, Query.orders, Query.users from './generated/prisma.graphql' +# import Permission, Order, OrderItem, CartItem, ItemWhereInput, ItemOrderByInput, allItems, Item, ItemCreateInput, ItemOrderByInput, ItemWhereInput, Query.orders, Query.users from './generated/prisma.graphql' type Query { me: User @@ -17,6 +17,7 @@ type Query { skip: Int first: Int ): [Item]! + order(id: ID!): Order! } # import Mutation from './generated/prisma.graphql' diff --git a/backend/src/utils.js b/backend/src/utils.js index 2988b3d..faf028b 100644 --- a/backend/src/utils.js +++ b/backend/src/utils.js @@ -1,16 +1,3 @@ -const jwt = require('jsonwebtoken'); - -function getUserId(ctx) { - const Authorization = ctx.request.get('Authorization'); - if (Authorization) { - const token = Authorization.replace('Bearer ', ''); - const { userId } = jwt.verify(token, process.env.APP_SECRET); - return userId; - } - // TODO: Don't throw when they aren't logged in - // throw new Error('Sorry, you must be logged in to do that!'); -} - function hasPermission(user, permissionsNeeded) { const matchedPermissions = user.permissions.filter(permissionTheyHave => permissionsNeeded.includes(permissionTheyHave) @@ -27,24 +14,4 @@ function hasPermission(user, permissionsNeeded) { } } -function checkForUserId(ctx) { - const Authorization = ctx.request.get('Authorization'); - if (Authorization) { - const token = Authorization.replace('Bearer ', ''); - const { userId } = jwt.verify(token, process.env.APP_SECRET); - return userId; - } -} - -class AuthError extends Error { - constructor() { - super('Not authorized'); - } -} - -module.exports = { - getUserId, - AuthError, - checkForUserId, - hasPermission, -}; +exports.hasPermission = hasPermission; diff --git a/frontend/components/AddToCart.js b/frontend/components/AddToCart.js index 3566c0c..18186d8 100644 --- a/frontend/components/AddToCart.js +++ b/frontend/components/AddToCart.js @@ -18,7 +18,11 @@ class AddToCart extends Component { const existingIndex = data.me.cart.findIndex(cartItem => cartItem.id === newCartItem.id); if (existingIndex >= 0) { // already in cache, just replace it - data.me.cart = [...data.me.cart.slice(0, existingIndex), newCartItem, ...data.me.cart.slice(existingIndex + 1)]; + data.me.cart = [ + ...data.me.cart.slice(0, existingIndex), + newCartItem, + ...data.me.cart.slice(existingIndex + 1), + ]; } else { data.me.cart = [...data.me.cart, newCartItem]; } diff --git a/frontend/components/ErrorMessage.js b/frontend/components/ErrorMessage.js index 3731554..c73a2b7 100644 --- a/frontend/components/ErrorMessage.js +++ b/frontend/components/ErrorMessage.js @@ -18,14 +18,14 @@ const StyledError = styled.div` } `; -const DisplayError = ({ error }) => { +const DisplayError = ({ error, refetch }) => { if (!error || !error.message) return null; if (error.networkError && error.networkError.result && error.networkError.result.errors.length) { return error.networkError.result.errors.map((error, i) => ( <StyledError key={i}> <p> <strong>Shoot!</strong> - {error.message} + {error.message.replace('GraphQL error: ', '')} </p> </StyledError> )); @@ -34,7 +34,8 @@ const DisplayError = ({ error }) => { <StyledError> <p> <strong>Shoot!</strong> - {error.message} + {error.message.replace('GraphQL error: ', '')} + <button onClick={refetch}>Try Again</button> </p> </StyledError> ); diff --git a/frontend/components/Item.js b/frontend/components/Item.js index 2ed5742..99d8d93 100644 --- a/frontend/components/Item.js +++ b/frontend/components/Item.js @@ -83,7 +83,7 @@ class ItemComponent extends React.Component { <div className="buttonList"> <Link href={{ - pathname: '/admin/update', + pathname: '/update', query: { id: item.id }, }} > diff --git a/frontend/components/Items.js b/frontend/components/Items.js index 3c28de2..22e2d75 100644 --- a/frontend/components/Items.js +++ b/frontend/components/Items.js @@ -32,8 +32,6 @@ class ItemList extends React.Component { }; render() { const fetchPolicy = this.state.refetch ? 'network-only' : 'cache-first'; - console.log(this.state.refetch, this.props.page); - console.log(fetchPolicy); return ( <Center key={this.props.page}> <Pagination page={this.props.page} /> @@ -46,13 +44,9 @@ class ItemList extends React.Component { fetchPolicy={fetchPolicy} > {({ data, error, loading }) => { - if (loading) return <div>Loading</div>; + if (loading) return null; if (error) return <div>Error</div>; - return ( - <Items key={this.props.page}> - {data.items.map(item => <Item key={item.id} item={item} />)} - </Items> - ); + return <Items>{data.items.map(item => <Item key={item.id} item={item} />)}</Items>; }} </Query> <Pagination page={this.props.page} /> diff --git a/frontend/components/Order.js b/frontend/components/Order.js index b38254f..f6743fa 100644 --- a/frontend/components/Order.js +++ b/frontend/components/Order.js @@ -7,6 +7,7 @@ import styled from 'styled-components'; import { SINGLE_ORDER_QUERY } from '../queries'; import formatMoney from '../lib/formatMoney'; import Dump from './Dump'; +import Error from './ErrorMessage'; const OrderStyles = styled.div` max-width: 1000px; @@ -51,10 +52,15 @@ class Order extends Component { render() { return ( - <Query query={SINGLE_ORDER_QUERY} variables={{ id: this.props.id }}> - {({ data: { order }, error, loading }) => { + <Query + query={SINGLE_ORDER_QUERY} + variables={{ id: this.props.id }} + fetchPolicy="network-only" + > + {({ data, error, loading, refetch }) => { if (loading) return <p>Loading...</p>; - if (!order || error) return <p>No Order Found!</p>; + if (error) return <Error error={error} refetch={refetch} />; + const order = data.order; return ( <OrderStyles> <Head> diff --git a/frontend/components/Page.js b/frontend/components/Page.js index 3c6e3b1..ab14b10 100644 --- a/frontend/components/Page.js +++ b/frontend/components/Page.js @@ -51,10 +51,15 @@ const StyledPage = styled.div` `; class Page extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + }; componentDidMount() { - // console.log('ComponentDidMount'); - // When the page loads, re-refetch the current user query - // client.query({ query: CURRENT_USER_QUERY, fetchPolicy: 'network-only' }); + // The first time we load in the client, we need to refetch the current user data + if (typeof window !== 'undefined' && !window.__CLIENTLOADED__) { + client.query({ query: CURRENT_USER_QUERY, fetchPolicy: 'network-only' }); + window.__CLIENTLOADED__ = true; + } } render() { return ( @@ -68,8 +73,5 @@ class Page extends React.Component { ); } } -Page.propTypes = { - children: PropTypes.node.isRequired, -}; export default Page; diff --git a/frontend/components/Permissions.js b/frontend/components/Permissions.js index 0b3976c..69a621b 100644 --- a/frontend/components/Permissions.js +++ b/frontend/components/Permissions.js @@ -1,44 +1,18 @@ import React from 'react'; import { Query, Mutation } from 'react-apollo'; -import styled from 'styled-components'; -import { BarLoader } from 'react-spinners'; -import { perPage } from '../config'; import { ALL_USERS_QUERY, UPDATE_PERMISSIONS_MUTATION } from '../queries/index'; import Error from './ErrorMessage'; -import Form from './styles/Form'; import SickButton from './styles/SickButton'; +import Table from './styles/Table'; -const PermissionsBox = styled.div` - border: 1px solid ${props => props.theme.offWhite}; - box-shadow: ${props => props.theme.bs}; - margin-bottom: 5rem; - padding: 2rem; - label { - cursor: pointer; - span { - transition: all 0.1s; - padding: 0 1rem; - display: block; - border: 1px solid ${props => props.theme.offWhite}; - border-left-width: 20px; - } - input { - display: none; - } - input:checked + span { - border-color: red; - } - margin-right: 1rem; - margin-bottom: 1rem; - } - .labels { - display: flex; - flex-wrap: wrap; - & > * { - flex: 0 1 auto; - } - } -`; +const possiblePermissions = [ + 'ADMIN', + 'USER', + 'ITEMCREATE', + 'ITEMUPDATE', + 'ITEMDELETE', + 'PERMISSIONUPDATE', +]; class User extends React.Component { state = { @@ -60,22 +34,12 @@ class User extends React.Component { return ( <Mutation mutation={UPDATE_PERMISSIONS_MUTATION}> {(updatePermissions, { loading, error }) => ( - <PermissionsBox key={user.id} className="user"> - <BarLoader className="barLoader" width="100%" height={5} color="red" loading={loading} /> + <tr key={user.id} className="user"> <Error error={error} /> - <h2> - {user.name} -- {user.email} - </h2> - <div className="labels"> - {[ - 'ADMIN', - 'USER', - 'ITEMCREATE', - 'ITEMUPDATE', - 'ITEMDELETE', - 'PERMISSIONUPDATE', - 'NUKE', - ].map(permission => ( + <td>{user.name}</td> + <td>{user.email}</td> + {possiblePermissions.map(permission => ( + <td> <label key={permission} htmlFor={`${user.id}-permission-${permission}`}> <input type="checkbox" @@ -85,25 +49,26 @@ class User extends React.Component { onChange={this.handlePermissionsChange} value={permission} /> - <span>{permission}</span> </label> - ))} - </div> - <SickButton - type="button" - onClick={async () => { - const res = await updatePermissions({ - variables: { - permissions: this.state.permissions, - userId: this.props.user.id, - }, - }); - console.log(res); - }} - > - Update Permissions - </SickButton> - </PermissionsBox> + </td> + ))} + <td> + <SickButton + type="button" + disabled={loading} + onClick={async () => { + const res = await updatePermissions({ + variables: { + permissions: this.state.permissions, + userId: this.props.user.id, + }, + }); + }} + > + Updat{loading ? 'ing' : 'e'} + </SickButton> + </td> + </tr> )} </Mutation> ); @@ -118,7 +83,17 @@ const Permissions = () => ( return ( <div> <h1>Manage User Permissions</h1> - {data.users.map(user => <User key={user.id} user={user} />)} + <Table> + <thead> + <tr> + <th>Name</th> + <th>Email</th> + {possiblePermissions.map(p => <th>{p}</th>)} + <th>👇🏻</th> + </tr> + </thead> + <tbody>{data.users.map(user => <User key={user.id} user={user} />)}</tbody> + </Table> </div> ); }} diff --git a/frontend/components/PleaseSignIn.js b/frontend/components/PleaseSignIn.js index 1533dbd..ff3b956 100644 --- a/frontend/components/PleaseSignIn.js +++ b/frontend/components/PleaseSignIn.js @@ -20,9 +20,16 @@ const PleaseSignIn = props => ( // check if they NO permissions, or they don't meet the requmrenets if ( !data.me.permissions || - !props.allowedPermissions.some(permission => data.me.permissions.contains(permission)) + !props.allowedPermissions.some(permission => data.me.permissions.includes(permission)) ) { - return <p>Insufficient Permissions to Manage Permissions</p>; + return ( + <p> + Insufficient Permissions to Manage Permissions. You have: + <strong>{data.me.permissions}</strong> + and you need + <strong>{props.allowedPermissions.join(' OR ')}</strong> + </p> + ); } } return props.children; diff --git a/frontend/components/Reset.js b/frontend/components/Reset.js index 5bc7f98..bbfc436 100644 --- a/frontend/components/Reset.js +++ b/frontend/components/Reset.js @@ -40,7 +40,7 @@ class Reset extends React.Component { }} refetchQueries={[{ query: CURRENT_USER_QUERY }]} > - {(resetMutation, { error, loading }) => ( + {(resetMutation, { error, loading, called }) => ( <Form onSubmit={e => this.resetPassword(e, resetMutation)}> <Error error={error} /> <fieldset disabled={loading} aria-busy={loading}> diff --git a/frontend/components/ResetRequest.js b/frontend/components/ResetRequest.js index fb6f660..4ed902e 100644 --- a/frontend/components/ResetRequest.js +++ b/frontend/components/ResetRequest.js @@ -3,6 +3,7 @@ import { Mutation } from 'react-apollo'; import { REQUEST_RESET_MUTATION } from '../queries'; import Form from './styles/Form'; import Error from './ErrorMessage'; +import Dump from './Dump'; class ResetRequest extends React.Component { state = { @@ -12,7 +13,7 @@ class ResetRequest extends React.Component { render() { return ( <Mutation mutation={REQUEST_RESET_MUTATION} variables={this.state}> - {(resetMutation, { loading, error }) => ( + {(resetMutation, { loading, error, called, data }) => ( <Form onSubmit={async e => { e.preventDefault(); @@ -21,6 +22,7 @@ class ResetRequest extends React.Component { }} > <Error error={error} /> + {!error && called && !loading && <p>Success! Check Your Email!</p>} <fieldset disabled={loading} aria-busy={loading}> <label htmlFor="email"> Email diff --git a/frontend/components/Search.js b/frontend/components/Search.js index 49c5023..a1b69cb 100644 --- a/frontend/components/Search.js +++ b/frontend/components/Search.js @@ -1,6 +1,6 @@ import Downshift from 'downshift'; import Router from 'next/router'; -import styled from 'styled-components'; +import styled, { keyframes } from 'styled-components'; import debounce from 'lodash.debounce'; import { client } from '../lib/withData'; import { SEARCH_ITEMS_QUERY } from '../queries'; @@ -35,6 +35,15 @@ const DropDownItem = styled.div` } `; +const glow = keyframes` + from { + box-shadow: 0 0 0px yellow; + } + + to { + box-shadow: 0 0 10px 1px yellow; + } +`; const SearchStyles = styled.div` position: relative; input { @@ -43,7 +52,7 @@ const SearchStyles = styled.div` border: 0; font-size: 2rem; &.loading { - background: red; + animation: ${glow} 0.5s ease-in-out infinite alternate; } } `; diff --git a/frontend/components/styles/SickButton.js b/frontend/components/styles/SickButton.js index 3bd5001..5b5352e 100644 --- a/frontend/components/styles/SickButton.js +++ b/frontend/components/styles/SickButton.js @@ -10,6 +10,11 @@ const SickButton = styled.button` font-size: 2rem; padding: 0.8rem 1.5rem; transform: skew(-2deg); + display: inline-block; + transition: all 0.5s; + &[disabled] { + opacity: 0.5; + } `; export default SickButton; diff --git a/frontend/components/styles/Table.js b/frontend/components/styles/Table.js new file mode 100644 index 0000000..e9d0673 --- /dev/null +++ b/frontend/components/styles/Table.js @@ -0,0 +1,31 @@ +import styled from 'styled-components'; + +const Table = styled.table` + border-spacing: 0; + width: 100%; + border: 1px solid ${props => props.theme.offWhite}; + thead { + font-size: 10px; + } + td, + th { + border-bottom: 1px solid ${props => props.theme.offWhite}; + border-right: 1px solid ${props => props.theme.offWhite}; + padding: 10px 5px; + position: relative; + &:last-child { + border-right: none; + width: 150px; + button { + width: 100%; + } + } + } + tr { + &:hover { + background: ${props => props.theme.offWhite}; + } + } +`; + +export default Table; diff --git a/frontend/pages/buy.js b/frontend/pages/buy.js deleted file mode 100644 index d6b1331..0000000 --- a/frontend/pages/buy.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Component } from 'react'; -import withData from '../lib/withData'; -import TakeMyMoney from '../components/TakeMyMoney'; - -class Buy extends Component { - render() { - return ( - <div> - <p>Buy</p> - <TakeMyMoney /> - </div> - ) - } -} - -export default withData(Buy); diff --git a/frontend/pages/diagram.js b/frontend/pages/diagram.js new file mode 100644 index 0000000..f7e9a7a --- /dev/null +++ b/frontend/pages/diagram.js @@ -0,0 +1,202 @@ +import Page from '../components/Page'; +import withData from '../lib/withData'; +import styled from 'styled-components'; + +const DiagramStyles = styled.div` + display: grid; + grid-template-columns: repeat(8, 1fr); + grid-column-gap: 20px; + ul { + margin: 0; + padding: 0; + list-style: none; + li { + border-bottom: 1px solid rgba(0, 0, 0, 0.2); + } + } + h2 { + text-align: center; + margin: 0; + background: black; + color: white; + width: calc(40px + 100%); + transform: translateX(-20px); + } + span.for { + font-size: 12px; + text-align: center; + margin-bottom: 2rem; + display: block; + } + .slat { + background: #f7f7f7; + padding: 20px; + min-height: 500px; + grid-column: span 2; + grid-row: 3; + line-height: 1.7; + } + + .location { + grid-column: span 4; + background: red; + padding: 5px; + text-align: center; + color: white; + text-transform: uppercase; + } + + .arrow { + font-size: 50px; + line-height: 1; + transform: translateX(-10px) translateX(-50%); + text-align: center; + grid-row: 2; + margin-bottom: -100%; + background: white; + height: 60px; + } + .arrow1 { + grid-column: 3; + } + .arrow2 { + grid-column: 5; + } + .arrow3 { + grid-column: 7; + } + img { + width: 100%; + height: 125px; + object-fit: contain; + } + .slat2 img { + filter: invert(100%); + } + .slat4 img { + mix-blend-mode: multiply; + } +`; + +const Diagram = () => ( + <DiagramStyles> + <span className="location frontend">Frontend</span> + <span className="location backend">Backend</span> + <div className="slat slat1"> + <img + src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/640px-React-icon.svg.png" + alt="React.js" + /> + <h2>React.js</h2> + <span className="for">For Building The Interface along with:</span> + <ul> + <li> + <strong>Next.js</strong> for server side rendering, routing and tooling + </li> + <li> + <strong>Styled Components</strong> for styling + </li> + <li> + <strong>React-Apollo</strong> for interfacing with Apollo Client + </li> + </ul> + </div> + <div className="slat slat2"> + <img + src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iOThweCIgaGVpZ2h0PSI0MnB4IiB2aWV3Qm94PSIwIDAgMTIwIDQyIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPHN0eWxlPgogICAgICAgIHRleHQgewogICAgICAgICAgICBmb250LWZhbWlseTogJ1NvdXJjZSBTYW5zIFBybycsICdIZWx2ZXRpY2EgTmV1ZScsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7CiAgICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxnIGlkPSJob21lcGFnZS0rLW5hdiIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9ImhvbWUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zNTAuMDAwMDAwLCAtMTUzOS4wMDAwMDApIiBmaWxsPSIjRkZGRkZGIj4KICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDM1MC4wMDAwMDAsIDE1MzguMDAwMDAwKSI+CiAgICAgICAgICAgICAgICA8ZyBpZD0ibG9nby1hcG9sbG8tc3BhY2UtY29weS0yOCIgZmlsbC1ydWxlPSJub256ZXJvIj4KICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iTGF5ZXJfMiI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxnIGlkPSJsb2dvX2Fwb2xsb19zcGFjZS1saW5rIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0OS42MTkxNTUsIDE1LjcxNjE3NSkgc2NhbGUoLTEsIDEpIHJvdGF0ZSgtMTgwLjAwMDAwMCkgdHJhbnNsYXRlKC00OS42MTkxNTUsIC0xNS43MTYxNzUpIHRyYW5zbGF0ZSgwLjExOTE1NSwgMC43MTYxNzUpIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxnIGlkPSJsb2dvX2Fwb2xsb19zcGFjZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsIC0wLjAwMDAwMCkiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxnIGlkPSJHcm91cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4zMzE4MjUsIDAuOTY1MzA5KSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJTaGFwZSIgcG9pbnRzPSIxNS44Mzg5MTQgMjAuNTYxMDg2IDEyLjkwNTQ2IDIwLjU2MTA4NiA4LjY3MDA0NTI1IDkuNTY4OTg5NDQgMTEuMzIzNDM4OSA5LjU2ODk4OTQ0IDEyLjAxNTU2NTYgMTEuNDMwOTUwMiAxNi4wMTc0OTYyIDExLjQzMDk1MDIgMTUuMjkzMDMxNyAxMy40OTEyODIxIDEyLjY1ODIyMDIgMTMuNDkxMjgyMSAxNC4zNzIyNDc0IDE4LjIyMDkzNTEgMTcuNDIxMTc2NSA5LjU2ODk4OTQ0IDIwLjA3NDMyODggOS41Njg5ODk0NCI+PC9wb2x5Z29uPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cG9seWdvbiBpZD0iU2hhcGUiIHBvaW50cz0iNjAuNDAwMjQxMyA5LjU2ODk4OTQ0IDYwLjQwMDI0MTMgMjAuNTYxMDg2IDYyLjc1NjkyMzEgMjAuNTYxMDg2IDYyLjc1NjkyMzEgMTEuNjI4OTU5MyA2Ny40MDQxNjI5IDExLjYyODk1OTMgNjcuNDA0MTYyOSA5LjU2ODk4OTQ0Ij48L3BvbHlnb24+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJTaGFwZSIgcG9pbnRzPSI3My45ODI3NDUxIDkuNTY4OTg5NDQgNzMuOTgyNzQ1MSAyMC41NjEwODYgNzYuMzM5NDI2OCAyMC41NjEwODYgNzYuMzM5NDI2OCAxMS42Mjg5NTkzIDgwLjk4NjkwOCAxMS42Mjg5NTkzIDgwLjk4NjkwOCA5LjU2ODk4OTQ0Ij48L3BvbHlnb24+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik00Ny4zNDg1MzcsMTguNTkxMTMxMiBDNDkuMjk3NDk2MiwxOC41OTExMzEyIDUwLjg4MzEzNzMsMTcuMDA1NzMxNSA1MC44ODMxMzczLDE1LjA1NjY1MTYgQzUwLjg4MzEzNzMsMTMuMTA3ODEzIDQ5LjI5NzQ5NjIsMTEuNTIyMTcxOSA0Ny4zNDg1MzcsMTEuNTIyMTcxOSBDNDUuMzk5NTc3NywxMS41MjIxNzE5IDQzLjgxNDE3OCwxMy4xMDc4MTMgNDMuODE0MTc4LDE1LjA1NjY1MTYgQzQzLjgxNDE3OCwxNy4wMDU4NTIyIDQ1LjM5OTU3NzcsMTguNTkxMTMxMiA0Ny4zNDg1MzcsMTguNTkxMTMxMiBaIE00Ny4zNDg1MzcsMjAuNzQyMjAyMSBDNDQuMjA4NTA2OCwyMC43NDIyMDIxIDQxLjY2Mjk4NjQsMTguMTk2NjgxNyA0MS42NjI5ODY0LDE1LjA1NjY1MTYgQzQxLjY2Mjk4NjQsMTEuOTE2NjIxNCA0NC4yMDg1MDY4LDkuMzcwOTgwMzkgNDcuMzQ4NTM3LDkuMzcwOTgwMzkgQzUwLjQ4ODU2NzEsOS4zNzA5ODAzOSA1My4wMzQwODc1LDExLjkxNjYyMTQgNTMuMDM0MDg3NSwxNS4wNTY2NTE2IEM1My4wMzQwODc1LDE4LjE5NjY4MTcgNTAuNDg4Njg3OCwyMC43NDIyMDIxIDQ3LjM0ODUzNywyMC43NDIyMDIxIFoiIGlkPSJTaGFwZSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNOTEuODIwNjkzOCwxOC41OTExMzEyIEM5My43Njk4OTQ0LDE4LjU5MTEzMTIgOTUuMzU1NTM1NCwxNy4wMDU3MzE1IDk1LjM1NTUzNTQsMTUuMDU2NjUxNiBDOTUuMzU1NTM1NCwxMy4xMDc4MTMgOTMuNzY5ODk0NCwxMS41MjIxNzE5IDkxLjgyMDY5MzgsMTEuNTIyMTcxOSBDODkuODcxNzM0NSwxMS41MjIxNzE5IDg4LjI4NjMzNDgsMTMuMTA3ODEzIDg4LjI4NjMzNDgsMTUuMDU2NjUxNiBDODguMjg2MzM0OCwxNy4wMDU4NTIyIDg5Ljg3MTczNDUsMTguNTkxMTMxMiA5MS44MjA2OTM4LDE4LjU5MTEzMTIgWiBNOTEuODIwNjkzOCwyMC43NDIyMDIxIEM4OC42ODA5MDUsMjAuNzQyMjAyMSA4Ni4xMzUxNDMzLDE4LjE5NjY4MTcgODYuMTM1MTQzMywxNS4wNTY2NTE2IEM4Ni4xMzUxNDMzLDExLjkxNjYyMTQgODguNjgwOTA1LDkuMzcwOTgwMzkgOTEuODIwNjkzOCw5LjM3MDk4MDM5IEM5NC45NjA3MjQsOS4zNzA5ODAzOSA5Ny41MDY0ODU3LDExLjkxNjYyMTQgOTcuNTA2NDg1NywxNS4wNTY2NTE2IEM5Ny41MDY0ODU3LDE4LjE5NjY4MTcgOTQuOTYwNzI0LDIwLjc0MjIwMjEgOTEuODIwNjkzOCwyMC43NDIyMDIxIFoiIGlkPSJTaGFwZSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMzIuMDU2MTA4NiwyMC41NjEwODYgTDI5LjI3MDk1MDIsMjAuNTYxMDg2IEwyOC44NzE0MzI5LDIwLjU2MTA4NiBMMjYuOTMwNjc4NywyMC41NjEwODYgTDI2LjkzMDY3ODcsOS41Njg4Njg3OCBMMjkuMjcwOTUwMiw5LjU2ODg2ODc4IEwyOS4yNzA5NTAyLDEzLjM1MTY3NDIgTDMyLjA1NjEwODYsMTMuMzUxNjc0MiBDMzQuMDA5NzczOCwxMy4zNTE2NzQyIDM1LjU5MzQ4NDIsMTUuMDAyOTU2MyAzNS41OTM0ODQyLDE2Ljk1NjM4MDEgQzM1LjU5MzQ4NDIsMTguOTEwMTY1OSAzNC4wMDk3NzM4LDIwLjU2MTA4NiAzMi4wNTYxMDg2LDIwLjU2MTA4NiBaIE0zMi4wNTYxMDg2LDE1LjUwMjk4NjQgTDI5LjI3MDk1MDIsMTUuNTAyOTg2NCBMMjkuMjcwOTUwMiwxOC40MDk4OTQ0IEwzMi4wNTYxMDg2LDE4LjQwOTg5NDQgQzMyLjgyMDYzMzUsMTguNDA5ODk0NCAzMy40NDIyOTI2LDE3LjcyMDc4NDMgMzMuNDQyMjkyNiwxNi45NTYzODAxIEMzMy40NDIyOTI2LDE2LjE5MTk3NTkgMzIuODIwNjMzNSwxNS41MDI5ODY0IDMyLjA1NjEwODYsMTUuNTAyOTg2NCBaIiBpZD0iU2hhcGUiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTI0LjkxMDA0NTIsNi4yMzI4ODA4NCBDMjQuNzA2MzY1LDYuMjMyODgwODQgMjQuNTI2NTc2Miw2LjEzMjAwNjAzIDI0LjQxNjc3MjIsNS45NzczMTUyMyBDMjQuNDE2NzcyMiw1Ljk3NzMxNTIzIDIzLjg4ODg2ODgsNS4zNzUwODI5NiAyMy42MDQyMjMyLDUuMDkwMDc1NDEgQzIyLjQwMDEyMDcsMy44ODU5NzI4NSAyMC45OTgxMjk3LDIuOTQwODE0NDggMTkuNDM3MzQ1NCwyLjI4MDU0Mjk5IEMxNy44MjIwMjExLDEuNTk3MjI0NzQgMTYuMTA1NzAxNCwxLjI1MDc5OTQgMTQuMzM1NTY1NiwxLjI1MDc5OTQgQzEyLjU2NTE4ODUsMS4yNTA3OTk0IDEwLjg0ODg2ODgsMS41OTcyMjQ3NCA5LjIzMzc4NTgyLDIuMjgwNTQyOTkgQzcuNjcyODgwODQsMi45NDA4MTQ0OCA2LjI3MDg4OTg5LDMuODg1OTcyODUgNS4wNjY5MDc5OSw1LjA5MDA3NTQxIEMzLjg2MjY4NDc3LDYuMjk0NDE5MzEgMi45MTc0MDU3Myw3LjY5NjI4OTU5IDIuMjU3MjU0OSw5LjI1NzA3MzkxIEMxLjU3NDE3Nzk4LDEwLjg3MjM5ODIgMS4yMjc2MzE5OCwxMi41ODg5NTkzIDEuMjI3NjMxOTgsMTQuMzU4OTc0NCBDMS4yMjc2MzE5OCwxNi4xMjg5ODk0IDEuNTc0MTc3OTgsMTcuODQ1NTUwNSAyLjI1NzI1NDksMTkuNDYwODc0OCBDMi45MTc0MDU3MywyMS4wMjE2NTkxIDMuODYyNjg0NzcsMjIuNDIzNTI5NCA1LjA2NjkwNzk5LDIzLjYyNzYzMiBDNi4yNzEwMTA1NiwyNC44MzE4NTUyIDcuNjczMDAxNTEsMjUuNzc3MDEzNiA5LjIzMzc4NTgyLDI2LjQzNzI4NTEgQzEwLjg0ODg2ODgsMjcuMTIwNjAzMyAxMi41NjUxODg1LDI3LjQ2NzAyODcgMTQuMzM1NTY1NiwyNy40NjcwMjg3IEMxNi4xMDU3MDE0LDI3LjQ2NzAyODcgMTcuODIyMTQxOCwyNy4xMjA2MDMzIDE5LjQzNzM0NTQsMjYuNDM3Mjg1MSBDMjAuNTc0MjM4MywyNS45NTY0NDA0IDIxLjYyNjMwNDcsMjUuMzIzNTU5NiAyMi41Nzk1NDc1LDI0LjU1MDIyNjIgQzIyLjUyODYyNzUsMjQuNDAwMjQxMyAyMi41MDAxNTA4LDI0LjIzOTM5NjcgMjIuNTAwMTUwOCwyNC4wNzIwMzYyIEMyMi41MDAxNTA4LDIzLjI1NTI2NCAyMy4xNjIzNTI5LDIyLjU5MzA2MTggMjMuOTc5MDA0NSwyMi41OTMwNjE4IEMyNC43OTU4OTc0LDIyLjU5MzA2MTggMjUuNDU4MDk5NSwyMy4yNTUyNjQgMjUuNDU4MDk5NSwyNC4wNzIwMzYyIEMyNS40NTgwOTk1LDI0Ljg4ODkyOTEgMjQuNzk1ODk3NCwyNS41NTEwMTA2IDIzLjk3OTAwNDUsMjUuNTUxMDEwNiBDMjMuNzc2Mjg5NiwyNS41NTEwMTA2IDIzLjU4Mjg2NTgsMjUuNTEwMTA1NiAyMy40MDY4MTc1LDI1LjQzNjAxODEgQzIwLjkzNzE5NDYsMjcuNDYwOTk1NSAxNy43Nzg0NjE1LDI4LjY3NjgwMjQgMTQuMzM1NTY1NiwyOC42NzY4MDI0IEM2LjQyNzg3MzMsMjguNjc2ODAyNCAwLjAxNzQ5NjIyOTMsMjIuMjY2NTQ2IDAuMDE3NDk2MjI5MywxNC4zNTg4NTM3IEMwLjAxNzQ5NjIyOTMsNi40NTExNjEzOSA2LjQyNzg3MzMsMC4wNDA3ODQzMTM3IDE0LjMzNTU2NTYsMC4wNDA3ODQzMTM3IEMxOC43NTg4NTM3LDAuMDQwNzg0MzEzNyAyMi43MTIwMzYyLDIuMDQ4MTQ0OCAyNS4zMzgxNTk5LDUuMjAwMTIwNjYgQzI1LjQ0NzQ4MTEsNS4zMDk1NjI1OSAyNS41MTU0MTQ4LDUuNDYwNzU0MTUgMjUuNTE1NDE0OCw1LjYyNzYzMTk4IEMyNS41MTUyOTQxLDUuOTYyMjMyMjggMjUuMjQ0NTI0OSw2LjIzMjg4MDg0IDI0LjkxMDA0NTIsNi4yMzI4ODA4NCBaIiBpZD0iU2hhcGUiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8dGV4dCBpZD0iQ0xJRU5ULUNvcHktOSIgb3BhY2l0eT0iMC41IiBmb250LXNpemU9IjE3IiBmb250LXdlaWdodD0ibm9ybWFsIiBsZXR0ZXItc3BhY2luZz0iMC44NzkzMTAzNjkiPgogICAgICAgICAgICAgICAgICAgIDx0c3BhbiB4PSI0Mi4yMTQxMzc4IiB5PSI0MiI+Q0xJRU5UPC90c3Bhbj4KICAgICAgICAgICAgICAgIDwvdGV4dD4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+" + alt="React.js" + /> + <h2>Apollo Client</h2> + <span className="for">For Data Management</span> + <small /> + <ul> + <li> + Performing GraphQL <strong>Mutations</strong> + </li> + <li> + Fetching GraphQL <strong>Queries</strong> + </li> + <li> + <strong>Caching</strong> GraphQL Data + </li> + <li> + Managing <strong>Local State</strong> + </li> + <li> + <strong>Error</strong> and <strong>Loading</strong> UI States + </li> + <li> + <small>Apollo-client replaces the need for redux + data fetching libraries</small> + </li> + </ul> + </div> + <div className="slat slat3"> + <img + src="https://camo.githubusercontent.com/389368863d9b9df25acd07644bad7642459c3533/68747470733a2f2f696d6775722e636f6d2f5376366a3042362e706e67" + alt="React.js" + /> + <h2>YOGA Server</h2> + + <span className="for">An Express GraphQL Server For:</span> + <ul> + <li> + Implementing <strong>Query</strong> and <strong>Mutation</strong>{' '} + <strong>Resolvers</strong> + </li> + <li> + Custom <strong>Server Side Logic</strong> + </li> + <li> + <strong>Charging</strong> Credit Cards + </li> + <li> + <strong>Sending</strong> Email + </li> + <li> + Performing <strong>Authentication</strong> + </li> + <li> + Checking <strong>Permissions</strong> + </li> + </ul> + </div> + <div className="slat slat4"> + <img + src="https://camo.githubusercontent.com/87336b0d10b0d1f27518e14c4a36f995babd6a2f/68747470733a2f2f696d6775722e636f6d2f485575313072482e706e67" + alt="React.js" + /> + <h2>Prisma Server</h2> + + <span className="for">A GraphQL Database Interface</span> + <ul> + <li> + Provides a set of GraphQL <strong>CRUD APIs</strong> for our (currently MySQL){' '} + <strong>Database</strong> + </li> + <li> + <strong>Schema</strong> Definition + </li> + <li> + Data <strong>Relationships</strong> + </li> + <li> + <strong>Queried</strong> Directly from our Yoga Server + </li> + <li> + <strong>Self-hosted</strong> or <strong>as-a-service</strong> + </li> + </ul> + </div> + <span className="arrow arrow1">↔</span> + <span className="arrow arrow2">↔</span> + <span className="arrow arrow3">↔</span> + </DiagramStyles> +); + +const DiagramPage = () => ( + <Page> + <Diagram /> + </Page> +); + +export default withData(DiagramPage); diff --git a/frontend/pages/admin/update.js b/frontend/pages/update.js index 2f445d7..b30cd1c 100644 --- a/frontend/pages/admin/update.js +++ b/frontend/pages/update.js @@ -1,8 +1,8 @@ import { Component } from 'react'; -import withData from '../../lib/withData'; -import UpdateItem from '../../components/UpdateItem'; -import Page from '../../components/Page'; -import PleaseSignIn from '../../components/PleaseSignIn'; +import withData from '../lib/withData'; +import UpdateItem from '../components/UpdateItem'; +import Page from '../components/Page'; +import PleaseSignIn from '../components/PleaseSignIn'; class Home extends Component { render() { diff --git a/frontend/queries/index.js b/frontend/queries/index.js index 5e9e28b..26eae35 100644 --- a/frontend/queries/index.js +++ b/frontend/queries/index.js @@ -102,11 +102,14 @@ export const SINGLE_ITEM_QUERY = gql` export const SINGLE_ORDER_QUERY = gql` query order($id: ID!) { - order(where: { id: $id }) { + order(id: $id) { id charge total createdAt + user { + id + } items { id title @@ -179,6 +182,7 @@ export const CURRENT_USER_QUERY = gql` id email name + permissions orders { charge id |
