summaryrefslogtreecommitdiffstats
path: root/frontend/components
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/components')
-rw-r--r--frontend/components/AddToCart.js7
-rw-r--r--frontend/components/CreateItem.js5
-rw-r--r--frontend/components/ErrorMessage.js4
-rw-r--r--frontend/components/Item.js3
-rw-r--r--frontend/components/Order.js60
-rw-r--r--frontend/components/OrderList.js102
-rw-r--r--frontend/components/Page.js10
-rw-r--r--frontend/components/Permissions.js128
-rw-r--r--frontend/components/Signin.js84
-rw-r--r--frontend/components/SingleItem.js39
-rw-r--r--frontend/components/styles/SickButton.js15
11 files changed, 379 insertions, 78 deletions
diff --git a/frontend/components/AddToCart.js b/frontend/components/AddToCart.js
index 4757296..1d20c0c 100644
--- a/frontend/components/AddToCart.js
+++ b/frontend/components/AddToCart.js
@@ -9,6 +9,7 @@ class AddToCart extends Component {
};
update = (proxy, payload) => {
+ console.log('Updating!');
const newCartItem = payload.data.addToCart;
const data = proxy.readQuery({ query: CURRENT_USER_QUERY });
const existingIndex = data.me.cart.findIndex(cartItem => cartItem.id === newCartItem.id);
@@ -25,7 +26,11 @@ class AddToCart extends Component {
const { id } = this.props;
return (
<Mutation mutation={ADD_TO_CART_MUTATION} variables={{ id }} update={this.update}>
- {addToCart => <button onClick={addToCart}>🛒 Add To Cart</button>}
+ {(addToCart, { loading }) => (
+ <button disabled={loading} onClick={addToCart}>
+ 🛒 Add{loading && 'ing'} To Cart
+ </button>
+ )}
</Mutation>
);
}
diff --git a/frontend/components/CreateItem.js b/frontend/components/CreateItem.js
index d166c81..ece62b4 100644
--- a/frontend/components/CreateItem.js
+++ b/frontend/components/CreateItem.js
@@ -60,12 +60,13 @@ class CreateItem extends Component {
<fieldset disabled={loading} aria-busy={loading}>
<label>
Image
- <input onChange={this.uploadFile} type="file" accept=".png, .jpg, .jpeg" />
+ <input required onChange={this.uploadFile} type="file" accept=".png, .jpg, .jpeg" />
{this.state.image ? <img src={this.state.image} width="100" alt={this.state.title} /> : null}
</label>
<label>
Title
<input
+ required
value={this.state.title}
onChange={this.handleChange}
type="text"
@@ -77,6 +78,7 @@ class CreateItem extends Component {
<label>
Price
<input
+ required
type="number"
id="price"
name="price"
@@ -87,6 +89,7 @@ class CreateItem extends Component {
</label>
<textarea
id="description"
+ required
name="description"
value={this.state.description}
onChange={this.handleChange}
diff --git a/frontend/components/ErrorMessage.js b/frontend/components/ErrorMessage.js
index c473731..3731554 100644
--- a/frontend/components/ErrorMessage.js
+++ b/frontend/components/ErrorMessage.js
@@ -21,8 +21,8 @@ const StyledError = styled.div`
const DisplayError = ({ error }) => {
if (!error || !error.message) return null;
if (error.networkError && error.networkError.result && error.networkError.result.errors.length) {
- return error.networkError.result.errors.map(error => (
- <StyledError>
+ return error.networkError.result.errors.map((error, i) => (
+ <StyledError key={i}>
<p>
<strong>Shoot!</strong>
{error.message}
diff --git a/frontend/components/Item.js b/frontend/components/Item.js
index 9bff385..050ec17 100644
--- a/frontend/components/Item.js
+++ b/frontend/components/Item.js
@@ -10,11 +10,10 @@ import PropTypes from 'prop-types';
const Item = styled.div`
background: white;
border: 1px solid ${props => props.theme.offWhite};
- box-shadow: 0 12px 24px 0 rgba(0, 0, 0, 0.09);
+ box-shadow: ${props => props.theme.bs};
position: relative;
display: grid;
align-content: start;
- /* grid-template-rows: repeat(auto-fit, minmax(100px,)); */
grid-auto-rows: fit-content;
img {
width: 100%;
diff --git a/frontend/components/Order.js b/frontend/components/Order.js
index a715483..603aae5 100644
--- a/frontend/components/Order.js
+++ b/frontend/components/Order.js
@@ -3,8 +3,46 @@ import { Query } from 'react-apollo';
import { format } from 'date-fns';
import Head from 'next/head';
import PropTypes from 'prop-types';
+import styled from 'styled-components';
import { SINGLE_ORDER_QUERY } from '../queries';
import formatMoney from '../lib/formatMoney';
+import Dump from './Dump';
+
+const OrderStyles = styled.div`
+ max-width: 1000px;
+ margin: 0 auto;
+ border: 1px solid ${props => props.theme.offWhite};
+ box-shadow: ${props => props.theme.bs};
+ padding: 2rem;
+ border-top: 10px solid red;
+ & > p {
+ display: grid;
+ grid-template-columns: 1fr 5fr;
+ margin: 0;
+ border-bottom: 1px solid ${props => props.theme.offWhite};
+ span {
+ padding: 1rem;
+ &:first-child {
+ font-weight: 900;
+ text-align: right;
+ }
+ }
+ }
+ .order-item {
+ border-bottom: 1px solid ${props => props.theme.offWhite};
+ display: grid;
+ grid-template-columns: 300px 1fr;
+ align-items: center;
+ grid-gap: 2rem;
+ margin: 2rem 0;
+ padding-bottom: 2rem;
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+`;
class Order extends Component {
static propTypes = {
@@ -18,10 +56,11 @@ class Order extends Component {
if (loading) return <p>Loading...</p>;
if (!order || error) return <p>No Order Found!</p>;
return (
- <div>
+ <OrderStyles>
<Head>
<title>Sick Fits - Order {order.id}</title>
</Head>
+ <Dump order={order} />
<p>
<span>Order Id:</span>
<span>{order.id}</span>
@@ -38,17 +77,26 @@ class Order extends Component {
<span>Order Total</span>
<span>{formatMoney(order.total)}</span>
</p>
- <p>You Bought {order.items.length} items in this order</p>
+ <p>
+ <span>Item Count</span>
+ <span>{order.items.length}</span>
+ </p>
<div className="items">
{order.items.map(item => (
<div className="order-item" key={item.id}>
- <h2>
- {item.quantity} OF {item.title}
- </h2>
+ <img src={item.image} alt={item.title} />
+ <div className="item-details">
+ <h2> {item.title} </h2>
+ <p>Qty: {item.quantity}</p>
+ <p>Each: {formatMoney(item.price)}</p>
+ <p>Subtotal: {formatMoney(item.price * item.quantity)}</p>
+ <p>SubTotal: {item.description}</p>
+ <p>{item.description}</p>
+ </div>
</div>
))}
</div>
- </div>
+ </OrderStyles>
);
}}
</Query>
diff --git a/frontend/components/OrderList.js b/frontend/components/OrderList.js
index ed7beae..5467f93 100644
--- a/frontend/components/OrderList.js
+++ b/frontend/components/OrderList.js
@@ -3,6 +3,54 @@ import { Query } from 'react-apollo';
import { format, formatDistance } from 'date-fns';
import Link from 'next/link';
import { USER_ORDERS_QUERY } from '../queries';
+import styled from 'styled-components';
+import formatMoney from '../lib/formatMoney';
+
+const OrderUl = styled.ul`
+ display: grid;
+ grid-gap: 4rem;
+ grid-template-columns: repeat(auto-fit, minmax(40%, 1fr));
+`;
+const OrderListItem = styled.li`
+ box-shadow: ${props => props.theme.bs};
+ list-style: none;
+ padding: 2rem;
+ border: 1px solid ${props => props.theme.offWhite};
+ h2 {
+ border-bottom: 2px solid red;
+ margin-top: 0;
+ margin-bottom: 2rem;
+ padding-bottom: 2rem;
+ }
+
+ .images {
+ display: grid;
+ grid-gap: 10px;
+ grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
+ margin-top: 1rem;
+ img {
+ height: 200px;
+ object-fit: cover;
+ width: 100%;
+ }
+ }
+ .order-meta {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(20px, 1fr));
+ display: grid;
+ grid-gap: 1rem;
+ text-align: center;
+ & > * {
+ margin: 0;
+ background: rgba(0, 0, 0, 0.03);
+ padding: 1rem 0;
+ }
+ strong {
+ display: block;
+ margin-bottom: 1rem;
+ }
+ }
+`;
class OrderList extends React.Component {
render() {
@@ -15,28 +63,42 @@ class OrderList extends React.Component {
return (
<div>
<h2>You have {orders.length} Orders</h2>
- <ul>
+ <OrderUl>
{orders.map(order => (
- <li key={order.id}>
- <ul className="order-items">
- <hr />
- <h2>
- <Link
- href={{
- pathname: '/order',
- query: { id: order.id },
- }}
- >
- <a>{order.id}</a>
- </Link>
- <time dateTime={order.createdAt} title={format(order.createdAt, 'MMMM D, YYYY h:mm A')}>
- {formatDistance(order.createdAt, new Date())}
- </time>
- </h2>
- </ul>
- </li>
+ <OrderListItem key={order.id}>
+ <Link
+ href={{
+ pathname: '/order',
+ query: { id: order.id },
+ }}
+ >
+ <a>
+ <div className="order-meta">
+ <p>
+ <strong>{order.items.reduce((a, b) => a + b.quantity, 0)}</strong>
+ Items
+ </p>
+ <p>
+ <strong>{order.items.length}</strong>
+ Products
+ </p>
+ <p>
+ <strong>{formatDistance(order.createdAt, new Date())}</strong>
+ ago
+ </p>
+ <p>
+ <strong>{formatMoney(order.total)}</strong>
+ Total
+ </p>
+ </div>
+ <div className="images">
+ {order.items.map(item => <img key={item.id} src={item.image} alt={item.title} />)}
+ </div>
+ </a>
+ </Link>
+ </OrderListItem>
))}
- </ul>
+ </OrderUl>
</div>
);
}}
diff --git a/frontend/components/Page.js b/frontend/components/Page.js
index bfe8492..031536b 100644
--- a/frontend/components/Page.js
+++ b/frontend/components/Page.js
@@ -13,6 +13,7 @@ const theme = {
lightgrey: '#E1E1E1',
offWhite: '#EDEDED',
maxWidth: '1300px',
+ bs: '0 12px 24px 0 rgba(0, 0, 0, 0.09)',
};
injectGlobal`
@@ -26,6 +27,7 @@ injectGlobal`
background-color: #ffffff;
margin: 0;
font-size: 1.5rem;
+ line-height: 2;
background: red;
}
*, *:before, *:after {
@@ -37,6 +39,12 @@ injectGlobal`
}
`;
+const Inner = styled.div`
+ max-width: 1000px;
+ margin: 0 auto;
+ padding: 2rem;
+`;
+
const StyledPage = styled.div`
color: ${props => props.theme.black};
background: white;
@@ -48,7 +56,7 @@ const Page = ({ children }) => (
<StyledPage className="main">
<Meta />
<Header />
- {children}
+ <Inner>{children}</Inner>
</StyledPage>
</ThemeProvider>
</UIProvider>
diff --git a/frontend/components/Permissions.js b/frontend/components/Permissions.js
new file mode 100644
index 0000000..0b3976c
--- /dev/null
+++ b/frontend/components/Permissions.js
@@ -0,0 +1,128 @@
+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';
+
+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;
+ }
+ }
+`;
+
+class User extends React.Component {
+ state = {
+ permissions: this.props.user.permissions,
+ };
+ handlePermissionsChange = e => {
+ const checkbox = e.target;
+ let updatedPermissions = [...this.state.permissions];
+ if (checkbox.checked) {
+ // add it in
+ updatedPermissions.push(checkbox.value);
+ } else {
+ updatedPermissions = updatedPermissions.filter(permission => permission !== checkbox.value);
+ }
+ this.setState({ permissions: updatedPermissions });
+ };
+ render() {
+ const { user } = this.props;
+ 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} />
+ <Error error={error} />
+ <h2>
+ {user.name} -- {user.email}
+ </h2>
+ <div className="labels">
+ {[
+ 'ADMIN',
+ 'USER',
+ 'ITEMCREATE',
+ 'ITEMUPDATE',
+ 'ITEMDELETE',
+ 'PERMISSIONUPDATE',
+ 'NUKE',
+ ].map(permission => (
+ <label key={permission} htmlFor={`${user.id}-permission-${permission}`}>
+ <input
+ type="checkbox"
+ checked={this.state.permissions.includes(permission)}
+ name={`permission-${permission}`}
+ id={`${user.id}-permission-${permission}`}
+ 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>
+ )}
+ </Mutation>
+ );
+ }
+}
+
+const Permissions = () => (
+ <Query query={ALL_USERS_QUERY}>
+ {({ data, error, loading }) => {
+ if (loading) return <div>Loading</div>;
+ if (error) return <Error error={error} />;
+ return (
+ <div>
+ <h1>Manage User Permissions</h1>
+ {data.users.map(user => <User key={user.id} user={user} />)}
+ </div>
+ );
+ }}
+ </Query>
+);
+
+export default Permissions;
diff --git a/frontend/components/Signin.js b/frontend/components/Signin.js
index 9f9eb96..846fd0c 100644
--- a/frontend/components/Signin.js
+++ b/frontend/components/Signin.js
@@ -1,5 +1,5 @@
import React, { Component } from 'react';
-import { Mutation } from 'react-apollo';
+import { Mutation, Query } from 'react-apollo';
import { SIGNIN_MUTATION, CURRENT_USER_QUERY } from '../queries';
import Error from './ErrorMessage';
import Form from './styles/Form';
@@ -10,15 +10,19 @@ class Signin extends Component {
password: 'abc123',
};
- loginUser = async (e, signin) => {
+ loginUser = async (e, signin, refetchUser) => {
e.preventDefault();
const res = await signin();
localStorage.setItem('token', res.data.signin.token);
+ await refetchUser();
// TODO refetch current user query
};
- update = (proxy, result) => {
- console.log(proxy, result);
+ update = (proxy, payload) => {
+ console.log(proxy);
+ const data = proxy.readQuery({ query: CURRENT_USER_QUERY });
+ data.me = payload.data.signin.user;
+ proxy.writeQuery({ query: CURRENT_USER_QUERY, data });
};
saveToState = e => {
@@ -27,46 +31,46 @@ class Signin extends Component {
};
render() {
+ // TODO / ASK - do I really have to wrap this in a query just to access the refetch function
return (
- <Mutation
- update={this.update}
- mutation={SIGNIN_MUTATION}
- variables={this.state}
- refetchQueries={[{ query: CURRENT_USER_QUERY }]}
- >
- {(signin, { data, loading, error }) => (
- <Form onSubmit={e => this.loginUser(e, signin)}>
- <Error error={error} />
- <fieldset disabled={loading} aria-busy={loading}>
- <label htmlFor="email">
- Email
- <input
- value={this.state.email}
- onChange={this.saveToState}
- name="email"
- type="text"
- placeholder="email"
- />
- </label>
+ <Query query={CURRENT_USER_QUERY}>
+ {({ refetch }) => (
+ <Mutation mutation={SIGNIN_MUTATION} variables={this.state}>
+ {(signin, { data, loading, error }) => (
+ <Form onSubmit={e => this.loginUser(e, signin, refetch)}>
+ <Error error={error} />
+ <fieldset disabled={loading} aria-busy={loading}>
+ <label htmlFor="email">
+ Email
+ <input
+ value={this.state.email}
+ onChange={this.saveToState}
+ name="email"
+ type="text"
+ placeholder="email"
+ />
+ </label>
- <label htmlFor="password">
- Password
- <input
- type="password"
- name="password"
- id="password"
- className="password"
- placeholder="password"
- value={this.state.password}
- onChange={this.saveToState}
- />
- </label>
+ <label htmlFor="password">
+ Password
+ <input
+ type="password"
+ name="password"
+ id="password"
+ className="password"
+ placeholder="password"
+ value={this.state.password}
+ onChange={this.saveToState}
+ />
+ </label>
- <button type="submit">Sign In!</button>
- </fieldset>
- </Form>
+ <button type="submit">Sign In!</button>
+ </fieldset>
+ </Form>
+ )}
+ </Mutation>
)}
- </Mutation>
+ </Query>
);
}
}
diff --git a/frontend/components/SingleItem.js b/frontend/components/SingleItem.js
index d4bec4f..53d5b9f 100644
--- a/frontend/components/SingleItem.js
+++ b/frontend/components/SingleItem.js
@@ -1,18 +1,47 @@
import { Query } from 'react-apollo';
import PropTypes from 'prop-types';
import { SINGLE_ITEM_QUERY } from '../queries/index';
+import Dump from './Dump';
+import styled from 'styled-components';
+import Link from 'next/link';
+import Error from './ErrorMessage';
+
+const SingleItemStyles = styled.div`
+ max-width: 1200px;
+ margin: 2rem auto;
+ box-shadow: ${props => props.theme.bs};
+ img {
+ width: 100%;
+ object-fit: cover;
+ }
+ .details {
+ margin: 3rem;
+ font-size: 3rem;
+ }
+`;
const SingleItem = props => (
<Query query={SINGLE_ITEM_QUERY} variables={{ id: props.id }}>
{({ data: { items }, loading, error }) => {
- if (loading || error) return null;
+ if (loading) return <p>Loading...</p>;
const [item] = items;
return (
- <div>
+ <SingleItemStyles>
+ <Error error={error} />
<img src={item.largeImage || item.image} alt={item.title} />
- <h2>Viewing {item.title}</h2>
- <p>{item.description}</p>
- </div>
+ <div className="details">
+ <h2>Viewing {item.title}</h2>
+ <p>{item.description}</p>
+ <Link
+ href={{
+ pathname: '/admin/update',
+ query: { id: item.id },
+ }}
+ >
+ <a>Edit ✏️</a>
+ </Link>
+ </div>
+ </SingleItemStyles>
);
}}
</Query>
diff --git a/frontend/components/styles/SickButton.js b/frontend/components/styles/SickButton.js
new file mode 100644
index 0000000..3bd5001
--- /dev/null
+++ b/frontend/components/styles/SickButton.js
@@ -0,0 +1,15 @@
+import styled from 'styled-components';
+
+const SickButton = styled.button`
+ background: red;
+ color: white;
+ font-weight: 500;
+ border: 0;
+ border-radius: 0;
+ text-transform: uppercase;
+ font-size: 2rem;
+ padding: 0.8rem 1.5rem;
+ transform: skew(-2deg);
+`;
+
+export default SickButton;