From 727f1624bec6f3e8761d914eceecf9e3a558772b Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Wed, 6 Sep 2017 13:17:28 -0700 Subject: [PATCH 1/9] Add backend. --- lab-nathan/backend | 1 + 1 file changed, 1 insertion(+) create mode 160000 lab-nathan/backend diff --git a/lab-nathan/backend b/lab-nathan/backend new file mode 160000 index 0000000..1fc6e99 --- /dev/null +++ b/lab-nathan/backend @@ -0,0 +1 @@ +Subproject commit 1fc6e992ae12d84d6e3f8dfc67a18c9f2eca29ad From 2abebf639c7ba27bf1ec2f85ee72147c0a803ac1 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Wed, 6 Sep 2017 22:29:02 -0700 Subject: [PATCH 2/9] Add authentication. --- lab-nathan/frontend/.babelrc | 4 + lab-nathan/frontend/.gitignore | 4 + lab-nathan/frontend/package.json | 38 ++++++++ .../frontend/src/actions/token-actions.js | 37 ++++++++ lab-nathan/frontend/src/components/app/app.js | 33 +++++++ .../src/components/landing/landing.js | 28 ++++++ .../frontend/src/components/login/login.js | 89 ++++++++++++++++++ lab-nathan/frontend/src/index.html | 9 ++ lab-nathan/frontend/src/lib/create-store.js | 6 ++ lab-nathan/frontend/src/lib/reporter.js | 14 +++ lab-nathan/frontend/src/lib/thunk.js | 6 ++ lab-nathan/frontend/src/lib/utilities.js | 19 ++++ lab-nathan/frontend/src/main.js | 5 + lab-nathan/frontend/src/reducers/reducers.js | 6 ++ .../frontend/src/reducers/token-reducer.js | 11 +++ lab-nathan/frontend/webpack.config.js | 91 +++++++++++++++++++ 16 files changed, 400 insertions(+) create mode 100644 lab-nathan/frontend/.babelrc create mode 100644 lab-nathan/frontend/.gitignore create mode 100644 lab-nathan/frontend/package.json create mode 100644 lab-nathan/frontend/src/actions/token-actions.js create mode 100644 lab-nathan/frontend/src/components/app/app.js create mode 100644 lab-nathan/frontend/src/components/landing/landing.js create mode 100644 lab-nathan/frontend/src/components/login/login.js create mode 100644 lab-nathan/frontend/src/index.html create mode 100644 lab-nathan/frontend/src/lib/create-store.js create mode 100644 lab-nathan/frontend/src/lib/reporter.js create mode 100644 lab-nathan/frontend/src/lib/thunk.js create mode 100644 lab-nathan/frontend/src/lib/utilities.js create mode 100644 lab-nathan/frontend/src/main.js create mode 100644 lab-nathan/frontend/src/reducers/reducers.js create mode 100644 lab-nathan/frontend/src/reducers/token-reducer.js create mode 100644 lab-nathan/frontend/webpack.config.js diff --git a/lab-nathan/frontend/.babelrc b/lab-nathan/frontend/.babelrc new file mode 100644 index 0000000..cf6ae40 --- /dev/null +++ b/lab-nathan/frontend/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015", "react"], + "plugins": ["transform-object-rest-spread"] +} \ No newline at end of file diff --git a/lab-nathan/frontend/.gitignore b/lab-nathan/frontend/.gitignore new file mode 100644 index 0000000..60a5626 --- /dev/null +++ b/lab-nathan/frontend/.gitignore @@ -0,0 +1,4 @@ +node_modules +coverage +build +.env \ No newline at end of file diff --git a/lab-nathan/frontend/package.json b/lab-nathan/frontend/package.json new file mode 100644 index 0000000..6641b4d --- /dev/null +++ b/lab-nathan/frontend/package.json @@ -0,0 +1,38 @@ +{ + "name": "frontend", + "version": "1.0.0", + "description": "", + "main": "index.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", + "superagent": "^3.6.0", + "uglifyjs-webpack-plugin": "^0.4.6", + "uuid": "^3.1.0", + "webpack": "^3.5.6", + "webpack-dev-server": "^2.7.1" + } +} diff --git a/lab-nathan/frontend/src/actions/token-actions.js b/lab-nathan/frontend/src/actions/token-actions.js new file mode 100644 index 0000000..5fc8298 --- /dev/null +++ b/lab-nathan/frontend/src/actions/token-actions.js @@ -0,0 +1,37 @@ +import superagent from 'superagent'; + +export const tokenSet = (token) => ({ + type: 'TOKEN_SET', + payload: token +}); + +export const tokenDelete = (token) => ({ + type: 'TOKEN_DELETE' +}); + +export const signupRequest = (user) => (dispatch) => { + return superagent.post(`${__API_URL__}/signup`) + .withCredentials() + .send(user) + .then(response => { + dispatch(tokenSet(response.text)); + try { + localStorage.token = response.text; + } + catch (error) { + console.log(error); + } + + return response; + }); +}; + +export const loginRequest = (user) => (dispatch) => { + return superagent.get(`${__API_URL__}/login`) + .withCredentials() + .auth(user.username, user.password) + .then(response => { + dispatch(tokenSet(response.text)) + return response; + }); +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/app/app.js b/lab-nathan/frontend/src/components/app/app.js new file mode 100644 index 0000000..ba82e3a --- /dev/null +++ b/lab-nathan/frontend/src/components/app/app.js @@ -0,0 +1,33 @@ +import React from 'react'; +import {Provider} from 'react-redux'; +import {BrowserRouter, Route, Link} from 'react-router-dom'; +import createStore from '../../lib/create-store.js'; +import Landing from '../landing/landing.js'; + +let store = createStore(); + +class App extends React.Component { + constructor(props) { + super(props); + } + + render() { + return ( +
+ + +
+
    +
  • Sign Up
  • +
  • Log In
  • +
