├── 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 | 
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 |
31 |
32 |
33 |
34 |
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 |
76 |
77 |
78 | Polls Home
79 |
80 |
81 |
82 | Poll not found
83 |
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 |
111 |
112 | { poll_option.value }
113 |
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 |
129 | { data.is_saving ? 'Submitting vote...' : 'Submit Vote' }
130 |
131 | )
132 | if (data.poll.vote_counted) {
133 | vote_button = (
134 |
135 | Thank you for voting!
136 |
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 |
162 |
163 |
164 | Polls Home
165 |
166 |
167 |
168 | )
169 | if (data.show_results) {
170 | main_area = (
171 |
174 | )
175 | nav_area = (
176 |
177 |
178 |
179 | Polls Home
180 |
181 |
182 |
183 | Back to poll
184 |
185 |
186 | )
187 | }
188 | return (
189 |
190 |
191 | { nav_area }
192 | { main_area }
193 |
195 | )
196 | }
197 | }
--------------------------------------------------------------------------------