Skip to content

Commit b21f6e1

Browse files
Merge pull request #153 from Web-Dev-Path/feature/blog
Integrate dev.to blogs into the website
2 parents 9c2af6e + 8836c77 commit b21f6e1

20 files changed

+477
-54
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
8282
## Unreleased
8383

8484
### Added
85+
- Blog page which pulls content from dev.to
8586

8687
### Fixed
8788

components/blog/BlogPostsContainer.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import CardsColumns from '@/components/containers/CardsColumns';
2+
import Card from '@/components/containers/Card';
3+
import Title from '@/components/snippets/Title';
4+
import Link from 'next/link';
5+
import styles from '@/styles/Blog.module.scss';
6+
import RevealContentContainer from '@/components/containers/RevealContentContainer';
7+
import { tagToHeading } from '@/utils/blogCategories';
8+
import Container from '@/components/containers/Container';
9+
10+
function* splitPosts(arr, n) {
11+
for (let i = 0; i < arr.length; i += n) {
12+
yield arr.slice(i, i + n);
13+
}
14+
}
15+
16+
const BlogPostsContainer = ({
17+
posts,
18+
heading,
19+
tag,
20+
swipe = true,
21+
viewall = true,
22+
}) => {
23+
// process posts props (e.g. insert default image)
24+
posts.map(post => {
25+
if (!post.image) {
26+
post.image = '/images/svg/blogImage.svg';
27+
}
28+
return post;
29+
});
30+
31+
return (
32+
<RevealContentContainer>
33+
<div className={styles.blogContainer}>
34+
{heading ? (
35+
<Title customClass='blogTitle' title={heading} />
36+
) : tag ? (
37+
<Title customClass='blogTitle' title={tagToHeading[tag]} />
38+
) : null}
39+
{
40+
// put in rows of 3 if more than 3 posts (for swipable cards)
41+
swipe ? (
42+
<>
43+
{[...splitPosts(posts, 3)].map((p, index) => (
44+
<CardsColumns key={index} cards={p} customClass='blog' />
45+
))}
46+
</>
47+
) : (
48+
<Container>
49+
<div className={styles.postContainer}>
50+
{posts.map((p, index) => (
51+
<Card customClass='blog' key={index} card={p} />
52+
))}
53+
</div>
54+
</Container>
55+
)
56+
}
57+
{viewall && posts.length >= 3 ? (
58+
<Link
59+
className={styles.viewAll}
60+
href={tag ? `/blog/category/${tag}` : '/blog/category/all'}
61+
>
62+
view all
63+
</Link>
64+
) : null}
65+
</div>
66+
</RevealContentContainer>
67+
);
68+
};
69+
70+
export default BlogPostsContainer;

components/blog/SearchBar.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import styles from '@/styles/SearchBar.module.scss';
2+
3+
const SearchBar = () => {
4+
return (
5+
<div className={styles.searchBar}>
6+
<input type='search' placeholder='keyword or topic' />
7+
<button type='submit'>Search</button>
8+
</div>
9+
);
10+
};
11+
12+
export default SearchBar;

components/blog/Tag.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import styles from '@/styles/Blog.module.scss';
2+
3+
const Tag = ({ text }) => {
4+
return <div className={styles.tag}>{text}</div>;
5+
};
6+
7+
export default Tag;

components/containers/Card.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import Image from 'next/image';
22
import Link from 'next/link';
33
import styles from '@/styles/Card.module.scss';
4+
import Tag from '@/components/blog/Tag';
45

