Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lab-nathan/backend
Submodule backend added at 1fc6e9
4 changes: 4 additions & 0 deletions lab-nathan/frontend/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["es2015", "react"],
"plugins": ["transform-object-rest-spread"]
}
4 changes: 4 additions & 0 deletions lab-nathan/frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
coverage
build
.env
41 changes: 41 additions & 0 deletions lab-nathan/frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack",
"watch": "webpack-dev-server --inline --hot",
"test": "jest"
},
"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",
"jest": "^21.0.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",
"redux-mock-store": "^1.2.3",
"superagent": "^3.6.0",
"uglifyjs-webpack-plugin": "^0.4.6",
"uuid": "^3.1.0",
"webpack": "^3.5.6",
"webpack-dev-server": "^2.7.1"
}
}
24 changes: 24 additions & 0 deletions lab-nathan/frontend/src/__test__/token-reducer.test.js
Original file line number Diff line number Diff line change
@@ -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);
})
});
23 changes: 23 additions & 0 deletions lab-nathan/frontend/src/actions/profile-actions.js
Original file line number Diff line number Diff line change
@@ -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;
})
}
37 changes: 37 additions & 0 deletions lab-nathan/frontend/src/actions/token-actions.js
Original file line number Diff line number Diff line change
@@ -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;
});
}
47 changes: 47 additions & 0 deletions lab-nathan/frontend/src/components/app/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import {BrowserRouter, Route, Link} from 'react-router-dom';
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';


class App extends React.Component {
componentDidMount() {
let token = util.readCookie('X-Sluggram-Token');
if (token) {
this.props.tokenSet(token);
}
}

render() {
return (
<div className='cfgram'>
<BrowserRouter>
<section>
<ul>
<li><Link to='/welcome/signup'>Sign Up</Link></li>
<li><Link to='/welcome/login'>Log In</Link></li>
<li><Link to='/settings'>Settings</Link></li>
</ul>
<Route exact path='/welcome/:auth' component={Landing} />
<Route exact path='/dashboard' component={Dashboard} />
<Route exact path='/settings' component={Settings} />
</section>
</BrowserRouter>
</div>
);
}
}

let mapStateToProps = (state) => ({
profile: state.profile
});

let mapDispatchToProps = (dispatch) => ({
tokenSet: (token) => dispatch(tokenSet(token))
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
11 changes: 11 additions & 0 deletions lab-nathan/frontend/src/components/dashboard/dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

class Dashboard extends React.Component {
render() {
return (
<h1>Welcome</h1>
);
}
}

export default Dashboard;
28 changes: 28 additions & 0 deletions lab-nathan/frontend/src/components/landing/landing.js
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<Login
auth={params.auth}
onComplete={handleComplete} />
</div>
);
}
}

let mapDispatchToProps = (dispatch) =>({
signup: (user) => dispatch(signupRequest(user)),
login: (user) => dispatch(loginRequest(user))
})

export default connect(null, mapDispatchToProps)(Landing);
89 changes: 89 additions & 0 deletions lab-nathan/frontend/src/components/login/login.js
Original file line number Diff line number Diff line change
@@ -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 (
<form
onSubmit={this.handleSubmit}
className='user-form'>

{utilities.renderIf(this.props.auth === 'signup',
<input
type='email'
name='email'
placeholder='email'
value={this.state.email}
onChange={this.handleChange} />
)}

{utilities.renderIf(this.state.usernameError,
<span className='tooltip'>
{this.state.usernameError}
</span>
)}

<input
type='text'
name='username'
placeholder='username'
value={this.state.username}
onChange={this.handleChange} />

{utilities.renderIf(this.state.passwordError,
<span className='tooltip'>
{this.state.passwordError}
</span>
)}

<input
type='password'
name='password'
placeholder='password'
value={this.state.password}
onChange={this.handleChange} />

<button type='submit'>{this.props.auth}</button>

</form>
);
}
}

export default Login;
69 changes: 69 additions & 0 deletions lab-nathan/frontend/src/components/profile-form/profile-form.js
Original file line number Diff line number Diff line change
@@ -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 (
<form
className='profile-form'
onSubmit={this.handleSubmit}>

<img src={this.state.preview} />

<input
type='file'
name='avatar'
onChange={this.handleChange} />

<textarea
name='bio'
value = {this.state.bio}
onChange={this.handleChange}>
</textarea>

<button type='submit'>{this.props.buttonText}</button>
</form>
)
}
}

export default ProfileForm;
Loading