From dcbc4e07b3ff6d538c1a57bd30f212f1146dfbe8 Mon Sep 17 00:00:00 2001 From: Bret Ldenburg Date: Wed, 6 Sep 2017 15:53:45 -0700 Subject: [PATCH 1/2] got the auth working for the lab --- .gitignore | 136 ++++++++++++++++++ frontend/.babelrc | 4 + frontend/package.json | 39 +++++ frontend/src/action/auth-actions.js | 35 +++++ frontend/src/component/app/index.js | 34 +++++ frontend/src/component/auth-form/index.js | 94 ++++++++++++ .../src/component/landing-container/index.js | 33 +++++ frontend/src/index.html | 9 ++ frontend/src/lib/app-create-store.js | 9 ++ frontend/src/lib/redux-reporter.js | 15 ++ frontend/src/lib/redux-thunk.js | 4 + frontend/src/lib/util.js | 19 +++ frontend/src/main.js | 5 + frontend/src/reducer/auth.js | 11 ++ frontend/src/reducer/index.js | 6 + frontend/src/style/base/_base.scss | 0 frontend/src/style/base/_reset.scss | 0 frontend/src/style/layout/_content.scss | 0 frontend/src/style/layout/_footer.scss | 0 frontend/src/style/layout/_header.scss | 0 frontend/src/style/lib/_vars.scss | 0 frontend/src/style/main.scss | 0 frontend/webpack.config.js | 86 +++++++++++ sluggram | 1 + 24 files changed, 540 insertions(+) create mode 100644 .gitignore create mode 100644 frontend/.babelrc create mode 100644 frontend/package.json create mode 100644 frontend/src/action/auth-actions.js create mode 100644 frontend/src/component/app/index.js create mode 100644 frontend/src/component/auth-form/index.js create mode 100644 frontend/src/component/landing-container/index.js create mode 100644 frontend/src/index.html create mode 100644 frontend/src/lib/app-create-store.js create mode 100644 frontend/src/lib/redux-reporter.js create mode 100644 frontend/src/lib/redux-thunk.js create mode 100644 frontend/src/lib/util.js create mode 100644 frontend/src/main.js create mode 100644 frontend/src/reducer/auth.js create mode 100644 frontend/src/reducer/index.js create mode 100644 frontend/src/style/base/_base.scss create mode 100644 frontend/src/style/base/_reset.scss create mode 100644 frontend/src/style/layout/_content.scss create mode 100644 frontend/src/style/layout/_footer.scss create mode 100644 frontend/src/style/layout/_header.scss create mode 100644 frontend/src/style/lib/_vars.scss create mode 100644 frontend/src/style/main.scss create mode 100644 frontend/webpack.config.js create mode 160000 sluggram diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..345130c --- /dev/null +++ b/.gitignore @@ -0,0 +1,136 @@ +# Created by https://www.gitignore.io/api/osx,vim,node,macos,windows + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +### OSX ### + +# Icon must end with two \r + +# Thumbnails + +# Files that might appear in the root of a volume + +# Directories potentially created on remote AFP share + +### Vim ### +# swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/osx,vim,node,macos,windows diff --git a/frontend/.babelrc b/frontend/.babelrc new file mode 100644 index 0000000..47c9ace --- /dev/null +++ b/frontend/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015", "react"], + "plugins": ["transform-object-rest-spread"] +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..dc51e76 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,39 @@ +{ + "name": "frontend", + "version": "1.0.0", + "description": "", + "main": "webpack.config.js", + "scripts": { + "build": "webpack", + "watch": "webpack-dev-server --inline --hot" + }, + "keywords": [], + "author": "", + "license": "ISC", + "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.7", + "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": "^4.2.0", + "react-router-dom": "^4.2.2", + "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.6", + "webpack-dev-server": "^2.7.1" + } +} diff --git a/frontend/src/action/auth-actions.js b/frontend/src/action/auth-actions.js new file mode 100644 index 0000000..4cb0e90 --- /dev/null +++ b/frontend/src/action/auth-actions.js @@ -0,0 +1,35 @@ +import superagent from 'superagent'; + +export const tokenSet = (token) => ({ + type: 'TOKEN_SET', + payload: token +}) + +export const tokenDelete = () => ({ + type: 'TOKEN_DELETE' +}) + +export const signupRequest = (user) => (dispatch) => { + return superagent.post(`${__API_URL__}/signup`) + .withCredentials() + .send(user) + .then(res => { + dispatch(tokenSet(res.text)); + try { + localStorage.token = res.text; + } catch (err) { + console.log(err); + } + return res; + }) +} + +export const loginRequest = (user) => (dispatch) => { + return superagent.get(`${__API_URL__}/login`) + .withCredentials() + .auth(user.username, user.password) + .then(res => { + dispatch(tokenSet(res.text)); + return res; + }) +} diff --git a/frontend/src/component/app/index.js b/frontend/src/component/app/index.js new file mode 100644 index 0000000..117b156 --- /dev/null +++ b/frontend/src/component/app/index.js @@ -0,0 +1,34 @@ +import React from 'react'; +import {Provider} from 'react-redux'; +import {BrowserRouter, Route, Link} from 'react-router-dom'; +import appStoreCreate from '../../lib/app-create-store.js'; +import LandingContainer from '../landing-container'; + +let store = appStoreCreate(); + +class App extends React.Component { + render() { + return ( +
+ + +
+
+

