├── CHECKS ├── .gitignore ├── .babelrc ├── config.js ├── components ├── App.js ├── Partials │ ├── Footer.js │ ├── Results.js │ └── Header.js ├── index.js ├── Pages │ ├── PollList.js │ └── SinglePoll.js └── AppState.js ├── server.js ├── server-dev.js ├── webpack.dev.config.js ├── webpack.config.js ├── README.md ├── package.json ├── index.html └── bucket.json /CHECKS: -------------------------------------------------------------------------------- 1 | WAIT=30 2 | ATTEMPTS=10 3 | / Voting App 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | public -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015", 5 | "stage-1" 6 | ], 7 | "plugins": ["transform-decorators-legacy", "react-hot-loader/babel"] 8 | } 9 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: process.env.NODE_ENV, 3 | cosmicjs: { 4 | bucket: { 5 | slug: process.env.COSMIC_BUCKET || 'voting-app', 6 | read_key: process.env.COSMIC_READ_KEY, 7 | write_key: process.env.COSMIC_WRITE_KEY 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { observer } from 'mobx-react' 3 | import AppState from './AppState' 4 | const data = new AppState() 5 | export default class App extends Component { 6 | render() { 7 | const Routes = React.cloneElement(this.props.children, { data }) 8 | return Routes 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /components/Partials/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import S from 'shorti' 3 | export default class Footer extends Component { 4 | render() { 5 | return ( 6 |
7 |
8 | To create your own polls sign up free at Cosmic JS 9 |
10 |
11 | ) 12 | } 13 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var app = express() 3 | var hogan = require('hogan-express') 4 | app.engine('html', hogan) 5 | app.set('views', __dirname + '/') 6 | app.set('port', process.env.PORT || 3000) 7 | app.use(express.static(__dirname + '/public')) 8 | app.get('*', function(req, res){ 9 | res.render('index.html') 10 | }) 11 | console.log('Listening at localhost:' + app.get('port')) 12 | app.listen(app.get('port')) -------------------------------------------------------------------------------- /server-dev.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var WebpackDevServer = require('webpack-dev-server') 3 | var config = require('./webpack.dev.config') 4 | new WebpackDevServer(webpack(config), { 5 | publicPath: config.output.publicPath, 6 | hot: true, 7 | historyApiFallback: true 8 | }).listen(3000, 'localhost', function (err, result) { 9 | if (err) 10 | console.log(err) 11 | console.log('Listening at localhost:3000') 12 | }) 13 | -------------------------------------------------------------------------------- /webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | module.exports = { 4 | devtool: 'inline-source-map', 5 | entry: [ 6 | 'react-hot-loader/patch', 7 | 'webpack-dev-server/client?http://localhost:3000', 8 | 'webpack/hot/only-dev-server', 9 | './components/index' 10 | ], 11 | output: { 12 | path: path.join(__dirname, 'dist'), 13 | filename: 'bundle.js', 14 | publicPath: '/' 15 | }, 16 | plugins: [ 17 | new webpack.HotModuleReplacementPlugin() 18 | ], 19 | resolve: { 20 | extensions: ['', '.js'] 21 | }, 22 | module: { 23 | loaders: [{ 24 | test: /\.jsx?$/, 25 | loaders: ['babel'], 26 | include: path.join(__dirname, 'components') 27 | }] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | module.exports = { 4 | entry: [ 5 | './components/index' 6 | ], 7 | output: { 8 | path: path.join(__dirname, 'public'), 9 | filename: 'bundle.js', 10 | publicPath: '/' 11 | }, 12 | resolve: { 13 | extensions: ['', '.js'] 14 | }, 15 | module: { 16 | loaders: [{ 17 | test: /\.jsx?$/, 18 | loaders: ['babel'], 19 | include: path.join(__dirname, 'components') 20 | }] 21 | }, 22 | plugins: [ 23 | new webpack.DefinePlugin({ 24 | 'process.env.COSMIC_BUCKET': JSON.stringify(process.env.COSMIC_BUCKET), 25 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 26 | 'process.env.COSMIC_READ_KEY': JSON.stringify(process.env.COSMIC_READ_KEY), 27 | 'process.env.COSMIC_WRITE_KEY': JSON.stringify(process.env.COSMIC_WRITE_KEY) 28 | }) 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { Router, Route, IndexRoute, browserHistory } from 'react-router' 4 | import { AppContainer } from 'react-hot-loader' 5 | import App from './App' 6 | import SinglePoll from './Pages/SinglePoll' 7 | import PollList from './Pages/PollList' 8 | render( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ) 19 | if (process.env.NODE_ENV !== 'production' && module.hot) { 20 | module.hot.accept('./App', () => { 21 | const NextApp = require('./App').default 22 | render( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | , 31 | document.getElementById('root') 32 | ) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /components/Pages/PollList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { observer } from 'mobx-react' 3 | import { Link } from 'react-router' 4 | import S from 'shorti' 5 | import Header from '../Partials/Header' 6 | import Footer from '../Partials/Footer' 7 | 8 | @observer 9 | export default class PollList extends Component { 10 | componentDidMount() { 11 | this.props.data.show_results = false 12 | } 13 | handlePollClick(poll) { 14 | this.props.data.poll = poll 15 | this.props.history.push('/' + poll.slug) 16 | } 17 | render() { 18 | const data = this.props.data 19 | let list_area 20 | if (data && data.polls) { 21 | list_area = ( 22 | 36 | ) 37 | } 38 | return ( 39 |
40 |
41 |
42 | { list_area } 43 |
44 |
46 | ) 47 | } 48 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Voting App 2 | ===================== 3 | ![alt text](https://cosmicjs.imgix.net/f7064050-6295-11e6-9ea5-2d6de777d379-voting-app.jpg?w=200 "Voting App") 4 | 5 | A voting app that uses [React](https://facebook.github.io/react) for UI, [MobX](https://mobxjs.github.io/mobx) for state management, [React Router](https://github.com/reactjs/react-router) for smooth view transitions, [Cosmic JS](https://cosmicjs.com) for the [CMS API](https://cosmicjs.com), [React Bootstrap](https://react-bootstrap.github.io/) for the frontend framework, [Shorti](https://www.npmjs.com/package/shorti) for easy inline styles. View the [demo here](http://voting-app.cosmicapp.co/). 6 | 7 | ### Getting started 8 | ``` 9 | git clone https://github.com/cosmicjs/cosmicapp-voting-app 10 | cd cosmicapp-voting-app 11 | npm install 12 | ``` 13 | ##### Run in development with hot reloading 14 | 15 | ``` 16 | npm run development 17 | open http://localhost:3000 18 | ``` 19 | ##### Run in production connected to default bucket 20 | ``` 21 | npm start 22 | open http://localhost:3000 23 | ``` 24 | ### Create Your Own Polls 25 | It's easy to create your own polls. All you have to do is:
26 | 1. Create a bucket on Cosmic JS.
27 | 2. Find the Voting App in the Apps section of your bucket.
28 | 3. Install the voting app.
29 | 4. Deploy your app to the web 30 | ##### Run in production connected to your bucket 31 | ``` 32 | COSMIC_BUCKET=your-bucket-slug npm start 33 | open http://localhost:3000 34 | ``` 35 | ### CMS API 36 | By default the posts are connected to the Cosmic JS bucket `voting-app`. [Sign up for Cosmic JS](https://cosmicjs.com) to add your own bucket, and edit the `config.js` file to point to your bucket. 37 | -------------------------------------------------------------------------------- /components/Partials/Results.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { observer } from 'mobx-react' 3 | import { Nav, NavItem, ProgressBar } from 'react-bootstrap' 4 | import S from 'shorti' 5 | import _ from 'lodash' 6 | 7 | @observer 8 | export default class Results extends Component { 9 | render() { 10 | const data = this.props.data 11 | const options = data.poll.metafields 12 | let votes = data.votes 13 | votes = votes.filter(vote => { 14 | return vote.metafield.poll_id.value === data.poll._id 15 | }) 16 | const total_votes = votes.length 17 | return ( 18 |
19 |
20 |
21 |

22 | { data.poll ? data.poll.title : '' } 23 |

24 |
25 |
26 |
Total votes: { total_votes }
27 | { 28 | options.map((option, i) => { 29 | const option_votes = votes.filter(vote => { 30 | return _.find(vote.metafields, { value: option.title }) 31 | }) 32 | const percent = Math.floor(100 * (option_votes.length / total_votes)) 33 | return ( 34 |
35 | { option.value } 36 | 37 |
38 | ) 39 | }) 40 | } 41 |
42 |
43 |
44 | ) 45 | } 46 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easy-mobx-example", 3 | "version": "1.0.0", 4 | "description": "Easy Example for MobX + React. Includes hot code reloading in development mode and minification in production mode. Also includes a demonstration of how to hook up a CMS API and do database calls and loading states.", 5 | "scripts": { 6 | "development": "NODE_ENV=development node server-dev.js", 7 | "start": "NODE_ENV=production webpack -p; NODE_ENV=production node server.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/tonyspiro/easy-mobx-example.git" 12 | }, 13 | "keywords": [ 14 | "react", 15 | "reactjs", 16 | "mobx", 17 | "easy-example" 18 | ], 19 | "author": "Tony Spiro (http://github.com/tonyspiro)", 20 | "license": "MIT", 21 | "homepage": "http://tonyspiro.com", 22 | "devDependencies": { 23 | "webpack-dev-server": "^1.14.1" 24 | }, 25 | "dependencies": { 26 | "babel-core": "^6.10.4", 27 | "babel-loader": "^6.2.4", 28 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 29 | "babel-preset-es2015": "^6.9.0", 30 | "babel-preset-react": "^6.5.0", 31 | "babel-preset-stage-1": "^6.5.0", 32 | "cosmicjs": "^2.1.2", 33 | "express": "^4.14.0", 34 | "hogan-express": "^0.5.2", 35 | "lodash": "^4.14.2", 36 | "mobx": "^2.2.2", 37 | "mobx-react": "^3.3.1", 38 | "mobx-react-devtools": "^4.2.0", 39 | "react": "^15.1.0", 40 | "react-bootstrap": "^0.30.2", 41 | "react-dom": "^15.1.0", 42 | "react-hot-loader": "^3.0.0-beta.2", 43 | "react-router": "^2.6.1", 44 | "react-router-bootstrap": "^0.23.1", 45 | "shorti": "^1.1.6", 46 | "slug": "^0.9.1", 47 | "webpack": "^1.13.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /components/Partials/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Col } from 'react-bootstrap' 3 | import S from 'shorti' 4 | export default class Header extends Component { 5 | render() { 6 | return ( 7 |
8 |
9 | 10 |

Voting App

11 | 12 | 13 |
14 |
15 | 16 | 17 |   View Code on GitHub 18 | 19 |    20 | 21 | 22 |   Share on Facebook 23 | 24 |    25 | 26 | 27 |   Share on Twitter 28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 | 36 | 37 | Proudly powered by Cosmic JS 38 | 39 |
40 |
41 |
42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Voting App 5 | 6 | 7 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /components/AppState.js: -------------------------------------------------------------------------------- 1 | import { observable, extendObservable } from 'mobx' 2 | import { getObjects, getObject, addObject } from 'cosmicjs' 3 | import _ from 'lodash' 4 | import config from '../config' 5 | export default class AppState { 6 | @observable polls = [] 7 | @observable poll = {} 8 | @observable options_selected = [] 9 | @observable is_saving = false 10 | @observable show_results = false 11 | @observable not_found = null 12 | removeVoteCount() { 13 | localStorage.removeItem(this.poll._id) 14 | } 15 | vote(object) { 16 | this.is_saving = true 17 | if (config.cosmicjs.bucket.write_key) 18 | object.write_key = config.cosmicjs.bucket.write_key 19 | addObject({ bucket: config.cosmicjs.bucket }, object, (err, res) => { 20 | // Set local storage 21 | window.localStorage.setItem(this.poll._id, _.find(this.options_selected, { poll: this.poll._id }).value) 22 | // Go to results page 23 | this.showResults() 24 | }) 25 | } 26 | showResults() { 27 | getObjects({ bucket: config.cosmicjs.bucket }, (err, res) => { 28 | this.is_saving = false 29 | this.poll.vote_counted = true 30 | if (res.objects) { 31 | const votes = res.objects.type.votes 32 | this.votes = votes 33 | this.show_results = true 34 | // Redo totals 35 | let polls = res.objects.type.polls 36 | polls = this.getVoteTotals(polls, votes) 37 | const poll_index = _.findIndex(this.polls, { _id: this.poll._id }) 38 | polls[poll_index].vote_counted = true 39 | this.polls = polls 40 | } 41 | }) 42 | } 43 | getVoteTotals(polls, votes) { 44 | let num_votes_keyed 45 | if (votes) { 46 | num_votes_keyed = [] 47 | votes.forEach(vote => { 48 | if (!num_votes_keyed[vote.metafields[0].value]) 49 | num_votes_keyed[vote.metafields[0].value] = 0 50 | num_votes_keyed[vote.metafields[0].value] = num_votes_keyed[vote.metafields[0].value] + 1 51 | }) 52 | } 53 | polls.forEach((poll, i) => { 54 | if (!num_votes_keyed) 55 | polls[i].num_votes = 0 56 | else 57 | polls[i].num_votes = num_votes_keyed[poll._id] 58 | }) 59 | return polls 60 | } 61 | constructor() { 62 | // Get all polls and votes 63 | getObjects({ bucket: config.cosmicjs.bucket }, (err, res) => { 64 | if (res.objects) { 65 | let polls = res.objects.type.polls 66 | const votes = res.objects.type.votes 67 | polls = this.getVoteTotals(polls, votes) 68 | // If already voted 69 | polls.forEach((poll, i) => { 70 | if (window.localStorage.getItem(polls[i]._id)) { 71 | this.options_selected.push({ poll: polls[i]._id, value: window.localStorage.getItem(polls[i]._id) }) 72 | polls[i].vote_counted = true 73 | } 74 | }) 75 | this.polls = polls 76 | this.votes = votes 77 | const slug = window.location.pathname.replace('/', '') 78 | if (slug) { 79 | const poll = _.find(res.objects.type.polls, { slug }) 80 | if (!poll) { 81 | console.log('not found') 82 | this.not_found = true 83 | return 84 | } 85 | this.poll = poll 86 | } 87 | // Remove vote for testing 88 | // this.removeVoteCount() 89 | } 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /bucket.json: -------------------------------------------------------------------------------- 1 | {"bucket":{"_id":"57ab5744ace85ab2340005ae","slug":"voting-app","title":"Voting App","object_types":[{"title":"Polls","slug":"polls","singular":"Poll","metafields":[],"options":{"slug_field":0,"content_editor":0,"add_metafields":0,"metafields_title":0,"metafields_key":0},"localization":false,"locales":""},{"title":"Votes","slug":"votes","singular":"Vote","metafields":[],"options":{"slug_field":0,"content_editor":0,"add_metafields":0,"metafields_title":0,"metafields_key":0},"localization":false,"locales":""}],"objects":[{"_id":"57ab5799ace85ab2340005b4","order":1,"slug":"who-should-be-the-next-president-of-the-united-states","title":"Who Should Be The Next President of the United States?","content":"","metafields":[{"value":"Hillary Clinton","key":"hillary_clinton","title":"Hillary Clinton","type":"text","children":[{"value":"ee630290-5f74-11e6-bdad-b5089a4979d9-hillary.jpg","key":"image","title":"Image","type":"file","children":false}]},{"value":"Donald Trump","key":"donald_trump","title":"Donald Trump","type":"text","children":[{"value":"bc1c2390-5f72-11e6-bdad-b5089a4979d9-the-don.jpg","key":"image","title":"Image","type":"file","children":false}]},{"value":"Neither","key":"neither","title":"Neither","type":"text","children":[{"value":"ba14bad0-5f72-11e6-bdad-b5089a4979d9-unicorn.jpg","key":"image","title":"Image","type":"file","children":false}]}],"bucket":"57ab5744ace85ab2340005ae","type_slug":"polls","created":"2016-08-10T16:34:33.429Z","user_id":"55767c3ffbcf5cbb13000001","options":{"slug_field":0,"content_editor":0,"add_metafields":0,"metafields_title":0,"metafields_key":0},"status":"published","modified":"2016-08-11T03:37:39.471Z"},{"_id":"57ab8be0ace85ab234000ae2","order":2,"slug":"what-is-the-best-fast-food-chain","title":"What is the best fast food chain?","content":"","metafields":[{"value":"Chick-fil-a","key":"chickfila","title":"Chick-fil-a","type":"text","children":false},{"value":"McDonald's","key":"mcdonalds","title":"McDonald's","type":"text","children":false},{"value":"Burger King","key":"burger_king","title":"Burger King","type":"text","children":false},{"value":"Wendy's","key":"wendys","title":"Wendy's","type":"text","children":false},{"value":"Whataburger","key":"whataburger","title":"Whataburger","type":"text","children":false},{"value":"In-N-Out","key":"innout","title":"In-N-Out","type":"text","children":false}],"bucket":"57ab5744ace85ab2340005ae","type_slug":"polls","created":"2016-08-10T20:17:36.283Z","user_id":"55767c3ffbcf5cbb13000001","options":{"slug_field":0,"content_editor":0},"status":"published","modified":"2016-08-11T21:03:30.493Z"}],"media":[{"_id":"57abef47ace85ab2340017e2","name":"ba14bad0-5f72-11e6-bdad-b5089a4979d9-unicorn.jpg","original_name":"unicorn.jpg","size":277169,"type":"image/jpeg","bucket":"57ab5744ace85ab2340005ae","created":"2016-08-11T03:21:43.434Z","folder":null,"location":"https://cosmicjs.com/uploads"},{"_id":"57abef4aace85ab2340017e5","name":"bc1c2390-5f72-11e6-bdad-b5089a4979d9-the-don.jpg","original_name":"the-don.jpg","size":536877,"type":"image/jpeg","bucket":"57ab5744ace85ab2340005ae","created":"2016-08-11T03:21:46.832Z","folder":null,"location":"https://cosmicjs.com/uploads"},{"_id":"57abf2faace85ab234001866","name":"ee630290-5f74-11e6-bdad-b5089a4979d9-hillary.jpg","original_name":"hillary.jpg","size":2036475,"type":"image/jpeg","bucket":"57ab5744ace85ab2340005ae","created":"2016-08-11T03:37:30.191Z","folder":null,"location":"https://cosmicjs.com/uploads"}]}} -------------------------------------------------------------------------------- /components/Pages/SinglePoll.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Nav, NavItem } from 'react-bootstrap' 3 | import { LinkContainer } from 'react-router-bootstrap' 4 | import S from 'shorti' 5 | import _ from 'lodash' 6 | import { observer } from 'mobx-react' 7 | import Results from '../Partials/Results' 8 | import Header from '../Partials/Header' 9 | import Footer from '../Partials/Footer' 10 | import slug from 'slug' 11 | 12 | @observer 13 | export default class SinglePoll extends Component { 14 | handleOptionClick(value, e) { 15 | const data = this.props.data 16 | if (data.poll.vote_counted) { 17 | e.stopPropagation() 18 | return false 19 | } 20 | data.options_selected = data.options_selected.filter(option => { 21 | return option.poll !== data.poll._id 22 | }) 23 | data.options_selected.push({ 24 | poll: data.poll._id, 25 | value 26 | }) 27 | } 28 | handleSubmitVote(e) { 29 | const data = this.props.data 30 | const option_value = _.find(data.options_selected, { poll: data.poll._id }).value 31 | if (!option_value) 32 | return 33 | if (data.poll.vote_counted) 34 | return 35 | const title = 'Q: ' + data.poll.title + ' A: ' + option_value 36 | const vote = { 37 | slug: slug(title), 38 | type_slug: 'votes', 39 | title, 40 | metafields: [ 41 | { 42 | title: 'Poll ID', 43 | key: 'poll_id', 44 | value: this.props.data.poll._id 45 | }, 46 | { 47 | title: 'Vote', 48 | key: 'vote', 49 | value: option_value 50 | } 51 | ], 52 | options: { 53 | slug: 0, 54 | content: 0 55 | } 56 | } 57 | this.props.data.vote(vote) 58 | } 59 | showResults() { 60 | this.props.data.show_results = true 61 | } 62 | hideResults() { 63 | this.props.data.show_results = false 64 | } 65 | render() { 66 | const data = this.props.data 67 | const option_selected = _.find(data.options_selected, { poll: data.poll._id }) 68 | let option_selected_value 69 | if (option_selected) 70 | option_selected_value = option_selected.value 71 | if (data.not_found && !data.poll._id) { 72 | return ( 73 |
74 |
75 | 82 |

Poll not found

83 |
84 |
85 | ) 86 | } 87 | if (!data.poll._id) { 88 | return ( 89 |
90 | 91 |
92 | ) 93 | } 94 | const options_area = data.poll.metafields.map((poll_option, i) => { 95 | let checked = false 96 | if (option_selected_value === poll_option.value) 97 | checked = true 98 | let option_image 99 | let has_image = false 100 | if (poll_option.children && poll_option.children[0].value) { 101 | has_image = true 102 | option_image = ( 103 |
104 | ) 105 | } 106 | return ( 107 |
  • 108 | { option_image } 109 |
    110 | 114 |
    115 |
    116 |
  • 117 | ) 118 | }) 119 | let view_results_button = ( 120 | You can view results after you vote 121 | ) 122 | if (data.poll.vote_counted) { 123 | view_results_button = ( 124 | View Results 125 | ) 126 | } 127 | let vote_button = ( 128 | 131 | ) 132 | if (data.poll.vote_counted) { 133 | vote_button = ( 134 | 137 | ) 138 | } 139 | let main_area = ( 140 |
    141 |
    142 |
    143 |

    144 | { data.poll ? data.poll.title : '' } 145 |

    146 |
    147 |
    148 |
      149 | { options_area } 150 |
    151 |
    152 |
    153 | { vote_button } 154 |      155 | { view_results_button } 156 |
    157 |
    158 |
    159 | ) 160 | let nav_area = ( 161 | 168 | ) 169 | if (data.show_results) { 170 | main_area = ( 171 | 174 | ) 175 | nav_area = ( 176 | 186 | ) 187 | } 188 | return ( 189 |
    190 |
    191 | { nav_area } 192 | { main_area } 193 |
    195 | ) 196 | } 197 | } --------------------------------------------------------------------------------