Skip to content

Commit edbbc08

Browse files
committed
Implements basic API integration for series feature
1 parent 9b2a61c commit edbbc08

File tree

10 files changed

+327
-30
lines changed

10 files changed

+327
-30
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ const Series = db.define(
2222
{
2323
fields: ['created_at'],
2424
},
25+
{
26+
fields: ['updated_at'],
27+
},
2528
{
2629
fields: ['fk_user_id', 'url_slug'],
2730
},
@@ -45,6 +48,7 @@ export const serializeSeries = data => ({
4548
'thumbnail',
4649
'url_slug',
4750
'created_at',
51+
'updated_at',
4852
]),
4953
user: {
5054
username: data.user.username,

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export const createSeries = async (ctx: Context) => {
125125
name, description, url_slug, posts, thumbnail,
126126
} = (ctx.request
127127
.body: any);
128-
if (checkEmpty(name) || checkEmpty(description)) {
128+
if (checkEmpty(name)) {
129129
ctx.status = 400;
130130
ctx.body = {
131131
name: 'EMPTY_FIELD',
@@ -180,6 +180,7 @@ export const listSeries = async (ctx: Context) => {
180180
include: [UserProfile],
181181
},
182182
],
183+
order: [['updated_at', 'DESC']],
183184
});
184185
ctx.body = seriesList.map(serializeSeries);
185186
} catch (e) {
@@ -273,7 +274,7 @@ export const updateSeries = async (ctx: Context) => {
273274
name, description, url_slug, posts, thumbnail,
274275
} = (ctx.request
275276
.body: any);
276-
if (checkEmpty(name) || checkEmpty(description)) {
277+
if (checkEmpty(name)) {
277278
ctx.status = 400;
278279
ctx.body = {
279280
name: 'EMPTY_FIELD',

velog-frontend/src/components/write/SubmitBoxSeries/SubmitBoxSeries.js

Lines changed: 116 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,130 @@
11
// @flow
2-
import React, { Component } from 'react';
2+
import React, { Component, Fragment } from 'react';
3+
import { escapeForUrl } from 'lib/common';
4+
import cn from 'classnames';
5+
import { type SeriesItemData } from 'store/modules/write';
36
import './SubmitBoxSeries.scss';
7+
import Button from '../../common/Button/Button';
8+
9+
type Props = {
10+
onCreateSeries: (payload: { name: string, urlSlug: string }) => any,
11+
list: ?(SeriesItemData[]),
12+
};
13+
type State = {
14+
cancelling: boolean,
15+
editing: boolean,
16+
name: string,
17+
urlSlug: string,
18+
};
19+
class SubmitBoxSeries extends Component<Props, State> {
20+
input = React.createRef();
21+
timeoutId: ?any = null;
22+
23+
state = {
24+
cancelling: false,
25+
editing: false,
26+
name: '',
27+
urlSlug: '',
28+
};
29+
30+
onStartEditing = () => {
31+
this.setState({
32+
editing: true,
33+
});
34+
};
35+
36+
onCancelEditing = () => {
37+
this.setState({
38+
cancelling: true,
39+
});
40+
this.timeoutId = setTimeout(() => {
41+
this.setState({ editing: false }, () => {
42+
this.setState({
43+
cancelling: false,
44+
});
45+
});
46+
}, 150);
47+
};
48+
49+
onCreateSeries = () => {
50+
const { name, urlSlug } = this.state;
51+
this.props.onCreateSeries({
52+
name,
53+
urlSlug,
54+
});
55+
this.onCancelEditing();
56+
};
57+
58+
onUrlWrapperClick = () => {
59+
if (!this.input.current) return;
60+
this.input.current.focus();
61+
};
62+
63+
onChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
64+
const { value, name } = e.target;
65+
this.setState({
66+
[name]: value,
67+
});
68+
};
69+
70+
componentDidUpdate(prevProps: Props, prevState: State) {
71+
if (prevState.name !== this.state.name) {
72+
this.setState({
73+
urlSlug: escapeForUrl(this.state.name),
74+
});
75+
}
76+
}
77+
78+
componentWillUnmount() {
79+
if (this.timeoutId) {
80+
clearTimeout(this.timeoutId);
81+
}
82+
}
483

5-
type Props = {};
6-
class SubmitBoxSeries extends Component<Props> {
784
render() {
85+
const { name, urlSlug, editing, cancelling } = this.state;
86+
const { list } = this.props;
87+
888
return (
989
<div className="SubmitBoxSeries">
1090
<h3>시리즈 설정</h3>
1191
<div className="list-wrapper">
12-
<form>
13-
<input placeholder="새로운 시리즈 이름을 입력하세요." />
14-
</form>
92+
<div className={cn('create-form', { editing, cancelling })}>
93+
<input
94+
className="regular"
95+
placeholder="새로운 시리즈 이름을 입력하세요."
96+
onChange={this.onChange}
97+
onFocus={this.onStartEditing}
98+
name="name"
99+
value={name}
100+
/>
101+
{editing && (
102+
<div className={cn('editing', { cancelling })}>
103+
<div className="url-wrapper" onClick={this.onUrlWrapperClick}>
104+
<div className="text">/@velopert/series/</div>
105+
<input name="urlSlug" ref={this.input} onChange={this.onChange} value={urlSlug} />
106+
</div>
107+
<div className="buttons">
108+
<Button cancel onClick={this.onCancelEditing}>
109+
취소
110+
</Button>
111+
<Button onClick={this.onCreateSeries}>추가</Button>
112+
</div>
113+
</div>
114+
)}
115+
</div>
15116
<div className="list">
16-
<div className="item">하이 나는 아이템</div>
17-
<div className="item">하이 나는 아이템</div>
18-
<div className="item active">하이 나는 아이템</div>
19-
<div className="item">하이 나는 아이템</div>
20-
<div className="item">하이 나는 아이템</div>
21-
<div className="item">하이 나는 아이템</div>
117+
{list &&
118+
list.map(item => (
119+
<div className="item" key={item.id}>
120+
{item.name}
121+
</div>
122+
))}
22123
</div>
23124
</div>
125+
<Button large fullWidth className="select">
126+
선택
127+
</Button>
24128
</div>
25129
);
26130
}

velog-frontend/src/components/write/SubmitBoxSeries/SubmitBoxSeries.scss

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
@import 'utils';
2+
3+
@keyframes editingAppear {
4+
0% {
5+
opacity: 0;
6+
transform: translateY(-2.5625rem);
7+
}
8+
100% {
9+
opacity: 1;
10+
tranform: translateY(0rem);
11+
}
12+
}
13+
14+
@keyframes editingDisappear {
15+
0% {
16+
opacity: 1;
17+
tranform: translateY(0rem);
18+
}
19+
100% {
20+
opacity: 0;
21+
transform: translateY(-2.5625rem);
22+
}
23+
}
224
.SubmitBoxSeries {
3-
border: 1px solid $oc-gray-3;
25+
border: 1px solid $oc-gray-7;
426
background: white;
527
padding: 1.5rem;
628
color: $oc-gray-9;
@@ -11,19 +33,71 @@
1133
.list-wrapper {
1234
margin-top: 0.75rem;
1335
border: 1px solid $oc-gray-2;
14-
form {
36+
height: 17.8125rem;
37+
display: flex;
38+
flex-direction: column;
39+
.create-form {
1540
background: $oc-gray-2;
1641
padding: 0.75rem;
42+
transition: 0.15s all ease-in-out;
43+
height: 3.6875rem;
44+
&.editing {
45+
height: 8.625rem;
46+
}
47+
&.cancelling {
48+
height: 3.6875rem;
49+
}
50+
1751
input {
18-
outline: none;
19-
width: 100%;
20-
font-size: 0.875rem;
52+
&.regular {
53+
position: relative;
54+
z-index: 2;
55+
border: 1px solid $oc-gray-3;
56+
outline: none;
57+
width: 100%;
58+
font-size: 0.875rem;
59+
padding: 0.5rem;
60+
}
61+
}
62+
.url-wrapper {
2163
padding: 0.5rem;
64+
border: 1px solid $oc-gray-3;
65+
display: flex;
66+
font-size: 0.75rem;
67+
background: white;
68+
margin-top: 0.5rem;
69+
line-height: 1.125;
70+
align-items: center;
71+
.text {
72+
color: $oc-gray-6;
73+
}
74+
input {
75+
color: $oc-gray-9;
76+
display: block;
77+
flex: 1;
78+
font-size: 0.75rem;
79+
line-height: 1.125;
80+
border: none;
81+
outline: none;
82+
}
83+
}
84+
.editing {
85+
animation: editingAppear 0.15s ease-in-out;
86+
animation-fill-mode: forwards;
87+
&.cancelling {
88+
animation: editingDisappear 0.15s ease-in-out;
89+
animation-fill-mode: forwards;
90+
}
91+
.buttons {
92+
display: flex;
93+
justify-content: flex-end;
94+
margin-top: 0.5rem;
95+
}
2296
}
2397
}
2498
.list {
99+
flex: 1;
25100
background: $oc-gray-0;
26-
height: 14rem;
27101
overflow-y: auto;
28102
.item {
29103
background: white;
@@ -37,4 +111,7 @@
37111
}
38112
}
39113
}
114+
.select {
115+
margin-top: 1rem;
116+
}
40117
}

velog-frontend/src/components/write/WriteSeriesConfigure/WriteSeriesConfigure.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import './WriteSeriesConfigure.scss';
66

77
type Props = {
88
current: ?string,
9+
onOpenModal: () => any,
910
};
1011

11-
const WriteSeriesConfigure = ({ current }: Props) => {
12+
const WriteSeriesConfigure = ({ current, onOpenModal }: Props) => {
1213
return (
13-
<div className="WriteSeriesConfigure">
14+
<div className="WriteSeriesConfigure" onClick={onOpenModal}>
1415
{current ? (
1516
<div className="edit" />
1617
) : (

velog-frontend/src/containers/write/SubmitBoxContainer.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type Props = {
3535
username: ?string,
3636
urlSlug: ?string,
3737
isPrivate: boolean,
38+
seriesMode: boolean,
3839
} & ContextRouter;
3940

4041
class SubmitBoxContainer extends Component<Props> {
@@ -317,6 +318,7 @@ class SubmitBoxContainer extends Component<Props> {
317318
username,
318319
urlSlug,
319320
isPrivate,
321+
seriesMode,
320322
} = this.props;
321323

322324
const postLink = username && postData && `/@${username}/${postData.url_slug}`;
@@ -359,7 +361,7 @@ class SubmitBoxContainer extends Component<Props> {
359361
/>
360362
)
361363
}
362-
series={<SubmitBoxSeriesContainer />}
364+
series={seriesMode && <SubmitBoxSeriesContainer />}
363365
/>
364366
);
365367
}
@@ -383,6 +385,7 @@ const enhance = compose(
383385
username: user.user && user.user.username,
384386
urlSlug: write.submitBox.url_slug,
385387
isPrivate: write.submitBox.is_private,
388+
seriesMode: write.seriesModal.visible,
386389
}),
387390
() => ({}),
388391
),

0 commit comments

Comments
 (0)