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

Commit 38086cd

Browse files
committed
Implement email permission togglers
1 parent b7b3cd8 commit 38086cd

File tree

10 files changed

+227
-2
lines changed

10 files changed

+227
-2
lines changed

velog-backend/src/router/me/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ me.post('/unregister', meCtrl.unregister);
1313
me.get('/email-info', meCtrl.getEmailInfo);
1414
me.patch('/email', meCtrl.changeEmail);
1515
me.post('/resend-certmail', meCtrl.resendCertmail);
16+
me.patch('/email-permissions', meCtrl.updateEmailPermissions);
1617

1718
export default me;

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,32 @@ export const resendCertmail = async (ctx: Context) => {
213213
ctx.throw(500, e);
214214
}
215215
};
216+
217+
export const updateEmailPermissions = async (ctx: Context) => {
218+
// validate request body
219+
const schema = Joi.object().keys({
220+
email_notification: Joi.boolean().required(),
221+
email_promotion: Joi.boolean().required(),
222+
});
223+
const result = Joi.validate(ctx.request.body, schema);
224+
if (result.error) {
225+
ctx.status = 400;
226+
return;
227+
}
228+
const permissions: {
229+
email_notification: boolean,
230+
email_promotion: boolean,
231+
} = (ctx.request.body: any);
232+
233+
try {
234+
const userMeta = await UserMeta.findOne({
235+
where: {
236+
fk_user_id: ctx.user.id,
237+
},
238+
});
239+
await userMeta.update(permissions);
240+
ctx.status = 204;
241+
} catch (e) {
242+
ctx.throw(500, e);
243+
}
244+
};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// @flow
2+
3+
import React, { Component } from 'react';
4+
import cx from 'classnames';
5+
import './Toggler.scss';
6+
7+
type Props = {
8+
name?: string,
9+
text: string,
10+
value: boolean,
11+
onChange: (event: { name: string, value: boolean }) => void,
12+
className?: string,
13+
};
14+
15+
type State = {
16+
localValue: boolean,
17+
};
18+
19+
class Toggler extends Component<Props, State> {
20+
state = {
21+
localValue: false,
22+
};
23+
24+
constructor(props: Props) {
25+
super(props);
26+
this.state.localValue = props.value;
27+
}
28+
29+
onToggle = () => {
30+
this.setState({
31+
localValue: !this.state.localValue,
32+
});
33+
};
34+
35+
componentDidUpdate(prevProps: Props, prevState: State) {
36+
if (this.state.localValue !== prevState.localValue) {
37+
this.props.onChange({
38+
name: this.props.name || '',
39+
value: this.state.localValue,
40+
});
41+
return;
42+
}
43+
if (this.state.localValue !== this.props.value) {
44+
this.setState({
45+
localValue: this.props.value,
46+
});
47+
}
48+
}
49+
50+
render() {
51+
const { text } = this.props;
52+
return (
53+
<div className={cx('Toggler', this.props.className)}>
54+
<div
55+
className={cx('toggle-box', { active: this.state.localValue })}
56+
onClick={this.onToggle}
57+
>
58+
<div className="circle" />
59+
</div>
60+
<div className="text">{text}</div>
61+
</div>
62+
);
63+
}
64+
}
65+
66+
export default Toggler;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
@import 'utils';
2+
3+
.Toggler {
4+
display: flex;
5+
align-items: center;
6+
& + .Toggler {
7+
margin-top: 0.5rem;
8+
}
9+
10+
.toggle-box {
11+
background: $oc-gray-3;
12+
height: 32px;
13+
width: 56px;
14+
border-radius: 16px;
15+
position: relative;
16+
margin-right: 0.5rem;
17+
cursor: pointer;
18+
transition: 0.125s ease-in background;
19+
.circle {
20+
transition: 0.125s ease-in transform;
21+
background: white;
22+
width: 24px;
23+
height: 24px;
24+
top: 4px;
25+
left: 4px;
26+
border-radius: 12px;
27+
position: absolute;
28+
}
29+
30+
&.active {
31+
background: $oc-violet-6;
32+
.circle {
33+
transform: translate(24px);
34+
}
35+
}
36+
}
37+
.text {
38+
font-size: 0.875rem;
39+
color: $oc-gray-9;
40+
font-weight: 600;
41+
}
42+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// @flow
2+
import Toggler from './Toggler';
3+
4+
export default Toggler;

velog-frontend/src/components/settings/SettingEmail/SettingEmail.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
import React, { Component, Fragment } from 'react';
33
import type { EmailInfoData } from 'store/modules/settings';
44
import WarningIcon from 'react-icons/lib/md/warning';
5+
import Toggler from 'components/common/Toggler';
56
import cx from 'classnames';
67
import './SettingEmail.scss';
78

89
type Props = {
910
emailInfo: EmailInfoData,
11+
onSaveEmailPermissions: () => Promise<any>,
1012
onChangeEmail: (email: string) => Promise<any>,
1113
onResendCertmail: () => Promise<any>,
14+
onUpdateEmailPermission: (payload: { name: string, value: boolean }) => void,
1215
};
1316

1417
type State = {
@@ -62,8 +65,12 @@ class SettingEmail extends Component<Props, State> {
6265
});
6366
};
6467