To Auth, or not to Auth

+ +
+ +
+
+
+
+ ) + } +} + +export default App; diff --git a/frontend/src/component/auth-form/index.js b/frontend/src/component/auth-form/index.js new file mode 100644 index 0000000..e2cb130 --- /dev/null +++ b/frontend/src/component/auth-form/index.js @@ -0,0 +1,94 @@ +import React from 'react'; +import * as util from '../../lib/util.js'; + +class AuthForm extends React.Component { + constructor(props) { + super(props); + this.state = { + username: '', + email: '', + password: '', + usernameError: null, + passwordError: null, + emailError: null, + error: null + } + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } + + handleChange(e) { + let {name, value} = e.target; + + this.setState({ + [name]: value, + usernameError: name === 'username' && !value ? 'username required' : null, + emailError: name === 'email' && !value ? 'email required' : null, + passwordError: name === 'password' && !value ? 'password required' : null + }) + } + + handleSubmit(e) { + e.preventDefault(); + this.props.onComplete(this.state) + .then(() => { + this.setState({username: '', email: '', password: ''}) + }) + .catch(error => { + console.error(error); + this.setState({error}); + }) + } + + render() { + return ( +
+ + {util.renderIf(this.props.auth === 'signup', + + )} + + {util.renderIf(this.state.usernameError, + + {this.state.usernameError} + + )} + + + + {util.renderIf(this.state.passwordError, + + {this.state.passwordError} + + )} + + + + +
+ ) + } +} + +export default AuthForm; diff --git a/frontend/src/component/landing-container/index.js b/frontend/src/component/landing-container/index.js new file mode 100644 index 0000000..fc47366 --- /dev/null +++ b/frontend/src/component/landing-container/index.js @@ -0,0 +1,33 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import AuthForm from '../auth-form'; +import * as util from '../../lib/util.js'; +import {signupRequest, loginRequest} from '../../action/auth-actions.js'; + +class LandingContainer extends React.Component { + render() { + let {params} = this.props.match; + + let handleComplete = params.auth === 'login' + ? this.props.login + : this.props.signup + + return ( +
+ +
+ ) + } +} + +let mapDispatchToProps = (dispatch) => { + return { + signup: (user) => dispatch(signupRequest(user)), + login: (user) => dispatch(loginRequest(user)) + } +} + +export default connect(undefined, mapDispatchToProps)(LandingContainer); diff --git a/frontend/src/index.html b/frontend/src/index.html new file mode 100644 index 0000000..968a343 --- /dev/null +++ b/frontend/src/index.html @@ -0,0 +1,9 @@ + + + + Auth Lab + + +
+ + diff --git a/frontend/src/lib/app-create-store.js b/frontend/src/lib/app-create-store.js new file mode 100644 index 0000000..432c33d --- /dev/null +++ b/frontend/src/lib/app-create-store.js @@ -0,0 +1,9 @@ +import reducer from '../reducer'; +import thunk from './redux-thunk.js'; +import reporter from './redux-reporter.js'; +import {createStore, applyMiddleware} from 'redux'; + +let appStoreCreate = () => + createStore(reducer, applyMiddleware(thunk, reporter)) + +export default appStoreCreate; diff --git a/frontend/src/lib/redux-reporter.js b/frontend/src/lib/redux-reporter.js new file mode 100644 index 0000000..0a347af --- /dev/null +++ b/frontend/src/lib/redux-reporter.js @@ -0,0 +1,15 @@ +let reporter = store => next => action => { + console.log('__ACTION__', action); + + try { + let result = next(action); + console.log('__STATE__', store.getState()); + return result; + } catch (error) { + error.action = action; + console.error('__ERROR__', error); + return error; + } +} + +export default reporter; diff --git a/frontend/src/lib/redux-thunk.js b/frontend/src/lib/redux-thunk.js new file mode 100644 index 0000000..49771bd --- /dev/null +++ b/frontend/src/lib/redux-thunk.js @@ -0,0 +1,4 @@ +export default store => next => action => + typeof action === 'function' + ? action(store.dispatch, store.getState) + : next(action) diff --git a/frontend/src/lib/util.js b/frontend/src/lib/util.js new file mode 100644 index 0000000..3d4b5a2 --- /dev/null +++ b/frontend/src/lib/util.js @@ -0,0 +1,19 @@ +export const log = (...args) => + __DEBUG__ ? console.log(...args) : undefined; + +export const logError = (...args) => + __DEBUG__ ? console.error(...args) : undefined; + +export const renderIf = (test, component) => test ? component : undefined; + +export const classToggler = (options) => + Object.keys(options).filter(key => !!options[key]).join(' '); + +export const map = (list, ...args) => + Array.prototype.map.apply(list, args); + +export const filter = (list, ...args) => + Array.prototype.filter.apply(list, args); + +export const reduce = (list, ...args) => + Array.prototype.reduce.apply(list, args); diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..faafd74 --- /dev/null +++ b/frontend/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')); diff --git a/frontend/src/reducer/auth.js b/frontend/src/reducer/auth.js new file mode 100644 index 0000000..12a3e1c --- /dev/null +++ b/frontend/src/reducer/auth.js @@ -0,0 +1,11 @@ +export default (state=null, action) => { + let {type, payload} = action; + switch(type) { + case 'TOKEN_SET': + return payload + case 'TOKEN_DELETE': + return null + default: + return state + } +} diff --git a/frontend/src/reducer/index.js b/frontend/src/reducer/index.js new file mode 100644 index 0000000..a096f45 --- /dev/null +++ b/frontend/src/reducer/index.js @@ -0,0 +1,6 @@ +import {combineReducers} from 'redux'; +import auth from './auth.js'; + +export default combineReducers({ + auth, +}) diff --git a/frontend/src/style/base/_base.scss b/frontend/src/style/base/_base.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/style/base/_reset.scss b/frontend/src/style/base/_reset.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/style/layout/_content.scss b/frontend/src/style/layout/_content.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/style/layout/_footer.scss b/frontend/src/style/layout/_footer.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/style/layout/_header.scss b/frontend/src/style/layout/_header.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/style/lib/_vars.scss b/frontend/src/style/lib/_vars.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/style/main.scss b/frontend/src/style/main.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js new file mode 100644 index 0000000..e06dfe6 --- /dev/null +++ b/frontend/webpack.config.js @@ -0,0 +1,86 @@ +'use strict'; + +require('dotenv').config(); +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), + __API_URL__: JSON.stringify(process.env.API_URL) + }) +] + +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]' } + } + ] + } + ] + } +} diff --git a/sluggram b/sluggram new file mode 160000 index 0000000..1fc6e99 --- /dev/null +++ b/sluggram @@ -0,0 +1 @@ +Subproject commit 1fc6e992ae12d84d6e3f8dfc67a18c9f2eca29ad From bab59ac9cb23ded45e7d4af0d07b046be3a3c7a8 Mon Sep 17 00:00:00 2001 From: Bret Ldenburg Date: Thu, 7 Sep 2017 14:13:14 -0700 Subject: [PATCH 2/2] added the profile features to the rolling app --- frontend/src/action/profile-actions.js | 23 ++++++ frontend/src/component/app/index.js | 35 +++++++--- frontend/src/component/profile-form/index.js | 70 +++++++++++++++++++ .../src/component/settings-container/index.js | 49 +++++++++++++ frontend/src/lib/util.js | 28 ++++++++ frontend/src/main.js | 15 +++- frontend/src/reducer/profile.js | 21 ++++++ 7 files changed, 230 insertions(+), 11 deletions(-) create mode 100644 frontend/src/action/profile-actions.js create mode 100644 frontend/src/component/profile-form/index.js create mode 100644 frontend/src/component/settings-container/index.js create mode 100644 frontend/src/reducer/profile.js diff --git a/frontend/src/action/profile-actions.js b/frontend/src/action/profile-actions.js new file mode 100644 index 0000000..24256bd --- /dev/null +++ b/frontend/src/action/profile-actions.js @@ -0,0 +1,23 @@ +import superagent from 'superagent'; + +export const profileCreate = (profile) => ({ + type: 'PROFILE_CREATE', + payload: profile +}) + +export const profileUpdate = (profile) => ({ + type: 'PROFILE_UPDATE', + payload: profile +}) + +export const profileCreateRequest = (profile) => (dispatch, getState) => { + let {auth} = getState(); + return superagent.post(`${__API_URL__}/profiles`) + .set('Authorization', `Bearer ${auth}`) + .field('bio', profile.bio) + .attach('avatar', profile.avatar) + .then( res => { + dispatch(profileCreate(res.body)); + return res; + }) +} diff --git a/frontend/src/component/app/index.js b/frontend/src/component/app/index.js index 117b156..fcd70dc 100644 --- a/frontend/src/component/app/index.js +++ b/frontend/src/component/app/index.js @@ -1,34 +1,49 @@ import React from 'react'; -import {Provider} from 'react-redux'; +import {connect} from 'react-redux'; import {BrowserRouter, Route, Link} from 'react-router-dom'; -import appStoreCreate from '../../lib/app-create-store.js'; import LandingContainer from '../landing-container'; - -let store = appStoreCreate(); +import SettingsContainer from '../settings-container'; +import * as util from '../../lib/util.js'; +import {tokenSet} from '../../action/auth-actions.js'; class App extends React.Component { + componentDidMount() { + let token = util.readCookie('X-Sluggram-Token'); + if (token) { + this.props.tokenSet(token); + } + } + render() { return ( -
- +
-

To Auth, or not to Auth

+

cfgram

- + +
-
) } } -export default App; +let mapStateToProps = (state) => ({ + profile: state.profile +}) + +let mapDispatchToProps = (dispatch) => ({ + tokenSet: (token) => dispatch(tokenSet(token)) +}) + +export default connect(mapStateToProps, mapDispatchToProps)(App); diff --git a/frontend/src/component/profile-form/index.js b/frontend/src/component/profile-form/index.js new file mode 100644 index 0000000..51caac0 --- /dev/null +++ b/frontend/src/component/profile-form/index.js @@ -0,0 +1,70 @@ +import React from 'react'; +import * as util from '../../lib/util.js'; + +class ProfileForm extends React.Component { + constructor(props) { + super(props); + this.state = props.profile + ? {...props.profile, preview: ''} + : {bio: '', avatar: null, preview: ''} + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } + + componentWillReceiveProps(props) { + if (props.profile) { + this.setState(props.profile); + } + } + + handleChange(e) { + let {type, name} = e.target; + + if (name === 'bio') { + this.setState({bio: e.target.value}) + } + + if (name === 'avatar') { + let {files} = e.target; + let avatar = files[0]; + this.setState({avatar}); + util.photoToDataURL(avatar) + .then(preview => this.setState({preview})) + .catch(console.error); + } + } + + handleSubmit(e) { + e.preventDefault(); + this.props.onComplete(this.state); + } + + render() { + return ( +
+ + + + + + + + +
+ ) + } +} + +export default ProfileForm; diff --git a/frontend/src/component/settings-container/index.js b/frontend/src/component/settings-container/index.js new file mode 100644 index 0000000..b9efe1e --- /dev/null +++ b/frontend/src/component/settings-container/index.js @@ -0,0 +1,49 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import ProfileForm from '../profile-form'; +import {profileCreateRequest} from '../../action/profile-actions.js'; + +class SettingsContainer extends React.Component { + constructor(props) { + super(props); + this.handleProfileCreate = this.handleProfileCreate.bind(this); + this.handleProfileUpdate = this.handleProfileUpdate.bind(this); + } + + handleProfileCreate(profile) { + return this.props.profileCreate(profile) + .then(res => { + console.log('profile create response:', res); + }) + .catch(console.error) + } + + //TODO + handleProfileUpdate(){ } + + render() { + let handleComplete = this.props.profile + ? this.handleProfileCreate + : this.handleProfileUpdate + + return ( +
+

Profile Settings:

+ +
+ ) + } +} + +let mapStateToProps = (state) => ({ + profile: state.profile +}) + +let mapDispatchToProps = (dispatch) => ({ + profileCreate: (profile) => dispatch(profileCreateRequest(profile)) +}) + +export default connect(mapStateToProps, mapDispatchToProps)(SettingsContainer); diff --git a/frontend/src/lib/util.js b/frontend/src/lib/util.js index 3d4b5a2..71df0dd 100644 --- a/frontend/src/lib/util.js +++ b/frontend/src/lib/util.js @@ -1,3 +1,31 @@ +export const photoToDataURL = (file) => { + return new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.addEventListener('load', () => { + resolve(reader.result); + }) + reader.addEventListener('error', () => { + reject(reader.error); + }) + if (file) { + return reader.readAsDataURL(file) + } + }) +} + +// got from: +// https://stackoverflow.com/questions/14573223/set-cookie-and-get-cookie-with-javascript +export const readCookie = (name) => { + var nameEQ = name + '='; + var ca = document.cookie.split(';'); + for(var i=0; i < ca.length; i++) { + var c = ca[i]; + while(c.charAt(0) == ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); + } + return null; +} + export const log = (...args) => __DEBUG__ ? console.log(...args) : undefined; diff --git a/frontend/src/main.js b/frontend/src/main.js index faafd74..3650b7d 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,5 +1,18 @@ import React from 'react'; import ReactDom from 'react-dom'; +import {Provider} from 'react-redux'; + +import appStoreCreate from './lib/app-create-store.js'; import App from './component/app'; -ReactDom.render(, document.getElementById('root')); +let store = appStoreCreate(); + +let AppContainer = () => { + return ( + + + + ) +} + +ReactDom.render(, document.getElementById('root')); diff --git a/frontend/src/reducer/profile.js b/frontend/src/reducer/profile.js new file mode 100644 index 0000000..e1f735c --- /dev/null +++ b/frontend/src/reducer/profile.js @@ -0,0 +1,21 @@ +let validateProfileCreate = (profile) => { + if (!profile.avatar || !profile.bio || !profile._id || !profile.owner || !profile.username || !profile.email) { + throw new Error('VALIDATION ERROR: profile requires additional info'); + } +} + +export default (state=null, action) => { + let {type, payload} = action; + + switch(type) { + case 'PROFILE_CREATE': + validateProfileCreate(payload); + return payload; + case 'PROFILE_UPDATE': + return {...state, ...payload} + case 'LOGOUT': + return null + default: + return state + } +}