From cd0b07b3adb36fb5ec098f9e511ab45d774fee7b Mon Sep 17 00:00:00 2001 From: Wes Bos Date: Tue, 30 Jan 2018 12:41:01 -0500 Subject: Move to Prisma Backend --- frontend/.babelrc | 9 + frontend/.env | 1 + frontend/README.md | 28 + frontend/components/AddToCart.js | 100 + frontend/components/Cart.js | 46 + frontend/components/CartList.js | 114 + frontend/components/ChaChing.js | 56 + frontend/components/Count.js | 37 + frontend/components/CreateItem.js | 128 + frontend/components/ErrorMessage.js | 32 + frontend/components/Header.js | 42 + frontend/components/Item.js | 61 + frontend/components/Items.js | 97 + frontend/components/LoginAuth0.js | 76 + frontend/components/Meta.js | 24 + frontend/components/Nav.js | 46 + frontend/components/OrderList.js | 52 + frontend/components/Page.js | 24 + frontend/components/Pagination.js | 43 + frontend/components/Search.js | 68 + frontend/components/Signup.js | 94 + frontend/components/SingleItem.js | 36 + frontend/components/TakeMyMoney.js | 43 + frontend/components/UpdateItem.js | 75 + frontend/config.js | 5 + frontend/enhancers.js | 18 + frontend/lib/formatMoney.js | 3 + frontend/lib/image.js | 6 + frontend/lib/initApollo.js | 62 + frontend/lib/withData.js | 76 + frontend/notes.md | 68 + frontend/package-lock.json | 11493 +++++++++++++++++++++++ frontend/package.json | 50 + frontend/pages/add.js | 16 + frontend/pages/admin/update.js | 16 + frontend/pages/buy.js | 16 + frontend/pages/cart.js | 21 + frontend/pages/index.js | 21 + frontend/pages/item.js | 11 + frontend/pages/items.js | 2 + frontend/pages/orders.js | 21 + frontend/pages/signup.js | 21 + frontend/queries/fragments.js | 16 + frontend/queries/index.js | 196 + frontend/queries/mutations.js | 0 frontend/routes.js | 6 + frontend/server.js | 22 + frontend/src/auth0/auth0Authentication.graphql | 8 + frontend/src/auth0/auth0Authentication.js | 121 + frontend/static/nprogress.css | 72 + 50 files changed, 13699 insertions(+) create mode 100644 frontend/.babelrc create mode 100644 frontend/.env create mode 100644 frontend/README.md create mode 100644 frontend/components/AddToCart.js create mode 100644 frontend/components/Cart.js create mode 100644 frontend/components/CartList.js create mode 100644 frontend/components/ChaChing.js create mode 100644 frontend/components/Count.js create mode 100644 frontend/components/CreateItem.js create mode 100644 frontend/components/ErrorMessage.js create mode 100644 frontend/components/Header.js create mode 100644 frontend/components/Item.js create mode 100644 frontend/components/Items.js create mode 100644 frontend/components/LoginAuth0.js create mode 100644 frontend/components/Meta.js create mode 100644 frontend/components/Nav.js create mode 100644 frontend/components/OrderList.js create mode 100644 frontend/components/Page.js create mode 100644 frontend/components/Pagination.js create mode 100644 frontend/components/Search.js create mode 100644 frontend/components/Signup.js create mode 100644 frontend/components/SingleItem.js create mode 100644 frontend/components/TakeMyMoney.js create mode 100644 frontend/components/UpdateItem.js create mode 100644 frontend/config.js create mode 100644 frontend/enhancers.js create mode 100644 frontend/lib/formatMoney.js create mode 100644 frontend/lib/image.js create mode 100644 frontend/lib/initApollo.js create mode 100644 frontend/lib/withData.js create mode 100644 frontend/notes.md create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/pages/add.js create mode 100644 frontend/pages/admin/update.js create mode 100644 frontend/pages/buy.js create mode 100644 frontend/pages/cart.js create mode 100644 frontend/pages/index.js create mode 100644 frontend/pages/item.js create mode 100644 frontend/pages/items.js create mode 100644 frontend/pages/orders.js create mode 100644 frontend/pages/signup.js create mode 100644 frontend/queries/fragments.js create mode 100644 frontend/queries/index.js create mode 100644 frontend/queries/mutations.js create mode 100644 frontend/routes.js create mode 100644 frontend/server.js create mode 100644 frontend/src/auth0/auth0Authentication.graphql create mode 100644 frontend/src/auth0/auth0Authentication.js create mode 100644 frontend/static/nprogress.css (limited to 'frontend') diff --git a/frontend/.babelrc b/frontend/.babelrc new file mode 100644 index 0000000..d9c3f91 --- /dev/null +++ b/frontend/.babelrc @@ -0,0 +1,9 @@ +{ + "presets": ["next/babel"], + "plugins": [ + [ + "styled-components", + { "ssr": true, "displayName": true, "preprocess": false } + ] + ] +} diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 0000000..ac968d9 --- /dev/null +++ b/frontend/.env @@ -0,0 +1 @@ +COOL=true diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..c254cc0 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,28 @@ +## Gotchas + +https://github.com/graphcool/feature-requests/issues/145#issuecomment-318968578 + +Clients -> Advanced Settings -> OAuth -> JsonWebToken Signature Algorithm: HS256 + + + +# Advanced-React + +## Testing + +Test Coverage --coverage +__tests__ +what.test.js +__mock__ +Watching --watch +__snapshots__ + +## Marketing + +I love this look and feel: http://wes.io/lxQe +https://www.youtube.com/watch?v=Y0ePh9OxSWo + + +## Questions +* Apollo Link State will be good for local variables +* Server Side Auth \ No newline at end of file diff --git a/frontend/components/AddToCart.js b/frontend/components/AddToCart.js new file mode 100644 index 0000000..e46a1f5 --- /dev/null +++ b/frontend/components/AddToCart.js @@ -0,0 +1,100 @@ +import { Component } from 'react'; +import { ADD_TO_CART_MUTATION, CURRENT_USER_QUERY, SINGLE_ITEM_QUERY } from '../queries'; +import { removeFromCartEnhancer, userEnhancer } from '../enhancers'; +import { graphql, compose } from 'react-apollo'; +import Transition from 'react-transition-group/Transition'; +import makeImage from '../lib/image'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; + +const JumpImg = styled.img` + border: 0 solid black; + max-width: 100px; + transition: all 0.5s; + position: fixed; + left: ${props => props.x}px; + top: -100%; + &.jump-entered { + border-color: green; + transform-origin: 0 0; + transform: scale(0); + top: ${props => props.y}px; + left: ${props => props.x}px; + } + &.jump-entering { + border-color: yellow; + top: ${props => props.y}px; + left: ${props => props.x}px; + } + &.jump-exited { + border-color: red; + } + &.jump-exiting { + border-color: yellow; + } +`; + +class AddToCart extends Component { + static propTypes = { + currentUserQuery: PropTypes.object, + }; + + componentDidMount() { + this.props.currentUserQuery.refetch(); + } + + addToCart = async () => { + const res = await this.props.addToCart({ + variables: { + userId: this.props.currentUserQuery.user.id, + itemId: this.props.id, + }, + }); + this.props.currentUserQuery.refetch(); + console.log(res); + }; + + removeFromCart = async () => { + const res = await this.props.removeFromCart({ + variables: { + userId: this.props.currentUserQuery.user.id, + itemId: this.props.id, + }, + }); + this.props.currentUserQuery.refetch(); + console.log(res); + }; + + render() { + const user = this.props.currentUserQuery.user; + // TODO WTF + if (!user || this.props.singleItemQuery.loading || !this.props.singleItemQuery.Item) return

