Skip to content

Commit 7b8e93b

Browse files
authored
Merge pull request #428 from velopert/feature/change-email
Feature/change email
2 parents 2bb6747 + f5251a9 commit 7b8e93b

21 files changed

+337
-49
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
REACT_APP_API_HOST=http://localhost:5000/
1+
REACT_APP_API_HOST=http://localhost:5001/
22
PUBLIC_URL=/
33
REACT_APP_GRAPHQL_HOST=https://v2cdn.velog.io/
44
REACT_APP_GRAPHQL_HOST_NOCDN=https://v2.velog.io/

scripts/start.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
1-
'use strict';
2-
31
// Do this as the first thing so that any code reading it knows the right env.
42
process.env.BABEL_ENV = 'development';
53
process.env.NODE_ENV = 'development';
64

75
// Makes the script crash on unhandled rejections instead of silently
86
// ignoring them. In the future, promise rejections that are not handled will
97
// terminate the Node.js process with a non-zero exit code.
10-
process.on('unhandledRejection', err => {
8+
process.on('unhandledRejection', (err) => {
119
throw err;
1210
});
1311

1412
// Ensure environment variables are read.
1513
require('../config/env');
1614

17-
1815
const fs = require('fs');
1916
const chalk = require('react-dev-utils/chalk');
2017
const webpack = require('webpack');
@@ -48,15 +45,15 @@ if (process.env.HOST) {
4845
console.log(
4946
chalk.cyan(
5047
`Attempting to bind to HOST environment variable: ${chalk.yellow(
51-
chalk.bold(process.env.HOST)
52-
)}`
53-
)
48+
chalk.bold(process.env.HOST),
49+
)}`,
50+
),
5451
);
5552
console.log(
56-
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
53+
`If this was unintentional, check that you haven't mistakenly set it in your shell.`,
5754
);
5855
console.log(
59-
`Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}`
56+
`Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}`,
6057
);
6158
console.log();
6259
}
@@ -70,7 +67,7 @@ checkBrowsers(paths.appPath, isInteractive)
7067
// run on a different port. `choosePort()` Promise resolves to the next free port.
7168
return choosePort(HOST, DEFAULT_PORT);
7269
})
73-
.then(port => {
70+
.then((port) => {
7471
if (port == null) {
7572
// We have not found a port.
7673
return;
@@ -82,9 +79,9 @@ checkBrowsers(paths.appPath, isInteractive)
8279
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
8380
const urls = prepareUrls(protocol, HOST, port);
8481
const devSocket = {
85-
warnings: warnings =>
82+
warnings: (warnings) =>
8683
devServer.sockWrite(devServer.sockets, 'warnings', warnings),
87-
errors: errors =>
84+
errors: (errors) =>
8885
devServer.sockWrite(devServer.sockets, 'errors', errors),
8986
};
9087
// Create a webpack compiler that is configured with custom messages.
@@ -99,16 +96,19 @@ checkBrowsers(paths.appPath, isInteractive)
9996
webpack,
10097
});
10198
// Load proxy config
102-
const proxySetting = require(paths.appPackageJson).proxy;
99+
const proxySetting =
100+
process.env.NODE_ENV !== 'production'
101+
? process.env.REACT_APP_API_HOST
102+
: require(paths.appPackageJson).proxy;
103103
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
104104
// Serve webpack assets generated by the compiler over a web server.
105105
const serverConfig = createDevServerConfig(
106106
proxyConfig,
107-
urls.lanUrlForConfig
107+
urls.lanUrlForConfig,
108108
);
109109
const devServer = new WebpackDevServer(compiler, serverConfig);
110110
// Launch WebpackDevServer.
111-
devServer.listen(port, HOST, err => {
111+
devServer.listen(port, HOST, (err) => {
112112
if (err) {
113113
return console.log(err);
114114
}
@@ -122,8 +122,8 @@ checkBrowsers(paths.appPath, isInteractive)
122122
if (process.env.NODE_PATH) {
123123
console.log(
124124
chalk.yellow(
125-
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
126-
)
125+
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.',
126+
),
127127
);
128128
console.log();
129129
}
@@ -132,14 +132,14 @@ checkBrowsers(paths.appPath, isInteractive)
132132
openBrowser(urls.localUrlForBrowser);
133133
});
134134

135-
['SIGINT', 'SIGTERM'].forEach(function(sig) {
136-
process.on(sig, function() {
135+
['SIGINT', 'SIGTERM'].forEach(function (sig) {
136+
process.on(sig, function () {
137137
devServer.close();
138138
process.exit();
139139
});
140140
});
141141
})
142-
.catch(err => {
142+
.catch((err) => {
143143
if (err && err.message) {
144144
console.log(err.message);
145145
}

src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ const EmailLoginPage = loadable(
2828
() => import('./pages/EmailLoginPage'),
2929
loadableConfig,
3030
);
31+
const EmailChangePage = loadable(
32+
() => import('./pages/EmailChangePage'),
33+
loadableConfig,
34+
);
35+
3136
const WritePage = loadable(() => import('./pages/WritePage'));
3237
const SearchPage = loadable(() => import('./pages/SearchPage'), loadableConfig);
3338
const SavesPage = loadable(() => import('./pages/SavesPage'), loadableConfig);
@@ -77,6 +82,7 @@ const App: React.FC<AppProps> = (props) => {
7782
<Route path="/@:username" component={VelogPage} />
7883
{/* <Route path="/@:username/:urlSlug" component={PostPage} /> */}
7984
<Route path="/email-login" component={EmailLoginPage} />
85+
<Route path="/email-change" component={EmailChangePage} />
8086
<Route path="/write" component={WritePage} />
8187
<Route path="/search" component={SearchPage} />
8288
<Route path="/saves" component={SavesPage} />

src/components/auth/AuthForm.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ const AuthFormBlock = styled.div`
5656
}
5757
`;
5858

59-
const Warning = styled.div`
60-
margin-top: 1rem;
61-
margin-bottom: 1rem;
62-
font-size: 0.875rem;
63-
color: ${themedPalette.text3};
64-
`;
59+
// const Warning = styled.div`
60+
// margin-top: 1rem;
61+
// margin-bottom: 1rem;
62+
// font-size: 0.875rem;
63+
// color: ${themedPalette.text3};
64+
// `;
6565

6666
export interface AuthFormProps {
6767
mode: AuthMode;

src/components/auth/AuthSocialButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const AuthSocialButton: React.FC<AuthSocialButtonProps> = ({
6262
const host =
6363
process.env.NODE_ENV === 'production'
6464
? process.env.REACT_APP_API_HOST
65-
: 'http://localhost:5000/';
65+
: 'http://localhost:5001/';
6666

6767
const redirectTo = `${host}api/v2/auth/social/redirect/${provider}?next=${currentPath}&isIntegrate=${
6868
isIntegrate ? 1 : 0

src/components/common/AdFeed.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useRef, useState } from 'react';
1+
import React, { useEffect, useRef } from 'react';
22
import styled from 'styled-components';
33
import { mediaQuery } from '../../lib/styles/media';
44
import gtag from '../../lib/gtag';

src/components/common/MarkdownRender.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import media from '../../lib/styles/media';
1313
import parse from 'html-react-parser';
1414
import { throttle } from 'throttle-debounce';
1515
import sanitize from 'sanitize-html';
16-
import palette from '../../lib/styles/palette';
1716
import math from 'remark-math';
1817
import remark2rehype from 'remark-rehype';
1918
import katex from 'rehype-katex';

src/components/common/PostCardGrid.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function PostCardGrid({ posts, loading, forHome, forPost }: PostCardGridProps) {
2828
}, []);
2929

3030
const postsWithAds = useMemo(() => {
31+
// eslint-disable-next-line
3132
if (1 === 1) return posts; // disable adsense
3233
if (user) return posts; // hide ads to users
3334
if (adBlocked) return posts;
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import React, { useState } from 'react';
2+
import SettingRow from './SettingRow';
3+
import useInput from '../../lib/hooks/useInput';
4+
import SettingInput from './SettingInput';
5+
import styled from 'styled-components';
6+
import Button from '../common/Button';
7+
import SettingEmailSuccess from './SettingEmailSuccess';
8+
import { toast } from 'react-toastify';
9+
import { EMAIL_EXISTS } from '../../lib/graphql/user';
10+
import client from '../../lib/graphql/client';
11+
12+
export type SettingEmailRowProps = {
13+
email: string;
14+
isEmailSent: boolean;
15+
onChangeEmail: (email: string) => Promise<void>;
16+
};
17+
18+
function SettingEmailRow({
19+
email,
20+
isEmailSent,
21+
onChangeEmail,
22+
}: SettingEmailRowProps) {
23+
const [edit, setEdit] = useState(false);
24+
const [value, onChange] = useInput(email ?? '');
25+
26+
const onSubmit = async (e: React.FormEvent) => {
27+
e.preventDefault();
28+
29+
if (!validateEmail(value)) {
30+
toast.error('잘못된 이메일 형식입니다.');
31+
return;
32+
}
33+
34+
if (value === email) {
35+
toast.error('새 이메일 주소가 현재 이메일과 동일합니다.');
36+
return;
37+
}
38+
39+
const response = await client.query<{ emailExists: boolean }>({
40+
query: EMAIL_EXISTS,
41+
fetchPolicy: 'network-only',
42+
variables: { email: value.trim() },
43+
});
44+
45+
if (response.data.emailExists) {
46+
toast.error('동일한 이메일이 존재합니다.');
47+
return;
48+
}
49+
50+
await onChangeEmail(value);
51+
setEdit(false);
52+
};
53+
54+
return (
55+
<SettingRow
56+
title="이메일 주소"
57+
description="회원 인증 또는 시스템에서 발송하는 이메일을 수신하는 주소입니다."
58+
editButton={!edit}
59+
showEditButton={!isEmailSent}
60+
onClickEdit={() => setEdit(true)}
61+
editButtonText="변경"
62+
>
63+
{edit ? (
64+
<Form onSubmit={onSubmit}>
65+
<SettingInput
66+
value={value}
67+
onChange={onChange}
68+
placeholder="이메일"
69+
/>
70+
<Button>변경</Button>
71+
</Form>
72+
) : isEmailSent ? (
73+
<SettingEmailSuccess />
74+
) : (
75+
email
76+
)}
77+
</SettingRow>
78+
);
79+
}
80+
81+
const Form = styled.form`
82+
display: flex;
83+
align-items: center;
84+
input {
85+
flex: 1;
86+
margin-right: 1rem;
87+
}
88+
`;
89+
90+
export default SettingEmailRow;
91+
92+
function validateEmail(email: string) {
93+
const re =
94+
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
95+
return re.test(String(email).toLowerCase());
96+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import * as React from 'react';
2+
import { MdCheck } from 'react-icons/md';
3+
import styled from 'styled-components';
4+
import palette from '../../lib/styles/palette';
5+
6+
function SettingEmailSuccess() {
7+
return (
8+
<SettingEmailSuccessBlock>
9+
<MdCheck className="icon" />
10+
<div className="text">
11+
메일이 전송되었습니다. 받은 편지함을 확인하세요.
12+
</div>
13+
</SettingEmailSuccessBlock>
14+
);
15+
}
16+
17+
const SettingEmailSuccessBlock = styled.div`
18+
display: flex;
19+
align-items: center;
20+
padding-left: 0.75rem;
21+
padding-right: 0.75rem;
22+
color: ${palette.teal6};
23+
white-space: pre;
24+
.icon {
25+
margin-right: 10px;
26+
}
27+
.description {
28+
font-size: 0.875rem;
29+
text-align: center;
30+
}
31+
`;
32+
33+
export default SettingEmailSuccess;

src/components/setting/SettingRow.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@ export type SettingRowProps = {
1010
onClickEdit?: () => void;
1111
editButton?: boolean;
1212
description?: string;
13+
showEditButton?: boolean;
14+
editButtonText?: string;
1315
};
1416

1517
function SettingRow({
1618
title,
1719
children,
1820
editButton,
1921
description,
22+
showEditButton = true,
2023
onClickEdit,
24+
editButtonText,
2125
}: SettingRowProps) {
2226
return (
2327
<Row>
@@ -27,9 +31,12 @@ function SettingRow({
2731
</div>
2832
<div className="block-for-mobile">
2933
<div className="contents">{children}</div>
30-
{editButton && (
34+
{editButton && showEditButton && (
3135
<div className="edit-wrapper">
32-
<SettingEditButton onClick={onClickEdit} />
36+
<SettingEditButton
37+
onClick={onClickEdit}
38+
customText={editButtonText}
39+
/>
3340
</div>
3441
)}
3542
</div>

0 commit comments

Comments
 (0)