Skip to content

Commit ecd41df

Browse files
committed
Implement post loading
1 parent 8ac0aa8 commit ecd41df

File tree

9 files changed

+169
-42
lines changed

9 files changed

+169
-42
lines changed

src/components/common/PostCard.tsx

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,55 @@
11
import React from 'react';
2-
import styled from 'styled-components';
2+
import styled, { css } from 'styled-components';
33
import RatioImage from './RatioImage';
44
import { ellipsis } from '../../lib/styles/utils';
55
import palette from '../../lib/styles/palette';
66
import { LikeIcon } from '../../static/svg';
7+
import { PartialPost } from '../../lib/graphql/post';
8+
import { formatDate } from '../../lib/utils';
9+
import { userThumbnail } from '../../static/images';
10+
import optimizeImage from '../../lib/optimizeImage';
711

8-
export type PostCardProps = {};
12+
export type PostCardProps = {
13+
post: PartialPost;
14+
};
915

10-
function PostCard(props: PostCardProps) {
16+
function PostCard({ post }: PostCardProps) {
1117
return (
1218
<Block>
13-
<RatioImage
14-
widthRatio={1.916}
15-
heightRatio={1}
16-
src="https://img.velog.io/images/velog/post/ebf87853-b6b7-47af-a659-d97fb39e66b0/velog_logo.png?w=768"
17-
/>
18-
<Content>
19-
<h4>벨로그 v2 업데이트 안내</h4>
20-
<p>
21-
Node.js, PHP 등 익숙한 언어들을 던지고 생뚱맞은 장고를 택한 이유는 단
22-
하나였습니다. 장고를 사용하시는 분들이 가장 많이 이야기하는,
23-
생산성입니다 내용이 더 길어지면 엉떻게 되니닌
24-
</p>
19+
{post.thumbnail && (
20+
<RatioImage
21+
widthRatio={1.916}
22+
heightRatio={1}
23+
src={optimizeImage(post.thumbnail, 640)}
24+
/>
25+
)}
26+
<Content clamp={!!post.thumbnail}>
27+
<h4>{post.title}</h4>
28+
<div className="description-wrapper">
29+
<p>
30+
{post.short_description}
31+
{post.short_description.length === 150 && '...'}
32+
</p>
33+
</div>
2534
<div className="sub-info">
26-
<span>2020년 2월 10일</span>
35+
<span>{formatDate(post.released_at)}</span>
2736
<span className="separator">·</span>
28-
<span>64개의 댓글</span>
37+
<span>{post.comments_count}개의 댓글</span>
2938
</div>
3039
</Content>
3140
<Footer>
3241
<div className="userinfo">
3342
<img
34-
src="https://img.velog.io/images/velog/%3C/span%3Eprofile%3Cspan%20class="x x-first x-last">/9aa07f66-5fcd-41f4-84f2-91d73afcec28/green favicon.png?w=120"
35-
alt="post-thumbnail"
43+
src={post.user.profile.thumbnail || userThumbnail}
44+
alt={`user thumbnail of ${post.user.username}`}
3645
/>
3746
<span>
38-
by <b>velopert</b>
47+
by <b>{post.user.username}</b>
3948
</span>
4049
</div>
4150
<div className="likes">
4251
<LikeIcon />
43-
65
52+
{post.likes}
4453
</div>
4554
</Footer>
4655
</Block>
@@ -54,10 +63,15 @@ const Block = styled.div`
5463
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.04);
5564
margin: 1rem;
5665
overflow: hidden;
66+
display: flex;
67+
flex-direction: column;
5768
`;
5869

59-
const Content = styled.div`
70+
const Content = styled.div<{ clamp: boolean }>`
6071
padding: 1rem;
72+
display: flex;
73+
flex: 1;
74+
flex-direction: column;
6175
h4 {
6276
font-size: 1rem;
6377
margin: 0;
@@ -66,18 +80,26 @@ const Content = styled.div`
6680
${ellipsis}
6781
color: ${palette.gray9};
6882
}
83+
.description-wrapper {
84+
flex: 1;
85+
}
6986
p {
7087
margin: 0;
7188
word-break: keep-all;
7289
overflow-wrap: break-word;
7390
font-size: 0.875rem;
7491
line-height: 1.5;
75-
height: 3.9375rem;
76-
display: -webkit-box;
77-
-webkit-line-clamp: 3;
78-
-webkit-box-orient: vertical;
79-
overflow: hidden;
80-
text-overflow: ellipsis;
92+
${props =>
93+
props.clamp &&
94+
css`
95+
height: 3.9375rem;
96+
display: -webkit-box;
97+
-webkit-line-clamp: 3;
98+
-webkit-box-orient: vertical;
99+
overflow: hidden;
100+
text-overflow: ellipsis;
101+
`}
102+
81103
color: ${palette.gray7};
82104
margin-bottom: 1.5rem;
83105
}

src/components/common/PostCardGrid.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
import React from 'react';
22
import styled from 'styled-components';
33
import PostCard from './PostCard';
4+
import { PartialPost } from '../../lib/graphql/post';
45

5-
export type PostCardGridProps = {};
6+
export type PostCardGridProps = {
7+
posts: PartialPost[];
8+
};
69

7-
function PostCardGrid(props: PostCardGridProps) {
10+
function PostCardGrid({ posts }: PostCardGridProps) {
811
return (
912
<Block>
10-
<PostCard />
11-
<PostCard />
12-
<PostCard />
13-
<PostCard />
14-
<PostCard />
15-
<PostCard />
16-
<PostCard />
17-
<PostCard />
13+
{posts.map(post => (
14+
<PostCard post={post} key={post.id} />
15+
))}
1816
</Block>
1917
);
2018
}

src/components/home/HomeSidebar.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import MainNoticeWidgetContainer from '../../containers/main/MainNoticeWidgetContainer';
4+
import MainTagWidgetContainer from '../../containers/main/MainTagWidgetContainer';
5+
import MainRightFooter from '../main/MainRightFooter';
6+
import Sticky from '../common/Sticky';
7+
8+
export type HomeSidebarProps = {};
9+
10+
function HomeSidebar(props: HomeSidebarProps) {
11+
return (
12+
<Sticky top={80}>
13+
<Block>
14+
<MainNoticeWidgetContainer />
15+
<MainTagWidgetContainer />
16+
<MainRightFooter />
17+
</Block>
18+
</Sticky>
19+
);
20+
}
21+
22+
const Block = styled.div``;
23+
24+
export default HomeSidebar;

src/lib/graphql/post.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ export const GET_POST_LIST = gql`
162162
comments_count
163163
tags
164164
is_private
165+
likes
165166
}
166167
}
167168
`;

src/pages/home/HomePage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import HomeLayout from '../../components/home/HomeLayout';
77
import { Route } from 'react-router-dom';
88
import TrendingPostsPage from './TrendingPostsPage';
99
import RecentPostsPage from './RecentPostsPage';
10+
import HomeSidebar from '../../components/home/HomeSidebar';
1011

1112
export type HomePageProps = {};
1213

@@ -27,7 +28,7 @@ function HomePage(props: HomePageProps) {
2728
<Route path={['/recent']} component={RecentPostsPage} />
2829
</>
2930
}
30-
side={<div>Hello</div>}
31+
side={<HomeSidebar />}
3132
/>
3233
</HomeResponsive>
3334
</HomeTemplate>

src/pages/home/RecentPostsPage.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import React from 'react';
2+
import useRecentPosts from './hooks/useRecentPosts';
3+
import PostCardGrid from '../../components/common/PostCardGrid';
24

35
export type RecentPostsPageProps = {};
46

57
function RecentPostsPage(props: RecentPostsPageProps) {
6-
return <div>몰라용</div>;
8+
const { data, loading } = useRecentPosts();
9+
10+
if (!data) return null;
11+
return <PostCardGrid posts={data.posts} />;
712
}
813

914
export default RecentPostsPage;

src/pages/home/TrendingPostsPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export type TrendingPageProps = {};
77
function TrendingPage(props: TrendingPageProps) {
88
const { data, loading } = useTrendingPosts();
99

10-
return <PostCardGrid />;
10+
if (!data) return null;
11+
return <PostCardGrid posts={data.trendingPosts} />;
1112
}
1213

1314
export default TrendingPage;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useQuery } from '@apollo/react-hooks';
2+
import { GET_POST_LIST, PartialPost } from '../../../lib/graphql/post';
3+
import { useCallback } from 'react';
4+
import useScrollPagination from '../../../lib/hooks/useScrollPagination';
5+
6+
export default function useRecentPosts() {
7+
const { data, loading, fetchMore } = useQuery<{ posts: PartialPost[] }>(
8+
GET_POST_LIST,
9+
{},
10+
);
11+
12+
const onLoadMore = useCallback(
13+
(cursor: string) => {
14+
fetchMore({
15+
variables: {
16+
cursor,
17+
},
18+
updateQuery: (prev, { fetchMoreResult }) => {
19+
if (!fetchMoreResult) return prev;
20+
return {
21+
posts: [...prev.posts, ...fetchMoreResult.posts],
22+
};
23+
},
24+
});
25+
},
26+
[fetchMore],
27+
);
28+
29+
const cursor = data?.posts[data?.posts.length - 1]?.id;
30+
31+
useScrollPagination({
32+
cursor,
33+
onLoadMore,
34+
});
35+
36+
return { data, loading };
37+
}

src/pages/home/hooks/useTrendingPosts.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,49 @@ import {
33
GetTrendingPostsResponse,
44
} from '../../../lib/graphql/post';
55
import { useQuery } from '@apollo/react-hooks';
6+
import { useCallback } from 'react';
7+
import useScrollPagination from '../../../lib/hooks/useScrollPagination';
68

79
export default function useTrendingPosts() {
8-
const { data, loading } = useQuery<GetTrendingPostsResponse>(
10+
const { data, loading, fetchMore } = useQuery<GetTrendingPostsResponse>(
911
GET_TRENDING_POSTS,
1012
);
1113

14+
const onLoadMoreByOffset = useCallback(
15+
(offset: number) => {
16+
fetchMore({
17+
variables: {
18+
offset,
19+
},
20+
updateQuery: (prev, { fetchMoreResult }) => {
21+
if (!fetchMoreResult) return prev;
22+
23+
// filter unique posts
24+
const idMap: Record<string, boolean> = prev.trendingPosts.reduce(
25+
(acc, current) => {
26+
Object.assign(acc, { [current.id]: true });
27+
return acc;
28+
},
29+
{},
30+
);
31+
32+
const uniquePosts = fetchMoreResult.trendingPosts.filter(
33+
post => !idMap[post.id],
34+
);
35+
36+
return {
37+
trendingPosts: [...prev.trendingPosts, ...uniquePosts],
38+
};
39+
},
40+
});
41+
},
42+
[fetchMore],
43+
);
44+
45+
useScrollPagination({
46+
offset: data?.trendingPosts.length,
47+
onLoadMoreByOffset,
48+
});
49+
1250
return { data, loading };
1351
}

0 commit comments

Comments
 (0)