Loading...

; + const cartIds = user.cart.map(item => item.id); + const image = this.props.singleItemQuery.Item.image || {}; + const isInCart = cartIds.includes(this.props.id); + const { x, y } = document.querySelector('.cart').getBoundingClientRect(); + return ( +
+ {isInCart ? ( + + ) : ( + + )} + + {status => } + +
+ ); + } +} + +const createOrderEnhancer = graphql(ADD_TO_CART_MUTATION, { name: 'addToCart' }); +const singleItemEnhancer = graphql(SINGLE_ITEM_QUERY, { + name: 'singleItemQuery', + options: ({ id }) => ({ + variables: { + id, + }, + }), +}); +export default compose(userEnhancer, createOrderEnhancer, removeFromCartEnhancer, singleItemEnhancer)(AddToCart); diff --git a/frontend/components/Cart.js b/frontend/components/Cart.js new file mode 100644 index 0000000..88b072c --- /dev/null +++ b/frontend/components/Cart.js @@ -0,0 +1,46 @@ +import { Component } from 'react'; +import { graphql, compose } from 'react-apollo'; +import styled from 'styled-components'; +import { CURRENT_USER_QUERY } from '../queries'; +import formatMoney from '../lib/formatMoney.js'; +import ChaChing from './ChaChing'; + +const CartStyles = styled.div` + background: white; + border-radius: 10px; + padding: 20px; + overflow: hidden; + display: flex; + align-items: center; +`; + +class Cart extends Component { + componentDidMount() { + // This fetches the new data, but doesn't populate the user via props + // this.props.currentUserQuery.refetch(); + // This fetches the new data, and populates the user via props + setTimeout(this.props.currentUserQuery.refetch, 1); + } + + render() { + // Check for loading state.. + const { loading, error } = this.props.currentUserQuery; + const { user } = this.props.currentUserQuery; + if (loading || error || !user) return

Cart Loading...

; + const total = user.cart.reduce((a, b) => a + b.price, 0); + + return ( + + There + {user.cart.length === 1 ? ' is ' : 'are '} + + {user.cart.length === 1 ? ' item ' : ' items '} + in your cart totaling + + + ); + } +} + +const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); +export default compose(userEnhancer)(Cart); diff --git a/frontend/components/CartList.js b/frontend/components/CartList.js new file mode 100644 index 0000000..b54aa39 --- /dev/null +++ b/frontend/components/CartList.js @@ -0,0 +1,114 @@ +import { Component } from 'react'; +import withData from '../lib/withData'; +import Items from '../components/Items'; +import Signup from '../components/Signup'; +import LoginAuth0 from '../components/LoginAuth0'; +import Page from '../components/Page'; +import { USER_ORDERS_QUERY, GET_CART_STATE } from '../queries'; +import { graphql, compose } from 'react-apollo'; +import has from 'lodash.has'; +import get from 'lodash.get'; +import styled from 'styled-components'; +import formatMoney from '../lib/formatMoney'; +import makeImage from '../lib/image'; +import TakeMyMoney from './TakeMyMoney'; +import { removeFromCartEnhancer } from '../enhancers'; +import { CURRENT_USER_QUERY } from '../queries'; +import PropTypes from 'prop-types'; + +const CartStyles = styled.div` + padding: 20px; + position: relative; + background: white; + position: fixed; + height: 100%; + top: 0; + right: 0; + bottom: 0; + transform: translateX(100%); + transition: all 0.3s; + box-shadow: 0 0 10px 3px rgba(0, 0, 0, 0.2); + z-index: 5; + ${props => props.open && `transform: translateX(0);`}; + .toggleOpen { + position: absolute; + top: 0; + left: 0; + transform: translateX(-100%); + } +`; + +class CartList extends Component { + static contextTypes = { + client: PropTypes.object, + }; + + componentDidMount() { + setTimeout(this.props.currentUserQuery.refetch, 1); + // Set the cart state to be closed + this.context.client.writeQuery({ + query: GET_CART_STATE, + data: { ui: { isCartOpen: true, __typename: 'Network' } }, + }); + } + + toggleOpen = () => { + this.props.uiQuery.updateQuery(() => ({ ui: { isCartOpen: false, __typename: 'Network' } })); + }; + + render() { + if (this.props.loading) { + return

Loading....

; + } + + if (this.props.error) { + return

Error....

; + } + + // if (!has(this.props, 'currentUserQuery.user.cart')) { + // return

Don't have it yet!

; + // } + // OMG Clean this up + let { user = {} } = this.props.currentUserQuery || {}; + if (!user) user = {}; + const cart = user.cart || []; + const userId = user.id; + // const userId = this.props.currentUserQuery.user.id; + + const total = cart.reduce((a, b) => a + b.price, 0); + return ( + + +

{cart.length} Items

+ + + + + +
+ ); + } +} + +const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); +const uiEnhancer = graphql(GET_CART_STATE, { name: 'uiQuery' }); +export default compose(userEnhancer, removeFromCartEnhancer, uiEnhancer)(CartList); diff --git a/frontend/components/ChaChing.js b/frontend/components/ChaChing.js new file mode 100644 index 0000000..5874c27 --- /dev/null +++ b/frontend/components/ChaChing.js @@ -0,0 +1,56 @@ +import TransitionGroup from 'react-transition-group/TransitionGroup'; +import Transition from 'react-transition-group/Transition'; +import styled from 'styled-components'; + +const CartCount = styled.div` + padding: 10px; + transition: all 0.5s; + display: inline-block; + background: white; + background: linear-gradient(to bottom, #eeeeee 0%, #eeeeee 100%); + transform: translateY(100%); + z-index: 1; + position: relative; + &.cart-entering { + background: green; + transform: translateY(100%); + } + &.cart-entered { + transform: translateY(0); + } + &.cart-exiting { + position: absolute; + z-index: 0; + left: 0; + transform: translateY(-100%); + } +`; + +const ChaChingStyles = styled.div` + position: relative; + display: inline-block; + overflow: hidden; +`; + +const ChaChing = ({ amount }) => ( + + + + {status => ( + + {amount} + + )} + + + +); + +export default ChaChing; diff --git a/frontend/components/Count.js b/frontend/components/Count.js new file mode 100644 index 0000000..800a902 --- /dev/null +++ b/frontend/components/Count.js @@ -0,0 +1,37 @@ +import React, { Component } from 'react' +import { graphql, gql } from 'react-apollo' + +import { ALL_ITEMS_QUERY } from '../queries'; + +class Count extends Component { + + render() { + + // 1 + if (this.props.allLinksQuery && this.props.allLinksQuery.loading) { + return
Loading
+ } + + // 2 + if (this.props.allLinksQuery && this.props.allLinksQuery.error) { + return
Error
+ } + + // 3 + const itemsToRender = this.props.allLinksQuery.allItems + + return ( +
+

