diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..13285db
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,4 @@
+{
+ "preset": ["es2015", "react"],
+ "plugins": ["transform-object-rest-spread"],
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b512c09
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..be4761b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "26-react-redux",
+ "version": "1.0.0",
+ "description": " 26: React & Redux ======",
+ "main": "main.js",
+ "scripts": {
+ "build": "webpack",
+ "watch": "webpack-dev-server --inline --hot"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/Loaye/26-react-redux.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/Loaye/26-react-redux/issues"
+ },
+ "homepage": "https://github.com/Loaye/26-react-redux#readme",
+ "dependencies": {
+ "babel-core": "^6.26.0",
+ "babel-loader": "^7.1.2",
+ "babel-plugin-transform-object-rest-spread": "^6.26.0",
+ "babel-preset-es2015": "^6.24.1",
+ "babel-preset-react": "^6.24.1",
+ "clean-webpack-plugin": "^0.1.16",
+ "css-loader": "^0.28.5",
+ "dotenv": "^4.0.0",
+ "extract-text-webpack-plugin": "^3.0.0",
+ "file-loader": "^0.11.2",
+ "html-webpack-plugin": "^2.30.1",
+ "node-sass": "^4.5.3",
+ "react": "^15.6.1",
+ "react-dom": "^15.6.1",
+ "react-redux": "^5.0.6",
+ "react-router-dom": "^4.2.2",
+ "react-test-renderer": "^15.6.1",
+ "redux": "^3.7.2",
+ "sass-loader": "^6.0.6",
+ "superagent": "^3.6.0",
+ "uglifyjs-webpack-plugin": "^0.4.6",
+ "url-loader": "^0.5.9",
+ "webpack": "^3.5.5",
+ "webpack-dev-server": "^2.7.1"
+ }
+}
diff --git a/src/action/category-action.js b/src/action/category-action.js
new file mode 100644
index 0000000..a44bccd
--- /dev/null
+++ b/src/action/category-action.js
@@ -0,0 +1,22 @@
+import uuid from'uuid/v1';
+
+export const categoryCreate = (category) => {
+ category.id = uuid();
+ category.timestamp = new Date();
+ return {
+ type: 'CATEGORY_CREATE',
+ payload: category
+ }
+}
+
+export const categoryUpdate = (category) => ({
+ type: 'CATEGORY_CREATE',
+ payload: category
+})
+
+export const categoryDelete = (category) => ({
+ type: 'CATEGORY_DELETE',
+ payload: category
+})
+
+export const categoryReset = () => ({type: 'CATEGORY_RESET'})
\ No newline at end of file
diff --git a/src/component/app/index.js b/src/component/app/index.js
new file mode 100644
index 0000000..d07c2b9
--- /dev/null
+++ b/src/component/app/index.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import {Provider} from 'react-redux';
+import {BroswerRouter} from 'react-router-dom';
+import createAppStore from '../../lib/store.js';
+import DashboardContainer from '../dashboard-container'
+
+const store = createAppStore();
+
+class App extends React.Component{
+ componentDidMount(){
+ store.subscribe(() => {
+ console.log('--STATE--', store.getState())
+ });
+
+ store.dispatch({type: null});
+ }
+
+ render(){
+ return(
+
+ )
+ }
+}
+
+export default App;
\ No newline at end of file
diff --git a/src/component/category-form/category-form.js b/src/component/category-form/category-form.js
new file mode 100644
index 0000000..94cdbc8
--- /dev/null
+++ b/src/component/category-form/category-form.js
@@ -0,0 +1,39 @@
+import React from 'react';
+
+class CategoryForm extends React.Component{
+ constructor(props){
+ super(props);
+
+ this.state = {
+ title: props.category ? props.category.title : ''
+ }
+
+ this.handleChange = this.handleChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ }
+
+ handleChange(e){
+ this.setState({title: e.target.value});
+ }
+
+ handleSubmit(e){
+ e.preventDefault();
+ this.props.onComplete(Object.assign({}, this.state))
+ }
+
+ render(){
+ return(
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/component/dashboard-container/index.js b/src/component/dashboard-container/index.js
new file mode 100644
index 0000000..45e392c
--- /dev/null
+++ b/src/component/dashboard-container/index.js
@@ -0,0 +1,52 @@
+import React from 'react';
+import {connect} from 'react-redux';
+
+import{
+ categoryCreate,
+ categoryUpdate,
+ categoryDelete,
+} from '../../action/category-actions.js';
+
+import categoryForm from '../category-form';
+
+class DashboardContainer extends React.Component{
+ componentDidMount(){
+ this.props.categoryCreate({title: 'some category'});
+ this.props.categoryCreate({title: 'another category'});
+ this.props.categoryCreate({title: 'cool category'});
+ }
+
+ render(){
+ return(
+
+ Dashboard
+
+
+
+ {this.props.categories.map((item) =>
+
+
{item.title}
+
+ )}
+
+ )
+ }
+}
+
+const mapStateToProps = (state) => {
+ return{
+ categories: state
+ }
+}
+
+const mapDispatchToProps = (dispatch, getState) => {
+ return{
+ categoryCreate: (category) => dispatch(categoryCreate(category)),
+ categoryUpdate: (category) => dispatch(categoryUpdate(category)),
+ categoryDelete: (category) => dispatch(categoryDelete(category)),
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(DashboardContainer);
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..7a9232b
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,9 @@
+
+
+
+ Budget Tracker
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/store.js b/src/lib/store.js
new file mode 100644
index 0000000..75700f6
--- /dev/null
+++ b/src/lib/store.js
@@ -0,0 +1,4 @@
+import {createStore} from 'redux';
+import reducer from '../reducer/category.js';
+
+export default () => createStore(reducer);
\ No newline at end of file
diff --git a/src/lib/util.js b/src/lib/util.js
new file mode 100644
index 0000000..e27054d
--- /dev/null
+++ b/src/lib/util.js
@@ -0,0 +1,4 @@
+export const renderIf = (test, component) => test ? component : undefined;
+
+export const classToggler = (options) =>
+ Object.keys(options).filter(key => !!options[key]).join(' ');
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..d209467
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,5 @@
+import React from 'react';
+import ReactDom from 'react-dom';
+import App from './component/app';
+
+ReactDom.render(, document.getElementById('root'));
\ No newline at end of file
diff --git a/src/reducer/category.js b/src/reducer/category.js
new file mode 100644
index 0000000..ce0752b
--- /dev/null
+++ b/src/reducer/category.js
@@ -0,0 +1,23 @@
+let initialState = [];
+
+export default (state=initialState, action) => {
+ let {type, payload} = action;
+
+ switch(type){
+ case 'CATEGORY_CREATE':
+ return [...state, payload]
+
+ case 'CATEGORY_UPDATE':
+ return state.map(category =>
+ category.id === payload.id ? payload : category)
+
+ case 'CATEGORY_DELETE':
+ return state.filter(category => category.id !== payload.id)
+
+ case 'CATEGORY_RESET':
+ return initialState
+
+ default:
+ return state
+ }
+}
\ No newline at end of file
diff --git a/webpack-config.js b/webpack-config.js
new file mode 100644
index 0000000..c9297fd
--- /dev/null
+++ b/webpack-config.js
@@ -0,0 +1,85 @@
+'use strict';
+
+require('dotenv').config({ path: `${__dirname}/.dev.env` });
+const production = process.env.NODE_ENV === 'production';
+
+const {DefinePlugin, EnvironmentPlugin} = require('webpack');
+const HtmlPlugin = require('html-webpack-plugin');
+const CleanPlugin = require('clean-webpack-plugin');
+const UglifyPlugin = require('uglifyjs-webpack-plugin');
+const ExtractPlugin = require('extract-text-webpack-plugin');
+
+let plugins = [
+ new EnvironmentPlugin(['NODE_ENV']),
+ new ExtractPlugin('bundle-[hash].css'),
+ new HtmlPlugin({ template: `${__dirname}/src/index.html` }),
+ new DefinePlugin({
+ __DEBUG__: JSON.stringify(!production)
+ })
+]
+
+if (production) {
+ plugins = plugins.concat([ new CleanPlugin(), new UglifyPlugin() ]);
+}
+
+module.exports = {
+ plugins,
+ entry: `${__dirname}/src/main.js`,
+ devServer: {
+ historyApiFallback: true
+ },
+ devtool: production ? undefined : 'eval',
+ output: {
+ path: `${__dirname}/build`,
+ filename: 'bundle-[hash].js',
+ publicPath: process.env.CDN_URL
+ },
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader'
+ },
+ {
+ test: /\.scss$/,
+ loader: ExtractPlugin.extract(['css-loader', 'sass-loader'])
+ },
+ {
+ test: /\.(woff|woff2|ttf|eot|glyph|\.svg)$/,
+ use: [
+ {
+ loader: 'url-loader',
+ options: {
+ limit: 10000,
+ name: 'font/[name].[ext]'
+ }
+ }
+ ]
+ },
+ {
+ test: /\.(jpg|jpeg|gif|png|tiff|svg)$/,
+ exclude: /\.glyph.svg/,
+ use: [
+ {
+ loader: 'url-loader',
+ options: {
+ limit: 6000,
+ name: 'image/[name].[ext]'
+ }
+ }
+ ]
+ },
+ {
+ test: /\.(mp3|aac|aiff|wav|flac|m4a|mp4|ogg)$/,
+ exclude: /\.glyph.svg/,
+ use: [
+ {
+ loader: 'file-loader',
+ options: { name: 'audio/[name].[ext]' }
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file