diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..40b878d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules/
\ No newline at end of file
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..500064c
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+meteor-redux
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..97626ba
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
new file mode 100644
index 0000000..b8387eb
--- /dev/null
+++ b/.idea/jsLibraryMappings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/meteor-redux.iml b/.idea/meteor-redux.iml
new file mode 100644
index 0000000..80caaa8
--- /dev/null
+++ b/.idea/meteor-redux.iml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..e500f7c
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..bfac488
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.meteor/.finished-upgraders b/.meteor/.finished-upgraders
new file mode 100644
index 0000000..dacc2c0
--- /dev/null
+++ b/.meteor/.finished-upgraders
@@ -0,0 +1,13 @@
+# This file contains information which helps Meteor properly upgrade your
+# app when you run 'meteor update'. You should check it into version control
+# with your project.
+
+notices-for-0.9.0
+notices-for-0.9.1
+0.9.4-platform-file
+notices-for-facebook-graph-api-2
+1.2.0-standard-minifiers-package
+1.2.0-meteor-platform-split
+1.2.0-cordova-changes
+1.2.0-breaking-changes
+1.3.0-split-minifiers-package
diff --git a/.meteor/.gitignore b/.meteor/.gitignore
new file mode 100644
index 0000000..4083037
--- /dev/null
+++ b/.meteor/.gitignore
@@ -0,0 +1 @@
+local
diff --git a/.meteor/.id b/.meteor/.id
new file mode 100644
index 0000000..13cc39d
--- /dev/null
+++ b/.meteor/.id
@@ -0,0 +1,7 @@
+# This file contains a token that is unique to your project.
+# Check it into your repository along with the rest of this directory.
+# It can be used for purposes such as:
+# - ensuring you don't accidentally deploy one app on top of another
+# - providing package authors with aggregated statistics
+
+13b1yi71jis8gxb3ceye
diff --git a/.meteor/packages b/.meteor/packages
new file mode 100644
index 0000000..d4c8001
--- /dev/null
+++ b/.meteor/packages
@@ -0,0 +1,21 @@
+# Meteor packages used by this project, one per line.
+# Check this file (and the other files in this directory) into your repository.
+#
+# 'meteor add' and 'meteor remove' will edit this file for you,
+# but you can also edit it by hand.
+
+meteor-base # Packages every Meteor app needs to have
+mobile-experience # Packages for a great mobile UX
+mongo # The database Meteor supports right now
+blaze-html-templates # Compile .html files into Meteor Blaze views
+reactive-var # Reactive variable for tracker
+jquery # Helpful client-side library
+tracker # Meteor's client-side reactive programming library
+
+standard-minifier-css # CSS minifier run for production mode
+standard-minifier-js # JS minifier run for production mode
+es5-shim # ECMAScript 5 compatibility for older browsers.
+ecmascript # Enable ECMAScript2015+ syntax in app code
+
+autopublish # Publish all data to the clients (for prototyping)
+insecure # Allow all DB writes from clients (for prototyping)
diff --git a/.meteor/platforms b/.meteor/platforms
new file mode 100644
index 0000000..efeba1b
--- /dev/null
+++ b/.meteor/platforms
@@ -0,0 +1,2 @@
+server
+browser
diff --git a/.meteor/release b/.meteor/release
new file mode 100644
index 0000000..940e0b5
--- /dev/null
+++ b/.meteor/release
@@ -0,0 +1 @@
+METEOR@1.3.2.4
diff --git a/.meteor/versions b/.meteor/versions
new file mode 100644
index 0000000..0e2f545
--- /dev/null
+++ b/.meteor/versions
@@ -0,0 +1,69 @@
+allow-deny@1.0.4
+autopublish@1.0.7
+autoupdate@1.2.9
+babel-compiler@6.6.4
+babel-runtime@0.1.8
+base64@1.0.8
+binary-heap@1.0.8
+blaze@2.1.7
+blaze-html-templates@1.0.4
+blaze-tools@1.0.8
+boilerplate-generator@1.0.8
+caching-compiler@1.0.4
+caching-html-compiler@1.0.6
+callback-hook@1.0.8
+check@1.2.1
+ddp@1.2.5
+ddp-client@1.2.7
+ddp-common@1.2.5
+ddp-server@1.2.6
+deps@1.0.12
+diff-sequence@1.0.5
+ecmascript@0.4.3
+ecmascript-runtime@0.2.10
+ejson@1.0.11
+es5-shim@4.5.10
+fastclick@1.0.11
+geojson-utils@1.0.8
+hot-code-push@1.0.4
+html-tools@1.0.9
+htmljs@1.0.9
+http@1.1.5
+id-map@1.0.7
+insecure@1.0.7
+jquery@1.11.8
+launch-screen@1.0.11
+livedata@1.0.18
+logging@1.0.12
+meteor@1.1.14
+meteor-base@1.0.4
+minifier-css@1.1.11
+minifier-js@1.1.11
+minimongo@1.0.16
+mobile-experience@1.0.4
+mobile-status-bar@1.0.12
+modules@0.6.1
+modules-runtime@0.6.3
+mongo@1.1.7
+mongo-id@1.0.4
+npm-mongo@1.4.43
+observe-sequence@1.0.11
+ordered-dict@1.0.7
+promise@0.6.7
+random@1.0.9
+reactive-var@1.0.9
+reload@1.1.8
+retry@1.0.7
+routepolicy@1.0.10
+spacebars@1.0.11
+spacebars-compiler@1.0.11
+standard-minifier-css@1.0.6
+standard-minifier-js@1.0.6
+templating@1.1.9
+templating-tools@1.0.4
+tracker@1.0.13
+ui@1.0.11
+underscore@1.0.8
+url@1.0.9
+webapp@1.2.8
+webapp-hashing@1.0.9
diff --git a/client/main.css b/client/main.css
new file mode 100644
index 0000000..3593c36
--- /dev/null
+++ b/client/main.css
@@ -0,0 +1,125 @@
+body {
+ font-family: sans-serif;
+ background-color: #315481;
+ background-image: linear-gradient(to bottom, #315481, #918e82 100%);
+ background-attachment: fixed;
+
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+
+ padding: 0;
+ margin: 0;
+
+ font-size: 14px;
+}
+
+.container {
+ max-width: 600px;
+ margin: 0 auto;
+ min-height: 100%;
+ background: white;
+}
+
+header {
+ background: #d2edf4;
+ background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);
+ padding: 20px 15px 15px 15px;
+ position: relative;
+}
+
+#login-buttons {
+ display: block;
+}
+
+h1 {
+ font-size: 1.5em;
+ margin: 0;
+ margin-bottom: 10px;
+ display: inline-block;
+ margin-right: 1em;
+}
+
+form {
+ margin-top: 10px;
+ margin-bottom: -10px;
+ position: relative;
+}
+
+.new-task input {
+ box-sizing: border-box;
+ padding: 10px 0;
+ background: transparent;
+ border: none;
+ width: 100%;
+ padding-right: 80px;
+ font-size: 1em;
+}
+
+.new-task input:focus{
+ outline: 0;
+}
+
+ul {
+ margin: 0;
+ padding: 0;
+ background: white;
+}
+
+.delete {
+ float: right;
+ font-weight: bold;
+ background: none;
+ font-size: 1em;
+ border: none;
+ position: relative;
+}
+
+li {
+ position: relative;
+ list-style: none;
+ padding: 15px;
+ border-bottom: #eee solid 1px;
+}
+
+li .text {
+ margin-left: 10px;
+}
+
+li.checked {
+ color: #888;
+}
+
+li.checked .text {
+ text-decoration: line-through;
+}
+
+li.private {
+ background: #eee;
+ border-color: #ddd;
+}
+
+header .hide-completed {
+ float: right;
+}
+
+.toggle-private {
+ margin-left: 5px;
+}
+
+@media (max-width: 600px) {
+ li {
+ padding: 12px 15px;
+ }
+
+ .search {
+ width: 150px;
+ clear: both;
+ }
+
+ .new-task input {
+ padding-bottom: 5px;
+ }
+}
\ No newline at end of file
diff --git a/client/main.html b/client/main.html
new file mode 100644
index 0000000..a84b4db
--- /dev/null
+++ b/client/main.html
@@ -0,0 +1,7 @@
+
+ Todo List
+
+
+
+
+
diff --git a/client/main.js b/client/main.js
new file mode 100644
index 0000000..25f7604
--- /dev/null
+++ b/client/main.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from '../imports/components/app.jsx';
+import { Provider } from 'react-redux'
+import store from '../imports/store';
+
+Meteor.startup(() => {
+ ReactDOM.render(
+ ,
+ document.getElementById('app'));
+});
diff --git a/imports/actionCreators.js b/imports/actionCreators.js
new file mode 100644
index 0000000..5197a94
--- /dev/null
+++ b/imports/actionCreators.js
@@ -0,0 +1,15 @@
+import { createAction } from 'redux-actions';
+
+let nextTodoId = 0;
+
+export const ADD_TODO = 'ADD_TODO';
+export const addTodo = createAction(ADD_TODO, text => ({ text, id: nextTodoId++ }));
+
+export const REMOVE_TODO = 'REMOVE_TODO';
+export const removeTodo = createAction(REMOVE_TODO, id => ({ id }));
+
+export const TOGGLE_TODO = 'TOGGLE_TODO';
+export const toggleTodo = createAction(TOGGLE_TODO, id => ({ id }));
+
+export const TOGGLE_VISIBILITY_FILTER = 'TOGGLE_VISIBILITY_FILTER';
+export const toggleVisibilityFilter = createAction(TOGGLE_VISIBILITY_FILTER);
diff --git a/imports/api/tasks.js b/imports/api/tasks.js
new file mode 100644
index 0000000..4a68367
--- /dev/null
+++ b/imports/api/tasks.js
@@ -0,0 +1,5 @@
+import { Mongo } from 'meteor/mongo';
+
+const Tasks = new Mongo.Collection('tasks');
+
+export default Tasks;
diff --git a/imports/components/app.jsx b/imports/components/app.jsx
new file mode 100644
index 0000000..48709b8
--- /dev/null
+++ b/imports/components/app.jsx
@@ -0,0 +1,65 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux'
+import TaskList from './list';
+import { addTodo, toggleVisibilityFilter } from '../actionCreators';
+
+class App extends Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+ }
+
+ renderTasks() {
+ return this.getTasks().map((task, i) => (
+
+ ));
+ }
+
+ handleSubmit(e) {
+ const todo = this.refs.textInput.value;
+ this.props.addTodo(todo);
+
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ render() {
+ return (
+
+
+
+
+
+ );
+ }
+}
+
+export default connect(
+ (state => ({
+ visibilityFilter: state.visibilityFilter === 'NONE'
+ })),
+ (dispatch) => ({
+ addTodo: (text) => dispatch(addTodo(text)),
+ toggleVisibilityFilter: () => dispatch(toggleVisibilityFilter())
+ })
+)(App);
diff --git a/imports/components/list.js b/imports/components/list.js
new file mode 100644
index 0000000..08b2910
--- /dev/null
+++ b/imports/components/list.js
@@ -0,0 +1,44 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux'
+import { toggleTodo, removeTodo } from '../actionCreators';
+
+import Task from './task.jsx';
+
+const mapStateToProps = (state) => {
+ return {
+ todos: state.todos.filter(todo => state.visibilityFilter === 'ALL' || !todo.checked)
+ }
+};
+
+const mapDispatchToProps = (dispatch) => ({
+ toggleTodo: (id) => dispatch(toggleTodo(id)),
+ removeTodo: (id) => dispatch(removeTodo(id)),
+});
+
+const EmptyTaskPlaceHolder = () => ();
+
+// App component - represents the whole app
+class TaskList extends Component {
+ renderTasks() {
+ return this.props.todos.map((task, i) => (
+ this.props.toggleTodo(task.id)}
+ onDeleted={() => this.props.removeTodo(task.id)}
+ />
+ ));
+ }
+
+ render() {
+ return (
+
+ {this.props.todos.length ?
+ this.renderTasks() :
+
+ }
+
+ );
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(TaskList);
diff --git a/imports/components/task.jsx b/imports/components/task.jsx
new file mode 100644
index 0000000..4b89fac
--- /dev/null
+++ b/imports/components/task.jsx
@@ -0,0 +1,34 @@
+import React, { Component, PropTypes } from 'react';
+
+// Task component - represents a single item
+class Task extends Component {
+ render() {
+ const taskClassName = this.props.task.checked ? 'checked' : '';
+
+ return (
+
+
+
+
+
+ {this.props.task.text}
+
+ );
+ }
+}
+
+Task.propTypes = {
+ // This component gets the task to display through a React prop.
+ // We can use propTypes to indicate it is required
+ task: PropTypes.object.isRequired,
+ onToggled: PropTypes.func,
+ onDeleted: PropTypes.func
+};
+
+export default Task;
diff --git a/imports/reducers.js b/imports/reducers.js
new file mode 100644
index 0000000..498929e
--- /dev/null
+++ b/imports/reducers.js
@@ -0,0 +1,49 @@
+import { combineReducers } from 'redux';
+import { ADD_TODO, TOGGLE_TODO, REMOVE_TODO, TOGGLE_VISIBILITY_FILTER } from './actionCreators';
+import update from 'immutability-helper';
+
+function todos(state = [], action) {
+ const payload = action.payload;
+ let index;
+
+ switch (action.type) {
+ case REMOVE_TODO:
+ index = state.findIndex(todo => todo.id === payload.id);
+
+ return update(state, {
+ $splice: [[index, 1]]
+ });
+ case ADD_TODO:
+ return state.concat([ {...action.payload} ])
+ case TOGGLE_TODO:
+ index = state.findIndex(todo => todo.id === payload.id);
+
+ if (index !== -1) {
+ return update(state, {
+ [index]: {
+ checked: { $set: !state[index].checked }
+ }
+ });
+ }
+
+ return state;
+ default:
+ return state
+ }
+}
+
+function visibilityFilter(state = 'ALL', action) {
+ switch (action.type) {
+ case TOGGLE_VISIBILITY_FILTER:
+ return state === 'NONE' ? 'ALL' : 'NONE';
+ default:
+ return state;
+ }
+}
+
+const todoApps = combineReducers({
+ visibilityFilter,
+ todos
+});
+
+export default todoApps;
\ No newline at end of file
diff --git a/imports/store.js b/imports/store.js
new file mode 100644
index 0000000..662d04f
--- /dev/null
+++ b/imports/store.js
@@ -0,0 +1,14 @@
+import { applyMiddleware, createStore } from 'redux';
+import thunk from 'redux-thunk';
+import promise from 'redux-promise';
+import createLogger from 'redux-logger';
+import todoApp from './reducers';
+
+const logger = createLogger();
+
+const store = createStore(
+ todoApp,
+ applyMiddleware(thunk, promise, logger)
+);
+
+export default store;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e70bb4e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "meteor-redux",
+ "private": true,
+ "scripts": {
+ "start": "meteor run"
+ },
+ "dependencies": {
+ "immutability-helper": "^2.0.0",
+ "meteor-node-stubs": "~0.2.0",
+ "react": "^15.1.0",
+ "react-dom": "^15.1.0",
+ "react-redux": "^4.4.5",
+ "redux": "^3.5.2",
+ "redux-actions": "^0.9.1",
+ "redux-logger": "^2.6.1",
+ "redux-promise": "^0.5.3",
+ "redux-thunk": "^2.1.0"
+ }
+}