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
115 changes: 115 additions & 0 deletions src/react/components/ErrorMessage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
@import "../scss/media";
@import "../scss/theme_variables";

.error-section {
height: 300px;
margin: 200px;

@media #{$mobile-only} {
height: 0;
display: block;
position: relative;
margin: 30vh 0;
}

p {
text-align: center;
padding: 0 25px;

&.strong {
margin-top: 25px;
font-weight: bold;
}
}

.skull {
width: $skull-size;
height: $skull-size;
position: relative;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;

.head {
width: 100%;
height: 75%;
border-radius: 15% / 20%;
position: absolute;
top: 0;
left: 0;
&:before, &:after {
content: "";
position: absolute;
border-radius: 50%;
width: 20%;
height: 30%;
bottom: 10%;
}
&:before {
left: 10%;
}
&:after {
right: 10%;
}
.crack {
width: 10%;
height: 10%;
position: absolute;
top: 0;
right: 25%;
transform: skew(-15deg);

&:before {
content: "";
position: absolute;
top: 100%;
left: $skull-size / 15;
border-right: $skull-size / 20 solid transparent;
border-left: $skull-size / 40 solid transparent;
}
}
}
.mouth {
width: 40%;
height: 25%;
position: absolute;
top: 75%;
left: 30%;
border-radius: 0 0 $skull-size / 10 $skull-size / 10;
&:before {
content: "";
position: absolute;
width: 15%;
height: 50%;
border-radius: 50% / 30%;
left: 42.5%;
top: -25%;
}
.teeth {
position: absolute;
bottom: 0;
left: 45%;
width: 10%;
height: 50%;
margin-bottom: -5%;
border-radius: 50% / 20%;

&:before, &:after {
content: "";
position: absolute;
width: 100%;
height: 100%;
border-radius: 50% / 20%;
}
&:before {
left: -250%;
}
&:after {
right: -250%;
}
}
}
}
}
24 changes: 24 additions & 0 deletions src/react/components/ErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import './ErrorMessage.scss';

interface ErrorMessageProps {
message: string;
}

export default function ErrorMessage({ message }: ErrorMessageProps) {
return (
<div className="error-section">
<div className="skull">
<div className="head">
<div className="crack"></div>
</div>
<div className="mouth">
<div className="teeth"></div>
</div>
</div>
<p className="strong">{message}</p>
<p>
If you are offline viewing, you'll need to visit this page with a network connection first before it can work offline.
</p>
</div>
);
}
108 changes: 108 additions & 0 deletions src/react/components/Feed.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
@import "../scss/media";
@import "../scss/theme_variables";

a {
text-decoration: none;
font-weight: bold;

&:hover {
text-decoration: underline;
};
}

ol {
padding: 0 40px;
margin: 0;

@media #{$mobile-only} {
box-sizing: border-box;
list-style: none;
padding: 0 10px;
}

li {
position: relative;
-webkit-transition: background-color .2s ease;
transition: background-color .2s ease;
}
}

.list-margin {
@media #{$mobile-only} {
margin-top: 55px;
}
}

.main-content {
position: relative;
width: 100%;
min-height: 100vh;
-webkit-transition: opacity .2s ease;
transition: opacity .2s ease;
box-sizing: border-box;
padding: 8px 0;
z-index: 0;
}

.post {
padding: 10px 0 10px 5px;
transition: background-color 0.2s ease;
border-bottom: 1px solid #CECECB;

.itemNum {
color: #696969;
position: absolute;
width: 30px;
text-align: right;
left: 0;
top: 4px;
}
}

.item-block {
display: block;
}


.nav {
padding: 10px 40px;
margin-top: 10px;
font-size: 17px;

a {
@media #{$mobile-only} {
text-decoration: none;
}
}

@media #{$mobile-only} {
margin: 20px 0;
text-align: center;
padding: 10px 80px;
height: 20px;
}

.prev {
padding-right: 20px;

@media #{$mobile-only} {
float: left;
padding-right: 0;
}
}

.more {
@media #{$mobile-only} {
float: right;
}
}
}

.job-header {
font-size: 15px;
padding: 0 40px 10px;

@media #{$mobile-only} {
padding: 60px 15px 25px 15px;
}
}
78 changes: 73 additions & 5 deletions src/react/components/Feed.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,85 @@
import { useParams } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom';
import ErrorMessage from './ErrorMessage';
import Item from './Item';
import Loader from './Loader';
import { fetchFeed } from '../api/hackernews';
import { FeedName } from '../models/feed-name.type';
import { Story } from '../models/story';
import './Feed.scss';

interface FeedProps {
feedName: FeedName;
}

export default function Feed({ feedName }: FeedProps) {
const { page } = useParams<'page'>();
const pageNum = page ? +page : 1;
const [items, setItems] = useState<Story[] | null>(null);
const [errorMessage, setErrorMessage] = useState('');
const [listStart, setListStart] = useState<number>();

useEffect(() => {
let ignore = false;

setErrorMessage('');
fetchFeed(feedName, pageNum)
.then(nextItems => {
if (ignore) {
return;
}

setItems(nextItems);
setListStart((pageNum - 1) * 30 + 1);
window.scrollTo(0, 0);
})
.catch(() => {
if (ignore) {
return;
}

setErrorMessage('Could not load ' + feedName + ' stories.');
});

return () => {
ignore = true;
};
}, [feedName, pageNum]);

return (
<main>
<h2>{feedName}</h2>
<p>Page {page}</p>
</main>
<div className="main-content">
{!items && !errorMessage && <Loader />}
{!items && errorMessage && <ErrorMessage message={errorMessage} />}
{items && (
<div>
{feedName === 'jobs' && (
<p className="job-header">
These are jobs at startups that were funded by Y Combinator.
You can also get a job at a YC startup through{' '}
<a href="https://triplebyte.com/?ref=yc_jobs">Triplebyte</a>.
</p>
)}
<ol className={feedName !== 'jobs' ? 'list-margin' : undefined} start={listStart}>
{items.map(item => (
<li key={item.id} className="post">
<Item item={item} />
</li>
))}
</ol>
<div className="nav">
{listStart !== 1 && (
<Link className="prev" to={`/${feedName}/${pageNum - 1}`}>
‹ Prev
</Link>
)}
{items.length === 30 && (
<Link className="more" to={`/${feedName}/${pageNum + 1}`}>
More ›
</Link>
)}
</div>
</div>
)}
</div>
);
}
Loading