diff --git a/package.json b/package.json
index f45faa9..fe2fd35 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "goals-todos-api": "^1.0.0",
"react": "^16.10.1",
"react-dom": "^16.10.1",
"react-redux": "^7.1.1",
diff --git a/src/actions/goals.js b/src/actions/goals.js
new file mode 100644
index 0000000..7d9ce81
--- /dev/null
+++ b/src/actions/goals.js
@@ -0,0 +1,43 @@
+import API from 'goals-todos-api'
+
+export const ADD_GOAL = 'ADD_GOAL'
+export const REMOVE_GOAL = 'REMOVE_GOAL'
+
+function addGoal(goal) {
+ return {
+ type: ADD_GOAL,
+ goal
+ }
+}
+
+function removeGoal(id) {
+ return {
+ type: REMOVE_GOAL,
+ id
+ }
+}
+
+export function handleAddGoal(name, callback) {
+ return (dispatch) => {
+ return API.saveGoal(name)
+ .then((goal) => {
+ dispatch(addGoal(goal))
+ callback()
+ })
+ .catch(() => {
+ alert('An error occurred. Try again.')
+ })
+ }
+}
+
+export function handleDeleteGoal(goal) {
+ return (dispatch) => {
+ dispatch(removeGoal(goal.id))
+ return API.deleteGoal(goal.id)
+ .catch(() => {
+ dispatch(addGoal(goal))
+ alert('An error occurred. Try again.')
+ })
+ }
+}
+
diff --git a/src/actions/shared.js b/src/actions/shared.js
new file mode 100644
index 0000000..abe46e0
--- /dev/null
+++ b/src/actions/shared.js
@@ -0,0 +1,22 @@
+import API from 'goals-todos-api'
+
+export const RECEIVE_DATA = 'RECEIVE_DATA'
+
+function receiveDataAction(todos, goals) {
+ return {
+ type: RECEIVE_DATA,
+ todos,
+ goals
+ }
+}
+
+export function handleInitialData() {
+ return (dispatch) => {
+ return Promise.all([
+ API.fetchTodos(),
+ API.fetchGoals()
+ ]).then(([todos, goals]) => {
+ dispatch(receiveDataAction(todos, goals))
+ })
+ }
+}
\ No newline at end of file
diff --git a/src/actions/todos.js b/src/actions/todos.js
new file mode 100644
index 0000000..75cc9ff
--- /dev/null
+++ b/src/actions/todos.js
@@ -0,0 +1,61 @@
+import API from 'goals-todos-api'
+
+export const ADD_TODO = 'ADD_TODO'
+export const REMOVE_TODO = 'REMOVE_TODO'
+export const TOGGLE_TODO = 'TOGGLE_TODO'
+
+function addTodo(todo) {
+ return {
+ type: ADD_TODO,
+ todo
+ }
+}
+
+function removeTodo(id) {
+ return {
+ type: REMOVE_TODO,
+ id
+ }
+}
+
+function toggleTodo(id) {
+ return {
+ type: TOGGLE_TODO,
+ id
+ }
+}
+
+export function handleDeleteTodo(todo) {
+ return (dispatch) => {
+ dispatch(removeTodo(todo.id))
+ return API.deleteTodo(todo.id)
+ .catch(() => {
+ dispatch(addTodo(todo))
+ alert('An error occurred. Try again.')
+ })
+ }
+}
+
+export function handleAddTodo(name, callback) {
+ return (dispatch) => {
+ return API.saveTodo(name)
+ .then((todo) => {
+ dispatch(addTodo(todo))
+ callback()
+ })
+ .catch(() => {
+ alert('An error occurred. Try again.')
+ })
+ }
+}
+
+export function handleToggle(id) {
+ return (dispatch) => {
+ dispatch(toggleTodo(id))
+ return API.saveTodoToggle(id)
+ .catch(() => {
+ dispatch(toggleTodo(id))
+ alert('An error occurred. Try again.')
+ })
+ }
+}
\ No newline at end of file
diff --git a/src/components/App.js b/src/components/App.js
index 59dd663..19b0c20 100644
--- a/src/components/App.js
+++ b/src/components/App.js
@@ -1,11 +1,33 @@
-import React from 'react';
-
-function App() {
- return (
-
- Hello world
-
- );
+import React from 'react'
+import ConnectedTodos from './Todos'
+import ConnectedGoals from './Goals'
+import { connect } from 'react-redux'
+import {
+ handleInitialData
+} from '../actions/shared'
+
+class App extends React.Component {
+ componentDidMount() {
+ const { dispatch } = this.props
+
+ dispatch(handleInitialData())
+ }
+ render() {
+ const { loading } = this.props
+
+ if (loading === true) {
+ return Loading
+ }
+
+ return (
+
+
+
+
+ )
+ }
}
-export default App;
+export default connect((state) => ({
+ loading: state.loading
+}))(App)
diff --git a/src/components/Goals.js b/src/components/Goals.js
new file mode 100644
index 0000000..8862520
--- /dev/null
+++ b/src/components/Goals.js
@@ -0,0 +1,46 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import List from './List'
+import {
+ handleAddGoal,
+ handleDeleteGoal
+} from '../actions/goals'
+
+class Goals extends React.Component {
+ addItem = (event) => {
+ event.preventDefault()
+
+ this.props.dispatch(handleAddGoal(
+ this.input.value,
+ () => this.input.value = ''
+ ))
+ }
+
+ removeItem = (goal) => {
+ this.props.dispatch(handleDeleteGoal(goal))
+ }
+
+ render() {
+ return (
+
+
Goals
+ this.input = input}
+ />
+
+
+
+ )
+ }
+}
+
+export default connect((state) => ({
+ goals: state.goals
+}))(Goals)
\ No newline at end of file
diff --git a/src/components/List.js b/src/components/List.js
new file mode 100644
index 0000000..1a846e8
--- /dev/null
+++ b/src/components/List.js
@@ -0,0 +1,18 @@
+import React from 'react'
+
+export default function List(props) {
+ return (
+
+ {props.items.map((item) => (
+ -
+ props.toggle && props.toggle(item.id)}
+ style={{ textDecoration: item.complete ? 'line-through' : 'none' }}>
+ {item.name}
+
+
+
+ ))}
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/Todos.js b/src/components/Todos.js
new file mode 100644
index 0000000..a1780cc
--- /dev/null
+++ b/src/components/Todos.js
@@ -0,0 +1,53 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import List from './List'
+import {
+ handleAddTodo,
+ handleDeleteTodo,
+ handleToggle
+} from '../actions/todos'
+
+class Todos extends React.Component {
+ addItem = (event) => {
+ event.preventDefault()
+
+ this.props.dispatch(handleAddTodo(
+ this.input.value,
+ () => this.input.value = ''
+ ))
+ }
+
+ removeItem = (todo) => {
+ this.props.dispatch(handleDeleteTodo(todo))
+ }
+
+ toggleItem = (id) => {
+ this.props.dispatch(handleToggle(id))
+ }
+
+ render() {
+ const { todos } = this.props
+
+ return (
+
+
Todo List
+ this.input = input} />
+
+
+
+ )
+ }
+}
+
+export default connect((state) => ({
+ todos: state.todos
+}))(Todos)
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 4c4b3f6..7f64681 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,5 +2,16 @@ import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App';
+import reducer from './reducers'
+import middleware from './middleware'
+import { Provider } from 'react-redux'
+import { createStore } from 'redux'
-ReactDOM.render(, document.getElementById('root'));
+const store = createStore(reducer, middleware)
+
+ReactDOM.render(
+
+
+
+ ,
+ document.getElementById('root'));
diff --git a/src/middleware/checker.js b/src/middleware/checker.js
new file mode 100644
index 0000000..771a7f8
--- /dev/null
+++ b/src/middleware/checker.js
@@ -0,0 +1,21 @@
+import { ADD_TODO } from '../actions/todos'
+import { ADD_GOAL } from '../actions/goals'
+
+const checker = (store) => (next) => (action) => {
+ if (
+ action.type === ADD_TODO &&
+ action.todo.name.toLowerCase().indexOf('bitcoin') !== -1
+ ) {
+ return alert("Nope. That's a bad idea.")
+ }
+ if (
+ action.type === ADD_GOAL &&
+ action.goal.name.toLowerCase().indexOf('bitcoin') !== -1
+ ) {
+ return alert("Nope. That's a bad idea.")
+ }
+
+ return next(action)
+}
+
+export default checker
\ No newline at end of file
diff --git a/src/middleware/index.js b/src/middleware/index.js
new file mode 100644
index 0000000..35b4dc1
--- /dev/null
+++ b/src/middleware/index.js
@@ -0,0 +1,10 @@
+import checker from './checker'
+import logger from './logger'
+import thunk from 'redux-thunk'
+import { applyMiddleware } from 'redux'
+
+export default applyMiddleware(
+ thunk,
+ checker,
+ logger
+)
\ No newline at end of file
diff --git a/src/middleware/logger.js b/src/middleware/logger.js
new file mode 100644
index 0000000..145de59
--- /dev/null
+++ b/src/middleware/logger.js
@@ -0,0 +1,10 @@
+const logger = (store) => (next) => (action) => {
+ console.group(action.type)
+ console.log('The action: ', action)
+ const result = next(action)
+ console.log('The new state: ', store.getState())
+ console.groupEnd()
+ return result
+}
+
+export default logger
\ No newline at end of file
diff --git a/src/reducers/goals.js b/src/reducers/goals.js
new file mode 100644
index 0000000..e996a23
--- /dev/null
+++ b/src/reducers/goals.js
@@ -0,0 +1,16 @@
+import { ADD_GOAL, REMOVE_GOAL } from '../actions/goals'
+
+import { RECEIVE_DATA } from '../actions/shared'
+
+export default function goals(state = [], action) {
+ switch (action.type) {
+ case ADD_GOAL:
+ return state.concat([action.goal])
+ case REMOVE_GOAL:
+ return state.filter((goal) => goal.id !== action.id)
+ case RECEIVE_DATA:
+ return action.goals
+ default:
+ return state
+ }
+}
\ No newline at end of file
diff --git a/src/reducers/index.js b/src/reducers/index.js
new file mode 100644
index 0000000..231c82e
--- /dev/null
+++ b/src/reducers/index.js
@@ -0,0 +1,11 @@
+import { combineReducers } from 'redux'
+
+import todos from './todos'
+import goals from './goals'
+import loading from './loading'
+
+export default combineReducers({
+ todos,
+ goals,
+ loading
+})
\ No newline at end of file
diff --git a/src/reducers/loading.js b/src/reducers/loading.js
new file mode 100644
index 0000000..d8c5cac
--- /dev/null
+++ b/src/reducers/loading.js
@@ -0,0 +1,10 @@
+import { RECEIVE_DATA } from '../actions/shared'
+
+export default function loading(state = true, action) {
+ switch (action.type) {
+ case RECEIVE_DATA:
+ return false
+ default:
+ return state
+ }
+}
\ No newline at end of file
diff --git a/src/reducers/todos.js b/src/reducers/todos.js
new file mode 100644
index 0000000..ee748f9
--- /dev/null
+++ b/src/reducers/todos.js
@@ -0,0 +1,20 @@
+import { ADD_TODO, REMOVE_TODO, TOGGLE_TODO } from '../actions/todos'
+
+import { RECEIVE_DATA } from '../actions/shared'
+
+export default function todos(state = [], action) {
+ switch (action.type) {
+ case ADD_TODO:
+ return state.concat([action.todo])
+ case REMOVE_TODO:
+ return state.filter((todo) => todo.id !== action.id)
+ case TOGGLE_TODO:
+ return state.map((todo) => todo.id !== action.id ? todo :
+ Object.assign({}, todo, { complete: !todo.complete })
+ )
+ case RECEIVE_DATA:
+ return action.todos
+ default:
+ return state
+ }
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index db97090..9a08e0b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4344,6 +4344,11 @@ globby@^6.1.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+goals-todos-api@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/goals-todos-api/-/goals-todos-api-1.0.0.tgz#1086716697ad41b519b899a55e050773d98ffbff"
+ integrity sha512-mrKMYU7VCtz4a6K1Ba1CKxuNuyrnw0OyiMOgORm2p5WR1FvCA5JOOc3nBhEYgZFN2AEmnUSU/tmh0JmNJremlA==
+
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.2"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02"