summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWes Bos <wesbos@gmail.com>2018-04-12 16:31:01 -0400
committerWes Bos <wesbos@gmail.com>2018-04-12 16:31:01 -0400
commit97b6de6aa437f7a913a33b9e6aa18a919faf2caf (patch)
tree431e12a535baf1d2ad59601ee7c5e8084ae7a263
parentd22616d5a675f5a381c741784333151255713b45 (diff)
a whole bunch of things that should be in their own commits
-rw-r--r--backend/database/datamodel.graphql1
-rw-r--r--backend/database/prisma.yml4
-rw-r--r--backend/database/seed.graphql24
-rw-r--r--backend/src/index.js6
-rw-r--r--backend/src/mail.js18
-rw-r--r--backend/src/resolvers/Mutation.js95
-rw-r--r--backend/src/resolvers/Query.js48
-rw-r--r--backend/src/schema.graphql3
-rw-r--r--backend/src/utils.js35
-rw-r--r--frontend/components/AddToCart.js6
-rw-r--r--frontend/components/ErrorMessage.js7
-rw-r--r--frontend/components/Item.js2
-rw-r--r--frontend/components/Items.js10
-rw-r--r--frontend/components/Order.js12
-rw-r--r--frontend/components/Page.js14
-rw-r--r--frontend/components/Permissions.js113
-rw-r--r--frontend/components/PleaseSignIn.js11
-rw-r--r--frontend/components/Reset.js2
-rw-r--r--frontend/components/ResetRequest.js4
-rw-r--r--frontend/components/Search.js13
-rw-r--r--frontend/components/styles/SickButton.js5
-rw-r--r--frontend/components/styles/Table.js31
-rw-r--r--frontend/pages/buy.js16
-rw-r--r--frontend/pages/diagram.js202
-rw-r--r--frontend/pages/update.js (renamed from frontend/pages/admin/update.js)8
-rw-r--r--frontend/queries/index.js6
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