Skip to content

Commit 738f60a

Browse files
committed
Add AddReview, move REVIEWS_QUERY to graphql/Review.js
1 parent e040e3c commit 738f60a

File tree

6 files changed

+299
-57
lines changed

6 files changed

+299
-57
lines changed

src/components/AddReview.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import React, { Component } from 'react'
2+
import PropTypes from 'prop-types'
3+
import StarInput from 'react-star-rating-component'
4+
import Button from 'material-ui/Button'
5+
import TextField from 'material-ui/TextField'
6+
import StarIcon from 'material-ui-icons/Star'
7+
import StarBorderIcon from 'material-ui-icons/StarBorder'
8+
import gql from 'graphql-tag'
9+
import { graphql } from 'react-apollo'
10+
11+
import { validateReview } from '../lib/validators'
12+
import { REVIEWS_QUERY, REVIEW_ENTRY } from '../graphql/Review'
13+
14+
const GREY = '#0000008a'
15+
16+
class AddReview extends Component {
17+
state = {
18+
text: '',
19+
stars: null,
20+
errorText: null
21+
}
22+
23+
updateText = event => {
24+
this.setState({ text: event.target.value })
25+
}
26+
27+
updateStars = stars => {
28+
this.setState({ stars })
29+
}
30+
31+
handleSubmit = event => {
32+
event.preventDefault()
33+
const { text, stars } = this.state
34+
35+
const errors = validateReview({ text, stars })
36+
if (errors.text) {
37+
this.setState({ errorText: errors.text })
38+
return
39+
}
40+
41+
this.props.addReview(text, stars)
42+
43+
this.props.done()
44+
}
45+
46+
render() {
47+
return (
48+
<form
49+
className="AddReview"
50+
autoComplete="off"
51+
onSubmit={this.handleSubmit}
52+
>
53+
<TextField
54+
className="AddReview-text"
55+
label="Review text"
56+
value={this.state.text}
57+
onChange={this.updateText}
58+
helperText={this.state.errorText}
59+
error={!!this.state.errorText}
60+
multiline
61+
rowsMax="10"
62+
margin="normal"
63+
autoFocus={true}
64+
/>
65+
66+
<StarInput
67+
className="AddReview-stars"
68+
starCount={5}
69+
editing={true}
70+
value={this.state.stars}
71+
onStarClick={this.updateStars}
72+
renderStarIcon={(currentStar, rating) =>
73+
currentStar > rating ? <StarBorderIcon /> : <StarIcon />
74+
}
75+
starColor={GREY}
76+
emptyStarColor={GREY}
77+
name="stars"
78+
/>
79+
80+
<div className="AddReview-actions">
81+
<Button className="AddReview-cancel" onClick={this.props.done}>
82+
Cancel
83+
</Button>
84+
85+
<Button type="submit" color="primary" className="AddReview-submit">
86+
Add review
87+
</Button>
88+
</div>
89+
</form>
90+
)
91+
}
92+
}
93+
94+
AddReview.propTypes = {
95+
done: PropTypes.func.isRequired,
96+
user: PropTypes.shape({
97+
name: PropTypes.string.isRequired,
98+
photo: PropTypes.string.isRequired,
99+
username: PropTypes.string.isRequired
100+
}).isRequired,
101+
addReview: PropTypes.func.isRequired
102+
}
103+
104+
const ADD_REVIEW_MUTATION = gql`
105+
mutation AddReview($input: CreateReviewInput!) {
106+
createReview(input: $input) {
107+
...ReviewEntry
108+
}
109+
}
110+
${REVIEW_ENTRY}
111+
`
112+
113+
const withMutation = graphql(ADD_REVIEW_MUTATION, {
114+
props: ({ ownProps: { user }, mutate }) => ({
115+
addReview: (text, stars) => {
116+
mutate({
117+
variables: {
118+
input: { text, stars }
119+
},
120+
optimisticResponse: {
121+
createReview: {
122+
__typename: 'Review',
123+
id: null,
124+
text,
125+
stars,
126+
createdAt: new Date(),
127+
favorited: false,
128+
author: {
129+
__typename: 'User',
130+
name: user.name,
131+
photo: user.photo,
132+
username: user.username
133+
}
134+
}
135+
},
136+
update: (store, { data: { createReview: newReview } }) => {
137+
const data = store.readQuery({
138+
query: REVIEWS_QUERY
139+
})
140+
data.reviews.unshift(newReview)
141+
store.writeQuery({ query: REVIEWS_QUERY, data })
142+
}
143+
})
144+
}
145+
})
146+
})
147+
148+
export default withMutation(AddReview)

src/components/Review.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import times from 'lodash/times'
1515
import remove from 'lodash/remove'
1616
import gql from 'graphql-tag'
1717
import { graphql } from 'react-apollo'
18+
import { propType } from 'graphql-anywhere'
19+
20+
import { REVIEW_ENTRY } from '../graphql/Review'
1821