+ +
+
+
+
+ ); + } +} + +export default App; diff --git a/lab-nathan/frontend/src/components/landing/landing.js b/lab-nathan/frontend/src/components/landing/landing.js new file mode 100644 index 0000000..092cec0 --- /dev/null +++ b/lab-nathan/frontend/src/components/landing/landing.js @@ -0,0 +1,28 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import {signupRequest, loginRequest} from '../../actions/token-actions.js'; +import Login from '../login/login.js'; + +class Landing extends React.Component { + render() { + let {params} = this.props.match; + let handleComplete = params.auth === 'login' + ? this.props.login + : this.props.signup; + + return ( +
+ +
+ ); + } +} + +let mapDispatchToProps = (dispatch) =>({ + signup: (user) => dispatch(signupRequest(user)), + login: (user) => dispatch(loginRequest(user)) +}) + +export default connect(null, mapDispatchToProps)(Landing); \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/login/login.js b/lab-nathan/frontend/src/components/login/login.js new file mode 100644 index 0000000..c34f288 --- /dev/null +++ b/lab-nathan/frontend/src/components/login/login.js @@ -0,0 +1,89 @@ +import React from 'react'; +import * as utilities from '../../lib/utilities.js'; + +class Login 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 ( +
+ + {utilities.renderIf(this.props.auth === 'signup', + + )} + + {utilities.renderIf(this.state.usernameError, + + {this.state.usernameError} + + )} + + + + {utilities.renderIf(this.state.passwordError, + + {this.state.passwordError} + + )} + + + + + +
+ ); + } +} + +export default Login; diff --git a/lab-nathan/frontend/src/index.html b/lab-nathan/frontend/src/index.html new file mode 100644 index 0000000..44d42a5 --- /dev/null +++ b/lab-nathan/frontend/src/index.html @@ -0,0 +1,9 @@ + + + + CF Gram + + +
+ + \ No newline at end of file diff --git a/lab-nathan/frontend/src/lib/create-store.js b/lab-nathan/frontend/src/lib/create-store.js new file mode 100644 index 0000000..be5ac95 --- /dev/null +++ b/lab-nathan/frontend/src/lib/create-store.js @@ -0,0 +1,6 @@ +import {createStore, applyMiddleware} from 'redux'; +import reducers from '../reducers/reducers.js'; +import thunk from './thunk.js'; +import reporter from './reporter.js'; + +export default () => createStore(reducers, applyMiddleware(thunk, reporter)); \ No newline at end of file diff --git a/lab-nathan/frontend/src/lib/reporter.js b/lab-nathan/frontend/src/lib/reporter.js new file mode 100644 index 0000000..7350185 --- /dev/null +++ b/lab-nathan/frontend/src/lib/reporter.js @@ -0,0 +1,14 @@ +export default 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; + } +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/lib/thunk.js b/lab-nathan/frontend/src/lib/thunk.js new file mode 100644 index 0000000..c3a44ca --- /dev/null +++ b/lab-nathan/frontend/src/lib/thunk.js @@ -0,0 +1,6 @@ +export default store => next => action => { + if (typeof action === 'function') + return action(store.dispatch, store.getState); + else + return next(action); +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/lib/utilities.js b/lab-nathan/frontend/src/lib/utilities.js new file mode 100644 index 0000000..81eeb2d --- /dev/null +++ b/lab-nathan/frontend/src/lib/utilities.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 => Boolean(options)).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); \ No newline at end of file diff --git a/lab-nathan/frontend/src/main.js b/lab-nathan/frontend/src/main.js new file mode 100644 index 0000000..cf45095 --- /dev/null +++ b/lab-nathan/frontend/src/main.js @@ -0,0 +1,5 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import App from './components/app/app.js'; + +ReactDom.render(, document.getElementById('root')); \ No newline at end of file diff --git a/lab-nathan/frontend/src/reducers/reducers.js b/lab-nathan/frontend/src/reducers/reducers.js new file mode 100644 index 0000000..50c0298 --- /dev/null +++ b/lab-nathan/frontend/src/reducers/reducers.js @@ -0,0 +1,6 @@ +import {combineReducers} from 'redux'; +import tokenReducer from './token-reducer.js'; + +export default combineReducers({ + token: tokenReducer +}); \ No newline at end of file diff --git a/lab-nathan/frontend/src/reducers/token-reducer.js b/lab-nathan/frontend/src/reducers/token-reducer.js new file mode 100644 index 0000000..796a6a2 --- /dev/null +++ b/lab-nathan/frontend/src/reducers/token-reducer.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 + } +} \ No newline at end of file diff --git a/lab-nathan/frontend/webpack.config.js b/lab-nathan/frontend/webpack.config.js new file mode 100644 index 0000000..bee537e --- /dev/null +++ b/lab-nathan/frontend/webpack.config.js @@ -0,0 +1,91 @@ +'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'); + +console.log(process.env.API_URL); + +let plugins = [ + new EnvironmentPlugin(['NODE_ENV']), + new ExtractPlugin('bundle-[hash].css'), + new HtmlPlugin({ template: `${__dirname}/src/index.html` }), + new DefinePlugin({ + '__DEBUG__': JSON.stringify('development'), + '__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|ape)$/, + exclude: /\.glyph.svg/, + use: [ + { + loader: 'file-loader', + options: { + name: 'audio/[name].[ext]' + } + } + ] + } + ] + } +}; From 0416d25c78c4ad85e503f09613c10726c9eedc90 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Wed, 6 Sep 2017 22:50:24 -0700 Subject: [PATCH 3/9] Add tests. --- lab-nathan/frontend/package.json | 5 +++- .../src/__test__/token-reducer.test.js | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 lab-nathan/frontend/src/__test__/token-reducer.test.js diff --git a/lab-nathan/frontend/package.json b/lab-nathan/frontend/package.json index 6641b4d..79b4cb9 100644 --- a/lab-nathan/frontend/package.json +++ b/lab-nathan/frontend/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "build": "webpack", - "watch": "webpack-dev-server --inline --hot" + "watch": "webpack-dev-server --inline --hot", + "test": "jest" }, "keywords": [], "author": "", @@ -22,6 +23,7 @@ "extract-text-webpack-plugin": "^3.0.0", "file-loader": "^0.11.2", "html-webpack-plugin": "^2.30.1", + "jest": "^21.0.1", "node-sass": "^4.5.3", "react": "^15.6.1", "react-dom": "^15.6.1", @@ -29,6 +31,7 @@ "react-router": "^4.2.0", "react-router-dom": "^4.2.2", "redux": "^3.7.2", + "redux-mock-store": "^1.2.3", "superagent": "^3.6.0", "uglifyjs-webpack-plugin": "^0.4.6", "uuid": "^3.1.0", diff --git a/lab-nathan/frontend/src/__test__/token-reducer.test.js b/lab-nathan/frontend/src/__test__/token-reducer.test.js new file mode 100644 index 0000000..0e0ca69 --- /dev/null +++ b/lab-nathan/frontend/src/__test__/token-reducer.test.js @@ -0,0 +1,24 @@ +import tokenReducer from '../reducers/token-reducer.js'; + +describe('Token Reducer', function() { + it('TOKEN_SET should return a token.', function() { + let testAction = { + type: 'TOKEN_SET', + payload: 'kasdf;kasdfj;asdfj;s' + }; + + let result = tokenReducer(null, testAction); + + expect(result).toEqual(testAction.payload); + }); + + it('TOKEN_DELETE should delete a token.', function() { + let testAction = { + type: 'TOKEN_DELETE' + }; + + let result = tokenReducer(null, testAction); + + expect(result).toEqual(null); + }) +}); \ No newline at end of file From f493f701291f145aca591339a0c859eeba2ede8e Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Wed, 6 Sep 2017 23:42:09 -0700 Subject: [PATCH 4/9] Add dashboard. --- lab-nathan/frontend/src/components/app/app.js | 2 ++ .../frontend/src/components/dashboard/dashboard.js | 11 +++++++++++ 2 files changed, 13 insertions(+) create mode 100644 lab-nathan/frontend/src/components/dashboard/dashboard.js diff --git a/lab-nathan/frontend/src/components/app/app.js b/lab-nathan/frontend/src/components/app/app.js index ba82e3a..aac5353 100644 --- a/lab-nathan/frontend/src/components/app/app.js +++ b/lab-nathan/frontend/src/components/app/app.js @@ -3,6 +3,7 @@ import {Provider} from 'react-redux'; import {BrowserRouter, Route, Link} from 'react-router-dom'; import createStore from '../../lib/create-store.js'; import Landing from '../landing/landing.js'; +import Dashboard from '../dashboard/dashboard.js'; let store = createStore(); @@ -22,6 +23,7 @@ class App extends React.Component {
  • Log In
  • + diff --git a/lab-nathan/frontend/src/components/dashboard/dashboard.js b/lab-nathan/frontend/src/components/dashboard/dashboard.js new file mode 100644 index 0000000..e11f438 --- /dev/null +++ b/lab-nathan/frontend/src/components/dashboard/dashboard.js @@ -0,0 +1,11 @@ +import React from 'react'; + +class Dashboard extends React.Component { + render() { + return ( +

    Welcome

    + ); + } +} + +export default Dashboard; \ No newline at end of file From 7b577c2a322753cbc8580f67ae0d85c1319bd355 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Sun, 10 Sep 2017 23:43:25 -0700 Subject: [PATCH 5/9] Add profile creation with image uplaods. --- .../frontend/src/actions/profile-actions.js | 23 +++++++ lab-nathan/frontend/src/components/app/app.js | 48 ++++++++----- .../components/profile-form/profile-form.js | 69 +++++++++++++++++++ .../src/components/settings/settings.js | 49 +++++++++++++ lab-nathan/frontend/src/lib/utilities.js | 42 ++++++++++- lab-nathan/frontend/src/main.js | 12 +++- .../frontend/src/reducers/profile-reducer.js | 22 ++++++ lab-nathan/frontend/src/reducers/reducers.js | 4 +- 8 files changed, 248 insertions(+), 21 deletions(-) create mode 100644 lab-nathan/frontend/src/actions/profile-actions.js create mode 100644 lab-nathan/frontend/src/components/profile-form/profile-form.js create mode 100644 lab-nathan/frontend/src/components/settings/settings.js create mode 100644 lab-nathan/frontend/src/reducers/profile-reducer.js diff --git a/lab-nathan/frontend/src/actions/profile-actions.js b/lab-nathan/frontend/src/actions/profile-actions.js new file mode 100644 index 0000000..62a804f --- /dev/null +++ b/lab-nathan/frontend/src/actions/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 {token} = getState(); + return superagent.post(`${__API_URL__}/profiles`) + .set('Authorization', `Bearer ${token}`) + .field('bio', profile.bio) + .attach('avatar', profile.avatar) + .then(response => { + dispatch(profileCreate(response.body)); + return response; + }) +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/app/app.js b/lab-nathan/frontend/src/components/app/app.js index aac5353..a655b09 100644 --- a/lab-nathan/frontend/src/components/app/app.js +++ b/lab-nathan/frontend/src/components/app/app.js @@ -1,35 +1,47 @@ import React from 'react'; -import {Provider} from 'react-redux'; import {BrowserRouter, Route, Link} from 'react-router-dom'; -import createStore from '../../lib/create-store.js'; import Landing from '../landing/landing.js'; import Dashboard from '../dashboard/dashboard.js'; +import Settings from '../settings/settings.js'; +import * as util from '../../lib/utilities.js'; +import {tokenSet} from '../../actions/token-actions.js'; +import {connect} from 'react-redux'; -let store = createStore(); class App extends React.Component { - constructor(props) { - super(props); + componentDidMount() { + let token = util.readCookie('X-Sluggram-Token'); + if (token) { + this.props.tokenSet(token); + } } render() { return (
    - - -
    -
      -
    • Sign Up
    • -
    • Log In
    • -
    - - -
    -
    -
    + +
    +
      +
    • Sign Up
    • +
    • Log In
    • +
    • Settings
    • +
    + + + +
    +
    ); } } -export default App; +let mapStateToProps = (state) => ({ + profile: state.profile +}); + +let mapDispatchToProps = (dispatch) => ({ + tokenSet: (token) => dispatch(tokenSet(token)) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(App); \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/profile-form/profile-form.js b/lab-nathan/frontend/src/components/profile-form/profile-form.js new file mode 100644 index 0000000..4b4ac6d --- /dev/null +++ b/lab-nathan/frontend/src/components/profile-form/profile-form.js @@ -0,0 +1,69 @@ +import React from 'react'; +import * as util from '../../lib/utilities.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; \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/settings/settings.js b/lab-nathan/frontend/src/components/settings/settings.js new file mode 100644 index 0000000..431f0d0 --- /dev/null +++ b/lab-nathan/frontend/src/components/settings/settings.js @@ -0,0 +1,49 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import ProfileForm from '../profile-form/profile-form.js'; +import {profileCreateRequest} from '../../actions/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(response => { + console.log(response); + }) + .catch(console.error); + } + + handleProfileUpdate() { + // TODO + } + + 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); \ No newline at end of file diff --git a/lab-nathan/frontend/src/lib/utilities.js b/lab-nathan/frontend/src/lib/utilities.js index 81eeb2d..0349f70 100644 --- a/lab-nathan/frontend/src/lib/utilities.js +++ b/lab-nathan/frontend/src/lib/utilities.js @@ -16,4 +16,44 @@ export const filter = (list, ...args) => Array.prototype.filter.apply(list, args); export const reduce = (list, ...args) => -Array.prototype.reduce.apply(list, args); \ No newline at end of file +Array.prototype.reduce.apply(list, args); + +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); + } + + return reject(new Error('USAGE ERROR: requires file')); + }); +} + +// https://stackoverflow.com/questions/14573223/set-cookie-and-get-cookie-with-javascript +export const readCookie = (name) => { + var nameEquals = name + '='; + var attributes = document.cookie.split(';'); + + for (var i = 0; i < attributes.length; i++) { + var attribute = attributes[i]; + + while (attribute.charAt(0) == ' ') { + attribute = attribute.substring(1, attribute.length); + } + + if (attribute.indexOf(nameEquals) == 0) { + return attribute.substring(nameEquals.length, attribute.length); + } + } + + return null; +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/main.js b/lab-nathan/frontend/src/main.js index cf45095..0b18b68 100644 --- a/lab-nathan/frontend/src/main.js +++ b/lab-nathan/frontend/src/main.js @@ -1,5 +1,15 @@ import React from 'react'; import ReactDom from 'react-dom'; +import {Provider} from 'react-redux'; import App from './components/app/app.js'; +import createStore from './lib/create-store.js'; -ReactDom.render(, document.getElementById('root')); \ No newline at end of file +let AppContainer = () => { + return ( + + + + ) +} + +ReactDom.render(, document.getElementById('root')); \ No newline at end of file diff --git a/lab-nathan/frontend/src/reducers/profile-reducer.js b/lab-nathan/frontend/src/reducers/profile-reducer.js new file mode 100644 index 0000000..e0ffa5f --- /dev/null +++ b/lab-nathan/frontend/src/reducers/profile-reducer.js @@ -0,0 +1,22 @@ +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; + } +} diff --git a/lab-nathan/frontend/src/reducers/reducers.js b/lab-nathan/frontend/src/reducers/reducers.js index 50c0298..f4da263 100644 --- a/lab-nathan/frontend/src/reducers/reducers.js +++ b/lab-nathan/frontend/src/reducers/reducers.js @@ -1,6 +1,8 @@ import {combineReducers} from 'redux'; import tokenReducer from './token-reducer.js'; +import profileReducer from './profile-reducer.js'; export default combineReducers({ - token: tokenReducer + token: tokenReducer, + profile: profileReducer }); \ No newline at end of file From 64317fcc90e517f3108224c4d7c8380bb95bd87e Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Mon, 11 Sep 2017 00:02:05 -0700 Subject: [PATCH 6/9] Scaffold styling. --- lab-nathan/frontend/src/style/base/_base.scss | 0 .../frontend/src/style/base/_reset.scss | 48 +++++++++++++++++++ .../frontend/src/style/layout/_content.scss | 0 .../frontend/src/style/layout/_footer.scss | 0 .../frontend/src/style/layout/_header.scss | 0 lab-nathan/frontend/src/style/lib/_vars.scss | 0 lab-nathan/frontend/src/style/main.scss | 0 7 files changed, 48 insertions(+) create mode 100644 lab-nathan/frontend/src/style/base/_base.scss create mode 100644 lab-nathan/frontend/src/style/base/_reset.scss create mode 100644 lab-nathan/frontend/src/style/layout/_content.scss create mode 100644 lab-nathan/frontend/src/style/layout/_footer.scss create mode 100644 lab-nathan/frontend/src/style/layout/_header.scss create mode 100644 lab-nathan/frontend/src/style/lib/_vars.scss create mode 100644 lab-nathan/frontend/src/style/main.scss diff --git a/lab-nathan/frontend/src/style/base/_base.scss b/lab-nathan/frontend/src/style/base/_base.scss new file mode 100644 index 0000000..e69de29 diff --git a/lab-nathan/frontend/src/style/base/_reset.scss b/lab-nathan/frontend/src/style/base/_reset.scss new file mode 100644 index 0000000..af94440 --- /dev/null +++ b/lab-nathan/frontend/src/style/base/_reset.scss @@ -0,0 +1,48 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/style/layout/_content.scss b/lab-nathan/frontend/src/style/layout/_content.scss new file mode 100644 index 0000000..e69de29 diff --git a/lab-nathan/frontend/src/style/layout/_footer.scss b/lab-nathan/frontend/src/style/layout/_footer.scss new file mode 100644 index 0000000..e69de29 diff --git a/lab-nathan/frontend/src/style/layout/_header.scss b/lab-nathan/frontend/src/style/layout/_header.scss new file mode 100644 index 0000000..e69de29 diff --git a/lab-nathan/frontend/src/style/lib/_vars.scss b/lab-nathan/frontend/src/style/lib/_vars.scss new file mode 100644 index 0000000..e69de29 diff --git a/lab-nathan/frontend/src/style/main.scss b/lab-nathan/frontend/src/style/main.scss new file mode 100644 index 0000000..e69de29 From e9370609d7fc7a2c1888814cc5203fb7c1e801b9 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Mon, 11 Sep 2017 23:48:00 -0700 Subject: [PATCH 7/9] Adds styling, photos, etc. --- lab-nathan/frontend/package.json | 3 + .../frontend/src/actions/photo-actions.js | 67 +++++++ .../frontend/src/actions/token-actions.js | 6 + .../frontend/src/assets/camera-icon.svg | 180 ++++++++++++++++++ lab-nathan/frontend/src/assets/noise.png | Bin 0 -> 10101 bytes .../frontend/src/components/app/_app.scss | 5 + lab-nathan/frontend/src/components/app/app.js | 47 ----- .../frontend/src/components/app/index.js | 81 ++++++++ .../src/components/avatar/_avatar.scss | 5 + .../frontend/src/components/avatar/index.js | 8 + .../src/components/content/_content.scss | 5 + .../frontend/src/components/content/index.js | 14 ++ .../src/components/dashboard/dashboard.js | 11 -- .../src/components/dashboard/index.js | 51 +++++ .../src/components/footer/_footer.scss | 7 + .../frontend/src/components/footer/index.js | 14 ++ .../src/components/header/_header.scss | 10 + .../frontend/src/components/header/index.js | 14 ++ .../src/components/landing/_landing.scss | 6 + .../landing/{landing.js => index.js} | 5 +- .../frontend/src/components/login/_login.scss | 28 +++ .../components/login/{login.js => index.js} | 3 +- .../frontend/src/components/logo/_logo.scss | 27 +++ .../frontend/src/components/logo/index.js | 18 ++ .../src/components/menu-item/_menu-item.scss | 14 ++ .../src/components/menu-item/index.js | 12 ++ .../frontend/src/components/menu/_menu.scss | 6 + .../frontend/src/components/menu/index.js | 14 ++ .../src/components/photo-form/index.js | 69 +++++++ .../src/components/photo-item/index.js | 72 +++++++ .../profile-form/_profile-form.scss | 40 ++++ .../{profile-form.js => index.js} | 2 + .../frontend/src/components/profile/index.js | 7 + .../src/components/settings/_settings.scss | 7 + .../settings/{settings.js => index.js} | 8 +- lab-nathan/frontend/src/lib/create-store.js | 2 +- lab-nathan/frontend/src/lib/utilities.js | 14 ++ lab-nathan/frontend/src/main.js | 3 +- .../src/reducers/{reducers.js => index.js} | 4 +- .../frontend/src/reducers/photos-reducer.js | 17 ++ .../frontend/src/reducers/profile-reducer.js | 61 ++++-- .../frontend/src/reducers/token-reducer.js | 1 + lab-nathan/frontend/src/style/base/_base.scss | 0 lab-nathan/frontend/src/style/lib/_vars.scss | 0 lab-nathan/frontend/src/style/main.scss | 0 .../frontend/src/styles/base/_base.scss | 3 + .../src/{style => styles}/base/_reset.scss | 0 .../{style => styles}/layout/_content.scss | 0 .../src/{style => styles}/layout/_footer.scss | 0 .../src/{style => styles}/layout/_header.scss | 0 lab-nathan/frontend/src/styles/lib/_vars.scss | 11 ++ lab-nathan/frontend/src/styles/main.scss | 4 + lab-nathan/frontend/webpack.config.js | 9 +- 53 files changed, 907 insertions(+), 88 deletions(-) create mode 100644 lab-nathan/frontend/src/actions/photo-actions.js create mode 100644 lab-nathan/frontend/src/assets/camera-icon.svg create mode 100644 lab-nathan/frontend/src/assets/noise.png create mode 100644 lab-nathan/frontend/src/components/app/_app.scss delete mode 100644 lab-nathan/frontend/src/components/app/app.js create mode 100644 lab-nathan/frontend/src/components/app/index.js create mode 100644 lab-nathan/frontend/src/components/avatar/_avatar.scss create mode 100644 lab-nathan/frontend/src/components/avatar/index.js create mode 100644 lab-nathan/frontend/src/components/content/_content.scss create mode 100644 lab-nathan/frontend/src/components/content/index.js delete mode 100644 lab-nathan/frontend/src/components/dashboard/dashboard.js create mode 100644 lab-nathan/frontend/src/components/dashboard/index.js create mode 100644 lab-nathan/frontend/src/components/footer/_footer.scss create mode 100644 lab-nathan/frontend/src/components/footer/index.js create mode 100644 lab-nathan/frontend/src/components/header/_header.scss create mode 100644 lab-nathan/frontend/src/components/header/index.js create mode 100644 lab-nathan/frontend/src/components/landing/_landing.scss rename lab-nathan/frontend/src/components/landing/{landing.js => index.js} (88%) create mode 100644 lab-nathan/frontend/src/components/login/_login.scss rename lab-nathan/frontend/src/components/login/{login.js => index.js} (94%) create mode 100644 lab-nathan/frontend/src/components/logo/_logo.scss create mode 100644 lab-nathan/frontend/src/components/logo/index.js create mode 100644 lab-nathan/frontend/src/components/menu-item/_menu-item.scss create mode 100644 lab-nathan/frontend/src/components/menu-item/index.js create mode 100644 lab-nathan/frontend/src/components/menu/_menu.scss create mode 100644 lab-nathan/frontend/src/components/menu/index.js create mode 100644 lab-nathan/frontend/src/components/photo-form/index.js create mode 100644 lab-nathan/frontend/src/components/photo-item/index.js create mode 100644 lab-nathan/frontend/src/components/profile-form/_profile-form.scss rename lab-nathan/frontend/src/components/profile-form/{profile-form.js => index.js} (95%) create mode 100644 lab-nathan/frontend/src/components/profile/index.js create mode 100644 lab-nathan/frontend/src/components/settings/_settings.scss rename lab-nathan/frontend/src/components/settings/{settings.js => index.js} (85%) rename lab-nathan/frontend/src/reducers/{reducers.js => index.js} (66%) create mode 100644 lab-nathan/frontend/src/reducers/photos-reducer.js delete mode 100644 lab-nathan/frontend/src/style/base/_base.scss delete mode 100644 lab-nathan/frontend/src/style/lib/_vars.scss delete mode 100644 lab-nathan/frontend/src/style/main.scss create mode 100644 lab-nathan/frontend/src/styles/base/_base.scss rename lab-nathan/frontend/src/{style => styles}/base/_reset.scss (100%) rename lab-nathan/frontend/src/{style => styles}/layout/_content.scss (100%) rename lab-nathan/frontend/src/{style => styles}/layout/_footer.scss (100%) rename lab-nathan/frontend/src/{style => styles}/layout/_header.scss (100%) create mode 100644 lab-nathan/frontend/src/styles/lib/_vars.scss create mode 100644 lab-nathan/frontend/src/styles/main.scss diff --git a/lab-nathan/frontend/package.json b/lab-nathan/frontend/package.json index 79b4cb9..4e62764 100644 --- a/lab-nathan/frontend/package.json +++ b/lab-nathan/frontend/package.json @@ -25,6 +25,7 @@ "html-webpack-plugin": "^2.30.1", "jest": "^21.0.1", "node-sass": "^4.5.3", + "raw-loader": "^0.5.1", "react": "^15.6.1", "react-dom": "^15.6.1", "react-redux": "^5.0.6", @@ -32,8 +33,10 @@ "react-router-dom": "^4.2.2", "redux": "^3.7.2", "redux-mock-store": "^1.2.3", + "sass-loader": "^6.0.6", "superagent": "^3.6.0", "uglifyjs-webpack-plugin": "^0.4.6", + "url-loader": "^0.5.9", "uuid": "^3.1.0", "webpack": "^3.5.6", "webpack-dev-server": "^2.7.1" diff --git a/lab-nathan/frontend/src/actions/photo-actions.js b/lab-nathan/frontend/src/actions/photo-actions.js new file mode 100644 index 0000000..9f9f824 --- /dev/null +++ b/lab-nathan/frontend/src/actions/photo-actions.js @@ -0,0 +1,67 @@ +import superagent from 'superagent' + +// sync actions +export const userPhotosSet = (photos) => ({ + type: 'USER_PHOTOS_SET', + payload: photos, +}) + +export const userPhotoCreate = (photo) => ({ + type: 'USER_PHOTO_CREATE', + payload: photo, +}) + +export const userPhotoUpdate = (photo) => ({ + type: 'USER_PHOTO_UPDATE', + payload: photo, +}) + +export const userPhotoDelete = (photo) => ({ + type: 'USER_PHOTO_DELETE', + payload: photo, +}) + +// async actions +export const userPhotosFetchRequest = () => (dispatch, getState) => { + let {auth} = getState() + return superagent.get(`${__API_URL__}/photos/me`) + .set('Authorization', `Bearer ${auth}`) + .then(res => { + dispatch(userPhotosSet(res.body.data)) + return res + }) +} + +export const userPhotoCreateRequest = (photo) => (dispatch, getState) => { + let {auth} = getState() + return superagent.post(`${__API_URL__}/photos`) + .set('Authorization', `Bearer ${auth}`) + .field('description', photo.description) + .attach('photo', photo.photo) + .then((res) => { + dispatch(userPhotoCreate(res.body)) + return res + }) +} + +export const userPhotoDeleteRequest = (photo) => (dispatch, getState) => { + let {auth} = getState() + return superagent.delete(`${__API_URL__}/photos/${photo._id}`) + .set('Authorization', `Bearer ${auth}`) + .then(res => { + dispatch(userPhotoDelete(photo)) + return res + }) +} + +export const userPhotoUpdateRequest = (photo) => (dispatch, getState) => { + let {auth} = getState() + return superagent.put(`${__API_URL__}/photos/${photo._id}`) + .set('Authorization', `Bearer ${auth}`) + .field('description', photo.description) + .attach('photo', photo.photo) + .then(res => { + dispatch(userPhotoUpdate(res.body)) + return res + }) +} diff --git a/lab-nathan/frontend/src/actions/token-actions.js b/lab-nathan/frontend/src/actions/token-actions.js index 5fc8298..a81198b 100644 --- a/lab-nathan/frontend/src/actions/token-actions.js +++ b/lab-nathan/frontend/src/actions/token-actions.js @@ -1,4 +1,5 @@ import superagent from 'superagent'; +import * as util from '../lib/utilities.js'; export const tokenSet = (token) => ({ type: 'TOKEN_SET', @@ -9,6 +10,11 @@ export const tokenDelete = (token) => ({ type: 'TOKEN_DELETE' }); +export const logout = () => { + util.deleteCookie('X-Sluggram-Token') + return { type: 'LOGOUT' } +} + export const signupRequest = (user) => (dispatch) => { return superagent.post(`${__API_URL__}/signup`) .withCredentials() diff --git a/lab-nathan/frontend/src/assets/camera-icon.svg b/lab-nathan/frontend/src/assets/camera-icon.svg new file mode 100644 index 0000000..2538ac2 --- /dev/null +++ b/lab-nathan/frontend/src/assets/camera-icon.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + Camera + 2012-10-29T03:23:26 + + https://openclipart.org/detail/172916/camera-by-eternaltyro-172916 + + + eternaltyro + + + + + Camera + photo + + + + + + + + + + + diff --git a/lab-nathan/frontend/src/assets/noise.png b/lab-nathan/frontend/src/assets/noise.png new file mode 100644 index 0000000000000000000000000000000000000000..dd37251116087b00cb981dcb0418ede0c35d83c7 GIT binary patch literal 10101 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 - -
    -
      -
    • Sign Up
    • -
    • Log In
    • -
    • Settings
    • -
    - - - -
    -
    - - ); - } -} - -let mapStateToProps = (state) => ({ - profile: state.profile -}); - -let mapDispatchToProps = (dispatch) => ({ - tokenSet: (token) => dispatch(tokenSet(token)) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(App); \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/app/index.js b/lab-nathan/frontend/src/components/app/index.js new file mode 100644 index 0000000..9867a00 --- /dev/null +++ b/lab-nathan/frontend/src/components/app/index.js @@ -0,0 +1,81 @@ +import './_app.scss'; +import React from 'react'; +import {BrowserRouter, Route, Link} from 'react-router-dom'; +import Logo from '../logo'; +import Landing from '../landing'; +import Dashboard from '../dashboard'; +import Settings from '../settings'; +import Header from '../header'; +import Content from '../content'; +import Menu from '../menu'; +import MenuItem from '../menu-item'; +import Footer from '../footer'; +import Avatar from '../avatar'; +import * as util from '../../lib/utilities.js'; +import {tokenSet, logout} from '../../actions/token-actions.js'; +import {connect} from 'react-redux'; + + +class App extends React.Component { + constructor(props) { + super(props); + } + + componentDidMount() { + let token = util.readCookie('X-Sluggram-Token'); + if (token) { + this.props.tokenSet(token); + } + } + + render() { + return ( + +
    +
    + + {util.renderIf(!this.props.loggedIn, + + Sign Up + Log In + + )} + {util.renderIf(this.props.loggedIn, + + Dashboard + Settings + + )} + {util.renderIf(this.props.profile, + + )} + {util.renderIf(this.props.loggedIn, + + )} +
    + + + + + +
    +
    +
    + ); + } +} + +let mapStateToProps = (state) => ({ + loggedIn: !!state.token, + profile: state.profile +}); + +let mapDispatchToProps = (dispatch) => ({ + tokenSet: (token) => dispatch(tokenSet(token)), + logout: () => dispatch(logout()) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(App); \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/avatar/_avatar.scss b/lab-nathan/frontend/src/components/avatar/_avatar.scss new file mode 100644 index 0000000..b0dd19f --- /dev/null +++ b/lab-nathan/frontend/src/components/avatar/_avatar.scss @@ -0,0 +1,5 @@ +.avatar { + & img { + height: 48px; + } +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/avatar/index.js b/lab-nathan/frontend/src/components/avatar/index.js new file mode 100644 index 0000000..008de3b --- /dev/null +++ b/lab-nathan/frontend/src/components/avatar/index.js @@ -0,0 +1,8 @@ +import './_avatar.scss'; +import React from 'react' + +export default (props) => ( +
    + +
    +) diff --git a/lab-nathan/frontend/src/components/content/_content.scss b/lab-nathan/frontend/src/components/content/_content.scss new file mode 100644 index 0000000..bd118e4 --- /dev/null +++ b/lab-nathan/frontend/src/components/content/_content.scss @@ -0,0 +1,5 @@ +main { + flex: 1; + text-align: center; + background: url('../../assets/noise.png'); +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/content/index.js b/lab-nathan/frontend/src/components/content/index.js new file mode 100644 index 0000000..7150ea2 --- /dev/null +++ b/lab-nathan/frontend/src/components/content/index.js @@ -0,0 +1,14 @@ +import './_content.scss'; +import React from 'react'; + +class Content extends React.Component { + render() { + return ( +
    + {this.props.children} +
    + ); + } +} + +export default Content; \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/dashboard/dashboard.js b/lab-nathan/frontend/src/components/dashboard/dashboard.js deleted file mode 100644 index e11f438..0000000 --- a/lab-nathan/frontend/src/components/dashboard/dashboard.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -class Dashboard extends React.Component { - render() { - return ( -

    Welcome

    - ); - } -} - -export default Dashboard; \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/dashboard/index.js b/lab-nathan/frontend/src/components/dashboard/index.js new file mode 100644 index 0000000..48b82c3 --- /dev/null +++ b/lab-nathan/frontend/src/components/dashboard/index.js @@ -0,0 +1,51 @@ +import React from 'react' +import {connect} from 'react-redux' +import * as util from '../../lib/utilities.js' +import * as photoActions from '../../actions/photo-actions.js' + +import PhotoForm from '../photo-form' +import PhotoItem from '../photo-item' + +class DashboardContainer extends React.Component { + constructor(props){ + super(props) + } + + componentDidMount(){ + this.props.userPhotosFetch() + .catch(util.logError) + } + + render(){ + return ( +
    +

    dashboard

    + { + return this.props.userPhotoCreate(photo) + .catch(console.error) + }} + /> + {this.props.userPhotos.map(photo => + + )} +
    + ) + } +} + +let mapStateToProps = (state) => ({ + userProfile: state.profile, + userPhotos: state.photos, +}) + +let mapDispatchToProps = (dispatch) => ({ + userPhotoCreate: (photo) => dispatch(photoActions.userPhotoCreateRequest(photo)), + userPhotosFetch: (photos) => dispatch(photoActions.userPhotosFetchRequest()), +}) + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(DashboardContainer) diff --git a/lab-nathan/frontend/src/components/footer/_footer.scss b/lab-nathan/frontend/src/components/footer/_footer.scss new file mode 100644 index 0000000..5a7221a --- /dev/null +++ b/lab-nathan/frontend/src/components/footer/_footer.scss @@ -0,0 +1,7 @@ +@import '../../styles/lib/vars'; + +footer { + min-height: 50px; + background: $bookend-background; + box-shadow: $bookend-shadow; +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/footer/index.js b/lab-nathan/frontend/src/components/footer/index.js new file mode 100644 index 0000000..5bd592d --- /dev/null +++ b/lab-nathan/frontend/src/components/footer/index.js @@ -0,0 +1,14 @@ +import './_footer.scss'; +import React from 'react'; + +class Footer extends React.Component { + render() { + return ( +
    + {this.props.children} +
    + ); + } +} + +export default Footer; \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/header/_header.scss b/lab-nathan/frontend/src/components/header/_header.scss new file mode 100644 index 0000000..d890f93 --- /dev/null +++ b/lab-nathan/frontend/src/components/header/_header.scss @@ -0,0 +1,10 @@ +@import '../../styles/lib/vars'; + +header { + min-height: 75px; + background: $bookend-background; + box-shadow: $bookend-shadow; + + display: flex; + align-items: center; +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/header/index.js b/lab-nathan/frontend/src/components/header/index.js new file mode 100644 index 0000000..1568780 --- /dev/null +++ b/lab-nathan/frontend/src/components/header/index.js @@ -0,0 +1,14 @@ +import './_header.scss'; +import React from 'react'; + +class Header extends React.Component { + render() { + return ( +
    + {this.props.children} +
    + ); + } +} + +export default Header; \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/landing/_landing.scss b/lab-nathan/frontend/src/components/landing/_landing.scss new file mode 100644 index 0000000..3dbe16b --- /dev/null +++ b/lab-nathan/frontend/src/components/landing/_landing.scss @@ -0,0 +1,6 @@ +@import '../../styles/lib/vars'; + +.landing { + height: 100%; + display: flex; +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/landing/landing.js b/lab-nathan/frontend/src/components/landing/index.js similarity index 88% rename from lab-nathan/frontend/src/components/landing/landing.js rename to lab-nathan/frontend/src/components/landing/index.js index 092cec0..cba8eb5 100644 --- a/lab-nathan/frontend/src/components/landing/landing.js +++ b/lab-nathan/frontend/src/components/landing/index.js @@ -1,7 +1,8 @@ +import './_landing.scss'; import React from 'react'; import {connect} from 'react-redux'; import {signupRequest, loginRequest} from '../../actions/token-actions.js'; -import Login from '../login/login.js'; +import Login from '../login'; class Landing extends React.Component { render() { @@ -11,7 +12,7 @@ class Landing extends React.Component { : this.props.signup; return ( -
    +
    diff --git a/lab-nathan/frontend/src/components/login/_login.scss b/lab-nathan/frontend/src/components/login/_login.scss new file mode 100644 index 0000000..5b74a69 --- /dev/null +++ b/lab-nathan/frontend/src/components/login/_login.scss @@ -0,0 +1,28 @@ +@import '../../styles/lib/vars'; + +.user-form { + background: url('../../assets/noise.png') #dadada; + padding: $gutter * 1.5 $gutter; + margin: auto; + border-radius: 5px; + box-shadow: 3px 3px 10px -4px rgba(0, 0, 0, 0.75); + + & input { + display: block; + margin: $gutter / 3 auto; + padding: $input-padding; + font-size: $input-font-size; + border-radius: $input-border-radius; + border: $input-border; + } + + & button { + margin-top: $gutter / 2; + padding: $input-padding * 3 / 4 $input-padding * 2; + font-size: 1rem; + border-radius: 3px; + border: none; + background: url('../../assets/noise.png') #666; + color: white; + } +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/login/login.js b/lab-nathan/frontend/src/components/login/index.js similarity index 94% rename from lab-nathan/frontend/src/components/login/login.js rename to lab-nathan/frontend/src/components/login/index.js index c34f288..1e9305c 100644 --- a/lab-nathan/frontend/src/components/login/login.js +++ b/lab-nathan/frontend/src/components/login/index.js @@ -1,3 +1,4 @@ +import './_login.scss'; import React from 'react'; import * as utilities from '../../lib/utilities.js'; @@ -79,7 +80,7 @@ class Login extends React.Component { value={this.state.password} onChange={this.handleChange} /> - + ); diff --git a/lab-nathan/frontend/src/components/logo/_logo.scss b/lab-nathan/frontend/src/components/logo/_logo.scss new file mode 100644 index 0000000..030910f --- /dev/null +++ b/lab-nathan/frontend/src/components/logo/_logo.scss @@ -0,0 +1,27 @@ +@import '../../styles/lib/vars'; + +#logo { + margin: 0 $gutter; + flex: 1; +} + +#logo-text { + font-size: $logo-size; + font-weight: bold; + font-family: 'Playfair Display', serif; + color: $primary-dark; + display: inline-block; + vertical-align: middle; +} + +#logo-image { + height: 48px; + width: 48px; + display: inline-block; + vertical-align: middle; + margin-right: $gutter / 4; +} + +#path3869, #path3853 { + fill: $primary-dark; +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/logo/index.js b/lab-nathan/frontend/src/components/logo/index.js new file mode 100644 index 0000000..ec5bbae --- /dev/null +++ b/lab-nathan/frontend/src/components/logo/index.js @@ -0,0 +1,18 @@ +import './_logo.scss'; +import React from 'react'; + +class Logo extends React.Component { + render() { + let data = require(`../../assets/camera-icon.svg`) + let innerHtml = {__html: data} + + return ( + + ); + } +} + +export default Logo; \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/menu-item/_menu-item.scss b/lab-nathan/frontend/src/components/menu-item/_menu-item.scss new file mode 100644 index 0000000..f82af54 --- /dev/null +++ b/lab-nathan/frontend/src/components/menu-item/_menu-item.scss @@ -0,0 +1,14 @@ +@import '../../styles/lib/vars'; + +.menu-item { + display: inline; + margin-left: $gutter / 2; + font-size: 1.125rem; + color: $primary-dark; + font-family: serif; + + & a { + color: inherit; + text-decoration: none; + } +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/menu-item/index.js b/lab-nathan/frontend/src/components/menu-item/index.js new file mode 100644 index 0000000..c90460a --- /dev/null +++ b/lab-nathan/frontend/src/components/menu-item/index.js @@ -0,0 +1,12 @@ +import './_menu-item.scss'; +import React from 'react'; + +class MenuItem extends React.Component { + render() { + return ( +
  • {this.props.children}
  • + ); + } +} + +export default MenuItem; \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/menu/_menu.scss b/lab-nathan/frontend/src/components/menu/_menu.scss new file mode 100644 index 0000000..51c2273 --- /dev/null +++ b/lab-nathan/frontend/src/components/menu/_menu.scss @@ -0,0 +1,6 @@ +@import '../../styles/lib/vars'; + +.menu { + margin-right: $gutter; + font-family: $font-family; +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/menu/index.js b/lab-nathan/frontend/src/components/menu/index.js new file mode 100644 index 0000000..d13ed2b --- /dev/null +++ b/lab-nathan/frontend/src/components/menu/index.js @@ -0,0 +1,14 @@ +import './_menu.scss'; +import React from 'react'; + +class Menu extends React.Component { + render() { + return ( +
      + {this.props.children} +
    + ); + } +} + +export default Menu; \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/photo-form/index.js b/lab-nathan/frontend/src/components/photo-form/index.js new file mode 100644 index 0000000..bc3e5c4 --- /dev/null +++ b/lab-nathan/frontend/src/components/photo-form/index.js @@ -0,0 +1,69 @@ +import React from 'react' +import * as util from '../../lib/utilities.js' + +class PhotoForm extends React.Component { + constructor(props){ + super(props) + this.state = props.photo + ? props.photo + : {description: '' , preview: '', photo: null} + + this.handleChange = this.handleChange.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) + } + + handleChange(e){ + console.log('event', e.target) + let {name} = e.target + if(name == 'description'){ + this.setState({description: e.target.value}) + } + + if(name == 'photo'){ + let {files} = e.target + let photo = files[0] + this.setState({photo}) + util.photoToDataURL(photo) + .then(preview => this.setState({preview})) + .catch(console.error) + } + } + + handleSubmit(e){ + e.preventDefault() + return this.props.onComplete(this.state) + .then(() => { + if(!this.props.profile){ + this.setState({description: '' , preview: '', photo: null}) + } + }) + } + + render(){ + return ( +
    + + {util.renderIf(this.state.preview, + )} + + + + + +
    + ) + } +} + +export default PhotoForm diff --git a/lab-nathan/frontend/src/components/photo-item/index.js b/lab-nathan/frontend/src/components/photo-item/index.js new file mode 100644 index 0000000..2a94656 --- /dev/null +++ b/lab-nathan/frontend/src/components/photo-item/index.js @@ -0,0 +1,72 @@ +import React from 'react' +import {connect} from 'react-redux' + +import PhotoForm from '../photo-form' +import * as util from '../../lib/utilities.js' +import * as photoActions from '../../actions/photo-actions.js' + +export class PhotoItem extends React.Component { + constructor(props){ + super(props) + + this.state = { + editing: false + } + + this.handleDelete = this.handleDelete.bind(this) + this.handleUpdate = this.handleUpdate.bind(this) + } + + handleDelete(){ + return this.props.deletePhoto(this.props.photo) + .then(console.log) + .catch(console.error) + } + + handleUpdate(photo){ + return this.props.updatePhoto(photo) + .then(() => { + this.setState({editing: false}) + }) + .catch(console.error) + } + + + render(){ + let {photo} = this.props + return ( +
    + {util.renderIf(!this.state.editing, +
    + +

    {photo.description}

    + + this.setState({editing: true})} className='fa fa-pencil fa-3x' /> +
    + )} + + {util.renderIf(this.state.editing, +
    + +
    + )} +
    + ) + } +} + +let mapStateToProps = () => ({}) +let mapDispatchToProps = (dispatch) => ({ + deletePhoto: (photo) => dispatch(photoActions.userPhotoDeleteRequest(photo)), + updatePhoto: (photo) => dispatch(photoActions.userPhotoUpdateRequest(photo)), +}) + + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(PhotoItem) diff --git a/lab-nathan/frontend/src/components/profile-form/_profile-form.scss b/lab-nathan/frontend/src/components/profile-form/_profile-form.scss new file mode 100644 index 0000000..107c638 --- /dev/null +++ b/lab-nathan/frontend/src/components/profile-form/_profile-form.scss @@ -0,0 +1,40 @@ +@import '../../styles/lib/vars'; + +.profile-form { + background: url('../../assets/noise.png') #dadada; + padding: $gutter; + margin: auto; + border-radius: 5px; + box-shadow: 3px 3px 10px -4px rgba(0, 0, 0, 0.75); + + & img { + max-height: 300px; + } + + & input { + display: block; + padding: $input-padding; + font-size: $input-font-size; + border-radius: $input-border-radius; + } + + & button { + margin-top: $gutter / 2; + padding: $input-padding * 3 / 4 $input-padding * 2; + font-size: 1rem; + border-radius: 3px; + border: none; + background: url('../../assets/noise.png') #666; + color: white; + } + + & textarea { + display: block; + width: 100%; + min-height: 100px; + font-size: inherit; + font-family: helvetica; + padding: 10px; + box-sizing: border-box; + } +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/profile-form/profile-form.js b/lab-nathan/frontend/src/components/profile-form/index.js similarity index 95% rename from lab-nathan/frontend/src/components/profile-form/profile-form.js rename to lab-nathan/frontend/src/components/profile-form/index.js index 4b4ac6d..d9dd444 100644 --- a/lab-nathan/frontend/src/components/profile-form/profile-form.js +++ b/lab-nathan/frontend/src/components/profile-form/index.js @@ -1,3 +1,4 @@ +import './_profile-form.scss'; import React from 'react'; import * as util from '../../lib/utilities.js'; @@ -56,6 +57,7 @@ class ProfileForm extends React.Component { diff --git a/lab-nathan/frontend/src/components/profile/index.js b/lab-nathan/frontend/src/components/profile/index.js new file mode 100644 index 0000000..cc93ed6 --- /dev/null +++ b/lab-nathan/frontend/src/components/profile/index.js @@ -0,0 +1,7 @@ +import React from 'react' + +export default (props) => ( +
    + +
    +) \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/settings/_settings.scss b/lab-nathan/frontend/src/components/settings/_settings.scss new file mode 100644 index 0000000..07e3b58 --- /dev/null +++ b/lab-nathan/frontend/src/components/settings/_settings.scss @@ -0,0 +1,7 @@ +@import '../../styles/lib/vars'; + +.settings-container { + height: 100%; + display: flex; + flex-direction: column; +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/settings/settings.js b/lab-nathan/frontend/src/components/settings/index.js similarity index 85% rename from lab-nathan/frontend/src/components/settings/settings.js rename to lab-nathan/frontend/src/components/settings/index.js index 431f0d0..d0e7db9 100644 --- a/lab-nathan/frontend/src/components/settings/settings.js +++ b/lab-nathan/frontend/src/components/settings/index.js @@ -1,6 +1,7 @@ +import './_settings.scss'; import React from 'react'; import {connect} from 'react-redux'; -import ProfileForm from '../profile-form/profile-form.js'; +import ProfileForm from '../profile-form'; import {profileCreateRequest} from '../../actions/profile-actions.js'; class SettingsContainer extends React.Component { @@ -29,10 +30,9 @@ class SettingsContainer extends React.Component { return (
    -

    Profile Settings

    + buttonText='create profile' + onComplete={this.handleProfileCreate} />
    ); } diff --git a/lab-nathan/frontend/src/lib/create-store.js b/lab-nathan/frontend/src/lib/create-store.js index be5ac95..b52ae88 100644 --- a/lab-nathan/frontend/src/lib/create-store.js +++ b/lab-nathan/frontend/src/lib/create-store.js @@ -1,5 +1,5 @@ import {createStore, applyMiddleware} from 'redux'; -import reducers from '../reducers/reducers.js'; +import reducers from '../reducers'; import thunk from './thunk.js'; import reporter from './reporter.js'; diff --git a/lab-nathan/frontend/src/lib/utilities.js b/lab-nathan/frontend/src/lib/utilities.js index 0349f70..025acc9 100644 --- a/lab-nathan/frontend/src/lib/utilities.js +++ b/lab-nathan/frontend/src/lib/utilities.js @@ -56,4 +56,18 @@ export const readCookie = (name) => { } return null; +} + +export const createCookie = (name,value,days) => { + if (days) { + var date = new Date(); + date.setTime(date.getTime()+(days*24*60*60*1000)); + var expires = "; expires="+date.toGMTString(); + } + else var expires = ""; + document.cookie = name+"="+value+expires+"; path=/"; +} + +export const deleteCookie = (name) => { + createCookie(name,"",-1); } \ No newline at end of file diff --git a/lab-nathan/frontend/src/main.js b/lab-nathan/frontend/src/main.js index 0b18b68..811dacf 100644 --- a/lab-nathan/frontend/src/main.js +++ b/lab-nathan/frontend/src/main.js @@ -1,7 +1,8 @@ +import './styles/main.scss'; import React from 'react'; import ReactDom from 'react-dom'; import {Provider} from 'react-redux'; -import App from './components/app/app.js'; +import App from './components/app'; import createStore from './lib/create-store.js'; let AppContainer = () => { diff --git a/lab-nathan/frontend/src/reducers/reducers.js b/lab-nathan/frontend/src/reducers/index.js similarity index 66% rename from lab-nathan/frontend/src/reducers/reducers.js rename to lab-nathan/frontend/src/reducers/index.js index f4da263..6d09997 100644 --- a/lab-nathan/frontend/src/reducers/reducers.js +++ b/lab-nathan/frontend/src/reducers/index.js @@ -1,8 +1,10 @@ import {combineReducers} from 'redux'; import tokenReducer from './token-reducer.js'; import profileReducer from './profile-reducer.js'; +import photosReducer from './photos-reducer.js'; export default combineReducers({ token: tokenReducer, - profile: profileReducer + profile: profileReducer, + photos: photosReducer }); \ No newline at end of file diff --git a/lab-nathan/frontend/src/reducers/photos-reducer.js b/lab-nathan/frontend/src/reducers/photos-reducer.js new file mode 100644 index 0000000..8872dd5 --- /dev/null +++ b/lab-nathan/frontend/src/reducers/photos-reducer.js @@ -0,0 +1,17 @@ +export default (state=[], action) => { + let {type, payload} = action + switch(type){ + case 'USER_PHOTOS_SET': + return payload + case 'USER_PHOTO_CREATE': + return [payload, ...state] + case 'USER_PHOTO_UPDATE': + return state.map(item => item._id === payload._id ? payload : item) + case 'USER_PHOTO_DELETE': + return state.filter(item => item._id !== payload._id) + case 'LOGOUT': + return [] + default: + return state + } +} diff --git a/lab-nathan/frontend/src/reducers/profile-reducer.js b/lab-nathan/frontend/src/reducers/profile-reducer.js index e0ffa5f..fe2c79e 100644 --- a/lab-nathan/frontend/src/reducers/profile-reducer.js +++ b/lab-nathan/frontend/src/reducers/profile-reducer.js @@ -1,22 +1,47 @@ -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'); - } +import superagent from 'superagent' + +// sync action creators +export const userProfileCreate = (profile) => ({ + type: 'USER_PROFILE_CREATE', + payload: profile, +}) + +export const userProfileUpdate = (profile) => ({ + type: 'USER_PROFILE_UPDATE', + payload: profile, +}) + +// async action creators +export const userProfileCreateRequest = (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(userProfileCreate(res.body)) + return res + }) } -export default (state = null, action) => { - let {type, payload} = action; +export const userProfileUpdateRequest = (profile) => (dispatch, getState) => { + let {auth} = getState() + return superagent.put(`${__API_URL__}/profiles/${profile._id}`) + .set('Authorization', `Bearer ${auth}`) + .field('bio', profile.bio) + .attach('avatar', profile.avatar) + .then(res => { + dispatch(userProfileCreate(res.body)) + return res + }) +} - switch(type) { - case 'PROFILE_CREATE': { - validateProfileCreate(payload); - return payload; - } - case 'PROFILE_UPDATE': - return {...state, ...payload}; - case 'LOGOUT': - return null; - default: - return state; - } +export const userProfileFetchRequest = () => (dispatch, getState) => { + let {auth} = getState() + return superagent.get(`${__API_URL__}/profiles/me`) + .set('Authorization', `Bearer ${auth}`) + .then(res => { + dispatch(userProfileCreate(res.body)) + return res + }) } diff --git a/lab-nathan/frontend/src/reducers/token-reducer.js b/lab-nathan/frontend/src/reducers/token-reducer.js index 796a6a2..7e41a61 100644 --- a/lab-nathan/frontend/src/reducers/token-reducer.js +++ b/lab-nathan/frontend/src/reducers/token-reducer.js @@ -4,6 +4,7 @@ export default (state = null, action) => { case 'TOKEN_SET': return payload case 'TOKEN_DELETE': + case 'LOGOUT': return null default: return state diff --git a/lab-nathan/frontend/src/style/base/_base.scss b/lab-nathan/frontend/src/style/base/_base.scss deleted file mode 100644 index e69de29..0000000 diff --git a/lab-nathan/frontend/src/style/lib/_vars.scss b/lab-nathan/frontend/src/style/lib/_vars.scss deleted file mode 100644 index e69de29..0000000 diff --git a/lab-nathan/frontend/src/style/main.scss b/lab-nathan/frontend/src/style/main.scss deleted file mode 100644 index e69de29..0000000 diff --git a/lab-nathan/frontend/src/styles/base/_base.scss b/lab-nathan/frontend/src/styles/base/_base.scss new file mode 100644 index 0000000..47a749b --- /dev/null +++ b/lab-nathan/frontend/src/styles/base/_base.scss @@ -0,0 +1,3 @@ +html, body, #root { + height: 100%; +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/style/base/_reset.scss b/lab-nathan/frontend/src/styles/base/_reset.scss similarity index 100% rename from lab-nathan/frontend/src/style/base/_reset.scss rename to lab-nathan/frontend/src/styles/base/_reset.scss diff --git a/lab-nathan/frontend/src/style/layout/_content.scss b/lab-nathan/frontend/src/styles/layout/_content.scss similarity index 100% rename from lab-nathan/frontend/src/style/layout/_content.scss rename to lab-nathan/frontend/src/styles/layout/_content.scss diff --git a/lab-nathan/frontend/src/style/layout/_footer.scss b/lab-nathan/frontend/src/styles/layout/_footer.scss similarity index 100% rename from lab-nathan/frontend/src/style/layout/_footer.scss rename to lab-nathan/frontend/src/styles/layout/_footer.scss diff --git a/lab-nathan/frontend/src/style/layout/_header.scss b/lab-nathan/frontend/src/styles/layout/_header.scss similarity index 100% rename from lab-nathan/frontend/src/style/layout/_header.scss rename to lab-nathan/frontend/src/styles/layout/_header.scss diff --git a/lab-nathan/frontend/src/styles/lib/_vars.scss b/lab-nathan/frontend/src/styles/lib/_vars.scss new file mode 100644 index 0000000..2fe7b91 --- /dev/null +++ b/lab-nathan/frontend/src/styles/lib/_vars.scss @@ -0,0 +1,11 @@ +$primary: hsla(140, 52%, 55%, 1); +$primary-dark: hsla(140, 52%, 25%, 1);; +$bookend-background: url('../../assets/noise.png') hsl(140, 52%, 55%); +$bookend-shadow: 0px 0px 5px 2px rgba(0,0,0,0.5); +$logo-size: 2.25rem; +$gutter: 30px; +$font-family: 'helvetica', sans-serif; +$input-padding: 10px; +$input-font-size: 1.125rem; +$input-border-radius: 3px; +$input-border: 1px solid #ddd; \ No newline at end of file diff --git a/lab-nathan/frontend/src/styles/main.scss b/lab-nathan/frontend/src/styles/main.scss new file mode 100644 index 0000000..bdfb943 --- /dev/null +++ b/lab-nathan/frontend/src/styles/main.scss @@ -0,0 +1,4 @@ +@import url('https://fonts.googleapis.com/css?family=Playfair+Display'); +@import "./lib/vars"; +@import "./base/reset"; +@import "./base/base"; \ No newline at end of file diff --git a/lab-nathan/frontend/webpack.config.js b/lab-nathan/frontend/webpack.config.js index bee537e..f5e302f 100644 --- a/lab-nathan/frontend/webpack.config.js +++ b/lab-nathan/frontend/webpack.config.js @@ -49,8 +49,13 @@ module.exports = { test: /\.scss$/, loader: ExtractPlugin.extract(['css-loader', 'sass-loader']) }, + { + test: /\.(svg)$/, + loader: 'raw-loader', + }, { test: /\.(woff|woff2|ttf|eot|glyph|\.svg)$/, + exclude: /\camera-icon.svg$/, use: [ { loader: 'url-loader', @@ -63,7 +68,7 @@ module.exports = { }, { test: /\.(jpg|jpeg|gif|png|tiff|svg)$/, - exclude: /\.glyph.svg/, + exclude: /\glyph.svg|camera-icon.svg$/, use: [ { loader: 'url-loader', @@ -76,7 +81,7 @@ module.exports = { }, { test: /\.(mp3|aac|aiff|wav|flac|m4a|mp4|ogg|ape)$/, - exclude: /\.glyph.svg/, + exclude: /\glyph.svg|camera-icon.svg$/, use: [ { loader: 'file-loader', From 2df6eb7935b41cc0285c26cd16f8e52ab8961025 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Tue, 12 Sep 2017 15:55:14 -0700 Subject: [PATCH 8/9] Fixes profile image. --- .../frontend/src/actions/profile-actions.js | 45 ++++++++--- .../frontend/src/components/app/index.js | 59 +-------------- .../src/components/avatar/_avatar.scss | 3 + .../src/components/dashboard/index.js | 14 ++-- .../src/components/header/_header.scss | 4 + .../frontend/src/components/header/index.js | 75 ++++++++++++++++++- .../frontend/src/components/settings/index.js | 4 +- .../frontend/src/reducers/profile-reducer.js | 65 ++++++---------- 8 files changed, 149 insertions(+), 120 deletions(-) diff --git a/lab-nathan/frontend/src/actions/profile-actions.js b/lab-nathan/frontend/src/actions/profile-actions.js index 62a804f..06409cf 100644 --- a/lab-nathan/frontend/src/actions/profile-actions.js +++ b/lab-nathan/frontend/src/actions/profile-actions.js @@ -1,23 +1,48 @@ -import superagent from 'superagent'; +import superagent from 'superagent' +// sync action creators export const profileCreate = (profile) => ({ type: 'PROFILE_CREATE', - payload: profile -}); + payload: profile, +}) export const profileUpdate = (profile) => ({ type: 'PROFILE_UPDATE', - payload: profile -}); + payload: profile, +}) +// async action creators export const profileCreateRequest = (profile) => (dispatch, getState) => { - let {token} = getState(); + let {token} = getState() return superagent.post(`${__API_URL__}/profiles`) .set('Authorization', `Bearer ${token}`) .field('bio', profile.bio) .attach('avatar', profile.avatar) - .then(response => { - dispatch(profileCreate(response.body)); - return response; + .then(res => { + dispatch(profileCreate(res.body)) + return res }) -} \ No newline at end of file +} + +export const profileUpdateRequest = (profile) => (dispatch, getState) => { + let {token} = getState() + return superagent.put(`${__API_URL__}/profiles/${profile._id}`) + .set('Authorization', `Bearer ${token}`) + .field('bio', profile.bio) + .attach('avatar', profile.avatar) + .then(res => { + dispatch(profileCreate(res.body)) + return res + }) +} + +export const profileFetchRequest = () => (dispatch, getState) => { + let {token} = getState() + return superagent.get(`${__API_URL__}/profiles/me`) + .set('Authorization', `Bearer ${token}`) + .then(res => { + console.log(res); + dispatch(profileCreate(res.body)) + return res + }) +} diff --git a/lab-nathan/frontend/src/components/app/index.js b/lab-nathan/frontend/src/components/app/index.js index 9867a00..98833c4 100644 --- a/lab-nathan/frontend/src/components/app/index.js +++ b/lab-nathan/frontend/src/components/app/index.js @@ -1,61 +1,20 @@ import './_app.scss'; import React from 'react'; -import {BrowserRouter, Route, Link} from 'react-router-dom'; -import Logo from '../logo'; +import {BrowserRouter, Route} from 'react-router-dom'; import Landing from '../landing'; import Dashboard from '../dashboard'; import Settings from '../settings'; import Header from '../header'; -import Content from '../content'; -import Menu from '../menu'; -import MenuItem from '../menu-item'; import Footer from '../footer'; -import Avatar from '../avatar'; -import * as util from '../../lib/utilities.js'; -import {tokenSet, logout} from '../../actions/token-actions.js'; -import {connect} from 'react-redux'; +import Content from '../content'; class App extends React.Component { - constructor(props) { - super(props); - } - - componentDidMount() { - let token = util.readCookie('X-Sluggram-Token'); - if (token) { - this.props.tokenSet(token); - } - } - render() { return (
    -
    - - {util.renderIf(!this.props.loggedIn, - - Sign Up - Log In - - )} - {util.renderIf(this.props.loggedIn, - - Dashboard - Settings - - )} - {util.renderIf(this.props.profile, - - )} - {util.renderIf(this.props.loggedIn, - - )} -
    + @@ -68,14 +27,4 @@ class App extends React.Component { } } -let mapStateToProps = (state) => ({ - loggedIn: !!state.token, - profile: state.profile -}); - -let mapDispatchToProps = (dispatch) => ({ - tokenSet: (token) => dispatch(tokenSet(token)), - logout: () => dispatch(logout()) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(App); \ No newline at end of file +export default App; \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/avatar/_avatar.scss b/lab-nathan/frontend/src/components/avatar/_avatar.scss index b0dd19f..ecc8449 100644 --- a/lab-nathan/frontend/src/components/avatar/_avatar.scss +++ b/lab-nathan/frontend/src/components/avatar/_avatar.scss @@ -1,5 +1,8 @@ +@import '../../styles/lib/vars'; + .avatar { & img { height: 48px; + margin-right: $gutter / 2; } } \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/dashboard/index.js b/lab-nathan/frontend/src/components/dashboard/index.js index 48b82c3..203d9d0 100644 --- a/lab-nathan/frontend/src/components/dashboard/index.js +++ b/lab-nathan/frontend/src/components/dashboard/index.js @@ -12,7 +12,7 @@ class DashboardContainer extends React.Component { } componentDidMount(){ - this.props.userPhotosFetch() + this.props.photosFetch() .catch(util.logError) } @@ -23,11 +23,11 @@ class DashboardContainer extends React.Component { { - return this.props.userPhotoCreate(photo) + return this.props.photoCreate(photo) .catch(console.error) }} /> - {this.props.userPhotos.map(photo => + {this.props.photos.map(photo => )}
    @@ -36,13 +36,13 @@ class DashboardContainer extends React.Component { } let mapStateToProps = (state) => ({ - userProfile: state.profile, - userPhotos: state.photos, + profile: state.profile, + photos: state.photos, }) let mapDispatchToProps = (dispatch) => ({ - userPhotoCreate: (photo) => dispatch(photoActions.userPhotoCreateRequest(photo)), - userPhotosFetch: (photos) => dispatch(photoActions.userPhotosFetchRequest()), + photoCreate: (photo) => dispatch(photoActions.userPhotoCreateRequest(photo)), + photosFetch: (photos) => dispatch(photoActions.userPhotosFetchRequest()), }) export default connect( diff --git a/lab-nathan/frontend/src/components/header/_header.scss b/lab-nathan/frontend/src/components/header/_header.scss index d890f93..8d52b4f 100644 --- a/lab-nathan/frontend/src/components/header/_header.scss +++ b/lab-nathan/frontend/src/components/header/_header.scss @@ -7,4 +7,8 @@ header { display: flex; align-items: center; +} + +#logout-button { + margin-right: $gutter / 2; } \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/header/index.js b/lab-nathan/frontend/src/components/header/index.js index 1568780..a6cdc2e 100644 --- a/lab-nathan/frontend/src/components/header/index.js +++ b/lab-nathan/frontend/src/components/header/index.js @@ -1,14 +1,85 @@ import './_header.scss'; import React from 'react'; +import {Link} from 'react-router-dom'; +import Logo from '../logo'; +import Menu from '../menu'; +import MenuItem from '../menu-item'; +import Avatar from '../avatar'; +import * as util from '../../lib/utilities.js'; +import {tokenSet, logout} from '../../actions/token-actions.js'; +import {profileFetchRequest} from '../../actions/profile-actions.js'; +import {connect} from 'react-redux'; class Header extends React.Component { + constructor(props){ + super(props) + this.validateRoute = this.validateRoute.bind(this) + this.handleLogout = this.handleLogout.bind(this) + } + + componentDidMount(){ + this.validateRoute(this.props) + } + + validateRoute(props){ + let {match, history} = props + let token = util.readCookie('X-Sluggram-Token') + + if(!token){ + return history.replace('/welcome/signup') + } + + this.props.tokenSet(token) + this.props.profileFetch() + .catch(() => { + console.log('PROFILE FETCH ERROR: user does not have a userProfile') + if(!match.url.startsWith('/settings')){ + return history.replace('/settings') + } + }) + } + + handleLogout(){ + this.props.logout() + this.props.history.push('/welcome/login') + } + render() { return (
    - {this.props.children} + + {util.renderIf(!this.props.loggedIn, + + Sign Up + Log In + + )} + {util.renderIf(this.props.loggedIn, + + Dashboard + Settings + + )} + {util.renderIf(this.props.profile, + + )} + {util.renderIf(this.props.loggedIn, + + )}
    ); } } -export default Header; \ No newline at end of file +let mapStateToProps = (state) => ({ + loggedIn: !!state.token, + profile: state.profile +}); + +let mapDispatchToProps = (dispatch) => ({ + tokenSet: (token) => dispatch(tokenSet(token)), + logout: () => dispatch(logout()), + profileFetch: () => dispatch(profileFetchRequest()) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Header); \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/settings/index.js b/lab-nathan/frontend/src/components/settings/index.js index d0e7db9..5f5514b 100644 --- a/lab-nathan/frontend/src/components/settings/index.js +++ b/lab-nathan/frontend/src/components/settings/index.js @@ -31,8 +31,8 @@ class SettingsContainer extends React.Component { return (
    + buttonText='create profile' + onComplete={this.handleProfileCreate} />
    ); } diff --git a/lab-nathan/frontend/src/reducers/profile-reducer.js b/lab-nathan/frontend/src/reducers/profile-reducer.js index fe2c79e..96cea91 100644 --- a/lab-nathan/frontend/src/reducers/profile-reducer.js +++ b/lab-nathan/frontend/src/reducers/profile-reducer.js @@ -1,47 +1,24 @@ -import superagent from 'superagent' - -// sync action creators -export const userProfileCreate = (profile) => ({ - type: 'USER_PROFILE_CREATE', - payload: profile, -}) - -export const userProfileUpdate = (profile) => ({ - type: 'USER_PROFILE_UPDATE', - payload: profile, -}) - -// async action creators -export const userProfileCreateRequest = (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(userProfileCreate(res.body)) - return res - }) -} - -export const userProfileUpdateRequest = (profile) => (dispatch, getState) => { - let {auth} = getState() - return superagent.put(`${__API_URL__}/profiles/${profile._id}`) - .set('Authorization', `Bearer ${auth}`) - .field('bio', profile.bio) - .attach('avatar', profile.avatar) - .then(res => { - dispatch(userProfileCreate(res.body)) - return res - }) +let validateProfileCreate = (profile) => { + if(!profile.avatar || !profile.bio || !profile._id + || !profile.owner || !profile.username || !profile.email){ + throw new Error('VALIDATION ERROR: profile requires avatar and bio') + } } -export const userProfileFetchRequest = () => (dispatch, getState) => { - let {auth} = getState() - return superagent.get(`${__API_URL__}/profiles/me`) - .set('Authorization', `Bearer ${auth}`) - .then(res => { - dispatch(userProfileCreate(res.body)) - return res - }) +export default (state=null, action) => { + let {type, payload} = action + switch(type){ + case 'PROFILE_CREATE': + validateProfileCreate(payload) + return payload + case 'PROFILE_UPDATE': + if(!state) + throw new Error('USAGE ERROR: can not update when profile is null') + validateProfileCreate(payload) + return {...state, ...payload} + case 'LOGOUT': + return null + default: + return state + } } From 0ac175923c36220339f64385b58878d079d57630 Mon Sep 17 00:00:00 2001 From: Nathan Harrenstein Date: Tue, 12 Sep 2017 23:37:09 -0700 Subject: [PATCH 9/9] Add google oauth and fix bugs. --- .../frontend/src/actions/photo-actions.js | 16 +++--- .../assets/btn_google_dark_disabled_ios.svg | 24 +++++++++ .../src/assets/btn_google_dark_focus_ios.svg | 51 +++++++++++++++++++ .../src/assets/btn_google_dark_normal_ios.svg | 50 ++++++++++++++++++ .../assets/btn_google_dark_pressed_ios.svg | 50 ++++++++++++++++++ .../assets/btn_google_light_disabled_ios.svg | 24 +++++++++ .../src/assets/btn_google_light_focus_ios.svg | 44 ++++++++++++++++ .../assets/btn_google_light_normal_ios.svg | 43 ++++++++++++++++ .../assets/btn_google_light_pressed_ios.svg | 43 ++++++++++++++++ .../src/components/dashboard/_dashboard.scss | 5 ++ .../src/components/dashboard/index.js | 8 +-- .../google-oauth/_google-oauth.scss | 31 +++++++++++ .../src/components/google-oauth/index.js | 27 ++++++++++ .../frontend/src/components/header/index.js | 10 ++-- .../frontend/src/components/login/_login.scss | 40 ++++++++------- .../frontend/src/components/login/index.js | 4 ++ .../src/components/menu-item/_menu-item.scss | 2 +- .../components/photo-form/_photo-form.scss | 41 +++++++++++++++ .../src/components/photo-form/index.js | 7 +-- .../profile-form/_profile-form.scss | 3 +- .../src/components/profile-form/index.js | 1 + .../frontend/src/components/settings/index.js | 23 ++++++--- lab-nathan/frontend/src/styles/main.scss | 1 + lab-nathan/frontend/webpack.config.js | 9 ++-- 24 files changed, 506 insertions(+), 51 deletions(-) create mode 100644 lab-nathan/frontend/src/assets/btn_google_dark_disabled_ios.svg create mode 100644 lab-nathan/frontend/src/assets/btn_google_dark_focus_ios.svg create mode 100644 lab-nathan/frontend/src/assets/btn_google_dark_normal_ios.svg create mode 100644 lab-nathan/frontend/src/assets/btn_google_dark_pressed_ios.svg create mode 100644 lab-nathan/frontend/src/assets/btn_google_light_disabled_ios.svg create mode 100644 lab-nathan/frontend/src/assets/btn_google_light_focus_ios.svg create mode 100644 lab-nathan/frontend/src/assets/btn_google_light_normal_ios.svg create mode 100644 lab-nathan/frontend/src/assets/btn_google_light_pressed_ios.svg create mode 100644 lab-nathan/frontend/src/components/dashboard/_dashboard.scss create mode 100644 lab-nathan/frontend/src/components/google-oauth/_google-oauth.scss create mode 100644 lab-nathan/frontend/src/components/google-oauth/index.js create mode 100644 lab-nathan/frontend/src/components/photo-form/_photo-form.scss diff --git a/lab-nathan/frontend/src/actions/photo-actions.js b/lab-nathan/frontend/src/actions/photo-actions.js index 9f9f824..367e78b 100644 --- a/lab-nathan/frontend/src/actions/photo-actions.js +++ b/lab-nathan/frontend/src/actions/photo-actions.js @@ -23,9 +23,9 @@ export const userPhotoDelete = (photo) => ({ // async actions export const userPhotosFetchRequest = () => (dispatch, getState) => { - let {auth} = getState() + let {token} = getState() return superagent.get(`${__API_URL__}/photos/me`) - .set('Authorization', `Bearer ${auth}`) + .set('Authorization', `Bearer ${token}`) .then(res => { dispatch(userPhotosSet(res.body.data)) return res @@ -33,9 +33,9 @@ export const userPhotosFetchRequest = () => (dispatch, getState) => { } export const userPhotoCreateRequest = (photo) => (dispatch, getState) => { - let {auth} = getState() + let {token} = getState() return superagent.post(`${__API_URL__}/photos`) - .set('Authorization', `Bearer ${auth}`) + .set('Authorization', `Bearer ${token}`) .field('description', photo.description) .attach('photo', photo.photo) .then((res) => { @@ -45,9 +45,9 @@ export const userPhotoCreateRequest = (photo) => (dispatch, getState) => { } export const userPhotoDeleteRequest = (photo) => (dispatch, getState) => { - let {auth} = getState() + let {token} = getState() return superagent.delete(`${__API_URL__}/photos/${photo._id}`) - .set('Authorization', `Bearer ${auth}`) + .set('Authorization', `Bearer ${token}`) .then(res => { dispatch(userPhotoDelete(photo)) return res @@ -55,9 +55,9 @@ export const userPhotoDeleteRequest = (photo) => (dispatch, getState) => { } export const userPhotoUpdateRequest = (photo) => (dispatch, getState) => { - let {auth} = getState() + let {token} = getState() return superagent.put(`${__API_URL__}/photos/${photo._id}`) - .set('Authorization', `Bearer ${auth}`) + .set('Authorization', `Bearer ${token}`) .field('description', photo.description) .attach('photo', photo.photo) .then(res => { diff --git a/lab-nathan/frontend/src/assets/btn_google_dark_disabled_ios.svg b/lab-nathan/frontend/src/assets/btn_google_dark_disabled_ios.svg new file mode 100644 index 0000000..ecda263 --- /dev/null +++ b/lab-nathan/frontend/src/assets/btn_google_dark_disabled_ios.svg @@ -0,0 +1,24 @@ + + + + btn_google_dark_disabled_ios + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lab-nathan/frontend/src/assets/btn_google_dark_focus_ios.svg b/lab-nathan/frontend/src/assets/btn_google_dark_focus_ios.svg new file mode 100644 index 0000000..dbf2c96 --- /dev/null +++ b/lab-nathan/frontend/src/assets/btn_google_dark_focus_ios.svg @@ -0,0 +1,51 @@ + + + + btn_google_dark_focus_ios + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lab-nathan/frontend/src/assets/btn_google_dark_normal_ios.svg b/lab-nathan/frontend/src/assets/btn_google_dark_normal_ios.svg new file mode 100644 index 0000000..8464cb2 --- /dev/null +++ b/lab-nathan/frontend/src/assets/btn_google_dark_normal_ios.svg @@ -0,0 +1,50 @@ + + + + btn_google_dark_normal_ios + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lab-nathan/frontend/src/assets/btn_google_dark_pressed_ios.svg b/lab-nathan/frontend/src/assets/btn_google_dark_pressed_ios.svg new file mode 100644 index 0000000..3552b43 --- /dev/null +++ b/lab-nathan/frontend/src/assets/btn_google_dark_pressed_ios.svg @@ -0,0 +1,50 @@ + + + + btn_google_dark_pressed_ios + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lab-nathan/frontend/src/assets/btn_google_light_disabled_ios.svg b/lab-nathan/frontend/src/assets/btn_google_light_disabled_ios.svg new file mode 100644 index 0000000..b433d04 --- /dev/null +++ b/lab-nathan/frontend/src/assets/btn_google_light_disabled_ios.svg @@ -0,0 +1,24 @@ + + + + btn_google_light_disabled_ios + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lab-nathan/frontend/src/assets/btn_google_light_focus_ios.svg b/lab-nathan/frontend/src/assets/btn_google_light_focus_ios.svg new file mode 100644 index 0000000..1f3ee4f --- /dev/null +++ b/lab-nathan/frontend/src/assets/btn_google_light_focus_ios.svg @@ -0,0 +1,44 @@ + + + + btn_google_light_focus_ios + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lab-nathan/frontend/src/assets/btn_google_light_normal_ios.svg b/lab-nathan/frontend/src/assets/btn_google_light_normal_ios.svg new file mode 100644 index 0000000..032b6ac --- /dev/null +++ b/lab-nathan/frontend/src/assets/btn_google_light_normal_ios.svg @@ -0,0 +1,43 @@ + + + + btn_google_light_normal_ios + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lab-nathan/frontend/src/assets/btn_google_light_pressed_ios.svg b/lab-nathan/frontend/src/assets/btn_google_light_pressed_ios.svg new file mode 100644 index 0000000..ee593d7 --- /dev/null +++ b/lab-nathan/frontend/src/assets/btn_google_light_pressed_ios.svg @@ -0,0 +1,43 @@ + + + + btn_google_light_pressed_ios + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/dashboard/_dashboard.scss b/lab-nathan/frontend/src/components/dashboard/_dashboard.scss new file mode 100644 index 0000000..c9db236 --- /dev/null +++ b/lab-nathan/frontend/src/components/dashboard/_dashboard.scss @@ -0,0 +1,5 @@ +.dashboard { + height: 100%; + display: flex; + flex-direction: column; +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/dashboard/index.js b/lab-nathan/frontend/src/components/dashboard/index.js index 203d9d0..8cf8c86 100644 --- a/lab-nathan/frontend/src/components/dashboard/index.js +++ b/lab-nathan/frontend/src/components/dashboard/index.js @@ -1,3 +1,4 @@ +import './_dashboard.scss'; import React from 'react' import {connect} from 'react-redux' import * as util from '../../lib/utilities.js' @@ -8,18 +9,17 @@ import PhotoItem from '../photo-item' class DashboardContainer extends React.Component { constructor(props){ - super(props) + super(props); } componentDidMount(){ this.props.photosFetch() - .catch(util.logError) + .catch(util.logError); } render(){ return ( -
    -

    dashboard

    +
    { diff --git a/lab-nathan/frontend/src/components/google-oauth/_google-oauth.scss b/lab-nathan/frontend/src/components/google-oauth/_google-oauth.scss new file mode 100644 index 0000000..cedbd62 --- /dev/null +++ b/lab-nathan/frontend/src/components/google-oauth/_google-oauth.scss @@ -0,0 +1,31 @@ +.google-oauth { + background-color: #4285F4; + color: white; + font-family: 'Roboto', sans-serif; + font-size: 14px; + margin: 0 auto; + width: 175px; + height: 40px; + border-radius: 3px; + display: block; + text-align: left; + + + & div { + display: inline-block; + vertical-align: middle; + height: 40px; + width: 40px; + } + + & svg { + height: 40px; + width: 40px; + } + + & p { + display: inline-block; + vertical-align: middle; + padding-left: 5px; + } +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/google-oauth/index.js b/lab-nathan/frontend/src/components/google-oauth/index.js new file mode 100644 index 0000000..45f1a9b --- /dev/null +++ b/lab-nathan/frontend/src/components/google-oauth/index.js @@ -0,0 +1,27 @@ +import './_google-oauth.scss'; +import React from 'react'; +import superagent from 'superagent'; + +class GoogleOAuth extends React.Component { + render() { + let data = require(`../../assets/btn_google_dark_normal_ios.svg`); + let innerHtml = {__html: data}; + + let AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'; + let clientIDQuery = `client_id=${__GOOGLE_CLIENT_ID__}` + let responseTypeQuery = 'response_type=code'; + let scopeQuery = 'scope=openid%20profile%20email'; + let promptQuery = 'prompt=consent'; + let redirectURIQuery = 'redirect_uri=http://localhost:3000/oauth/google/code'; + let formattedURI = `${AUTH_URL}?${clientIDQuery}&${responseTypeQuery}&${scopeQuery}&${promptQuery}&${redirectURIQuery}`; + + return ( + +
    +

    Sign in with Google

    +
    + ); + } +} + +export default GoogleOAuth; \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/header/index.js b/lab-nathan/frontend/src/components/header/index.js index a6cdc2e..738320d 100644 --- a/lab-nathan/frontend/src/components/header/index.js +++ b/lab-nathan/frontend/src/components/header/index.js @@ -50,21 +50,21 @@ class Header extends React.Component { {util.renderIf(!this.props.loggedIn, - Sign Up - Log In + sign up + log in )} {util.renderIf(this.props.loggedIn, - Dashboard - Settings + dashboard + settings )} {util.renderIf(this.props.profile, )} {util.renderIf(this.props.loggedIn, - + )} ); diff --git a/lab-nathan/frontend/src/components/login/_login.scss b/lab-nathan/frontend/src/components/login/_login.scss index 5b74a69..32d6b85 100644 --- a/lab-nathan/frontend/src/components/login/_login.scss +++ b/lab-nathan/frontend/src/components/login/_login.scss @@ -7,22 +7,28 @@ border-radius: 5px; box-shadow: 3px 3px 10px -4px rgba(0, 0, 0, 0.75); - & input { - display: block; - margin: $gutter / 3 auto; - padding: $input-padding; - font-size: $input-font-size; - border-radius: $input-border-radius; - border: $input-border; - } + & input { + display: block; + margin: $gutter / 3 auto; + padding: $input-padding; + font-size: $input-font-size; + border-radius: $input-border-radius; + border: $input-border; + } - & button { - margin-top: $gutter / 2; - padding: $input-padding * 3 / 4 $input-padding * 2; - font-size: 1rem; - border-radius: 3px; - border: none; - background: url('../../assets/noise.png') #666; - color: white; - } + & button { + margin-top: $gutter / 2; + padding: $input-padding * 3 / 4 $input-padding * 2; + font-size: 1rem; + border-radius: 3px; + border: none; + background: url('../../assets/noise.png') #666; + color: white; + } + + & .separator { + height: 1px; + background-color: #bbb; + margin: $gutter 0; + } } \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/login/index.js b/lab-nathan/frontend/src/components/login/index.js index 1e9305c..d5a9cf2 100644 --- a/lab-nathan/frontend/src/components/login/index.js +++ b/lab-nathan/frontend/src/components/login/index.js @@ -1,6 +1,7 @@ import './_login.scss'; import React from 'react'; import * as utilities from '../../lib/utilities.js'; +import GoogleOAuth from '../google-oauth'; class Login extends React.Component { constructor(props) { @@ -82,6 +83,9 @@ class Login extends React.Component { +
    + + ); } diff --git a/lab-nathan/frontend/src/components/menu-item/_menu-item.scss b/lab-nathan/frontend/src/components/menu-item/_menu-item.scss index f82af54..2010bb6 100644 --- a/lab-nathan/frontend/src/components/menu-item/_menu-item.scss +++ b/lab-nathan/frontend/src/components/menu-item/_menu-item.scss @@ -5,7 +5,7 @@ margin-left: $gutter / 2; font-size: 1.125rem; color: $primary-dark; - font-family: serif; + font-family: $font-family; & a { color: inherit; diff --git a/lab-nathan/frontend/src/components/photo-form/_photo-form.scss b/lab-nathan/frontend/src/components/photo-form/_photo-form.scss new file mode 100644 index 0000000..8015689 --- /dev/null +++ b/lab-nathan/frontend/src/components/photo-form/_photo-form.scss @@ -0,0 +1,41 @@ +@import '../../styles/lib/vars'; + +.photo-form { + background: url('../../assets/noise.png') #dadada; + padding: $gutter; + margin: auto; + border-radius: 5px; + box-shadow: 3px 3px 10px -4px rgba(0, 0, 0, 0.75); + + & img { + max-height: 300px; + } + + & input { + display: block; + margin: $gutter / 3 auto; + padding: $input-padding; + font-size: $input-font-size; + border-radius: $input-border-radius; + } + + & button { + margin-top: $gutter / 2; + padding: $input-padding * 3 / 4 $input-padding * 2; + font-size: 1rem; + border-radius: 3px; + border: none; + background: url('../../assets/noise.png') #666; + color: white; + } + + & textarea { + display: block; + width: 100%; + min-height: 100px; + font-size: inherit; + font-family: helvetica; + padding: 10px; + box-sizing: border-box; + } +} \ No newline at end of file diff --git a/lab-nathan/frontend/src/components/photo-form/index.js b/lab-nathan/frontend/src/components/photo-form/index.js index bc3e5c4..e69537a 100644 --- a/lab-nathan/frontend/src/components/photo-form/index.js +++ b/lab-nathan/frontend/src/components/photo-form/index.js @@ -1,4 +1,5 @@ -import React from 'react' +import './_photo-form.scss'; +import React from 'react'; import * as util from '../../lib/utilities.js' class PhotoForm extends React.Component { @@ -13,7 +14,6 @@ class PhotoForm extends React.Component { } handleChange(e){ - console.log('event', e.target) let {name} = e.target if(name == 'description'){ this.setState({description: e.target.value}) @@ -54,9 +54,10 @@ class PhotoForm extends React.Component { onChange={this.handleChange} /> - diff --git a/lab-nathan/frontend/src/components/profile-form/_profile-form.scss b/lab-nathan/frontend/src/components/profile-form/_profile-form.scss index 107c638..3ba7c44 100644 --- a/lab-nathan/frontend/src/components/profile-form/_profile-form.scss +++ b/lab-nathan/frontend/src/components/profile-form/_profile-form.scss @@ -13,11 +13,12 @@ & input { display: block; + margin: $gutter / 3 auto; padding: $input-padding; font-size: $input-font-size; border-radius: $input-border-radius; } - + & button { margin-top: $gutter / 2; padding: $input-padding * 3 / 4 $input-padding * 2; diff --git a/lab-nathan/frontend/src/components/profile-form/index.js b/lab-nathan/frontend/src/components/profile-form/index.js index d9dd444..00fc820 100644 --- a/lab-nathan/frontend/src/components/profile-form/index.js +++ b/lab-nathan/frontend/src/components/profile-form/index.js @@ -43,6 +43,7 @@ class ProfileForm extends React.Component { } render() { + console.log(this.state.preview); return (
    + profile={this.props.profile} + buttonText={buttonText} + onComplete={handleComplete} />
    ); } @@ -43,7 +49,8 @@ let mapStateToProps = (state) => ({ }) let mapDispatchToProps = (dispatch) => ({ - profileCreate: (profile) => dispatch(profileCreateRequest(profile)) + profileCreate: (profile) => dispatch(profileCreateRequest(profile)), + profileUpdate: (profile) => dispatch(profileUpdateRequest(profile)) }) export default connect(mapStateToProps, mapDispatchToProps)(SettingsContainer); \ No newline at end of file diff --git a/lab-nathan/frontend/src/styles/main.scss b/lab-nathan/frontend/src/styles/main.scss index bdfb943..bcea48b 100644 --- a/lab-nathan/frontend/src/styles/main.scss +++ b/lab-nathan/frontend/src/styles/main.scss @@ -1,4 +1,5 @@ @import url('https://fonts.googleapis.com/css?family=Playfair+Display'); +@import url('https://fonts.googleapis.com/css?family=Roboto'); @import "./lib/vars"; @import "./base/reset"; @import "./base/base"; \ No newline at end of file diff --git a/lab-nathan/frontend/webpack.config.js b/lab-nathan/frontend/webpack.config.js index f5e302f..78950d4 100644 --- a/lab-nathan/frontend/webpack.config.js +++ b/lab-nathan/frontend/webpack.config.js @@ -18,7 +18,8 @@ let plugins = [ new HtmlPlugin({ template: `${__dirname}/src/index.html` }), new DefinePlugin({ '__DEBUG__': JSON.stringify('development'), - '__API_URL__': JSON.stringify(process.env.API_URL) + '__API_URL__': JSON.stringify(process.env.API_URL), + '__GOOGLE_CLIENT_ID__': JSON.stringify(process.env.GOOGLE_CLIENT_ID) }) ]; @@ -55,7 +56,7 @@ module.exports = { }, { test: /\.(woff|woff2|ttf|eot|glyph|\.svg)$/, - exclude: /\camera-icon.svg$/, + exclude: /\camera-icon.svg|btn_google_dark_normal_ios.svg$/, use: [ { loader: 'url-loader', @@ -68,7 +69,7 @@ module.exports = { }, { test: /\.(jpg|jpeg|gif|png|tiff|svg)$/, - exclude: /\glyph.svg|camera-icon.svg$/, + exclude: /\glyph.svg|camera-icon.svg|btn_google_dark_normal_ios.svg$/, use: [ { loader: 'url-loader', @@ -81,7 +82,7 @@ module.exports = { }, { test: /\.(mp3|aac|aiff|wav|flac|m4a|mp4|ogg|ape)$/, - exclude: /\glyph.svg|camera-icon.svg$/, + exclude: /\glyph.svg|camera-icon.svg|btn_google_dark_normal_ios.svg$/, use: [ { loader: 'file-loader',