├── .DS_Store
├── .babelrc
├── .gitignore
├── LICENSE.md
├── README.md
├── index.html
├── karma.conf.js
├── main.js
├── model
└── todo.js
├── package-lock.json
├── package.json
├── renderer.js
├── server.js
├── src
├── electron-starter.js
├── registerServiceWorker.js
├── style.js
└── todos
│ ├── actions
│ ├── index-test.js
│ └── index.js
│ ├── components
│ ├── App.js
│ ├── Todo.js
│ ├── TodoBoard.js
│ ├── TodoList-test.js
│ └── TodoList.js
│ ├── containers
│ ├── TodoContainer.js
│ ├── TodoFormContainer.js
│ └── TodoListContainer.js
│ ├── reducers
│ ├── index.js
│ ├── todos-test.js
│ └── todos.js
│ └── store
│ ├── configureStore.dev.js
│ ├── configureStore.js
│ └── configureStore.prod.js
├── tests.webpack.js
└── webpack.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amelieoller/electron-reactjs-mongo-express/e09fc9f3fc455a6d1826656e8e4a8335850b69dd/.DS_Store
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015", "react"
4 | ],
5 | "plugins": ["transform-object-rest-spread"]
6 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea/
3 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 | ==================
3 |
4 | Statement of Purpose
5 | ---------------------
6 |
7 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
8 |
9 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
10 |
11 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
12 |
13 | 1. Copyright and Related Rights.
14 | --------------------------------
15 | A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
16 |
17 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
18 | ii. moral rights retained by the original author(s) and/or performer(s);
19 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
20 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;
21 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work;
22 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
23 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
24 |
25 | 2. Waiver.
26 | -----------
27 | To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
28 |
29 | 3. Public License Fallback.
30 | ----------------------------
31 | Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
32 |
33 | 4. Limitations and Disclaimers.
34 | --------------------------------
35 |
36 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
37 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
38 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
39 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Electron-ReactJS-Node-Mongo app
2 |
3 | **Clone and run for a quick way to see Electron in action.**
4 |
5 | This is an Electron application written in ReactJS that implements a ToDo list that is stored in a mongoDB.
6 |
7 |
8 | ## Installation
9 |
10 | To clone and run this repository you'll need [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) installed on your computer. From your command line:
11 |
12 | ```bash
13 | # Install Node & MongoDB
14 | # Clone this repository
15 | $ git clone https://github.com/jwncoexists/electron-reactjs-mongo-express
16 | # Go into the repository
17 | $ cd electron-reactjs-mongo-express
18 | # Install dependencies
19 | $ npm install
20 | # Start up MongoDB
21 | $ mongod
22 |
23 | ```
24 |
25 | ## To Use
26 |
27 | # Run the app
28 | $ npm run start
29 |
30 | # Run the tests
31 | $ npm run test
32 |
33 | ## License
34 |
35 | MIT
36 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | To-Do APP With Electron, React, Mongo, Express
6 |
7 |
8 |
9 |
10 |
11 | Loading...
12 |
13 |
14 |
15 |
33 |
38 |
39 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // karma.conf.js
2 | var webpack = require('webpack');
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | browsers: ['Chrome'],
7 | singleRun: true,
8 | frameworks: ['jasmine'],
9 | files: [
10 | 'tests.webpack.js'
11 | ],
12 | preprocessors: {
13 | 'tests.webpack.js': ['webpack']
14 | },
15 | reporters: ['dots'],
16 | webpack: {
17 | module: {
18 | loaders: [
19 | {test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader'}
20 | ]
21 | },
22 | watch: true
23 | },
24 | webpackServer: {
25 | noInfo: true
26 | }
27 | });
28 | };
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | const electron = require('electron')
2 | // Module to control application life.
3 | const app = electron.app
4 | // Module to create native browser window.
5 | const BrowserWindow = electron.BrowserWindow
6 |
7 | const path = require('path')
8 | const url = require('url')
9 |
10 | // Keep a global reference of the window object, if you don't, the window will
11 | // be closed automatically when the JavaScript object is garbage collected.
12 | let mainWindow
13 |
14 | function createWindow () {
15 | // Create the browser window.
16 | mainWindow = new BrowserWindow({width: 800, height: 600})
17 |
18 | // and load the index.html of the app.
19 | mainWindow.loadURL(url.format({
20 | pathname: path.join(__dirname, 'index.html'),
21 | protocol: 'file:',
22 | slashes: true
23 | }))
24 |
25 | // Open the DevTools.
26 | // mainWindow.webContents.openDevTools()
27 |
28 | // Emitted when the window is closed.
29 | mainWindow.on('closed', function () {
30 | // Dereference the window object, usually you would store windows
31 | // in an array if your app supports multi windows, this is the time
32 | // when you should delete the corresponding element.
33 | mainWindow = null
34 | })
35 | }
36 |
37 | // This method will be called when Electron has finished
38 | // initialization and is ready to create browser windows.
39 | // Some APIs can only be used after this event occurs.
40 | app.on('ready', createWindow)
41 |
42 | // Quit when all windows are closed.
43 | app.on('window-all-closed', function () {
44 | // On OS X it is common for applications and their menu bar
45 | // to stay active until the user quits explicitly with Cmd + Q
46 | if (process.platform !== 'darwin') {
47 | app.quit()
48 | }
49 | })
50 |
51 | app.on('activate', function () {
52 | // On OS X it's common to re-create a window in the app when the
53 | // dock icon is clicked and there are no other windows open.
54 | if (mainWindow === null) {
55 | createWindow()
56 | }
57 | })
58 |
59 | // In this file you can include the rest of your app's specific main process
60 | // code. You can also put them in separate files and require them here.
61 |
--------------------------------------------------------------------------------
/model/todo.js:
--------------------------------------------------------------------------------
1 |
2 | 'use strict';
3 | //import dependency
4 | var mongoose = require('mongoose');
5 | var Schema = mongoose.Schema;
6 | //create new instance of the mongoose.schema. the schema takes an
7 | //object that shows the shape of your database entries.
8 | var TodoSchema = new Schema({
9 | title: {type: String, required: true},
10 | completed: {type: Boolean, default: false}
11 | });
12 | //export our module to use in server.js
13 | module.exports = mongoose.model('Todo', TodoSchema);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-mern-app",
3 | "version": "1.0.0",
4 | "description": "A minimal Electron application",
5 | "main": "main.js",
6 | "scripts": {
7 | "start": "NODE_ENV=DEV concurrently \"nodemon server.js\" \"sleep 2 && electron .\"",
8 | "start-electron": "electron .",
9 | "start-server": "nodemon server.js",
10 | "bundle": "webpack",
11 | "watch": "NODE_ENV=DEV nodemon server.js",
12 | "start:prod": "webpack && electron .",
13 | "test": "karma start"
14 | },
15 | "repository": "https://github.com/electron/electron-quick-start",
16 | "keywords": [
17 | "Electron",
18 | "mern",
19 | "demo"
20 | ],
21 | "author": "Jennifer Nelson",
22 | "license": "MIT",
23 | "dependencies": {
24 | "axios": "^0.16.2",
25 | "body-parser": "^1.18.0",
26 | "express": "^4.15.4",
27 | "foreman": "^2.0.0",
28 | "install": "^0.10.1",
29 | "marked": "^0.3.6",
30 | "material-ui": "^0.19.1",
31 | "mongoose": "^4.11.11",
32 | "nodemon": "^1.12.0",
33 | "npm": "^5.4.1",
34 | "react-addons-css-transition-group": "^15.6.0",
35 | "react-dom": "^15.6.1",
36 | "react-material-icons": "^1.0.2",
37 | "react-redux": "^5.0.6",
38 | "react-router": "^4.2.0",
39 | "react-scripts": "1.0.13",
40 | "react-transition-group": "^2.2.0",
41 | "redux": "^3.7.2",
42 | "redux-form": "^7.0.4",
43 | "redux-promise": "^0.5.3"
44 | },
45 | "devDependencies": {
46 | "babel-core": "^6.26.0",
47 | "babel-jest": "^21.0.2",
48 | "babel-loader": "^7.1.2",
49 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
50 | "babel-polyfill": "^6.26.0",
51 | "babel-preset-es2015": "^6.24.1",
52 | "babel-preset-react": "^6.24.1",
53 | "babel-preset-react-hmre": "^1.1.1",
54 | "concurrently": "^3.5.0",
55 | "css-loader": "^0.28.7",
56 | "d3": "^4.10.2",
57 | "electron": "~1.6.2",
58 | "expect": "^21.1.0",
59 | "jasmine": "^2.8.0",
60 | "jasmine-core": "^2.8.0",
61 | "jest": "^21.0.2",
62 | "karma": "^1.7.1",
63 | "karma-chrome-launcher": "^2.2.0",
64 | "karma-cli": "^1.0.1",
65 | "karma-jasmine": "^1.1.0",
66 | "karma-mocha": "^1.3.0",
67 | "karma-sourcemap-loader": "^0.3.7",
68 | "karma-webpack": "^2.0.4",
69 | "node-sass": "^4.5.3",
70 | "react": "^15.6.1",
71 | "react-test-renderer": "^15.6.1",
72 | "redux-logger": "^3.0.6",
73 | "sass-loader": "^6.0.6",
74 | "style-loader": "^0.18.2",
75 | "webpack": "^3.6.0",
76 | "webpack-dev-middleware-webpack-2": "^1.5.1",
77 | "webpack-hot-middleware": "^2.19.1"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/renderer.js:
--------------------------------------------------------------------------------
1 | // This file is required by the index.html file and will
2 | // be executed in the renderer process for that window.
3 | // All of the Node.js APIs are available in this process.
4 | import App from "./src/todos/components/App";
5 |
6 | const React = require('react');
7 | const ReactDOM = require('react-dom');
8 | import { Provider } from 'react-redux';
9 | import configureStore from './src/todos/store/configureStore.dev.js';
10 |
11 | const store = configureStore();
12 |
13 | ReactDOM.render(
14 |
15 |
16 |
17 | , document.getElementById('app'));
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | // var path = require('path');
2 | // var webpack = require('webpack');
3 | // var express = require('express');
4 | // var config = require('./webpack.config');
5 | //
6 | // var app = express();
7 | // var compiler = webpack(config);
8 | //
9 | // const port = process.env.PORT || 8080;
10 | //
11 | // app.use(require('webpack-dev-middleware')(compiler, {
12 | // noInfo: true,
13 | // publicPath: config.output.publicPath,
14 | // hot: true,
15 | // inline: true,
16 | // }));
17 | //
18 | // app.use(require('webpack-hot-middleware')(compiler));
19 | //
20 | // app.get('*', function(req, res) {
21 | // res.sendFile(path.join(__dirname, 'index.html'));
22 | // });
23 | //
24 | // app.listen(port, function(err) {
25 | // if (err) {
26 | // return console.error(err);
27 | // }
28 | // console.log(`Listening at http://localhost:${port}/`);
29 | // })
30 |
31 | 'use strict'
32 | var path = require('path');
33 | var webpack = require('webpack');
34 | var express = require('express');
35 | var config = require('./webpack.config');
36 | // first we import our dependencies…
37 | var express = require('express');
38 | var mongoose = require('mongoose');
39 | var bodyParser = require('body-parser');
40 | var Todo = require('./model/todo');
41 | var app = express();
42 | var compiler = webpack(config);
43 | var router = express.Router();
44 | //set our port to either a predetermined port number if you have set
45 | //it up, or 3001
46 | // var port = process.env.API_PORT || 3001;
47 | const port = process.env.PORT || 8080;
48 |
49 | app.use(require('webpack-dev-middleware')(compiler, {
50 | noInfo: true,
51 | publicPath: config.output.publicPath,
52 | hot: true,
53 | inline: true,
54 | }));
55 |
56 | app.use(require('webpack-hot-middleware')(compiler));
57 |
58 |
59 | mongoose.connect('localhost:27017/electron-mern-todo');
60 | //now we should configure the API to use bodyParser and look for
61 | //JSON data in the request body
62 | app.use(bodyParser.urlencoded({ extended: true }));
63 | app.use(bodyParser.json());
64 | //To prevent errors from Cross Origin Resource Sharing, we will set
65 | //our headers to allow CORS with middleware like so:
66 | app.use(function(req, res, next) {
67 | res.setHeader('Access-Control-Allow-Origin', '*');
68 | res.setHeader('Access-Control-Allow-Credentials', 'true');
69 | res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS,POST,PUT,DELETE');
70 | res.setHeader('Access-Control-Allow-Headers', 'Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers');
71 | //and remove cacheing so we get the most recent todos
72 | res.setHeader('Cache-Control', 'no-cache');
73 | next();
74 | });
75 | //now we can set the route path & initialize the API
76 | router.get('/', function(req, res) {
77 | res.json({ message: 'API Initialized!'});
78 | });
79 | //adding the /todos route to our /api router
80 | router.route('/todos')
81 | //retrieve all todos from the database
82 | .get(function(req, res) {
83 | //looks at our Todo Schema
84 | Todo.find(function(err, todos) {
85 | if (err)
86 | res.send(err);
87 | //responds with a json object of our database todos.
88 | res.json(todos)
89 | });
90 | })
91 | //post new todo to the database
92 | .post(function(req, res) {
93 | var todo = new Todo();
94 | //body parser lets us use the req.body
95 | todo.title = req.body.title;
96 | todo.completed = req.body.completed ? req.body.completed : false;
97 | todo.save(function(err) {
98 | if (err)
99 | res.send(err);
100 | res.json(todo);
101 | });
102 | });
103 | //Adding a route to a specific todos based on the database ID
104 | router.route('/todos/:todo_id')
105 | //The put method gives us the chance to update our todo based on
106 | //the ID passed to the route
107 | .put(function(req, res) {
108 | Todo.findById(req.params.todo_id, function(err, todo) {
109 | if (err)
110 | res.send(err);
111 | //setting the new title, completed to whatever was changed. If
112 | //nothing was changed we will not alter the field.
113 | (req.body.title) ? todo.title = req.body.title : null;
114 | (req.body.completed !== undefined) ? todo.completed = req.body.completed : false;
115 | //save todo
116 | todo.save(function(err) {
117 | if (err)
118 | res.send(err);
119 | res.json({ message: 'ToDo has been updated' });
120 | });
121 | });
122 | })
123 | //delete method for removing a todo from our database
124 | .delete(function(req, res) {
125 | //selects the todo by its ID, then removes it.
126 | Todo.remove({ _id: req.params.todo_id }, function(err, todo) {
127 | if (err)
128 | res.send(err);
129 | res.json({ message: 'To Do has been deleted' })
130 | })
131 | });
132 | //Use our router configuration when we call /api
133 | app.use('/api', router);
134 | //starts the server and listens for requests
135 |
136 | app.get('*', function(req, res) {
137 | res.sendFile(path.join(__dirname, 'index.html'));
138 | });
139 |
140 | app.listen(port, function(err) {
141 | if (err) {
142 | return console.error(err);
143 | }
144 | console.log(`Listening at http://localhost:${port}/`);
145 | })
146 |
147 | //server.js
148 | // 'use strict'
149 | //first we import our dependencies…
150 | // var express = require('express');
151 | // var mongoose = require('mongoose');
152 | // var bodyParser = require('body-parser');
153 | // var Comment = require('./model/comments');
154 | // //and create our instances
155 | // var app = express();
156 | // var router = express.Router();
157 | // //set our port to either a predetermined port number if you have set
158 | // //it up, or 3001
159 | // var port = process.env.API_PORT || 3001;
160 | // mongoose.connect('localhost:27017/electron-mern-todo');
161 | // //now we should configure the API to use bodyParser and look for
162 | // //JSON data in the request body
163 | // app.use(bodyParser.urlencoded({ extended: true }));
164 | // app.use(bodyParser.json());
165 | // //To prevent errors from Cross Origin Resource Sharing, we will set
166 | // //our headers to allow CORS with middleware like so:
167 | // app.use(function(req, res, next) {
168 | // res.setHeader('Access-Control-Allow-Origin', '*');
169 | // res.setHeader('Access-Control-Allow-Credentials', 'true');
170 | // res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS,POST,PUT,DELETE');
171 | // res.setHeader('Access-Control-Allow-Headers', 'Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers');
172 | // //and remove cacheing so we get the most recent comments
173 | // res.setHeader('Cache-Control', 'no-cache');
174 | // next();
175 | // });
176 | // //now we can set the route path & initialize the API
177 | // router.get('/', function(req, res) {
178 | // res.json({ message: 'API Initialized!'});
179 | // });
180 | // //adding the /comments route to our /api router
181 | // router.route('/comments')
182 | // //retrieve all comments from the database
183 | // .get(function(req, res) {
184 | // //looks at our Comment Schema
185 | // Comment.find(function(err, comments) {
186 | // if (err)
187 | // res.send(err);
188 | // //responds with a json object of our database comments.
189 | // res.json(comments)
190 | // });
191 | // })
192 | // //post new comment to the database
193 | // .post(function(req, res) {
194 | // var comment = new Comment();
195 | // //body parser lets us use the req.body
196 | // comment.author = req.body.author;
197 | // comment.text = req.body.text;
198 | // comment.save(function(err) {
199 | // if (err)
200 | // res.send(err);
201 | // res.json({ message: 'Comment successfully added!' });
202 | // });
203 | // });
204 | // //Adding a route to a specific comment based on the database ID
205 | // router.route('/comments/:comment_id')
206 | // //The put method gives us the chance to update our comment based on
207 | // //the ID passed to the route
208 | // .put(function(req, res) {
209 | // Comment.findById(req.params.comment_id, function(err, comment) {
210 | // if (err)
211 | // res.send(err);
212 | // //setting the new author and text to whatever was changed. If
213 | // //nothing was changed we will not alter the field.
214 | // (req.body.author) ? comment.author = req.body.author : null;
215 | // (req.body.text) ? comment.text = req.body.text : null;
216 | // //save comment
217 | // comment.save(function(err) {
218 | // if (err)
219 | // res.send(err);
220 | // res.json({ message: 'Comment has been updated' });
221 | // });
222 | // });
223 | // })
224 | // //delete method for removing a comment from our database
225 | // .delete(function(req, res) {
226 | // //selects the comment by its ID, then removes it.
227 | // Comment.remove({ _id: req.params.comment_id }, function(err, comment) {
228 | // if (err)
229 | // res.send(err);
230 | // res.json({ message: 'Comment has been deleted' })
231 | // })
232 | // });
233 | // //Use our router configuration when we call /api
234 | // app.use('/api', router);
235 | // //starts the server and listens for requests
236 | // app.listen(port, function() {
237 | // console.log('api running on port: ' + port);
238 | // });
--------------------------------------------------------------------------------
/src/electron-starter.js:
--------------------------------------------------------------------------------
1 | const electron = require('electron');
2 | // Module to control application life.
3 | const app = electron.app;
4 | // Module to create native browser window.
5 | const BrowserWindow = electron.BrowserWindow;
6 |
7 | const path = require('path');
8 | const url = require('url');
9 |
10 | // Keep a global reference of the window object, if you don't, the window will
11 | // be closed automatically when the JavaScript object is garbage collected.
12 | let mainWindow;
13 |
14 | function createWindow() {
15 | // Create the browser window.
16 | mainWindow = new BrowserWindow({width: 800, height: 600});
17 |
18 | // and load the index.html of the app.
19 | mainWindow.loadURL('http://localhost:3000');
20 |
21 | // Open the DevTools.
22 | mainWindow.webContents.openDevTools();
23 |
24 | // Emitted when the window is closed.
25 | mainWindow.on('closed', function () {
26 | // Dereference the window object, usually you would store windows
27 | // in an array if your app supports multi windows, this is the time
28 | // when you should delete the corresponding element.
29 | mainWindow = null
30 | })
31 | }
32 |
33 | // This method will be called when Electron has finished
34 | // initialization and is ready to create browser windows.
35 | // Some APIs can only be used after this event occurs.
36 | app.on('ready', createWindow);
37 |
38 | // Quit when all windows are closed.
39 | app.on('window-all-closed', function () {
40 | // On OS X it is common for applications and their menu bar
41 | // to stay active until the user quits explicitly with Cmd + Q
42 | if (process.platform !== 'darwin') {
43 | app.quit()
44 | }
45 | });
46 |
47 | app.on('activate', function () {
48 | // On OS X it's common to re-create a window in the app when the
49 | // dock icon is clicked and there are no other windows open.
50 | if (mainWindow === null) {
51 | createWindow()
52 | }
53 | });
54 |
55 | // In this file you can include the rest of your app's specific main process
56 | // code. You can also put them in separate files and require them here.
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (!isLocalhost) {
36 | // Is not local host. Just register service worker
37 | registerValidSW(swUrl);
38 | } else {
39 | // This is running on localhost. Lets check if a service worker still exists or not.
40 | checkValidServiceWorker(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW(swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker(swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister() {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/style.js:
--------------------------------------------------------------------------------
1 | //style.js
2 | const style = {
3 | body: {
4 | fontFamily: 'Roboto'
5 | },
6 | todoBoard: {
7 | margin: '50px',
8 | paddingTop: '20px'
9 | },
10 | todoTable: {
11 | width:'80vw',
12 | margin:'5px 0px 5px 20px',
13 | fontFamily:'Helvetica, sans-serif'
14 | },
15 | title: {
16 | marginLeft: '50px',
17 | textAlign:'left',
18 | textTransform:'uppercase'
19 | },
20 | todoList: {
21 | border:'1px solid #f1f1f1',
22 | padding:'0 12px',
23 | maxHeight:'70vh',
24 | overflow:'scroll'
25 | },
26 | todo: {
27 | backgroundColor:'#fafafa',
28 | margin:'10px',
29 | padding:'3px 10px',
30 | fontSize:'.85rem'
31 | },
32 | todoForm: {
33 | marginTop: '50px',
34 | marginLeft:'50px',
35 | paddingBottom: '50px',
36 | justifyContent:'space-between'
37 | },
38 | TodoContainer: {
39 | marginTop: '50px',
40 | marginLeft:'50px',
41 | paddingBottom: '50px',
42 | justifyContent:'space-between'
43 | },
44 | todoFormTitle: {
45 | minWidth:'150px',
46 | margin:'3px',
47 | padding:'0 10px',
48 | borderRadius:'3px',
49 | height:'40px',
50 | flex:'2'
51 | },
52 | todoFormTextField: {
53 | marginLeft:' 25px',
54 | minWidth:'380px',
55 | fontFamily: 'Indie Flower',
56 | fontSize: '1.5em'
57 | },
58 | todoFormButton: {
59 | marginLeft:'15px'
60 | },
61 | todoFormCompleted: {
62 | margin:'3px',
63 | padding:'0 10px',
64 | height:'40px',
65 | flex:'2'
66 | },
67 | todoFormPost: {
68 | minWidth:'75px',
69 | flex:'1',
70 | height:'40px',
71 | margin:'5px 3px',
72 | fontSize:'1rem',
73 | backgroundColor:'#A3CDFD',
74 | borderRadius:'3px',
75 | color:'#fff',
76 | textTransform:'uppercase',
77 | letterSpacing:'.055rem',
78 | border:'none'
79 | },
80 | deleteLink: {
81 | color: '#777',
82 | fontSize:'8px'
83 | },
84 | updateLink: {
85 | color: '#777',
86 | fontSize:'8px'
87 | },
88 | toDoStyle: {
89 | fontFamily: 'Indie Flower',
90 | fontSize: '2em',
91 | color: '#00BCD4',
92 | marginLeft: '10px',
93 | marginTop: '-10px',
94 | textDecoration: 'none'
95 | },
96 | completedToDoStyle: {
97 | fontFamily: 'Indie Flower',
98 | fontSize: '2em',
99 | color: '#00BCD4',
100 | marginLeft: '10px',
101 | marginTop: '-10px',
102 | textDecoration: 'line-through'
103 | },
104 | completedStyle: {
105 | color: '#777',
106 | textAlign: 'left'
107 | },
108 | exampleEnter: {
109 | opacity: "0.01"
110 | },
111 | exampleEnterActive: {
112 | opacity: '1',
113 | transition: 'opacity 500ms ease-in'
114 | },
115 | exampleLeave: {
116 | opacity: '1'
117 | },
118 | exampleLeaveActive: {
119 | opacity: '0.01',
120 | transition: 'opacity 300ms ease-in'
121 | }
122 | }
123 | module.exports = style;
--------------------------------------------------------------------------------
/src/todos/actions/index-test.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import * as actions from './index';
3 |
4 | describe('Actions', () => {
5 | it('should create a FETCH_TODOS_FAILURE action', () => {
6 | const err = {
7 | message: 'bad request'
8 | }
9 | const expectedAction = {
10 | type: 'FETCH_TODOS_FAILURE',
11 | };
12 | expect(actions.fetchTodosFailure(err).type).toEqual(expectedAction.type);
13 | });
14 | });
--------------------------------------------------------------------------------
/src/todos/actions/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | //To Do list
4 | export const FETCH_TODOS = 'FETCH_TODOS';
5 | export const FETCH_TODOS_SUCCESS = 'FETCH_TODOS_SUCCESS';
6 | export const FETCH_TODOS_FAILURE = 'FETCH_TODOS_FAILURE';
7 | export const RESET_TODOS = 'RESET_TODOS';
8 |
9 | //Create new to do
10 | export const CREATE_TODO = 'CREATE_TODO';
11 | export const CREATE_TODO_SUCCESS = 'CREATE_TODO_SUCCESS';
12 | export const CREATE_TODO_FAILURE = 'CREATE_TODO_FAILURE';
13 | export const RESET_NEW_TODO = 'RESET_NEW_TODO';
14 |
15 | //Update to do
16 | export const UPDATE_TODO = 'UPDATE_TODO';
17 | export const UPDATE_TODO_SUCCESS = 'CREATE_TODO_SUCCESS';
18 | export const UPDATE_TODO_FAILURE = 'CREATE_TODO_FAILURE';
19 |
20 | //Fetch to do
21 | export const FETCH_TODO = 'FETCH_TODO';
22 | export const FETCH_TODO_SUCCESS = 'FETCH_TODO_SUCCESS';
23 | export const FETCH_TODO_FAILURE = 'FETCH_TODO_FAILURE';
24 | export const RESET_ACTIVE_TODO = 'RESET_ACTIVE_TODO';
25 |
26 | //Delete to do
27 | export const DELETE_TODO = 'DELETE_TODO';
28 | export const DELETE_TODO_SUCCESS = 'DELETE_TODO_SUCCESS';
29 | export const DELETE_TODO_FAILURE = 'DELETE_TODO_FAILURE';
30 | export const RESET_DELETED_TODO = 'RESET_DELETED_TODO';
31 |
32 | const ROOT_URL = 'http://localhost:8080/api';
33 | export function fetchTodos() {
34 | const request = axios.get('http://localhost:8080/api/todos/');
35 | return {
36 | type: FETCH_TODOS,
37 | payload: request
38 | };
39 | }
40 |
41 | export function fetchTodosSuccess(todos) {
42 | return {
43 | type: FETCH_TODOS_SUCCESS,
44 | payload: todos
45 | };
46 | }
47 |
48 | export function fetchTodosFailure(error) {
49 | return {
50 | type: FETCH_TODOS_FAILURE,
51 | payload: error
52 | };
53 | }
54 |
55 | export function resetTodoFields() {
56 | return {
57 | type: RESET_TODO_FIELDS
58 | };
59 | }
60 |
61 | export function createTodo(newTodo) {
62 | const request = axios.post('http://localhost:8080/api/todos', newTodo);
63 |
64 | return {
65 | type: CREATE_TODO,
66 | payload: request
67 | };
68 | }
69 |
70 | export function createTodoSuccess(newTodo) {
71 | console.log('create todo success: ', newTodo);
72 | return {
73 | type: CREATE_TODO_SUCCESS,
74 | payload: newTodo
75 | };
76 | }
77 |
78 | export function createTodoFailure(error) {
79 | return {
80 | type: CREATE_TODO_FAILURE,
81 | payload: error
82 | };
83 | }
84 |
85 | export function resetNewTodo() {
86 | return {
87 | type: RESET_NEW_TODO
88 | }
89 | }
90 |
91 | export function updateTodo(id, todo) {
92 | const request = axios.put('http://localhost:8080/api/todos/' + id, todo);
93 | return {
94 | type: UPDATE_TODO,
95 | payload: request
96 | };
97 | }
98 |
99 | export function updateTodoSuccess(todo) {
100 | console.log('update todo success: ', todo);
101 | return {
102 | type: UPDATE_TODO_SUCCESS,
103 | payload: todo
104 | };
105 | }
106 |
107 | export function updateTodoFailure(error) {
108 | return {
109 | type: UPDATE_TODO_FAILURE,
110 | payload: error
111 | };
112 | }
113 |
114 | export function resetDeletedTodo() {
115 | return {
116 | type: RESET_DELETED_TODO
117 | };
118 | }
119 |
120 |
121 | export function fetchTodo(id) {
122 | const request = axios.get('http://localhost:8080/api/todos/' + id);
123 |
124 | return {
125 | type: FETCH_TODO,
126 | payload: request
127 | };
128 | }
129 |
130 |
131 | export function fetchTodoSuccess(activeTodo) {
132 | return {
133 | type: FETCH_TODO_SUCCESS,
134 | payload: activeTodo
135 | };
136 | }
137 |
138 | export function fetchTodoFailure(error) {
139 | return {
140 | type: FETCH_TODO_FAILURE,
141 | payload: error
142 | };
143 | }
144 |
145 | export function resetActiveTodo() {
146 | return {
147 | type: RESET_ACTIVE_TODO
148 | }
149 | }
150 |
151 |
152 | export function deleteTodo(id) {
153 | const request = axios.delete('http://localhost:8080/api/todos/' + id)
154 |
155 | return {
156 | type: DELETE_TODO,
157 | payload: request
158 | };
159 | }
160 |
161 | export function deleteTodoSuccess(deletedTodo) {
162 | return {
163 | type: DELETE_TODO_SUCCESS,
164 | payload: deletedTodo
165 | };
166 | }
167 |
168 | export function deleteTodoFailure(response) {
169 | return {
170 | type: DELETE_TODO_FAILURE,
171 | payload: response
172 | };
173 | }
--------------------------------------------------------------------------------
/src/todos/components/App.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { Component } from 'react';
3 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
4 | import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
5 | import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
6 | import getMuiTheme from 'material-ui/styles/getMuiTheme';
7 | import TodoBoard from './TodoBoard';
8 |
9 | class App extends Component {
10 | render() {
11 | return (
12 |
13 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/src/todos/components/Todo.js:
--------------------------------------------------------------------------------
1 | //Todo.js
2 | import React, { Component } from 'react';
3 | import style from '../../style';
4 | import CheckBox from 'react-material-icons/icons/toggle/check-box';
5 | import CheckBoxOutline from 'react-material-icons/icons/toggle/check-box-outline-blank';
6 | import Delete from 'react-material-icons/icons/action/delete';
7 | import IconButton from 'material-ui/IconButton';
8 | import {
9 | TableRow,
10 | TableRowColumn,
11 | } from 'material-ui/Table';
12 |
13 |
14 | class Todo extends Component {
15 | constructor(props) {
16 | super(props);
17 | this.state= {
18 | toBeUpdated: false,
19 | title: props.title,
20 | completed: props.completed
21 | };
22 | //binding all our functions to this class
23 | this.deleteTodo = this.deleteTodo.bind(this);
24 | this.toggleComplete = this.toggleComplete.bind(this);
25 | }
26 | toggleComplete(e) {
27 | e.preventDefault();
28 | let id = this.props.uniqueID;
29 | let title = (this.state.title) ? this.state.title : null;
30 | let completed = !this.state.completed ;
31 | this.setState({ completed: !this.state.completed });
32 | let todo = { title: title, completed: completed};
33 | this.props.updateTodo(id, todo);
34 | }
35 | deleteTodo(e) {
36 | e.preventDefault();
37 | let id = this.props.uniqueID;
38 | this.props.deleteTodo(id);
39 | }
40 |
41 | render() {
42 | let checkbox = null;
43 | if (this.state.completed) {
44 | checkbox =
45 | } else {
46 | checkbox =
47 | }
48 | return (
49 |
50 |
51 |
52 |
53 |
54 |
55 | { this.props.title }
56 |
57 |
58 |
59 | {checkbox}
60 |
61 |
62 |
63 | )
64 | }
65 | }
66 |
67 | export default Todo;
--------------------------------------------------------------------------------
/src/todos/components/TodoBoard.js:
--------------------------------------------------------------------------------
1 | //TodoBoard.js
2 | import React, { Component } from 'react';
3 | import TodoListContainer from '../containers/TodoListContainer';
4 | import TodoFormContainer from '../containers/TodoFormContainer';
5 | import style from '../../style';
6 | import Paper from 'material-ui/Paper';
7 |
8 |
9 | class TodoBoard extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = { data: [] };
13 | }
14 | render() {
15 | return (
16 |
17 |
18 |
To Do:
19 |
22 |
23 |
24 |
25 | )
26 | }
27 | }
28 | export default TodoBoard;
--------------------------------------------------------------------------------
/src/todos/components/TodoList-test.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amelieoller/electron-reactjs-mongo-express/e09fc9f3fc455a6d1826656e8e4a8335850b69dd/src/todos/components/TodoList-test.js
--------------------------------------------------------------------------------
/src/todos/components/TodoList.js:
--------------------------------------------------------------------------------
1 | //TodoList.js
2 | import React, { Component } from 'react';
3 | import Todo from './Todo';
4 | import style from '../../style';
5 | import {
6 | Table,
7 | TableBody,
8 | TableHeader,
9 | TableRow,
10 | TableHeaderColumn
11 | } from 'material-ui/Table';
12 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
13 |
14 | import TodoContainer from '../containers/TodoContainer';
15 |
16 |
17 | class TodoList extends Component {
18 | componentWillMount() {
19 | this.props.fetchTodos();
20 | }
21 | onCellHover(rowNumber, columnId) {
22 | console.log('on cell hover: ', rowNumber, columnId);
23 | }
24 | onCellHoverExit() {
25 |
26 | }
27 | renderTodos(todos) {
28 | let todoNodes = todos.map(todo => {
29 | return (
30 |
37 |
38 | )
39 | })
40 | return (
41 |
46 |
47 | { todoNodes }
48 |
49 |
50 | )
51 | }
52 | render() {
53 | const { todos, loading, error } = this.props.todoList;
54 |
55 | if(loading) {
56 | return
Loading To Do list...
57 | } else if(error) {
58 | return Error: {error.message}
59 | }
60 |
61 | return (
62 | this.renderTodos(todos)
63 | );
64 | }
65 | }
66 | export default TodoList;
--------------------------------------------------------------------------------
/src/todos/containers/TodoContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { deleteTodo, deleteTodoFailure, updateTodo, updateTodoFailure, fetchTodos, fetchTodosSuccess, fetchTodosFailure } from '../actions/index';
3 | import Todo from "../components/Todo";
4 |
5 |
6 | const mapStateToProps = (state) => {
7 | return {
8 | todoList: state.todos.todoList
9 | };
10 | }
11 |
12 | const mapDispatchToProps = (dispatch) => {
13 | return {
14 | fetchTodos: () => {
15 | dispatch(fetchTodos()).then((response) => {
16 | if (!response.error) {
17 | dispatch(fetchTodosSuccess(response.payload.data))
18 | } else {
19 | dispatch(fetchTodosFailure(response.payload.data));
20 | }
21 | });
22 | },
23 | deleteTodo: (id) => {
24 | console.log('deleting id: ', id);
25 | dispatch(deleteTodo(id)).then((response) => {
26 | if (!response.error) {
27 | console.log('Delete successful');
28 | dispatch(fetchTodos()).then((response) => {
29 | dispatch(fetchTodosSuccess(response.payload.data))
30 | })
31 | } else {
32 | console.log('Delete failure');
33 | dispatch(deleteTodoFailure(response.payload.data))
34 | }
35 | });
36 | },
37 | updateTodo: (id, todo) => {
38 | dispatch(updateTodo(id, todo)).then((response) => {
39 | if (!response.error) {
40 | console.log('Update successful');
41 | dispatch(fetchTodos()).then((response) => {
42 | dispatch(fetchTodosSuccess(response.payload.data))
43 | });
44 | } else {
45 | console.log('Update failure');
46 | dispatch(updateTodoFailure(response.payload.data))
47 | }
48 | });
49 | }
50 | }
51 | }
52 |
53 | const TodoContainer = connect(
54 | mapStateToProps,
55 | mapDispatchToProps
56 | )(Todo)
57 |
58 | export default TodoContainer;
--------------------------------------------------------------------------------
/src/todos/containers/TodoFormContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 | import { createTodo, createTodoSuccess, fetchTodos, fetchTodosSuccess, createTodoFailure, resetNewTodo} from '../actions/index';
4 | import style from '../../style';
5 | import RaisedButton from 'material-ui/RaisedButton';
6 | import TextField from 'material-ui/TextField';
7 |
8 | let TodoFormContainer = ({ dispatch }) => {
9 | let input
10 |
11 | return (
12 |
13 |
47 |
48 | )
49 | }
50 |
51 | TodoFormContainer = connect()(TodoFormContainer)
52 |
53 | export default TodoFormContainer
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/todos/containers/TodoListContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { fetchTodos, fetchTodosSuccess, fetchTodosFailure } from '../actions/index';
3 | import TodoList from '../components/TodoList';
4 |
5 |
6 | const mapStateToProps = (state) => {
7 | return {
8 | todoList: state.todos.todoList
9 | };
10 | }
11 |
12 | const mapDispatchToProps = (dispatch) => {
13 | return {
14 | fetchTodos: () => {
15 | dispatch(fetchTodos()).then((response) => {
16 | !response.error ? dispatch(fetchTodosSuccess(response.payload.data)) : dispatch(fetchTodosFailure(response.payload.data));
17 | });
18 | }
19 | }
20 | }
21 |
22 | const TodoListContainer = connect(
23 | mapStateToProps,
24 | mapDispatchToProps
25 | )(TodoList)
26 |
27 | export default TodoListContainer;
--------------------------------------------------------------------------------
/src/todos/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import todos from './todos'
3 |
4 | const todoApp = combineReducers({
5 | todos
6 | })
7 |
8 | export default todoApp
--------------------------------------------------------------------------------
/src/todos/reducers/todos-test.js:
--------------------------------------------------------------------------------
1 | import {todos} from './todos';
2 | import * as ActionType from '../actions/index';
3 |
4 |
--------------------------------------------------------------------------------
/src/todos/reducers/todos.js:
--------------------------------------------------------------------------------
1 | import {
2 | FETCH_TODOS, FETCH_TODOS_SUCCESS, FETCH_TODOS_FAILURE, RESET_TODOS,
3 | FETCH_TODO, FETCH_TODO_SUCCESS, FETCH_TODO_FAILURE, RESET_ACTIVE_TODO,
4 | CREATE_TODO, CREATE_TODO_SUCCESS, CREATE_TODO_FAILURE, RESET_NEW_TODO,
5 | DELETE_TODO, DELETE_TODO_SUCCESS, DELETE_TODO_FAILURE, RESET_DELETED_TODO,
6 | RESET_TODO_FIELDS
7 | } from '../actions/index';
8 |
9 |
10 | const INITIAL_STATE = {
11 | todoList: {todos: [], error: null, loading: false},
12 | newTodo:{todo: null, error: null, loading: false},
13 | activeTodo:{todo: null, error: null, loading: false},
14 | deletedTodo: {todo: null, error: null, loading: false}
15 | };
16 |
17 | const todos = (state = INITIAL_STATE, action) => {
18 | let error;
19 | switch(action.type) {
20 |
21 | case FETCH_TODOS:// start fetching and set loading = true
22 | return { ...state, todoList: {todos:[], error: null, loading: true} };
23 | case FETCH_TODOS_SUCCESS:// return list of todos and make loading = false
24 | return { ...state, todoList: {todos: action.payload, error: null, loading: false} };
25 | case FETCH_TODOS_FAILURE:// return error and make loading = false
26 | error = action.payload || {message: action.payload.message};//2nd one is network or server down errors
27 | return { ...state, todoList: {todos: [], error: error, loading: false} };
28 | case RESET_TODOS:// reset todoList to initial state
29 | return { ...state, todoList: {todos: [], error: null, loading: false} };
30 |
31 | case FETCH_TODO:
32 | return { ...state, activeTodo:{...state.activeTodo, loading: true}};
33 | case FETCH_TODO_SUCCESS:
34 | return { ...state, activeTodo: {todo: action.payload, error: null, loading: false}};
35 | case FETCH_TODO_FAILURE:
36 | error = action.payload || {message: action.payload.message};//2nd one is network or server down errors
37 | return { ...state, activeTodo: {todo: null, error: error, loading:false}};
38 | case RESET_ACTIVE_TODO:
39 | return { ...state, activeTodo: {todo: null, error: null, loading: false}};
40 |
41 | case CREATE_TODO:
42 | return {...state, newTodo: {...state.newTodo, loading: true}}
43 | case CREATE_TODO_SUCCESS:
44 | return {...state, newTodo: {todo: action.payload, error: null, loading: false}}
45 | case CREATE_TODO_FAILURE:
46 | error = action.payload || {message: action.payload.message};//2nd one is network or server down errors
47 | return {...state, newTodo: {todo: null, error: error, loading: false}}
48 | case RESET_NEW_TODO:
49 | return {...state, newTodo:{todo: null, error: null, loading: false}}
50 |
51 | case DELETE_TODO:
52 | return {...state, deletedTodo: {...state.deletedTodo, loading: true}}
53 | case DELETE_TODO_SUCCESS:
54 | return {...state, deletedTodo: {todo: action.payload, error: null, loading: false}}
55 | case DELETE_TODO_FAILURE:
56 | error = action.payload || {message: action.payload.message};//2nd one is network or server down errors
57 | return {...state, deletedTodo: {todo: null, error: error, loading: false}}
58 | case RESET_DELETED_TODO:
59 | return {...state, deletedTodo:{todo: null, error: null, loading: false}}
60 |
61 | case RESET_TODO_FIELDS:
62 | return {...state, newTodo:{...state.newTodo, error: null, loading: null}}
63 | default:
64 | return state;
65 | }
66 | }
67 |
68 | export default todos
--------------------------------------------------------------------------------
/src/todos/store/configureStore.dev.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import promise from 'redux-promise';
3 | import todoApp from '../reducers';
4 | import { createLogger } from 'redux-logger';
5 |
6 | const logger = createLogger();
7 |
8 | export default function configureStore(initialState) {
9 |
10 | if (module.hot) {
11 | // Enable Webpack hot module replacement for reducers
12 | module.hot.accept('../reducers', () => {
13 | const nextReducer = require('../reducers');
14 | store.replaceReducer(nextReducer);
15 | });
16 | }
17 |
18 | return createStore(
19 | todoApp,
20 | initialState,
21 | applyMiddleware(
22 | promise,
23 | logger
24 | )
25 | )
26 | };
--------------------------------------------------------------------------------
/src/todos/store/configureStore.js:
--------------------------------------------------------------------------------
1 | // import configureStore from './store/configureStore.dev';
2 | //
3 | // if (process.env.NODE_ENV === 'production' || (location && location.hostname !== 'localhost')) {
4 | // module.exports = require('./configureStore.prod');
5 | // } else {
6 | // const store
7 | // }
--------------------------------------------------------------------------------
/src/todos/store/configureStore.prod.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import rootReducer from '../reducers';
3 | import promise from 'redux-promise';
4 |
5 | // Middleware you want to use in production:
6 | const enhancer = applyMiddleware(promise);
7 |
8 | export default function configureStore(initialState) {
9 | // Note: only Redux >= 3.1.0 supports passing enhancer as third argument.
10 | // See https://github.com/rackt/redux/releases/tag/v3.1.0
11 | return createStore(rootReducer, initialState, enhancer);
12 | };
--------------------------------------------------------------------------------
/tests.webpack.js:
--------------------------------------------------------------------------------
1 | // tests.webpack.js
2 | var context = require.context('./src', true, /-test\.jsx?$/);
3 | context.keys().forEach(context);
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | const port = process.env.PORT || '8080';
5 |
6 | const config = {
7 | context: __dirname,
8 | entry: [
9 | 'babel-polyfill',
10 | path.resolve(__dirname, './renderer.js'),
11 | `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
12 | ],
13 | target: 'electron-renderer',
14 | output: {
15 | filename: 'renderer.bundle.js',
16 | path: __dirname + '/bundle',
17 | publicPath: `http://localhost:${port}/bundle/`,
18 | libraryTarget: 'commonjs2'
19 | },
20 | module: {
21 | loaders: [
22 | { test: /\.js$/,
23 | loader: 'babel-loader',
24 | include: [
25 | path.join(__dirname, 'src'),
26 | path.join(__dirname, 'renderer.js')
27 | ],
28 | exclude: /node_modules/,
29 | query: {
30 | presets: ['es2015', 'react', 'react-hmre']
31 | }
32 | },
33 | { test: /\.scss$/, loader: 'style-loader!css-loader!sass-loader' },
34 | {
35 | test: /\.json$/,
36 | loader: 'json-loader'
37 | }
38 | ]
39 | },
40 | plugins: [
41 | new webpack.HotModuleReplacementPlugin(),
42 | ]
43 | };
44 |
45 | module.exports = config;
--------------------------------------------------------------------------------