Skip to content

Commit

Permalink
49
Browse files Browse the repository at this point in the history
  • Loading branch information
wesbos committed Aug 21, 2018
1 parent 3065f07 commit 5d4fca1
Show file tree
Hide file tree
Showing 5 changed files with 338 additions and 0 deletions.
61 changes: 61 additions & 0 deletions stepped-solutions/47/frontend/components/Cart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { Query, Mutation } from 'react-apollo';
import gql from 'graphql-tag';
import { adopt } from 'react-adopt';
import User from './User';
import CartStyles from './styles/CartStyles';
import Supreme from './styles/Supreme';
import CloseButton from './styles/CloseButton';
import SickButton from './styles/SickButton';
import CartItem from './CartItem';
import calcTotalPrice from '../lib/calcTotalPrice';
import formatMoney from '../lib/formatMoney';

const LOCAL_STATE_QUERY = gql`
query {
cartOpen @client
}
`;

const TOGGLE_CART_MUTATION = gql`
mutation {
toggleCart @client
}
`;
/* eslint-disable */
const Composed = adopt({
user: ({ render }) => <User>{render}</User>,
toggleCart: ({ render }) => <Mutation mutation={TOGGLE_CART_MUTATION}>{render}</Mutation>,
localState: ({ render }) => <Query query={LOCAL_STATE_QUERY}>{render}</Query>,
});
/* eslint-enable */

const Cart = () => (
<Composed>
{({ user, toggleCart, localState }) => {
const me = user.data.me;
if (!me) return null;
return (
<CartStyles open={localState.data.cartOpen}>
<header>
<CloseButton onClick={toggleCart} title="close">
&times;
</CloseButton>
<Supreme>{me.name}'s Cart</Supreme>
<p>
You Have {me.cart.length} Item{me.cart.length === 1 ? '' : 's'} in your cart.
</p>
</header>
<ul>{me.cart.map(cartItem => <CartItem key={cartItem.id} cartItem={cartItem} />)}</ul>
<footer>
<p>{formatMoney(calcTotalPrice(me.cart))}</p>
<SickButton>Checkout</SickButton>
</footer>
</CartStyles>
);
}}
</Composed>
);

export default Cart;
export { LOCAL_STATE_QUERY, TOGGLE_CART_MUTATION };
75 changes: 75 additions & 0 deletions stepped-solutions/48/frontend/components/Header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Link from 'next/link';
import styled from 'styled-components';
import NProgress from 'nprogress';
import Router from 'next/router';
import Nav from './Nav';
import Cart from './Cart';
import Search from './Search';

Router.onRouteChangeStart = () => {
NProgress.start();
};
Router.onRouteChangeComplete = () => {
NProgress.done();
};

Router.onRouteChangeError = () => {
NProgress.done();
};

const Logo = styled.h1`
font-size: 4rem;
margin-left: 2rem;
position: relative;
z-index: 2;
transform: skew(-7deg);
a {
padding: 0.5rem 1rem;
background: ${props => props.theme.red};
color: white;
text-transform: uppercase;
text-decoration: none;
}
@media (max-width: 1300px) {
margin: 0;
text-align: center;
}
`;

const StyledHeader = styled.header`
.bar {
border-bottom: 10px solid ${props => props.theme.black};
display: grid;
grid-template-columns: auto 1fr;
justify-content: space-between;
align-items: stretch;
@media (max-width: 1300px) {
grid-template-columns: 1fr;
justify-content: center;
}
}
.sub-bar {
display: grid;
grid-template-columns: 1fr auto;
border-bottom: 1px solid ${props => props.theme.lightgrey};
}
`;

const Header = () => (
<StyledHeader>
<div className="bar">
<Logo>
<Link href="/">
<a>Sick Fits</a>
</Link>
</Logo>
<Nav />
</div>
<div className="sub-bar">
<Search />
</div>
<Cart />
</StyledHeader>
);

export default Header;
67 changes: 67 additions & 0 deletions stepped-solutions/48/frontend/components/Search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import Downshift from 'downshift';
import Router from 'next/router';
import { ApolloConsumer } from 'react-apollo';
import gql from 'graphql-tag';
import debounce from 'lodash.debounce';
import { DropDown, DropDownItem, SearchStyles } from './styles/DropDown';

