Skip to content

Commit

Permalink
initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
paultibbetts committed Feb 5, 2018
0 parents commit d9019f2
Show file tree
Hide file tree
Showing 27 changed files with 8,149 additions and 0 deletions.
21 changes: 21 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.

# dependencies
/node_modules

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# React Hacker News

Yet another React Hacker News clone

**Demo:** [https://ya-react-hn.now.sh](https://ya-react-hn.now.sh)

Featuring:

- [React 16](https://reactjs.org/)
- [React Router 4](https://reacttraining.com/react-router)
- [React Redux](https://redux.js.org/docs/basics/UsageWithReact.html)
- [Redux Thunk](https://github.com/gaearon/redux-thunk)
- [HN API](https://github.com/cheeaun/node-hnapi)
- CSS variables
- unread link highlighting
- clickable links in comments

27 changes: 27 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "react-hn",
"version": "0.1.0",
"private": true,
"dependencies": {
"anchorme": "^1.1.2",
"dompurify": "^1.0.3",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-redux": "^5.0.6",
"react-router-dom": "^4.2.2",
"react-scripts": "1.1.0",
"redux": "^3.7.2",
"redux-thunk": "^2.2.0",
"serve": "^6.4.9"
},
"scripts": {
"start": "react-scripts start",
"now-start": "serve --single ./build",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"devDependencies": {
"redux-devtools": "^3.4.1"
}
}
Binary file added public/favicon.ico
Binary file not shown.
17 changes: 17 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#20232a">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React HN</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
15 changes: 15 additions & 0 deletions public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"short_name": "React HN",
"name": "React Hacker News clone",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#20232a",
"background_color": "#ffffff"
}
9 changes: 9 additions & 0 deletions src/App.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
101 changes: 101 additions & 0 deletions src/Collection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { getCollection } from './actions';
import Item from './components/Item';
import Pagination from './components/Pagination';
import { scrollToTop, renderLoading } from './helpers';

class Collection extends Component {

componentDidMount() {
this.getData(this.props.type);
scrollToTop();
}

componentWillReceiveProps(nextProps) {
const typeChanged = this.props.type !== nextProps.type;
const pageChanged = this.props.match.params.page !== nextProps.match.params.page
if ( typeChanged || pageChanged ) {
this.getData(nextProps.type, nextProps.match.params.page);
}
}

getData(type, page = null) {
if (page === null) {
page = this.props.match.params.page || 1;
}
this.props.dispatch(getCollection(type, page));
}

renderContent(content) {
if (! content) return;
const page = this.props.match.params.page || 1;
if (content.length > 0) {
return (
<div>
{this.renderList(content, content.length)}
<Pagination page={page} type={this.props.type} />
</div>
);
}
if (! this.props.isFetching) {
const url = this.props.match.path.replace(':page?', page - 1);
return (
<div className="container content">
<p>There's nothing to show here…</p>
<a href={url}>
Try the previous page?
</a>
</div>
);
}
renderLoading();
}

renderList(data, perPage) {
let classNames = "collection content";
if (this.props.isFetching) {
classNames = `${classNames} is-fetching`;
}
return (
<div className={classNames}>
<ol className="collection__list">
{this.renderItems(data, perPage)}
</ol>
</div>
);
}

renderItems(data, perPage) {
const page = this.props.match.params.page || 1;
return data.map((data, index) => (
<li key={index}>
<Item
key={index}
index={index}
data={data}
page={page}
perPage={perPage}
/>
</li>
));
}

render() {
if (this.props.match.params.page > 10) {
return (
<Redirect to={this.props.match.path.replace(':page', 10)}/>
);
}
return (
<div className="container">
{ this.renderContent(this.props[this.props.type]) }
</div>
);
}
}

const mapStateToProps = state => state.collections;

export default connect(mapStateToProps)(Collection);
117 changes: 117 additions & 0 deletions src/Story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { getSingle } from './actions';
import Comment from './components/Comment';
import discussion from './components/discussion';
import { scrollToTop, renderLoading } from './helpers';

class Story extends Component {

componentWillMount() {
const { id } = this.props.match.params;
this.props.dispatch(getSingle('item', id));
}

componentDidMount() {
scrollToTop();
}

renderStory(data) {
if (data && Object.hasOwnProperty.call(data, 'comments')) {
return (
<div className="single container content">
<h1 className="single__title">
<a
className="single__link"
href={data.url}
>
{data.title}
{data.domain &&
<Fragment>
&nbsp;
<span className="single__domain">({data.domain})</span>
</Fragment>
}
</a>
</h1>
<div className="single__meta">
{data.points && (
<Fragment>
{data.points} {data.points === 1 ? 'point ' : 'points '}
by <Link to={`/user/${data.user}`}>{data.user}</Link>
</Fragment>
)}
{discussion(data) &&
<Fragment>
&nbsp;|&nbsp;
{discussion(data)}
</Fragment>
}
</div>
</div>
);
}
if (! this.props.isFetching) {
return (
<p>Nothing to show…</p>
)
}
return renderLoading();
}


renderComments(data) {
if (data && Object.hasOwnProperty.call(data, 'id')) {
if (! data.comments || data.comments.length === 0) return;
const commentsAmount = `Showing ${data.comments.length} ${data.comments.length === 1 ? 'comment' : 'comments'}`;
return (
<div className="container content">
{commentsAmount}
{this.renderCommentsCollection(data.comments)}
</div>
);
}
}

renderCommentsCollection(comments) {
return (
<ul className="comments__list">
{comments.map((comment, index) => (
<li
key={index}
className="comments__listItem"
>
<Comment data={comment} />
</li>
))}
</ul>
)
}

renderContents(data) {
let classNames;
if (this.props.isFetching) {
classNames = 'is-fetching';
}
return (
<div className={classNames}>
{this.renderStory(data)}
{this.renderComments(data)}
</div>
);
}

render() {
const { state } = this.props;
return (
<div className="container">
{this.renderContents(state)}
</div>
);
}
}

const mapStateToProps = state => state.data;

export default connect(mapStateToProps)(Story);
Loading

0 comments on commit d9019f2

Please sign in to comment.