Skip to content
Merged
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 .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,5 +44,6 @@ sketch

### next.js ###
.next
/out

# End of https://www.toptal.com/developers/gitignore/api/react
38 changes: 27 additions & 11 deletions pages/blog.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,20 +44,36 @@ const Text = styled.div`
text-align: center;
`;

const Blog = () => {
export const getStaticProps = async () => {
let blogItems = [];
let isError = false;

try {
const result = await getBlogItems();
blogItems = result.map((item) => item.toSerializableObject());
} catch (err) {
isError = true;
}

return {
props: {
blogItems,
isError,
},
revalidate: 10,
};
};

const Blog = ({ blogItems, isError }) => {
const router = useRouter();
const setMenu = useSetRecoilState(menuAtom);
const [blogItems, setBlogItems] = React.useState([]);
const isLoading = isError || router.isFallback;

React.useEffect(() => {
getBlogItems()
.then(data => {
setBlogItems(data);
})
.catch(err => {
alert('포스트를 불러오는데 실패했습니다.');
router.push('/');
});
if (isError) {
alert('포스트를 불러오는데 실패했습니다.');
router.push('/');
}
}, []);

return (
Expand All@@ -68,7 +84,7 @@ const Blog = () => {
<BackgroundText>VALUABLE</BackgroundText>
</TitleContainer>
<Text className="title">Friday Blog</Text>
<BlogList blogItems={blogItems} />
{!isError && <BlogList blogItems={blogItems} isLoading={isLoading} />}
<ScrollToTopButton />
</AnimatedPage>
);
Expand Down
89 changes: 61 additions & 28 deletions pages/post.js → pages/post/[id].js
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { useRouter } from 'next/router';
import { getNotionPost} from '../src/data/source/NotionApi';
import AnimatedPage from '../src/components/template/AnimatedPage';
import ScrollToTopButton from '../src/components/ScrollToTopButton';
import { menuAtom } from '../src/recoil/atom';
import { getNotionPost, getBlogItems } from '../../src/data/source/NotionApi';
import AnimatedPage from '../../src/components/template/AnimatedPage';
import ScrollToTopButton from '../../src/components/ScrollToTopButton';
import { menuAtom } from '../../src/recoil/atom';
import { NotionRenderer } from 'react-notion';
import styled, { css } from 'styled-components';

import 'react-notion/src/styles.css';
import 'prismjs/themes/prism-tomorrow.css';
import HitsBadge from '../src/components/blog/HitsBadge';
import HitsBadge from '../../src/components/blog/HitsBadge';
import { DiscussionEmbed } from 'disqus-react';

const Container = styled.div`
Expand DownExpand Up@@ -52,48 +52,81 @@ const cssOverrides = css`
}
`;

const Post = () => {
const setMenu = useSetRecoilState(menuAtom);
export const getStaticPaths = async () => {
const paths = [];

const router = useRouter();
const { id, title } = router.query;
try {
const blogItems = await getBlogItems();

for (const item of blogItems) {
paths.push({
params: {
id: item.id,
},
});
}
} catch (err) {
console.log(err);
}

return {
paths,
fallback: true,
};
};

export const getStaticProps = async ({ params }) => {
const id = params.id;
let notionData = {};
let isError = false;

try {
notionData = await getNotionPost(id);
} catch (err) {
isError = true;
}

return {
props: {
id,
notionData,
isError,
},
revalidate: 10,
};
};

const [notionData, setNotionData] = useState({});
const Post = ({ id, notionData, isError }) => {
const setMenu = useSetRecoilState(menuAtom);
const router = useRouter();

useEffect(() => {
if (id) {
getNotionPost(id)
.then(data => {
setNotionData(data);
})
.catch(err => {
alert('포스트를 불러오는데 실패했습니다.');
router.push('/blog');
});
if (isError) {
alert('포스트를 불러오는데 실패했습니다.');
router.push('/blog');
}
}, [id]);
}, []);

return (
<AnimatedPage>
<style>{cssOverrides}</style>
<Container>
{setMenu(1)}
{Object.keys(notionData).length > 0 ? (
{router.isFallback || isError ? (
<Placeholder>Loading...</Placeholder>
) : (
<ContentContainer>
<NotionRenderer blockMap={notionData} fullPage={true} />
<HitsBadge url={`https://fridayproject.co.kr/post?id=${id}`} />
<NotionRenderer blockMap={notionData ?? {}} fullPage={true} />
<HitsBadge url={`https://fridayproject.co.kr/post/${id}`} />
<Comment
shortname="friday-3"
config={{
url: `https://fridayproject.co.kr/post?id=${id}`,
title: title,
url: `https://fridayproject.co.kr/post/${id}`,
identifier: id,
language: 'ko',
}}
/>
</ContentContainer>
) : (
<Placeholder>Loading...</Placeholder>
)}
<ScrollToTopButton />
</Container>
Expand Down
35 changes: 11 additions & 24 deletions src/components/blog/BlogItem.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,26 +103,21 @@ const Placeholder = styled.div`
`;

