Skip to content
This repository was archived by the owner on Mar 3, 2020. It is now read-only.

Commit 0abd42d

Browse files
committed
Complete user history related UI
1 parent 91f58e0 commit 0abd42d

File tree

7 files changed

+186
-49
lines changed

7 files changed

+186
-49
lines changed

velog-frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"react-router-dom": "^4.2.2",
6262
"react-tooltip": "^3.4.3",
6363
"react-truncate": "^2.4.0",
64-
"recompose": "^0.26.0",
64+
"recompose": "^0.30.0",
6565
"redux": "^3.7.2",
6666
"redux-actions": "^2.4.0",
6767
"redux-pender": "^1.2.1",

velog-frontend/src/components/user/UserHistory/UserHistory.js

Lines changed: 80 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,69 +5,113 @@ import CommentIcon from 'react-icons/lib/fa/comment';
55
import HeartIcon from 'react-icons/lib/fa/heart';
66
import FakeLink from 'components/common/FakeLink';
77
import { type UserHistoryItem } from 'store/modules/profile';
8+
import { onlyUpdateForKeys } from 'recompose';
89
import './UserHistory.scss';
910

1011
type HistoryItemProps = {
1112
username: string,
1213
item: UserHistoryItem,
1314
};
1415

15-
const HistoryItem = ({ item, username }: HistoryItemProps) => {
16-
const url = `/@${item.post.user.username}/${item.post.url_slug}`;
17-
const { type } = item;
18-
return (
19-
<FakeLink className="HistoryItem" to={url}>
20-
{type === 'comment' ? (
21-
<div className="message">
22-
<CommentIcon className="comment" />@{username}님이 댓글을 남기셨습니다.
23-
</div>
24-
) : (
25-
<div className="message">
26-
<HeartIcon className="heart" />
27-
@{username}님이 이 포스트를 좋아합니다.
16+
const HistoryItem = onlyUpdateForKeys(['item', 'username'])(
17+
({ item, username }: HistoryItemProps) => {
18+
const url = `/@${item.post.user.username}/${item.post.url_slug}`;
19+
const { type } = item;
20+
return (
21+
<FakeLink className="HistoryItem" to={url}>
22+
{type === 'comment' ? (
23+
<div className="message">
24+
<CommentIcon className="comment" />@{username}님이 댓글을 남기셨습니다.
25+
</div>
26+
) : (
27+
<div className="message">
28+
<HeartIcon className="heart" />
29+
@{username}님이 이 포스트를 좋아합니다.
30+
</div>
31+
)}
32+
<div className="mini-postcard">
33+
{item.post.thumbnail && (
34+
<Fragment>
35+
<div className="thumbnail">
36+
<img src={item.post.thumbnail} alt="thumbnail" />
37+
</div>
38+
<div className="separator" />
39+
</Fragment>
40+
)}
41+
<div className="post-info">
42+
<h4>
43+
<Link to={url}>{item.post.title}</Link>
44+
</h4>
45+
<p>
46+
{item.post.short_description.slice(0, 150)}
47+
{item.post.short_description.length >= 150 && '...'}
48+
</p>
49+
</div>
2850
</div>
29-
)}
30-
<div className="mini-postcard">
31-
{item.post.thumbnail && (
32-
<Fragment>
33-
<div className="thumbnail">
34-
<img src={item.post.thumbnail} alt="thumbnail" />
35-
</div>
36-
<div className="separator" />
37-
</Fragment>
51+
{type === 'comment' && (
52+
<div className="comment-block">
53+
<div className="mark"></div>
54+
<div className="comment-text">{item.text}</div>
55+
</div>
3856
)}
57+
</FakeLink>
58+
);
59+
},
60+
);
61+
62+
HistoryItem.Placeholder = () => {
63+
return (
64+
<div className="HistoryItem placeholder">
65+
<div className="message gray-box" style={{ width: '60%' }} />
66+
<div className="mini-postcard">
67+
<div className="thumbnail">
68+
<div className="fake-img gray-box" />
69+
</div>
70+
<div className="separator" />
3971
<div className="post-info">
4072
<h4>
41-
<Link to={url}>{item.post.title}</Link>
73+
<span className="gray-box" style={{ width: '60%' }} />
4274
</h4>
4375
<p>
44-
{item.post.short_description.slice(0, 150)}
45-
{item.post.short_description.length >= 150 && '...'}
76+
<span className="gray-box" style={{ width: '90%' }} />
77+
<span className="gray-box" style={{ width: '100%' }} />
78+
<span className="gray-box" style={{ width: '95%' }} />
4679
</p>
4780
</div>
4881
</div>
49-
{type === 'comment' && (
50-
<div className="comment-block">
51-
<div className="mark"></div>
52-
<div className="comment-text">{item.text}</div>
53-
</div>
54-
)}
55-
</FakeLink>
82+
</div>
5683
);
5784
};
5885

5986
type Props = {
60-
username: string,
61-
data: UserHistoryItem[],
87+
username: ?string,
88+
data: ?(UserHistoryItem[]),
89+
loading: boolean,
6290
};
6391
class UserHistory extends Component<Props> {
6492
renderList() {
6593
const { username, data } = this.props;
94+
if (!data || !username) return null;
6695
return data.map(item => <HistoryItem username={username} item={item} key={item.id} />);
6796
}
6897
render() {
69-
return <div className="UserHistory">{this.renderList()}</div>;
98+
return (
99+
<div className="UserHistory">
100+
{this.props.data &&
101+
this.props.data.length === 0 &&
102+
!this.props.loading && <div className="no-history">활동 내역이 없습니다.</div>}
103+
{this.renderList()}
104+
{this.props.loading && (
105+
<Fragment>
106+
<HistoryItem.Placeholder />
107+
<HistoryItem.Placeholder />
108+
<HistoryItem.Placeholder />
109+
<HistoryItem.Placeholder />
110+
</Fragment>
111+
)}
112+
</div>
113+
);
70114
}
71115
}
72116

73-
export default UserHistory;
117+
export default onlyUpdateForKeys(['username', 'data', 'loading'])(UserHistory);

velog-frontend/src/components/user/UserHistory/UserHistory.scss

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
.UserHistory {
33
width: 768px;
44
margin: 0 auto;
5+
.no-history {
6+
color: $oc-gray-6;
7+
font-size: 1.125rem;
8+
text-align: center;
9+
}
510
.HistoryItem {
611
cursor: pointer;
712
padding-top: 2rem;
@@ -80,5 +85,32 @@
8085
padding-left: 1.5rem;
8186
}
8287
}
88+
&.placeholder {
89+
.gray-box {
90+
background: $oc-gray-1;
91+
border-radius: 2px;
92+
height: 1em;
93+
animation: Blink 0.5s ease-in-out;
94+
animation-iteration-count: infinite;
95+
animation-direction: alternate;
96+
display: block;
97+
}
98+
.thumbnail {
99+
background: none;
100+
border: none;
101+
}
102+
.fake-img {
103+
width: 100%;
104+
height: 100%;
105+
}
106+
.post-info {
107+
flex: 1;
108+
}
109+
p .gray-box {
110+
&+.gray-box {
111+
margin-top: 0.5rem;
112+
}
113+
}
114+
}
83115
}
84116
}

velog-frontend/src/containers/user/UserHistoryContainer.js

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,50 @@ import UserHistory from 'components/user/UserHistory/UserHistory';
66
import { withRouter, type ContextRouter } from 'react-router-dom';
77
import { compose } from 'redux';
88
import { type State } from 'store';
9-
import { type UserHistoryItem } from 'store/modules/profile';
9+
import { type UserHistoryItem, type Profile } from 'store/modules/profile';
10+
import throttle from 'lodash/throttle';
11+
import { getScrollBottom, preventStickBottom } from 'lib/common';
1012

1113
type Props = {
1214
userHistory: ?(UserHistoryItem[]),
1315
prefetched: ?(UserHistoryItem[]),
1416
prefetching: boolean,
1517
loading: boolean,
18+
historyEnd: boolean,
19+
profile: ?Profile,
20+
shouldCancel: boolean,
1621
} & ContextRouter;
1722
class UserHistoryContainer extends Component<Props> {
1823
lastOffset: ?number;
1924
initialize = async () => {
20-
const { match } = this.props;
25+
const { match, profile, shouldCancel, userHistory } = this.props;
2126
const { username } = match.params;
2227
try {
2328
if (!username) return;
24-
await ProfileActions.getUserHistory({ username });
29+
if (username === (profile && profile.username) && userHistory) return;
30+
if (!shouldCancel) {
31+
await ProfileActions.getUserHistory({ username });
32+
}
33+
this.prefetch();
2534
} catch (e) {
2635
console.log(e);
2736
}
2837
};
2938

39+
listenScroll = () => {
40+
window.addEventListener('scroll', this.onScroll);
41+
};
42+
43+
unlistenScroll = () => {
44+
window.removeEventListener('scroll', this.onScroll);
45+
};
46+
3047
prefetch = async () => {
31-
const { userHistory, prefetched, loading, prefetching, match } = this.props;
48+
const { userHistory, prefetched, loading, prefetching, match, historyEnd } = this.props;
3249
const { username } = match.params;
3350
if (!username || !userHistory || loading || prefetching) return;
51+
if ((!prefetched || prefetched.length === 0) && historyEnd) return;
52+
3453
// REVEAL
3554
ProfileActions.revealPrefetchedHistory();
3655
await Promise.resolve();
@@ -39,34 +58,52 @@ class UserHistoryContainer extends Component<Props> {
3958
username,
4059
offset: userHistory.length || 0,
4160
});
61+
this.onScroll();
62+
preventStickBottom();
4263
} catch (e) {
4364
console.log(e);
4465
}
4566
};
4667

68+
onScroll = throttle(() => {
69+
const scrollBottom = getScrollBottom();
70+
if (scrollBottom > 1000) return;
71+
this.prefetch();
72+
}, 250);
73+
4774
componentDidMount() {
4875
ProfileActions.setSideVisibility(false);
4976
this.initialize();
77+
this.listenScroll();
5078
}
5179
componentWillUnmount() {
5280
ProfileActions.setSideVisibility(true);
81+
this.unlistenScroll();
5382
}
5483

5584
render() {
56-
const { userHistory, match } = this.props;
57-
if (!userHistory) return null;
58-
return <UserHistory data={userHistory} username={match.params.username || ''} />;
85+
const { userHistory, match, loading, prefetching } = this.props;
86+
return (
87+
<UserHistory
88+
data={userHistory}
89+
username={match.params.username || ''}
90+
loading={loading || prefetching}
91+
/>
92+
);
5993
}
6094
}
6195