56
export default function Card({
6-
card: { image, altTag, title, content, link, linkText },
7+
card: { image, altTag, title, content, link, linkText, tagList },
78
customClass,
89
}) {
910
const LinkComponent = link?.startsWith('http') ? (
@@ -25,7 +26,16 @@ export default function Card({
2526
</div>
2627
)}
2728

28-
<h2 className={styles.title}>{title}</h2>
29+
<h2 className={styles.title} title={title}>
30+
{title}
31+
</h2>
32+
{tagList && tagList.length > 0 ? (
33+
<div className={styles.tagListContainer}>
34+
{tagList.map((tag, i) => (
35+
<Tag key={i} text={tag} />
36+
))}
37+
</div>
38+
) : null}
2939
<div className={styles.content}>
3040
<p>
3141
{content} {link && LinkComponent}

components/containers/CardsColumns.js

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,47 @@ import styles from '@/styles/CardsColumns.module.scss';
1010
export default function CardsColumns({ cards, customClass }) {
1111
return (
1212
<Container>
13-
<Swiper
14-
className={styles.swiper}
15-
mousewheel={true}
16-
grabCursor={true}
17-
modules={[Pagination]}
18-
pagination
19-
centerInsufficientSlides={true}
20-
slidesPerView={1}
21-
breakpoints={{
22-
768: {
23-
slidesPerView: 2,
24-
spaceBetween: 20,
25-
},
26-
1334: {
27-
slidesPerView: cards.length,
28-
spaceBetween: cards.length < 3 ? 75 : 20,
29-
},
30-
}}
13+
<div
14+
className={
15+
customClass
16+
? `${styles.cardColumns} ${styles[customClass]}`
17+
: styles.cardColumns
18+
}
3119
>
32-
{cards.map((card, index) => (
33-
<SwiperSlide key={index} className={styles.swiperSlide}>
34-
<div className={styles.inner__content}>
35-
<Card key={index} card={card} customClass={customClass} />
36-
</div>
37-
</SwiperSlide>
38-
))}
39-
</Swiper>
20+
<Swiper
21+
className={styles.swiper}
22+
mousewheel={true}
23+
grabCursor={true}
24+
modules={[Pagination]}
25+
pagination
26+
centerInsufficientSlides={false}
27+
slidesPerView={1}
28+
breakpoints={{
29+
768: {
30+
slidesPerView: 2,
31+
spaceBetween: 20,
32+
},
33+
1334:
34+
customClass === 'blog'
35+
? {
36+
slidesPerView: 3,
37+
spaceBetween: 20,
38+
}
39+
: {
40+
slidesPerView: cards.length,
41+
spaceBetween: cards.length < 3 ? 75 : 20,
42+
},
43+
}}
44+
>
45+
{cards.map((card, index) => (
46+
<SwiperSlide key={index} className={styles.swiperSlide}>
47+
<div className={styles.inner__content}>
48+
<Card key={index} card={card} customClass={customClass} />
49+
</div>
50+
</SwiperSlide>
51+
))}
52+
</Swiper>
53+
</div>
4054
</Container>
4155
);
4256
}

components/snippets/Title.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1-
const Title = ({ title }) => {
2-
return <h2>{title}</h2>;
1+
import styles from '@/styles/Title.module.scss';
2+
3+
const Title = ({ title, customClass }) => {
4+
return (
5+
<h2
6+
className={
7+
customClass ? `${styles.title} ${styles[customClass]}` : styles.title
8+
}
9+
>
10+
{title}
11+
</h2>
12+
);
313
};
414

515
export default Title;

next.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,12 @@ module.exports = withPWA({
1111
locales: ['en'],
1212
defaultLocale: 'en',
1313
},
14+
images: {
15+
remotePatterns: [
16+
{
17+
protocol: 'https',
18+
hostname: 'res.cloudinary.com',
19+
},
20+
],
21+
},
1422
});

pages/blog/category/[tag].js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import BlogPostsContainer from '@/components/blog/BlogPostsContainer';
2+
import { paths, tagToHeading } from '@/utils/blogCategories';
3+
import { useRouter } from 'next/router';
4+
5+
export default function BlogCategory({ posts }) {
6+
const router = useRouter();
7+
const { tag } = router.query;
8+
return (
9+
<BlogPostsContainer
10+
posts={posts}
11+
swipe={false}
12+
heading={tagToHeading[tag]}
13+
viewall={false}
14+
/>
15+
);
16+
}
17+
18+
export async function getStaticProps({ params }) {
19+
const res = await fetch('https://dev.to/api/articles?username=wdp');
20+
const posts = await res.json();
21+
22+
return {
23+
props: {
24+
posts: posts
25+
.filter(post => post.tag_list.includes(params.tag))
26+
.map(post => ({
27+
title: post.title,
28+
image: post.cover_image,
29+
altTag: post.title,
30+
content: post.description,
31+
link: post.url,
32+
linkText: 'Read more',
33+
tagList: post.tag_list,
34+
})),
35+
},
36+
};
37+
}
38+
39+
export async function getStaticPaths() {
40+
return {
41+
paths,
42+
fallback: false,
43+
};
44+
}

pages/blog/category/all.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import BlogPostsContainer from '@/components/blog/BlogPostsContainer';
2+
3+
export default function BlogCategory({ posts }) {
4+
return (
5+
<BlogPostsContainer
6+
posts={posts}
7+
swipe={false}
8+
heading='All Posts'
9+
viewall={false}
10+
/>
11+
);
12+
}
13+
14+
export async function getStaticProps() {
15+
const res = await fetch('https://dev.to/api/articles?username=wdp');
16+
const posts = await res.json();
17+
return {
18+
props: {
19+
posts: posts.map(post => ({
20+
title: post.title,
21+
image: post.cover_image,
22+
altTag: post.title,
23+
content: post.description,
24+
link: post.url,
25+
linkText: 'Read more',
26+
tagList: post.tag_list,
27+
})),
28+
},
29+
};
30+
}

0 commit comments

Comments
 (0)