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
85 changes: 85 additions & 0 deletions actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { CALL_API, Schemas } from '../utils';

/**
* [SEARCH_REQUEST description]
* @type {String}
*/
export const SEARCH_REQUEST = 'SEARCH_REQUEST';
export const SEARCH_SUCCESS = 'SEARCH_SUCCESS';
export const SEARCH_FAILURE = 'SEARCH_FAILURE';

function searchUsers(query) {
return {
[CALL_API]: {
types: [ SEARCH_REQUEST, SEARCH_SUCCESS, SEARCH_FAILURE ],
endpoint: `search/users?q=${query}`,
schema: Schemas.RESULTS_LIST
}
}
}

export function loadSearchResults(query, requiredFields = []) {
return (dispatch) => dispatch(searchUsers(query));
}

export const USER_REQUEST = 'USER_REQUEST';
export const USER_SUCCESS = 'USER_SUCCESS';
export const USER_FAILURE = 'USER_FAILURE';

function getUserData(login) {
return {
[CALL_API]: {
types: [ USER_REQUEST, USER_SUCCESS, USER_FAILURE ],
endpoint: `users/${login}`,
schema: Schemas.USER_DETAILS
}
}
}

export function loadUserDetails(login, requiredFields = []) {
return (dispatch) => dispatch(getUserData(login));
}

export const FOLLOWERS_REQUEST = 'FOLLOWERS_REQUEST';
export const FOLLOWERS_SUCCESS = 'FOLLOWERS_SUCCESS';
export const FOLLOWERS_FAILURE = 'FOLLOWERS_FAILURE';

function getFollowers(login) {
return {
[CALL_API]: {
types: [ FOLLOWERS_REQUEST, FOLLOWERS_SUCCESS, FOLLOWERS_FAILURE ],
endpoint: `users/${login}/followers`,
schema: Schemas.FOLLOWERS_LIST
}
}
}

export function loadFollowers(login, requiredFields = []) {
return (dispatch) => dispatch(getFollowers(login));
}

export const REPOS_REQUEST = 'REPOS_REQUEST';
export const REPOS_SUCCESS = 'REPOS_SUCCESS';
export const REPOS_FAILURE = 'REPOS_FAILURE';

function getRepos(login) {
return {
[CALL_API]: {
types: [ REPOS_REQUEST, REPOS_SUCCESS, REPOS_FAILURE ],
endpoint: `users/${login}/repos`,
schema: Schemas.REPOS_LIST
}
}
}

export function loadRepos(login, requiredFields = []) {
return (dispatch) => dispatch(getRepos(login));
}

export const RESET_ERROR_MESSAGE = 'RESET_ERROR_MESSAGE';

export function resetErrorMessage() {
return {
type: RESET_ERROR_MESSAGE
}
}
44 changes: 44 additions & 0 deletions components/List/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { Component, PropTypes } from 'react'
import { Link } from 'react-router'
import User from '../User'
import values from 'lodash/values'
import isEmpty from 'lodash/isEmpty'

export default class List extends Component {
constructor(props) {
super(props)
this.renderUser = this.renderUser.bind(this)
// this.handleLoadMoreClick = this.handleLoadMoreClick.bind(this)
}

renderUser(user) {
console.log('user: ', user);
return (
<div key={user.id}>
<User user={user}
/>
</div>
)
}
// renderLoadMore() {
// const { isFetching, onLoadMoreClick } = this.props
// return (
// <button style={{ fontSize: '150%' }}
// onClick={onLoadMoreClick}
// disabled={isFetching}>
// {isFetching ? 'Loading...' : 'Load More'}
// </button>
// )
// }

render() {
const { items } = this.props
const usersArray = values(items)

return (
<div>
{usersArray.map(this.renderUser)}
</div>
)
}
}
58 changes: 58 additions & 0 deletions components/Search/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { Component, PropTypes } from 'react';

