Skip to content

Commit 8ac0aa8

Browse files
committed
Initialize UI for PostCard
Legacy PostCard is renamed to FlatPostCard
1 parent 31c44b8 commit 8ac0aa8

20 files changed

+710
-283
lines changed

src/App.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import VelogPageFallback from './containers/velog/VelogPageFallback';
1212
import ErrorBoundary from './components/error/ErrorBoundary';
1313
import NotFoundPage from './pages/NotFoundPage';
1414
import { Helmet } from 'react-helmet-async';
15+
import HomePage from './pages/home/HomePage';
1516

1617
const loadableConfig = {
1718
fallback: <PageTemplate />,
@@ -57,12 +58,13 @@ const App: React.FC<AppProps> = props => {
5758
</Helmet>
5859
<ErrorBoundary>
5960
<Switch>
60-
<Route path="/" component={MainPage} exact />
61-
<Route path="/register" component={RegisterPage} />
61+
<Route path="/" component={HomePage} exact />
6262
<Route
6363
path="/:mode(trending|recent|following)"
64-
component={MainPage}
64+
component={HomePage}
6565
/>
66+
67+
<Route path="/register" component={RegisterPage} />
6668
<Route path="/@:username" component={VelogPage} />
6769
{/* <Route path="/@:username/:urlSlug" component={PostPage} /> */}
6870
<Route path="/email-login" component={EmailLoginPage} />
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
import React, { useRef } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import styled from 'styled-components';
4+
import palette from '../../lib/styles/palette';
5+
import { userThumbnail } from '../../static/images';
6+
import Tag from './TagItem';
7+
import { PartialPost } from '../../lib/graphql/post';
8+
import { formatDate } from '../../lib/utils';
9+
import usePrefetchPost from '../../lib/hooks/usePrefetchPost';
10+
import Skeleton from './Skeleton';
11+
import SkeletonTexts from './SkeletonTexts';
12+
import RatioImage from './RatioImage';
13+
import media from '../../lib/styles/media';
14+
import PrivatePostLabel from './PrivatePostLabel';
15+
import optimizeImage from '../../lib/optimizeImage';
16+
17+
const PostCardBlock = styled.div`
18+
padding-top: 4rem;
19+
padding-bottom: 4rem;
20+
${media.small} {
21+
padding-top: 2rem;
22+
padding-bottom: 2rem;
23+
}
24+
25+
& > a {
26+
color: inherit;
27+
text-decoration: none;
28+
}
29+
&:first-child {
30+
padding-top: 0;
31+
}
32+
.user-info {
33+
display: flex;
34+
align-items: center;
35+
img {
36+
width: 3rem;
37+
height: 3rem;
38+
display: block;
39+
margin-right: 1rem;
40+
background: ${palette.gray0};
41+
object-fit: cover;
42+
border-radius: 1.5rem;
43+
box-shadow: 0px 0 8px rgba(0, 0, 0, 0.1);
44+
${media.small} {
45+
width: 2rem;
46+
height: 2rem;
47+
border-radius: 1rem;
48+
}
49+
}
50+
.username {
51+
font-size: 0.875rem;
52+
color: ${palette.gray9};
53+
font-weight: bold;
54+
a {
55+
color: inherit;
56+
text-decoration: none;
57+
&:hover {
58+
color: ${palette.gray8};
59+
}
60+
}
61+
}
62+
margin-bottom: 1.5rem;
63+
${media.small} {
64+
margin-bottom: 0.75rem;
65+
}
66+
}
67+
.post-thumbnail {
68+
margin-bottom: 1rem;
69+
${media.small} {
70+
}
71+
}
72+
line-height: 1.5;
73+
h2 {
74+
font-size: 1.5rem;
75+
margin: 0;
76+
color: ${palette.gray9};
77+
word-break: keep-all;
78+
${media.small} {
79+
font-size: 1rem;
80+
}
81+
}
82+
p {
83+
margin-bottom: 2rem;
84+
margin-top: 0.5rem;
85+
font-size: 1rem;
86+
color: ${palette.gray7};
87+
word-break: keep-all;
88+
overflow-wrap: break-word;
89+
${media.small} {
90+
font-size: 0.875rem;
91+
margin-bottom: 1.5rem;
92+
}
93+
}
94+
.subinfo {
95+
display: flex;
96+
align-items: center;
97+
margin-top: 1rem;
98+
color: ${palette.gray6};
99+
font-size: 0.875rem;
100+
${media.small} {
101+
font-size: 0.75rem;
102+
}
103+
span {
104+
}
105+
.separator {
106+
margin-left: 0.5rem;
107+
margin-right: 0.5rem;
108+
}
109+
}
110+
.tags-wrapper {
111+
margin-bottom: -0.875rem;
112+
${media.small} {
113+
margin-bottom: -0.5rem;
114+
}
115+
}
116+
117+
& + & {
118+
border-top: 1px solid ${palette.gray2};
119+
}
120+
`;
121+
122+
interface PostCardProps {
123+
post: PartialPost;
124+
hideUser?: boolean;
125+
}
126+
127+
const FlatPostCard = ({ post, hideUser }: PostCardProps) => {
128+
const prefetch = usePrefetchPost(post.user.username, post.url_slug);
129+
const prefetchTimeoutId = useRef<number | null>(null);
130+
131+
const onMouseEnter = () => {
132+
prefetchTimeoutId.current = setTimeout(prefetch, 2000);
133+
};
134+
135+
const onMouseLeave = () => {
136+
if (prefetchTimeoutId.current) {
137+
clearTimeout(prefetchTimeoutId.current);
138+
}
139+
};
140+
141+
const url = `/@${post.user.username}/${post.url_slug}`;
142+
const velogUrl = `/@${post.user.username}`;
143+
144+
if (!post.user.profile) {
145+
console.log(post);
146+
}
147+
return (
148+
<PostCardBlock onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
149+
{!hideUser && (
150+
<div className="user-info">
151+
<Link to={velogUrl}>
152+
<img
153+
src={optimizeImage(
154+
post.user.profile?.thumbnail || userThumbnail,
155+
120,
156+
)}
157+
alt="thumbnail"
158+
/>
159+
</Link>
160+
<div className="username">
161+
<Link to={`/@${post.user.username}`}>{post.user.username}</Link>
162+
</div>
163+
</div>
164+
)}
165+
{post.thumbnail && (
166+
<Link to={url}>
167+
<RatioImage
168+
src={optimizeImage(post.thumbnail, 768)}
169+
alt="post-thumbnail"
170+
widthRatio={1.91}
171+
heightRatio={1}
172+
className="post-thumbnail"
173+
/>
174+
</Link>
175+
)}
176+
<Link to={url}>
177+
<h2>{post.title}</h2>
178+
</Link>
179+
<p>{post.short_description}</p>
180+
<div className="tags-wrapper">
181+
{post.tags.map(tag => (
182+
<Tag key={tag} name={tag} link />
183+
))}
184+
</div>
185+
<div className="subinfo">
186+
<span>{formatDate(post.released_at)}</span>
187+
<div className="separator">·</div>
188+
<span>{post.comments_count}개의 댓글</span>
189+
{post.is_private && (
190+
<>
191+
<div className="separator">·</div>
192+
<span>
193+
<PrivatePostLabel />
194+
</span>
195+
</>
196+
)}
197+
</div>
198+
</PostCardBlock>
199+
);
200+
};
201+
202+
export type PostCardSkeletonProps = {
203+
hideUser?: boolean;
204+
};
205+
206+
export function PostCardSkeleton({ hideUser }: PostCardSkeletonProps) {
207+
return (
208+
<SkeletonBlock>
209+
{!hideUser && (
210+
<div className="user-info">
211+
<Skeleton
212+
className="user-thumbnail-skeleton"
213+
circle
214+
marginRight="1rem"
215+
/>
216+
<div className="username">
217+
<Skeleton width="5rem" />
218+
</div>
219+
</div>
220+
)}
221+
<div className="post-thumbnail">
222+
<div className="thumbnail-skeleton-wrapper">
223+
<Skeleton className="skeleton" />
224+
</div>
225+
</div>
226+
<h2>
227+
<SkeletonTexts wordLengths={[4, 3, 2, 5, 3, 6]} useFlex />
228+
</h2>
229+
<div className="short-description">
230+
<div className="line">
231+
<SkeletonTexts wordLengths={[2, 4, 3, 6, 2, 7]} useFlex />
232+
</div>
233+
<div className="line">
234+
<SkeletonTexts wordLengths={[3, 2, 3, 4, 7, 3]} useFlex />
235+
</div>
236+
<div className="line">
237+
<SkeletonTexts wordLengths={[4, 3, 3]} />
238+
</div>
239+
</div>
240+
<div className="tags-skeleton">
241+
<Skeleton width="6rem" marginRight="0.875rem" />
242+
<Skeleton width="4rem" marginRight="0.875rem" />
243+
<Skeleton width="5rem" noSpacing />
244+
</div>
245+
<div className="subinfo">
246+
<Skeleton width="3em" marginRight="1rem" />
247+
<Skeleton width="6em" noSpacing />
248+
</div>
249+
</SkeletonBlock>
250+
);
251+
}
252+
253+
const SkeletonBlock = styled(PostCardBlock)`
254+
h2 {
255+
display: flex;
256+
margin-top: 1.375rem;
257+
margin-bottom: 0.375rem;
258+
}
259+
.user-thumbnail-skeleton {
260+
width: 3rem;
261+
height: 3rem;
262+
${media.small} {
263+
width: 2rem;
264+
height: 2rem;
265+
}
266+
}
267+
.thumbnail-skeleton-wrapper {
268+
width: 100%;
269+
padding-top: 52.35%;
270+
position: relative;
271+
.skeleton {
272+
position: absolute;
273+
top: 0;
274+
left: 0;
275+
width: 100%;
276+
height: 100%;
277+
}
278+
}
279+
.short-description {
280+
margin-bottom: 2rem;
281+
margin-top: 1rem;
282+
font-size: 1rem;
283+
.line {
284+
display: flex;
285+
}
286+
.line + .line {
287+
margin-top: 0.5rem;
288+
}
289+
}
290+
.tags-skeleton {
291+
line-height: 1;
292+
font-size: 2rem;
293+
${media.small} {
294+
font-size: 1.25rem;
295+
}
296+
}
297+
`;
298+
299+
export default React.memo(FlatPostCard);

src/components/common/PostCardList.tsx renamed to src/components/common/FlatPostCardList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import styled from 'styled-components';
3-
import PostCard, { PostCardSkeleton } from './PostCard';
3+
import PostCard, { PostCardSkeleton } from './FlatPostCard';
44
import { PartialPost } from '../../lib/graphql/post';
55
import palette from '../../lib/styles/palette';
66

0 commit comments

Comments
 (0)