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

Commit 355f44d

Browse files
committed
Implements series view
1 parent c1b6786 commit 355f44d

File tree

13 files changed

+224
-12
lines changed

13 files changed

+224
-12
lines changed

velog-backend/src/database/models/Series.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export const serializeSeries = data => ({
5151
'updated_at',
5252
]),
5353
user: {
54+
id: data.user.id,
55+
short_bio: data.user.user_profile.short_bio,
5456
username: data.user.username,
5557
thumbnail: data.user.user_profile.thumbnail,
5658
},

velog-backend/src/router/series/series.ctrl.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
// @flow
22
import type { Context } from 'koa';
33
import Joi from 'joi';
4-
import { checkEmpty, validateSchema, isUUID } from 'lib/common';
4+
import {
5+
checkEmpty,
6+
validateSchema,
7+
isUUID,
8+
formatShortDescription,
9+
} from 'lib/common';
510
import { UserProfile, User, Post } from 'database/models';
611
import pick from 'lodash/pick';
7-
import {
8-
getSeriesPostCountList,
9-
} from 'database/rawQuery/series';
12+
import { getSeriesPostCountList } from 'database/rawQuery/series';
1013
import SeriesPosts from '../../database/models/SeriesPosts';
1114
import Series, { serializeSeries } from '../../database/models/Series';
1215

