Showing
9 changed files
with
184 additions
and
18 deletions
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 | } | ... | ... |
frontend/src/auth/Login.module.scss
0 → 100644
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 | +} |
frontend/src/auth/Login.tsx
0 → 100644
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 | +} |
frontend/src/auth/useAuth.ts
0 → 100644
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) { | ... | ... |
-
Please register or login to post a comment