6296
const enhance = compose(
6397
withRouter,
6498
connect(
65-
({ profile, pender }: State) => ({
99+
({ profile, pender, common }: State) => ({
66100
userHistory: profile.userHistory,
67101
prefetched: profile.prefetchedHistory,
68102
loading: pender.pending['profile/GET_USER_HISTORY'],
69103
prefetching: pender.pending['profile/PREFETCH_USER_HISTORY'],
104+
historyEnd: profile.historyEnd,
105+
profile: profile.profile,
106+
shouldCancel: common.ssr && !common.router.altered,
70107
}),
71108
() => ({}),
72109
),

velog-frontend/src/lib/common.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export type Return<T> = Return_<*, T>;
4646
export const getScrollTop = () => {
4747
if (!document.body) return 0;
4848
const scrollTop = document.documentElement
49-
? document.documentElement.scrollTop
49+
? document.documentElement.scrollTop || document.body.scrollTop
5050
: document.body.scrollTop;
5151
return scrollTop;
5252
};

velog-frontend/src/store/modules/profile.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export type ProfileState = {
8989
prefetchedHistory: ?(UserHistoryItem[]),
9090
rawTagName: ?string,
9191
side: boolean,
92+
historyEnd: false,
9293
};
9394

9495
const initialState = {
@@ -98,6 +99,7 @@ const initialState = {
9899
userHistory: null,
99100
prefetchedHistory: null,
100101
side: true,
102+
historyEnd: false,
101103
};
102104

103105
const reducer = handleActions(
@@ -172,6 +174,7 @@ export default applyPenders(reducer, [
172174
return {
173175
...state,
174176
userHistory: payload.data,
177+
historyEnd: payload.data.length < 20,
175178
};
176179
},
177180
},
@@ -183,6 +186,7 @@ export default applyPenders(reducer, [
183186
return {
184187
...state,
185188
prefetchedHistory: filtered,
189+
historyEnd: payload.data.length < 20,
186190
};
187191
},
188192
},

0 commit comments

Comments
 (0)