Skip to content

Feature/change email #428

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
REACT_APP_API_HOST=http://localhost:5000/
REACT_APP_API_HOST=http://localhost:5001/
PUBLIC_URL=/
REACT_APP_GRAPHQL_HOST=https://v2cdn.velog.io/
REACT_APP_GRAPHQL_HOST_NOCDN=https://v2.velog.io/
40 changes: 20 additions & 20 deletions scripts/start.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
'use strict';

// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';

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

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


const fs = require('fs');
const chalk = require('react-dev-utils/chalk');
const webpack = require('webpack');
Expand Down Expand Up @@ -48,15 +45,15 @@ if (process.env.HOST) {
console.log(
chalk.cyan(
`Attempting to bind to HOST environment variable: ${chalk.yellow(
chalk.bold(process.env.HOST)
)}`
)
chalk.bold(process.env.HOST),
)}`,
),
);
console.log(
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
`If this was unintentional, check that you haven't mistakenly set it in your shell.`,
);
console.log(
`Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}`
`Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}`,
);
console.log();
}
Expand All @@ -70,7 +67,7 @@ checkBrowsers(paths.appPath, isInteractive)
// run on a different port. `choosePort()` Promise resolves to the next free port.
return choosePort(HOST, DEFAULT_PORT);
})
.then(port => {
.then((port) => {
if (port == null) {
// We have not found a port.
return;
Expand All @@ -82,9 +79,9 @@ checkBrowsers(paths.appPath, isInteractive)
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
const urls = prepareUrls(protocol, HOST, port);
const devSocket = {
warnings: warnings =>
warnings: (warnings) =>
devServer.sockWrite(devServer.sockets, 'warnings', warnings),
errors: errors =>
errors: (errors) =>
devServer.sockWrite(devServer.sockets, 'errors', errors),
};
// Create a webpack compiler that is configured with custom messages.
Expand All @@ -99,16 +96,19 @@ checkBrowsers(paths.appPath, isInteractive)
webpack,
});
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxySetting =
process.env.NODE_ENV !== 'production'
? process.env.REACT_APP_API_HOST
: require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
// Serve webpack assets generated by the compiler over a web server.
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
urls.lanUrlForConfig,
);
const devServer = new WebpackDevServer(compiler, serverConfig);
// Launch WebpackDevServer.
devServer.listen(port, HOST, err => {
devServer.listen(port, HOST, (err) => {
if (err) {
return console.log(err);
}
Expand All @@ -122,8 +122,8 @@ checkBrowsers(paths.appPath, isInteractive)
if (process.env.NODE_PATH) {
console.log(
chalk.yellow(
'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.'
)
'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.',
),
);
console.log();
}
Expand All @@ -132,14 +132,14 @@ checkBrowsers(paths.appPath, isInteractive)
openBrowser(urls.localUrlForBrowser);
});

['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
['SIGINT', 'SIGTERM'].forEach(function (sig) {
process.on(sig, function () {
devServer.close();
process.exit();
});
});
})
.catch(err => {
.catch((err) => {
if (err && err.message) {
console.log(err.message);
}
Expand Down
6 changes: 6 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ const EmailLoginPage = loadable(
() => import('./pages/EmailLoginPage'),
loadableConfig,
);
const EmailChangePage = loadable(
() => import('./pages/EmailChangePage'),
loadableConfig,
);

const WritePage = loadable(() => import('./pages/WritePage'));
const SearchPage = loadable(() => import('./pages/SearchPage'), loadableConfig);
const SavesPage = loadable(() => import('./pages/SavesPage'), loadableConfig);
Expand Down Expand Up @@ -77,6 +82,7 @@ const App: React.FC<AppProps> = (props) => {
<Route path="/@:username" component={VelogPage} />
{/* <Route path="/@:username/:urlSlug" component={PostPage} /> */}
<Route path="/email-login" component={EmailLoginPage} />
<Route path="/email-change" component={EmailChangePage} />
<Route path="/write" component={WritePage} />
<Route path="/search" component={SearchPage} />
<Route path="/saves" component={SavesPage} />
Expand Down
12 changes: 6 additions & 6 deletions src/components/auth/AuthForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ const AuthFormBlock = styled.div`
}
`;

const Warning = styled.div`
margin-top: 1rem;
margin-bottom: 1rem;
font-size: 0.875rem;
color: ${themedPalette.text3};
`;
// const Warning = styled.div`
// margin-top: 1rem;
// margin-bottom: 1rem;
// font-size: 0.875rem;
// color: ${themedPalette.text3};
// `;

export interface AuthFormProps {
mode: AuthMode;
Expand Down
2 changes: 1 addition & 1 deletion src/components/auth/AuthSocialButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const AuthSocialButton: React.FC<AuthSocialButtonProps> = ({
const host =
process.env.NODE_ENV === 'production'
? process.env.REACT_APP_API_HOST
: 'http://localhost:5000/';
: 'http://localhost:5001/';

const redirectTo = `${host}api/v2/auth/social/redirect/${provider}?next=${currentPath}&isIntegrate=${
isIntegrate ? 1 : 0
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/AdFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useRef } from 'react';
import styled from 'styled-components';
import { mediaQuery } from '../../lib/styles/media';
import gtag from '../../lib/gtag';
Expand Down
1 change: 0 additions & 1 deletion src/components/common/MarkdownRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import media from '../../lib/styles/media';
import parse from 'html-react-parser';
import { throttle } from 'throttle-debounce';
import sanitize from 'sanitize-html';
import palette from '../../lib/styles/palette';
import math from 'remark-math';
import remark2rehype from 'remark-rehype';
import katex from 'rehype-katex';
Expand Down
1 change: 1 addition & 0 deletions src/components/common/PostCardGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function PostCardGrid({ posts, loading, forHome, forPost }: PostCardGridProps) {
}, []);

const postsWithAds = useMemo(() => {
// eslint-disable-next-line
if (1 === 1) return posts; // disable adsense
if (user) return posts; // hide ads to users
if (adBlocked) return posts;
Expand Down
96 changes: 96 additions & 0 deletions src/components/setting/SettingEmailRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { useState } from 'react';
import SettingRow from './SettingRow';
import useInput from '../../lib/hooks/useInput';
import SettingInput from './SettingInput';
import styled from 'styled-components';
import Button from '../common/Button';
import SettingEmailSuccess from './SettingEmailSuccess';
import { toast } from 'react-toastify';
import { EMAIL_EXISTS } from '../../lib/graphql/user';
import client from '../../lib/graphql/client';

export type SettingEmailRowProps = {
email: string;
isEmailSent: boolean;
onChangeEmail: (email: string) => Promise<void>;
};

function SettingEmailRow({
email,
isEmailSent,
onChangeEmail,
}: SettingEmailRowProps) {
const [edit, setEdit] = useState(false);
const [value, onChange] = useInput(email ?? '');

const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();

if (!validateEmail(value)) {
toast.error('잘못된 이메일 형식입니다.');
return;
}

if (value === email) {
toast.error('새 이메일 주소가 현재 이메일과 동일합니다.');
return;
}

const response = await client.query<{ emailExists: boolean }>({
query: EMAIL_EXISTS,
fetchPolicy: 'network-only',
variables: { email: value.trim() },
});

if (response.data.emailExists) {
toast.error('동일한 이메일이 존재합니다.');
return;
}

await onChangeEmail(value);
setEdit(false);
};

return (
<SettingRow
title="이메일 주소"
description="회원 인증 또는 시스템에서 발송하는 이메일을 수신하는 주소입니다."
editButton={!edit}
showEditButton={!isEmailSent}
onClickEdit={() => setEdit(true)}
editButtonText="변경"
>
{edit ? (
<Form onSubmit={onSubmit}>
<SettingInput
value={value}
onChange={onChange}
placeholder="이메일"
/>
<Button>변경</Button>
</Form>
) : isEmailSent ? (
<SettingEmailSuccess />
) : (
email
)}
</SettingRow>
);
}

const Form = styled.form`
display: flex;
align-items: center;
input {
flex: 1;
margin-right: 1rem;
}
`;

export default SettingEmailRow;

function validateEmail(email: string) {
const re =
/^(([^<>()[\]\\.,;:\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,}))$/;
return re.test(String(email).toLowerCase());
}
33 changes: 33 additions & 0 deletions src/components/setting/SettingEmailSuccess.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import { MdCheck } from 'react-icons/md';
import styled from 'styled-components';
import palette from '../../lib/styles/palette';

function SettingEmailSuccess() {
return (
<SettingEmailSuccessBlock>
<MdCheck className="icon" />
<div className="text">
메일이 전송되었습니다. 받은 편지함을 확인하세요.
</div>
</SettingEmailSuccessBlock>
);
}

const SettingEmailSuccessBlock = styled.div`
display: flex;
align-items: center;
padding-left: 0.75rem;
padding-right: 0.75rem;
color: ${palette.teal6};
white-space: pre;
.icon {
margin-right: 10px;
}
.description {
font-size: 0.875rem;
text-align: center;
}
`;

export default SettingEmailSuccess;
11 changes: 9 additions & 2 deletions src/components/setting/SettingRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ export type SettingRowProps = {
onClickEdit?: () => void;
editButton?: boolean;
description?: string;
showEditButton?: boolean;
editButtonText?: string;
};

function SettingRow({
title,
children,
editButton,
description,
showEditButton = true,
onClickEdit,
editButtonText,
}: SettingRowProps) {
return (
<Row>
Expand All @@ -27,9 +31,12 @@ function SettingRow({
</div>
<div className="block-for-mobile">
<div className="contents">{children}</div>
{editButton && (
{editButton && showEditButton && (
<div className="edit-wrapper">
<SettingEditButton onClick={onClickEdit} />
<SettingEditButton
onClick={onClickEdit}
customText={editButtonText}
/>
</div>
)}
</div>
Expand Down
Loading