export default class Search extends Component {

static propTypes = {
value: PropTypes.string,
onChange: PropTypes.func.isRequired
}

constructor(props) {
super(props);
this.handleKeyUp = this.handleKeyUp.bind(this);
this.handleClick = this.handleClick.bind(this);
}

componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.props.value) {
this.setInputValue(nextProps.value);
}
}

getInputValue() {
return this.refs.input.value
}

setInputValue(val) {
// Generally mutating DOM is a bad idea in React components,
// but doing this for a single uncontrolled field is less fuss
// than making it controlled and maintaining a state for it.
this.refs.input.value = val
}

handleKeyUp(e) {
if (e.keyCode === 13) {
this.handleClick()
}
}

handleClick() {
this.props.onChange(this.getInputValue())
}

render() {
return (
<div style={{paddingTop: "5em"}}>
<h1>Search GitHub for Users:</h1>
<input size="45"
ref="input"
defaultValue={this.props.value}
onKeyUp={this.handleKeyUp} />
<button onClick={this.handleClick}>
Search
</button>

</div>
)
}
}
20 changes: 20 additions & 0 deletions components/User/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { Component, PropTypes } from 'react'
import { Link } from 'react-router'

export default class User extends Component {


render() {
const { user: { login, avatarUrl } } = this.props
return (
<div className="User" >
<Link to={`/${login}`}>
<img src={avatarUrl} width="72" height="72" style={{display: "inline"}} />
<h3>
{login}
</h3>
</Link>
</div>
)
}
}
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,20 @@
},
"homepage": "https://github.com/GasStationTV/react-coding-exercise#readme",
"dependencies": {
"async": "^2.0.0-rc.5",
"async-es": "^2.0.0-rc.5",
"bootstrap": "^4.0.0-alpha.2",
"humps": "^1.1.0",
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.13.1",
"normalizr": "^2.1.0",
"react": "0.14.7",
"react-dom": "^0.14.7"
"react-dom": "^0.14.7",
"react-redux": "^4.4.5",
"react-router-redux": "^4.0.4",
"redux": "^3.5.2",
"redux-logger": "^2.6.1",
"redux-thunk": "^2.1.0"
},
"devDependencies": {
"babel-eslint": "^6.0.2",
Expand Down
31 changes: 31 additions & 0 deletions reducers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as ActionTypes from '../actions';
import merge from 'lodash/merge';
import { routerReducer as routing } from 'react-router-redux';
import { combineReducers } from 'redux';

function entities(state = { users: {}, user: {}, followers: {}, repos: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities);
}
return state;
}

function errorMessage(state = null, action) {
const { type, error } = action;

if (type === ActionTypes.RESET_ERROR_MESSAGE) {
return null;
} else if (error) {
return action.error;
}

return state;
}

const mainReducer = combineReducers({
entities,
errorMessage,
routing
});

export default mainReducer;
20 changes: 13 additions & 7 deletions routes.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import React from 'react';
import { Route } from 'react-router';
import HomeView from './views/RootView';
import FishView from './views/FishView';
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import { Provider } from 'react-redux';
import HomeView from './views/RootView';
import App from './views/App';
import UsersView from './views/UsersView';
import UserDetails from './views/UserDetails';
import 'bootstrap/dist/css/bootstrap.min.css';

const routes = (
<Route path="/" component={HomeView} >
<Route path='fish' component={FishView} />
</Route>
<Route component={HomeView} >
<Route path="/" component={App} >
<Route path="/search/:query" component={UsersView} />
</Route>
<Route path="/:login" component={UserDetails} />
</Route>
);

export default routes;
27 changes: 27 additions & 0 deletions store/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import createLogger from 'redux-logger';
import api from '../utils';
import mainReducer from '../reducers';

export default function configureStore(preloadedState) {

const store = createStore(
mainReducer,
preloadedState,
compose(
applyMiddleware(thunk, api, createLogger()),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
)

if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers/index')
store.replaceReducer(nextRootReducer)
})
}

return store
}
Loading