68+
onChangeToggler = (event: { name: string, value: boolean }) => {
69+
this.props.onUpdateEmailPermission(event);
70+
};
71+
6572
render() {
66-
const { emailInfo } = this.props;
73+
const { emailInfo, onSaveEmailPermissions } = this.props;
6774
const { edit, email } = this.state;
6875
return (
6976
<div className="SettingEmail">
@@ -106,6 +113,27 @@ class SettingEmail extends Component<Props, State> {
106113
</div>
107114
</Fragment>
108115
)}
116+
{!edit &&
117+
emailInfo.is_certified && (
118+
<section>
119+
<h5>이메일 수신 설정</h5>
120+
<div className="togglers">
121+
<Toggler
122+
name="email_notification"
123+
text="댓글 알림"
124+
value={emailInfo.permissions.email_notification}
125+
onChange={this.onChangeToggler}
126+
/>
127+
<Toggler
128+
name="email_promotion"
129+
text="이벤트 및 프로모션"
130+
value={emailInfo.permissions.email_promotion}
131+
onChange={this.onChangeToggler}
132+
/>
133+
</div>
134+
<button onClick={onSaveEmailPermissions}>이메일 수신 설정 저장</button>
135+
</section>
136+
)}
109137
</div>
110138
);
111139
}

velog-frontend/src/components/settings/SettingEmail/SettingEmail.scss

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
display: flex;
99
align-items: center;
1010
justify-content: space-between;
11-
margin-bottom: 0.5rem;
11+
margin-bottom: 1rem;
1212
button {
1313
font-size: 0.875rem;
1414
border-radius: 4px;
@@ -119,4 +119,24 @@
119119
font-weight: 600;
120120
}
121121
}
122+
123+
section {
124+
h5 {
125+
font-size: 1.125rem;
126+
margin-bottom: 1rem;
127+
}
128+
button {
129+
border-radius: 4px;
130+
border: 1px solid $oc-gray-7;
131+
margin-top: 1rem;
132+
font-weight: 600;
133+
font-size: 0.875rem;
134+
padding: 0.5rem;
135+
cursor: pointer;
136+
&:hover {
137+
color: white;
138+
background: $oc-gray-7;
139+
}
140+
}
141+
}
122142
}

velog-frontend/src/containers/settings/SettingEmailContainer.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,26 @@ class SettingEmailContainer extends Component<Props> {
5151
BaseActions.showToast({ type: 'success', message: '이메일이 변경되었습니다.' });
5252
};
5353