1922
const StarRating = ({ rating }) => (
2023
<div>
@@ -106,18 +109,7 @@ class Review extends Component {
106109
}
107110

108111
Review.propTypes = {
109-
review: PropTypes.shape({
110-
id: PropTypes.string.isRequired,
111-
text: PropTypes.string.isRequired,
112-
stars: PropTypes.number,
113-
createdAt: PropTypes.number.isRequired,
114-
favorited: PropTypes.boolean,
115-
author: PropTypes.shape({
116-
name: PropTypes.string.isRequired,
117-
photo: PropTypes.string.isRequired,
118-
username: PropTypes.string.isRequired
119-
})
120-
}).isRequired,
112+
review: propType(REVIEW_ENTRY).isRequired,
121113
favorite: PropTypes.func.isRequired
122114
}
123115

src/components/Reviews.js

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,82 @@
1-
import React from 'react'
1+
import React, { Component } from 'react'
22
import PropTypes from 'prop-types'
33
import { graphql } from 'react-apollo'
4-
import gql from 'graphql-tag'
54
import get from 'lodash/get'
65
import FavoriteIcon from 'material-ui-icons/Favorite'
6+
import Button from 'material-ui/Button'
7+
import AddIcon from 'material-ui-icons/Add'
8+
import Modal from 'material-ui/Modal'
9+
import { propType } from 'graphql-anywhere'
710

811
import Review from './Review'
12+
import AddReview from './AddReview'
913

10-
const Reviews = ({ reviews, loading, user }) => {
11-
const favoriteCount = get(user, 'favoriteReviews.length')
14+
import { REVIEWS_QUERY, REVIEW_ENTRY } from '../graphql/Review'
1215

13-
return (
14-
<main className="Reviews mui-fixed">
15-
<div className="Reviews-header-wrapper">
16-
<header className="Reviews-header">
17-
{favoriteCount ? (
18-
<div className="Reviews-favorite-count">
19-
<FavoriteIcon />
20-
{favoriteCount}
16+
class Reviews extends Component {
17+
state = {
18+
addingReview: false
19+
}
20+
21+
addReview = () => {
22+
this.setState({ addingReview: true })
23+
}
24+
25+
doneAddingReview = () => {
26+
this.setState({ addingReview: false })
27+
}
28+
29+
render() {
30+
const { reviews, loading, user } = this.props
31+
const favoriteCount = get(user, 'favoriteReviews.length')
32+
33+
return (
34+
<main className="Reviews mui-fixed">
35+
<div className="Reviews-header-wrapper">
36+
<header className="Reviews-header">
37+
{favoriteCount ? (
38+
<div className="Reviews-favorite-count">
39+
<FavoriteIcon />
40+
{favoriteCount}
41+
</div>
42+
) : null}
43+
<h1>Reviews</h1>
44+
</header>
45+
</div>
46+
<div className="Reviews-content">
47+
{loading ? (
48+
<div className="Spinner" />
49+
) : (
50+
reviews.map(review => <Review key={review.id} review={review} />)
51+
)}
52+
53+
{user && (
54+
<div>
55+
<Button
56+
onClick={this.addReview}
57+
variant="fab"
58+
color="primary"
59+
className="Reviews-add"
60+
>
61+
<AddIcon />
62+
</Button>
63+
64+
<Modal
65+
open={this.state.addingReview}
66+
onClose={this.doneAddingReview}
67+
>
68+
<AddReview done={this.doneAddingReview} user={user} />
69+
</Modal>
2170
</div>
22-
) : null}
23-
<h1>Reviews</h1>
24-
</header>
25-
</div>
26-
<div className="Reviews-content">
27-
{loading ? (
28-
<div className="Spinner" />
29-
) : (
30-
reviews.map(review => <Review key={review.id} review={review} />)
31-
)}
32-
</div>
33-
</main>
34-
)
71+
)}
72+
</div>
73+
</main>
74+
)
75+
}
3576
}
3677

3778
Reviews.propTypes = {
38-
reviews: PropTypes.arrayOf(PropTypes.object),
79+
reviews: PropTypes.arrayOf(propType(REVIEW_ENTRY)),
3980
loading: PropTypes.bool.isRequired,
4081
user: PropTypes.shape({
4182
favoriteReviews: PropTypes.arrayOf(
@@ -46,24 +87,6 @@ Reviews.propTypes = {
4687
})
4788
}
4889

49-
const REVIEWS_QUERY = gql`
50-
query ReviewsQuery {
51-
reviews(limit: 20) {
52-
id
53-
text
54-
stars
55-
createdAt
56-
favorited
57-
author {
58-
id
59-
name
60-
photo
61-
username
62-
}
63-
}
64-
}
65-
`
66-
6790
const withReviews = graphql(REVIEWS_QUERY, {
6891
props: ({ data: { reviews, loading } }) => ({ reviews, loading })
6992
})

src/graphql/Review.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import gql from 'graphql-tag'
2+
3+
export const REVIEW_ENTRY = gql`
4+
fragment ReviewEntry on Review {
5+
id
6+
text
7+
stars
8+
createdAt
9+
favorited
10+
author {
11+
id
12+
name
13+
photo
14+
username
15+
}
16+
}
17+
`
18+
19+
export const REVIEWS_QUERY = gql`
20+
query ReviewsQuery {
21+
reviews(limit: 20) {
22+
...ReviewEntry
23+
}
24+
}
25+
${REVIEW_ENTRY}
26+
`

src/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getMainDefinition } from 'apollo-utilities'
1010
import { BrowserRouter } from 'react-router-dom'
1111
import { setContext } from 'apollo-link-context'
1212
import { getAuthToken } from 'auth0-helpers'
13+
import { MuiThemeProvider, createMuiTheme } from 'material-ui/styles'
1314

1415
import './index.css'
1516
import registerServiceWorker from './registerServiceWorker'
@@ -58,10 +59,18 @@ const cache = new InMemoryCache()
5859

5960
const client = new ApolloClient({ link, cache })
6061

62+
const GRAPHQL_PINK = '#e10098'
63+
64+
const theme = createMuiTheme({
65+
palette: { primary: { main: GRAPHQL_PINK } }
66+
})
67+
6168
ReactDOM.render(
6269
<BrowserRouter>
6370
<ApolloProvider client={client}>
64-
<App />
71+
<MuiThemeProvider theme={theme}>
72+
<App />
73+
</MuiThemeProvider>
6574
</ApolloProvider>
6675
</BrowserRouter>,
6776
document.getElementById('root')

0 commit comments

Comments
 (0)