export default function BlogItem({ blogItem }) {
const [authorData, setAuthorData] = React.useState(null);
const tags = blogItem.tags ?? [null, null];
let authorData = null;

React.useEffect(() => {
if (blogItem.author) {
let author = blogItem.author;
if (blogItem.email) {
author += `(${blogItem.email})`;
}
setAuthorData(author);
if (blogItem.author) {
authorData = blogItem.author;
if (blogItem.email) {
authorData += `(${blogItem.email})`;
}
}, [blogItem]);
}

return (
<BlogItemLayout
onClick={() => {
if (blogItem.id) {
Router.push({
pathname: '/post',
query: { id: blogItem.id, title: blogItem.title },
pathname: `/post/${blogItem.id}`,
});
}
}}
Expand All@@ -131,7 +126,7 @@ export default function BlogItem({ blogItem }) {
<ThumbnailArea>{blogItem.icon ?? '🖤'}</ThumbnailArea>
<TitleArea>{blogItem.title ?? <Placeholder width="90%" height="30px" />}</TitleArea>
<TagArea>
{tags.map((item, index) => (
{blogItem.tags.map((item, index) => (
<Tag key={index}>#{item ?? <Placeholder width="50px" />}</Tag>
))}
</TagArea>
Expand All@@ -143,21 +138,13 @@ export default function BlogItem({ blogItem }) {
</Info>
<Info>
<FontAwesomeIcon icon={faClock} />
{dateToText(blogItem.createdAt) ?? <Placeholder width="100px" />}
{blogItem.createdAt ?? <Placeholder width="100px" />}
</Info>
<Info>
<FontAwesomeIcon icon={faPenNib} />
{dateToText(blogItem.EditedAt) ?? <Placeholder width="100px" />}
{blogItem.EditedAt ?? <Placeholder width="100px" />}
</Info>
</InfoArea>
</BlogItemLayout>
);
}

function dateToText(date) {
if (!date) return null;
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return `${year}년 ${month}월 ${day}일`;
}
}
4 changes: 2 additions & 2 deletions src/components/blog/BlogList.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,14 +12,14 @@ const BlogListLayout = styled(motion.div)`
gap: 30px 0px;
`;

export default function BlogList({ blogItems }) {
export default function BlogList({ blogItems, isLoading }) {
const animationParams = {
initial: { opacity: 0, y: -10 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.3, ease: 'easeOut' },
};

const items = blogItems.length > 0 ? blogItems : [{}, {}, {}];
const items = isLoading ? [{}, {}, {}] : blogItems;

return (
<BlogListLayout>
Expand Down
27 changes: 24 additions & 3 deletions src/data/model/BlogPostSummary.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,16 +10,37 @@ export default class BlogPostSummary {
this.EditedAt = EditedAt;
}

toSerializableObject() {
return {
id: this.id,
title: this.title,
author: this.author,
email: this.email,
icon: this.icon,
tags: this.tags,
createdAt: dateToText(this.createdAt),
EditedAt: dateToText(this.EditedAt),
};
}

static fromRawData(rawData) {
return new BlogPostSummary({
id: rawData.id,
title: rawData.properties.제목.title[0].text.content,
title: rawData.properties.제목.title[0]?.text?.content ?? '', // 제목이 없는 경우 처리
author: rawData.properties.작성자.created_by.name,
email: rawData.properties.작성자.created_by.person.email,
icon: rawData.icon.emoji,
tags: rawData.properties.태그.multi_select.map((tag) => tag.name),
icon: rawData.icon?.emoji ?? '', // 아이콘이 없는 경우 처리
tags: rawData.properties.태그?.multi_select?.map((tag) => tag.name) ?? [], // 태그가 없는 경우 처리
createdAt: new Date(rawData.created_time),
EditedAt: new Date(rawData.last_edited_time),
});
}
}

function dateToText(date) {
if (!date) return null;
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return `${year}년 ${month}월 ${day}일`;
}
4 changes: 2 additions & 2 deletions src/data/source/NotionApi.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,7 +14,7 @@ const commonHeaders = {
export async function getBlogItems() {
try {
const result = await axios.post(
`/databases/${notionDatabaseKey}/query`,
`https://api.notion.com/v1/databases/${notionDatabaseKey}/query`,
{
page_size: 100,
sorts: [
Expand All@@ -35,7 +35,7 @@ export async function getBlogItems() {

export async function getNotionPost(id) {
try {
const result = await axios.get(`/page/${id}`);
const result = await axios.get(`https://notion-api.splitbee.io/v1/page/${id}`);
return result.data;
} catch (error) {
throw new Error(error);
Expand Down