54+
onUpdateEmailPermission = (payload: { name: string, value: boolean }) => {
55+
SettingsActions.updateEmailPermission(payload);
56+
};
57+
58+
onSaveEmailPermissions = async () => {
59+
const { emailInfo } = this.props;
60+
if (!emailInfo) return;
61+
await SettingsActions.saveEmailPermissions(emailInfo.permissions);
62+
BaseActions.showToast({ type: 'success', message: '이메일 수신 설정이 저장되었습니다.' });
63+
};
64+
5465
render() {
5566
if (!this.props.emailInfo) return null;
5667
return (
5768
<SettingEmail
5869
emailInfo={this.props.emailInfo}
5970
onChangeEmail={this.onChangeEmail}
6071
onResendCertmail={this.onResendCertmail}
72+
onUpdateEmailPermission={this.onUpdateEmailPermission}
73+
onSaveEmailPermissions={this.onSaveEmailPermissions}
6174
/>
6275
);
6376
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,10 @@ export const unregister = (unregisterToken: string) =>
5454
export const getEmailInfo = () => axios.get('/me/email-info');
5555
export const changeEmail = (email: string) => axios.patch('/me/email', { email });
5656
export const resendCertmail = () => axios.post('/me/resend-certmail');
57+
58+
export type SaveEmailPermissionsPayload = {
59+
email_notification: boolean,
60+
email_promotion: boolean,
61+
};
62+
export const saveEmailPermissions = (payload: SaveEmailPermissionsPayload) =>
63+
axios.patch('/me/email-permissions', payload);

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
getEmailInfo,
1212
changeEmail,
1313
resendCertmail,
14+
saveEmailPermissions,
1415
} from 'lib/api/me';
1516
import produce from 'immer';
1617
import { type Profile } from './profile';
@@ -24,6 +25,8 @@ const UNREGISTER = 'settings/UNREGISTER';
2425
const GET_EMAIL_INFO = 'settings/GET_EMAIL_INFO';
2526
const CHANGE_EMAIL = 'settings/CHANGE_EMAIL';
2627
const RESEND_CERTMAIL = 'settings/RESEND_CERTMAIL';
28+
const UPDATE_EMAIL_PERMISSION = 'settings/UPDATE_EMAIL_PERMISSION';
29+
const SAVE_EMAIL_PERMISSIONS = 'settings/SAVE_EMAIL_PERMISSIONS';
2730

2831
export const actionCreators = {
2932
getProfile: createAction(GET_PROFILE, getProfile),
@@ -35,6 +38,11 @@ export const actionCreators = {
3538
getEmailInfo: createAction(GET_EMAIL_INFO, getEmailInfo),
3639
changeEmail: createAction(CHANGE_EMAIL, changeEmail),
3740
resendCertmail: createAction(RESEND_CERTMAIL, resendCertmail),
41+
updateEmailPermission: createAction(
42+
UPDATE_EMAIL_PERMISSION,
43+
(payload: { name: string, value: boolean }) => payload,
44+
),
45+
saveEmailPermissions: createAction(SAVE_EMAIL_PERMISSIONS, saveEmailPermissions),
3846
};
3947

4048
export type UploadInfo = {
@@ -61,6 +69,7 @@ type GenerateUnregisterTokenResponseAction = GenericResponseAction<
6169
void,
6270
>;
6371
type GetMailInfoResponseAction = GenericResponseAction<EmailInfoData, void>;
72+
type UpdateEmailPermissionAction = ActionType<typeof actionCreators.updateEmailPermission>;
6473

6574
export type SettingsState = {
6675
profile: ?Profile,
@@ -86,6 +95,12 @@ const reducer = handleActions(
8695
askUnregister: payload,
8796
};
8897
},
98+
[UPDATE_EMAIL_PERMISSION]: (state, { payload }: UpdateEmailPermissionAction) => {
99+
return produce(state, (draft) => {
100+
if (!draft.emailInfo) return;
101+
draft.emailInfo.permissions[payload.name] = payload.value;
102+
});
103+
},
89104
},
90105
initialState,
91106
);

0 commit comments

Comments
 (0)