There are {itemsToRender.length} items for sale

+
+ ) + } + +} + +// 1 + +// We export the graphQL HOC - this will fetch the data and inject it into the Count compeont via props + +export { ALL_ITEMS_QUERY } +export default graphql(ALL_ITEMS_QUERY, { name: 'allLinksQuery' }) (Count) diff --git a/frontend/components/CreateItem.js b/frontend/components/CreateItem.js new file mode 100644 index 0000000..27b62e5 --- /dev/null +++ b/frontend/components/CreateItem.js @@ -0,0 +1,128 @@ +import React, { Component } from 'react'; +import { graphql, gql } from 'react-apollo'; +import { ALL_ITEMS_QUERY, CREATE_ITEM_MUTATION } from '../queries'; +import ErrorMessage from './ErrorMessage'; +import makeImage from '../lib/image'; +import { fileEndpoint } from '../config'; + +class CreateLink extends Component { + state = { + description: '', + title: '', + image: '', + price: 0, + fullPrice: 0, + loading: false, + error: { + message: '', + }, + }; + + componentWillReceiveProps(nextProps) { + console.log(nextProps); + } + + uploadFile = async e => { + this.setState({ loading: true }); + const files = e.currentTarget.files; + + const data = new FormData(); + data.append('data', files[0]); + + // use the file endpoint + const res = await fetch(fileEndpoint, { + method: 'POST', + body: data, + }); + const file = await res.json(); + this.setState({ image: file.id, loading: false }); + }; + + _createLink = async e => { + e.preventDefault(); + // pull the values from state + const { description, title, image, price, fullPrice } = this.state; + // create a mutation + // TODO: handle any errors + // turn loading on + this.setState({ loading: true }); + try { + const res = await this.props.createItemMutation({ + // pass in those variables from state + variables: { + description, + title, + price: parseInt(price), + fullPrice, + imageId: image, + }, + }); + } catch (error) { + this.setState({ error }); + console.log(error); + } + this.setState({ loading: false }); + }; + + render() { + return ( +
+ {this.state.loading ? 'LOADING...' : 'Ready!'} + + this.setState({ error: {} })} /> +
+

+ Image + +

+

+ Title + this.setState({ title: e.target.value })} + type="text" + placeholder="Title" + /> +

+ +