@@ -238,7 +241,8 @@ export const getSeries = async (ctx: Context) => {
238241
});
239242
serialized.posts = seriesPosts.map(p => ({
240243
index: p.index,
241-
...pick(p.post, ['id', 'thumbnail', 'title', 'released_at']),
244+
...pick(p.post, ['id', 'thumbnail', 'title', 'released_at', 'meta', 'url_slug']),
245+
body: formatShortDescription(p.post.body),
242246
}));
243247
ctx.body = serialized;
244248
} catch (e) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// @flow
2+
import React from 'react';
3+
import type { SeriesPostData } from 'store/modules/series';
4+
import { fromNow } from 'lib/common';
5+
import { Link } from 'react-router-dom';
6+
7+
import './SeriesPostItem.scss';
8+
9+
type Props = {
10+
post: SeriesPostData,
11+
username: string,
12+
};
13+
const SeriesPostItem = ({ post, username }: Props) => {
14+
return (
15+
<div className="SeriesPostItem">
16+
<h2>
17+
<Link to={`/@${username}/${post.url_slug}`}>{post.title}</Link>
18+
</h2>
19+
<div className="date">{fromNow(post.released_at)}</div>
20+
<p>{post.meta.short_description || post.body}</p>
21+
</div>
22+
);
23+
};
24+
25+
export default SeriesPostItem;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
@import 'utils';
2+
3+
.SeriesPostItem {
4+
padding-top: 2rem;
5+
padding-bottom: 2rem;
6+
h2 {
7+
margin: 0;
8+
font-size: 1.75rem;
9+
line-height: 1.5;
10+
a {
11+
&:hover {
12+
color: $oc-violet-4;
13+
text-decoration: underline;
14+
}
15+
}
16+
}
17+
.date {
18+
font-size: 0.875rem;
19+
color: $oc-gray-6;
20+
margin-bottom: 2rem;
21+
}
22+
p {
23+
font-size: 1.125rem;
24+
line-height: 1.5;
25+
color: $oc-gray-8;
26+
margin-bottom: 0;
27+
margin-top: 0;
28+
}
29+
& + & {
30+
border-top: 1px solid $oc-gray-2;
31+
}
32+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// @flow
2+
export { default } from './SeriesPostItem';

velog-frontend/src/components/series/SeriesViewer/SeriesViewer.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
// @flow
22
import React from 'react';
3+
import BookIcon from 'react-icons/lib/md/book';
4+
import { type SeriesData } from 'store/modules/series';
35
import './SeriesViewer.scss';
46
import HorizontalUserInfo from '../../common/HorizontalUserInfo/HorizontalUserInfo';
7+
import SeriesPostItem from '../SeriesPostItem/SeriesPostItem';
58

6-
type Props = {};
7-
const SeriesViewer = (props: Props) => {
9+
type Props = {
10+
series: SeriesData,
11+
};
12+
const SeriesViewer = ({ series }: Props) => {
813
return (
914
<div className="SeriesViewer">
10-
<HorizontalUserInfo.Placeholder />
15+
<HorizontalUserInfo user={series.user} />
16+
<div className="series-label">
17+
<BookIcon />
18+
<span>SERIES</span>
19+
</div>
20+
<h1>{series.name}</h1>
21+
<div className="list">
22+
{series.posts.map(p => (
23+
<SeriesPostItem key={p.id} post={p} username={series.user.username} />
24+
))}
25+
</div>
1126
</div>
1227
);
1328
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,32 @@
11
@import 'utils';
22
.SeriesViewer {
33
margin-top: 4rem;
4+
color: $oc-gray-9;
45
@include media('<medium') {
56
margin-top: 0;
67
}
8+
9+
.series-label {
10+
background: $oc-violet-4;
11+
color: white;
12+
width: 7.5rem;
13+
height: 3rem;
14+
font-size: 1.5rem;
15+
display: flex;
16+
align-items: center;
17+
justify-content: center;
18+
font-weight: 800;
19+
span {
20+
margin-left: 0.25rem;
21+
}
22+
}
23+
24+
h1 {
25+
margin-top: 1.5rem;
26+
color: $oc-gray-9;
27+
font-size: 2.5rem;
28+
line-height: 1.5;
29+
margin-bottom: 1rem;
30+
font-weight: 800;
31+
}
732
}
Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,38 @@
11
// @flow
22
import React, { Component } from 'react';
3+
import { withRouter, type ContextRouter } from 'react-router-dom';
4+
import { SeriesActions } from 'store/actionCreators';
5+
import { connect } from 'react-redux';
6+
import { type State } from 'store';
7+
import { type SeriesData } from 'store/modules/series';
38
import SeriesViewer from '../../components/series/SeriesViewer/SeriesViewer';
49

5-
type Props = {};
10+
type Props = {
11+
series: ?SeriesData,
12+
} & ContextRouter;
613

714
class SeriesViewerContainer extends Component<Props> {
15+
initialize = () => {
16+
const { username, urlSlug } = this.props.match.params;
17+
if (!username || !urlSlug) return;
18+
SeriesActions.getSeries({
19+
username,
20+
urlSlug,
21+
});
22+
};
23+
componentDidMount() {
24+
this.initialize();
25+
}
26+
827
render() {
9-
return <SeriesViewer />;
28+
const { series } = this.props;
29+
if (!series) return null;
30+
return <SeriesViewer series={series} />;
1031
}
1132
}
1233

13-
export default SeriesViewerContainer;
34+
export default withRouter(
35+
connect((state: State) => ({
36+
series: state.series.series,
37+
}))(SeriesViewerContainer),
38+
);

velog-frontend/src/lib/api/series.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,10 @@ export type CreateSeriesPayload = {
1212
export const createSeries = (payload: CreateSeriesPayload) => axios.post('/series', payload);
1313

1414
export const getSeriesList = (username: string) => axios.get(`/series/${username}`);
15+
16+
export type GetSeriesParams = {
17+
username: string,
18+
urlSlug: string,
19+
};
20+
export const getSeries = ({ username, urlSlug }: GetSeriesParams) =>
21+
axios.get(`/series/${username}/${urlSlug}`);

velog-frontend/src/lib/common.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export type ResponseAction = {
3232
meta: any,
3333
};
3434

35-
export type GenericResponseAction<D, M> = {
35+
export type GenericResponseAction<D, M = any> = {
3636
type: string,
3737
payload: {
3838
data: D,

velog-frontend/src/store/actionCreators.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { actionCreators as followActions } from './modules/follow';
1313
import { actionCreators as commonActions } from './modules/common';
1414
import { actionCreators as settingsActions } from './modules/settings';
1515
import { actionCreators as searchActions } from './modules/search';
16+
import { actionCreators as seriesActions } from './modules/series';
1617

1718
const { dispatch } = store;
1819

@@ -31,3 +32,4 @@ export const SettingsActions: typeof settingsActions = bindActionCreators(
3132
dispatch,
3233
);
3334
export const SearchActions: typeof searchActions = bindActionCreators(searchActions, dispatch);
35+
export const SeriesActions: typeof seriesActions = bindActionCreators(seriesActions, dispatch);

velog-frontend/src/store/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { FollowState } from './modules/follow';
1212
import type { CommonState } from './modules/common';
1313
import type { SettingsState } from './modules/settings';
1414
import type { SearchState } from './modules/search';
15+
import type { SeriesState } from './modules/series';
1516

1617
const store = configure(typeof window === 'undefined' ? undefined : window.__REDUX_STATE__);
1718

@@ -30,6 +31,7 @@ export type State = {
3031
common: CommonState,
3132
settings: SettingsState,
3233
search: SearchState,
34+
series: SeriesState,
3335
pender: {
3436
pending: any,
3537
success: any,
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// @flow
2+
import { createAction, handleActions, type ActionType } from 'redux-actions';
3+
import produce from 'immer';
4+
import { applyPenders, type GenericResponseAction } from 'lib/common';
5+
import * as SeriesAPI from 'lib/api/series';
6+
7+
const GET_SERIES = 'series/GET_SERIES';
8+
const INITIALIZE = 'series/INITIALIZE';
9+
10+
export const actionCreators = {
11+
getSeries: createAction(GET_SERIES, SeriesAPI.getSeries),
12+
initialize: createAction(INITIALIZE),
13+
};
14+
15+
export type SeriesPostData = {
16+
index: number,
17+
id: string,
18+
thumbnail: ?string,
19+
title: string,
20+
released_at: string,
21+
meta: {
22+
code_theme: string,
23+
short_description: string,
24+
},
25+
body: string,
26+
url_slug: string,
27+
};
28+
export type SeriesData = {
29+
id: string,
30+
name: string,
31+
description: string,
32+
thumbnail: ?string,
33+
created_at: string,
34+
updated_at: string,
35+
user: {
36+
id: string,
37+
short_bio: ?string,
38+
username: string,
39+
thumbnail: ?string,
40+
},
41+
posts: SeriesPostData[],
42+
};
43+
44+
type GetSeriesResponseAction = GenericResponseAction<SeriesData>;
45+
46+
export type SeriesState = {
47+
series: ?SeriesData,
48+
};
49+
50+
const initialState: SeriesState = {
51+
series: null,
52+
};
53+
54+
const reducer = handleActions(
55+
{
56+
[INITIALIZE]: () => initialState,
57+
},
58+
initialState,
59+
);
60+
61+
export default applyPenders(reducer, [
62+
{
63+
type: GET_SERIES,
64+
onSuccess: (state: SeriesState, { payload }: GetSeriesResponseAction) => {
65+
return {
66+
...state,
67+
series: payload.data,
68+
};
69+
},
70+
},
71+
]);

0 commit comments

Comments
 (0)