김재형

Implement login

This diff is collapsed. Click to expand it.
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
4 "description": "Dropbox alternative cloud file service", 4 "description": "Dropbox alternative cloud file service",
5 "private": true, 5 "private": true,
6 "dependencies": { 6 "dependencies": {
7 + "@ant-design/icons": "^4.2.1",
8 + "antd": "^4.3.3",
7 "classnames": "^2.2.6", 9 "classnames": "^2.2.6",
8 "ky": "^0.20.0", 10 "ky": "^0.20.0",
9 "miragejs": "^0.1.40", 11 "miragejs": "^0.1.40",
......
1 -import React from "react"; 1 +import React, { Fragment } from "react";
2 +import { Switch, Route, Redirect } from "react-router-dom";
3 +
4 +import { Login } from "auth/Login";
5 +import { useAuth } from "auth/useAuth";
2 6
3 export function App() { 7 export function App() {
4 - return <div>Hello World!</div>; 8 + const { token, login } = useAuth();
9 + return (
10 + <Fragment>
11 + <Switch>
12 + <Route path="/login">
13 + <Login login={login} />
14 + </Route>
15 + </Switch>
16 + {token === null && <Redirect to="/login" />}
17 + </Fragment>
18 + );
5 } 19 }
......
1 +.layout {
2 + height: 100%;
3 + align-items: center;
4 + justify-content: center;
5 +}
6 +
7 +.content {
8 + width: 640px;
9 + flex-grow: 0;
10 + background: #fff;
11 + padding: 80px 50px 50px;
12 +}
13 +
14 +#components-form-demo-normal-login .login-form-forgot {
15 + float: right;
16 +}
17 +
18 +#components-form-demo-normal-login .ant-col-rtl .login-form-forgot {
19 + float: left;
20 +}
21 +
22 +#components-form-demo-normal-login .login-form-button {
23 + width: 100%;
24 +}
1 +import React, { useCallback, useState } from "react";
2 +import { Form, Input, Button, Checkbox, Layout } from "antd";
3 +import { UserOutlined, LockOutlined } from "@ant-design/icons";
4 +import { useHistory } from "react-router-dom";
5 +
6 +import styles from "./Login.module.scss";
7 +
8 +export type LoginProps = {
9 + login: (
10 + username: string,
11 + password: string,
12 + remember: boolean
13 + ) => Promise<void>;
14 +};
15 +
16 +export function Login({ login }: LoginProps) {
17 + const [error, setError] = useState<boolean>(false);
18 + const history = useHistory();
19 +
20 + const handleLogin = useCallback(
21 + async ({ username, password, remember }) => {
22 + setError(false);
23 + try {
24 + await login(username, password, remember);
25 + history.push("/");
26 + } catch {
27 + setError(true);
28 + }
29 + },
30 + [login, history]
31 + );
32 +
33 + return (
34 + <Layout className={styles.layout}>
35 + <Layout.Content className={styles.content}>
36 + <Form
37 + name="login"
38 + initialValues={{ remember: true }}
39 + onFinish={handleLogin}
40 + >
41 + <Form.Item
42 + name="username"
43 + rules={[{ required: true, message: "아이디를 입력하세요" }]}
44 + {...(error && {
45 + validateStatus: "error",
46 + })}
47 + >
48 + <Input prefix={<UserOutlined />} placeholder="아이디" />
49 + </Form.Item>
50 + <Form.Item
51 + name="password"
52 + rules={[{ required: true, message: "Please input your Password!" }]}
53 + {...(error && {
54 + validateStatus: "error",
55 + help: "로그인에 실패했습니다",
56 + })}
57 + >
58 + <Input
59 + prefix={<LockOutlined />}
60 + type="password"
61 + placeholder="비밀번호"
62 + />
63 + </Form.Item>
64 + <Form.Item>
65 + <Form.Item name="remember" valuePropName="checked" noStyle>
66 + <Checkbox>자동 로그인</Checkbox>
67 + </Form.Item>
68 + </Form.Item>
69 +
70 + <Form.Item>
71 + <Button type="primary" htmlType="submit">
72 + 로그인
73 + </Button>
74 + </Form.Item>
75 + </Form>
76 + </Layout.Content>
77 + </Layout>
78 + );
79 +}
1 +import { useState, useCallback } from "react";
2 +import ky from "ky";
3 +
4 +interface LoginResponse {
5 + status: number;
6 + data: {
7 + access_token: string;
8 + refresh_token: string;
9 + expiration: string;
10 + };
11 +}
12 +
13 +interface Token {
14 + accessToken: string;
15 + refreshToken: string;
16 + expiration: Date;
17 +}
18 +
19 +export function useAuth() {
20 + const [token, setToken] = useState<Token | null>(() => {
21 + const item = localStorage.getItem("token");
22 + if (item) {
23 + const token = JSON.parse(item);
24 + token.expiration = new Date(token.expiration);
25 + return token;
26 + }
27 + return null;
28 + });
29 +
30 + const login = useCallback(
31 + async (username: string, password: string, remember: boolean) => {
32 + const response = await ky
33 + .post("/users/login", {
34 + json: {
35 + user_id: username,
36 + password: password,
37 + },
38 + })
39 + .json<LoginResponse>();
40 +
41 + const token = {
42 + accessToken: response.data.access_token,
43 + refreshToken: response.data.refresh_token,
44 + expiration: new Date(response.data.expiration),
45 + };
46 +
47 + setToken(token);
48 +
49 + if (remember) {
50 + localStorage.setItem("token", JSON.stringify(token));
51 + }
52 + },
53 + []
54 + );
55 +
56 + return { token, login };
57 +}
1 -body { 1 +#root {
2 - margin: 0; 2 + height: 100%;
3 - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 - "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 - sans-serif;
6 - -webkit-font-smoothing: antialiased;
7 - -moz-osx-font-smoothing: grayscale;
8 -}
9 -
10 -code {
11 - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 - monospace;
13 } 3 }
......
1 import React from "react"; 1 import React from "react";
2 import ReactDOM from "react-dom"; 2 import ReactDOM from "react-dom";
3 +import { BrowserRouter } from "react-router-dom";
3 4
5 +import "antd/dist/antd.css";
4 import "./index.css"; 6 import "./index.css";
5 7
6 import { App } from "./App"; 8 import { App } from "./App";
...@@ -10,9 +12,9 @@ import * as serviceWorker from "./serviceWorker"; ...@@ -10,9 +12,9 @@ import * as serviceWorker from "./serviceWorker";
10 import "./server"; 12 import "./server";
11 13
12 ReactDOM.render( 14 ReactDOM.render(
13 - <React.StrictMode> 15 + <BrowserRouter>
14 <App /> 16 <App />
15 - </React.StrictMode>, 17 + </BrowserRouter>,
16 document.getElementById("root") 18 document.getElementById("root")
17 ); 19 );
18 20
......
...@@ -46,8 +46,6 @@ createServer({ ...@@ -46,8 +46,6 @@ createServer({
46 factories: {}, 46 factories: {},
47 47
48 routes() { 48 routes() {
49 - this.namespace = "api";
50 -
51 this.get("/items/:item_id/children", (schema, request) => { 49 this.get("/items/:item_id/children", (schema, request) => {
52 const directory = schema.find("item", request.params.item_id); 50 const directory = schema.find("item", request.params.item_id);
53 if (!directory || !directory.is_folder) { 51 if (!directory || !directory.is_folder) {
......