Skip to content

Commit e1ecd8c

Browse files
committed
Implement user tag view (only for desktop for now)
Mobile view will be implemented tomorrow. #59
1 parent bd17663 commit e1ecd8c

File tree

10 files changed

+237
-10
lines changed

10 files changed

+237
-10
lines changed

Diff for: src/components/search/SearchInput.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ function SearchInput({
104104
}: SearchInputProps) {
105105
const [focus, toggleFocus] = useToggle(false);
106106
const [value, onChange] = useInput(initial);
107+
const mounted = useRef(false);
107108

108109
const inputRef = useRef<HTMLInputElement>(null);
109110

@@ -124,6 +125,10 @@ function SearchInput({
124125
};
125126

126127
useEffect(() => {
128+
if (!mounted.current) {
129+
mounted.current = true;
130+
return;
131+
}
127132
if (searchAsYouType) {
128133
debouncedSearch(value);
129134
}

Diff for: src/components/velog/SideArea.tsx

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import media from '../../lib/styles/media';
4+
import palette from '../../lib/styles/palette';
5+
6+
export type SideAreaProps = {
7+
children: React.ReactNode;
8+
};
9+
10+
function SideArea({ children }: SideAreaProps) {
11+
return (
12+
<Wrapper>
13+
<Block>{children}</Block>
14+
</Wrapper>
15+
);
16+
}
17+
18+
const Wrapper = styled.div`
19+
position: relative;
20+
`;
21+
22+
const Block = styled.div`
23+
position: absolute;
24+
margin-top: 4.25rem;
25+
width: 11.5rem;
26+
left: -13.5rem;
27+
28+
${media.large} {
29+
display: none;
30+
}
31+
`;
32+
33+
export default SideArea;

Diff for: src/components/velog/UserTagVerticalList.tsx

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import palette from '../../lib/styles/palette';
4+
import SideArea from './SideArea';
5+
import { Tag } from '../../lib/graphql/tags';
6+
import { Link } from 'react-router-dom';
7+
import { escapeForUrl } from '../../lib/utils';
8+
9+
export type UserTagVerticalListProps = {
10+
active: string | null;
11+
tags: Tag[];
12+
postsCount: number;
13+
username: string;
14+
};
15+
16+
function UserTagVerticalList({
17+
tags,
18+
active,
19+
postsCount,
20+
username,
21+
}: UserTagVerticalListProps) {
22+
return (
23+
<SideArea>
24+
<Block>
25+
<div className="title">태그 목록</div>
26+
<ul>
27+
<ListItem active={active === null}>
28+
<Link to={`/@${username}`}>전체보기</Link>
29+
<span>({postsCount})</span>
30+
</ListItem>
31+
{tags.map(tag => (
32+
<ListItem active={active === escapeForUrl(tag.name)} key={tag.id}>
33+
<Link to={`@${username}?tag=${escapeForUrl(tag.name)}`}>
34+
{tag.name}
35+
</Link>
36+
<span>({tag.posts_count})</span>
37+
</ListItem>
38+
))}
39+
</ul>
40+
</Block>
41+
</SideArea>
42+
);
43+
}
44+
45+
const Block = styled.div`
46+
.title {
47+
font-size: 1rem;
48+
line-height: 1.5;
49+
padding-bottom: 0.5rem;
50+
border-bottom: 1px solid ${palette.gray5};
51+
margin-bottom: 1rem;
52+
color: ${palette.gray7};
53+
font-weight: bold;
54+
}
55+
ul {
56+
list-style: none;
57+
/* margin-left: 0; */
58+
padding-left: 0;
59+
}
60+
`;
61+
62+
const ListItem = styled.li<{ active?: boolean }>`
63+
color: ${palette.gray8};
64+
font-size: 0.875rem;
65+
line-height: 1.5;
66+
67+
a {
68+
color: inherit;
69+
text-decoration: none;
70+
&:hover {
71+
color: ${palette.gray9};
72+
${props =>
73+
props.active &&
74+
`
75+
color: ${palette.teal5};
76+
`}
77+
text-decoration: underline;
78+
span {
79+
text-decoration: none;
80+
}
81+
}
82+
}
83+
84+
${props =>
85+
props.active &&
86+
`
87+
color: ${palette.teal5};
88+
font-weight: bold;
89+
`}
90+
91+
span {
92+
margin-left: 0.5rem;
93+
color: ${palette.gray6};
94+
font-weight: normal;
95+
}
96+
97+
& + & {
98+
margin-top: 0.25rem;
99+
}
100+
`;
101+
102+
export default UserTagVerticalList;

Diff for: src/components/velog/UserTags.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react';
2+
3+
import UserTagVerticalList from './UserTagVerticalList';
4+
import useUserTags from './hooks/useUserTags';
5+
6+
export type UserTagsProps = {
7+
username: string;
8+
tag: string | null;
9+
};
10+
11+
function UserTags({ username, tag }: UserTagsProps) {
12+
const { data, loading } = useUserTags(username);
13+
if (!data || loading) return null;
14+
15+
return (
16+
<>
17+
<UserTagVerticalList
18+
active={tag}
19+
tags={data.tags}
20+
postsCount={data.postsCount}
21+
username={username}
22+
/>
23+
</>
24+
);
25+
}
26+
27+
export default UserTags;

Diff for: src/components/velog/hooks/useUserTags.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useQuery } from '@apollo/react-hooks';
2+
import { GET_USER_TAGS, GetUserTagsResponse } from '../../../lib/graphql/tags';
3+
4+
export default function useUserTags(username: string) {
5+
const { data, loading } = useQuery<GetUserTagsResponse>(GET_USER_TAGS, {
6+
variables: {
7+
username,
8+
},
9+
});
10+
11+
return {
12+
data: data
13+
? { tags: data.userTags.tags, postsCount: data.userTags.posts_count }
14+
: null,
15+
loading,
16+
};
17+
}

Diff for: src/components/write/Toolbar.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ import {
99
MdFormatQuote,
1010
MdImage,
1111
MdCode,
12-
MdRemoveRedEye,
1312
} from 'react-icons/md';
1413
import palette from '../../lib/styles/palette';
1514
import zIndexes from '../../lib/styles/zIndexes';
16-
import { FaMarkdown } from 'react-icons/fa';
1715

1816
// box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.09);
1917
const ToolbarBlock = styled.div<{

Diff for: src/containers/velog/UserPosts.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import styled from 'styled-components';
1111

1212
interface UserPostsProps {
1313
username: string;
14+
tag: string | null;
1415
}
1516

16-
const UserPosts: React.FC<UserPostsProps> = ({ username }) => {
17+
const UserPosts: React.FC<UserPostsProps> = ({ username, tag }) => {
1718
const getPostList = useQuery<{ posts: PartialPost[] }>(GET_POST_LIST, {
1819
variables: {
1920
username,
21+
tag,
2022
},
2123
notifyOnNetworkStatusChange: true,
2224
});
@@ -29,6 +31,7 @@ const UserPosts: React.FC<UserPostsProps> = ({ username }) => {
2931
variables: {
3032
cursor,
3133
username,
34+
tag,
3235
},
3336
updateQuery: (prev, { fetchMoreResult }) => {
3437
if (!fetchMoreResult) return prev;
@@ -38,7 +41,7 @@ const UserPosts: React.FC<UserPostsProps> = ({ username }) => {
3841
},
3942
});
4043
},
41-
[getPostList, username],
44+
[getPostList, tag, username],
4245
);
4346

4447
if (!data || !data.posts) return <PostCardListSkeleton hideUser={true} />;

Diff for: src/lib/graphql/tags.ts

+22
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,21 @@ export const GET_TAGS = gql`
2020
}
2121
`;
2222

23+
export const GET_USER_TAGS = gql`
24+
query UserTags($username: String) {
25+
userTags(username: $username) {
26+
tags {
27+
id
28+
name
29+
description
30+
posts_count
31+
thumbnail
32+
}
33+
posts_count
34+
}
35+
}
36+
`;
37+
2338
export const GET_TAG = gql`
2439
query Tag($name: String!) {
2540
tag(name: $name) {
@@ -36,6 +51,13 @@ export type GetTagsResponse = {
3651
tags: Tag[];
3752
};
3853

54+
export type GetUserTagsResponse = {
55+
userTags: {
56+
tags: Tag[];
57+
posts_count: number;
58+
};
59+
};
60+
3961
export type GetTagResponse = {
4062
tag: Tag;
4163
};

Diff for: src/pages/velog/UserPage.tsx

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useEffect } from 'react';
22
import styled from 'styled-components';
33
import VelogResponsive from '../../components/velog/VelogResponsive';
44
import UserProfileContainer from '../../containers/velog/UserProfileContainer';
@@ -9,20 +9,34 @@ import SeriesTab from './tabs/SeriesTab';
99
import AboutTab from './tabs/AboutTab';
1010
import palette from '../../lib/styles/palette';
1111
import media from '../../lib/styles/media';
12+
import UserTags from '../../components/velog/UserTags';
13+
import qs from 'qs';
14+
import { usePrevious } from 'react-use';
1215

1316
const UserPageBlock = styled(VelogResponsive)``;
1417

1518
export interface UserPageProps
1619
extends RouteComponentProps<{ username: string; tab?: 'series' | 'about' }> {}
1720

18-
const UserPage: React.FC<UserPageProps> = ({ match }) => {
21+
const UserPage: React.FC<UserPageProps> = ({ match, location }) => {
1922
const { username, tab } = match.params;
23+
const { tag } = qs.parse(location.search, {
24+
ignoreQueryPrefix: true,
25+
}) as { tag: string | undefined };
26+
27+
const prevTag = usePrevious(tag);
28+
useEffect(() => {
29+
if (prevTag !== tag) {
30+
window.scrollTo(0, 0);
31+
}
32+
}, [prevTag, tag]);
2033

2134
return (
2235
<UserPageBlock>
2336
<UserProfileContainer username={username} />
2437
<MobileSeparator />
2538
<VelogTab username={username} tab={tab || 'posts'} />
39+
<UserTags username={username} tag={tag || null} />
2640
<Route path="/@:username" exact component={UserPostsTab} />
2741
<Route path="/@:username/series" component={SeriesTab} />
2842
<Route path="/@:username/about" component={AboutTab} />

Diff for: src/pages/velog/tabs/UserPostsTab.tsx

+10-4
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,15 @@ const UserPostsTab: React.FC<UserPostsTabProps> = ({
1919
location,
2020
history,
2121
}) => {
22-
const { q }: { q: string | undefined } = qs.parse(location.search, {
23-
ignoreQueryPrefix: true,
24-
});
22+
const {
23+
q,
24+
tag,
25+
}: { q: string | undefined; tag: string | undefined } = qs.parse(
26+
location.search,
27+
{
28+
ignoreQueryPrefix: true,
29+
},
30+
);
2531

2632
const { username } = match.params;
2733
usePreserveScroll('user/posts');
@@ -47,7 +53,7 @@ const UserPostsTab: React.FC<UserPostsTabProps> = ({
4753
{q ? (
4854
<SearchResult username={username} keyword={q} />
4955
) : (
50-
<UserPosts username={match.params.username} />
56+
<UserPosts username={match.params.username} tag={tag || null} />
5157
)}
5258
</Block>
5359
</>

0 commit comments

Comments
 (0)