const SEARCH_ITEMS_QUERY = gql`
query SEARCH_ITEMS_QUERY($searchTerm: String!) {
items(where: { OR: [{ title_contains: $searchTerm }, { description_contains: $searchTerm }] }) {
id
image
title
}
}
`;

class AutoComplete extends React.Component {
state = {
items: [],
loading: false,
};
onChange = debounce(async (e, client) => {
console.log('Searching...');
// turn loading on
this.setState({ loading: true });
// Manually query apollo client
const res = await client.query({
query: SEARCH_ITEMS_QUERY,
variables: { searchTerm: e.target.value },
});
this.setState({
items: res.data.items,
loading: false,
});
}, 350);
render() {
return (
<SearchStyles>
<div>
<ApolloConsumer>
{client => (
<input
type="search"
onChange={e => {
e.persist();
this.onChange(e, client);
}}
/>
)}
</ApolloConsumer>
<DropDown>
{this.state.items.map(item => (
<DropDownItem key={item.id}>
<img width="50" src={item.image} alt={item.title} />
{item.title}
</DropDownItem>
))}
</DropDown>
</div>
</SearchStyles>
);
}
}

export default AutoComplete;
93 changes: 93 additions & 0 deletions stepped-solutions/49/frontend/components/Search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';
import Downshift from 'downshift';
import Router from 'next/router';
import { ApolloConsumer } from 'react-apollo';
import gql from 'graphql-tag';
import debounce from 'lodash.debounce';
import { DropDown, DropDownItem, SearchStyles } from './styles/DropDown';

const SEARCH_ITEMS_QUERY = gql`
query SEARCH_ITEMS_QUERY($searchTerm: String!) {
items(where: { OR: [{ title_contains: $searchTerm }, { description_contains: $searchTerm }] }) {
id
image
title
}
}
`;

function routeToItem(item) {
Router.push({
pathname: '/item',
query: {
id: item.id,
},
});
}

class AutoComplete extends React.Component {
state = {
items: [],
loading: false,
};
onChange = debounce(async (e, client) => {
console.log('Searching...');
// turn loading on
this.setState({ loading: true });
// Manually query apollo client
const res = await client.query({
query: SEARCH_ITEMS_QUERY,
variables: { searchTerm: e.target.value },
});
this.setState({
items: res.data.items,
loading: false,
});
}, 350);
render() {
return (
<SearchStyles>
<Downshift onChange={routeToItem} itemToString={item => (item === null ? '' : item.title)}>
{({ getInputProps, getItemProps, isOpen, inputValue, highlightedIndex }) => (
<div>
<ApolloConsumer>
{client => (
<input
{...getInputProps({
type: 'search',
placeholder: 'Search For An Item',
id: 'search',
className: this.state.loading ? 'loading' : '',
onChange: e => {
e.persist();
this.onChange(e, client);
},
})}
/>
)}
</ApolloConsumer>
{isOpen && (
<DropDown>
{this.state.items.map((item, index) => (
<DropDownItem
{...getItemProps({ item })}
key={item.id}
highlighted={index === highlightedIndex}
>
<img width="50" src={item.image} alt={item.title} />
{item.title}
</DropDownItem>
))}
{!this.state.items.length &&
!this.state.loading && <DropDownItem> Nothing Found {inputValue}</DropDownItem>}
</DropDown>
)}
</div>
)}
</Downshift>
</SearchStyles>
);
}
}

export default AutoComplete;
42 changes: 42 additions & 0 deletions stepped-solutions/49/frontend/lib/withData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import withApollo from 'next-with-apollo';
import ApolloClient from 'apollo-boost';
import { endpoint } from '../config';
import { LOCAL_STATE_QUERY } from '../components/Cart';

function createClient({ headers }) {
return new ApolloClient({
uri: process.env.NODE_ENV === 'development' ? endpoint : endpoint,
request: operation => {
operation.setContext({
fetchOptions: {
credentials: 'include',
},
headers,
});
},
// local data
clientState: {
resolvers: {
Mutation: {
toggleCart(_, variables, { cache }) {
// read the cartOpen value from the cache
const { cartOpen } = cache.readQuery({
query: LOCAL_STATE_QUERY,
});
// Write the cart State to the opposite
const data = {
data: { cartOpen: !cartOpen },
};
cache.writeData(data);
return data;
},
},
},
defaults: {
cartOpen: false,
},
},
});
}

export default withApollo(createClient);

0 comments on commit 5d4fca1

Please sign in to comment.