diff --git a/net/web/src/App.styled.js b/net/web/src/App.styled.js index a3e351a9..25b48106 100644 --- a/net/web/src/App.styled.js +++ b/net/web/src/App.styled.js @@ -1,5 +1,4 @@ import styled from 'styled-components'; -import Colors from 'constants/Colors'; export const AppWrapper = styled.div` position: absolute; diff --git a/net/web/src/access/Access.jsx b/net/web/src/access/Access.jsx index 4ca4ce19..e1b2a825 100644 --- a/net/web/src/access/Access.jsx +++ b/net/web/src/access/Access.jsx @@ -4,6 +4,7 @@ import { AppContext } from 'context/AppContext'; import { ViewportContext } from 'context/ViewportContext'; import { AccessWrapper } from './Access.styled'; import { Login } from './login/Login'; +import { CreateAccount } from './createAccount/CreateAccount'; import login from 'images/login.png' @@ -19,12 +20,15 @@ export function Access({ mode }) { navigate('/session'); } } - }, [app]); + }, [app, navigate]); const Prompt = () => { if (mode === 'login') { return } + if (mode === 'create') { + return + } return <> } diff --git a/net/web/src/access/createAccount/CreateAccount.jsx b/net/web/src/access/createAccount/CreateAccount.jsx new file mode 100644 index 00000000..8ac90e7f --- /dev/null +++ b/net/web/src/access/createAccount/CreateAccount.jsx @@ -0,0 +1,77 @@ +import { Button, Modal, Form, Input } from 'antd'; +import { SettingOutlined, LockOutlined, UserOutlined } from '@ant-design/icons'; +import { CreateAccountWrapper } from './CreateAccount.styled'; +import { useCreateAccount } from './useCreateAccount.hook'; + +export function CreateAccount() { + + const { state, actions } = useCreateAccount(); + + const create = async () => { + try { + await actions.onCreateAccount(); + } + catch(err) { + Modal.error({ + title: 'Create Account Error', + content: 'Please check with you administrator.', + }); + } + } + + const keyDown = (e) => { + if (e.key === 'Enter') { + create() + } + } + + return ( + +
+ Databag +
actions.onSettings()}> + +
+
+
Create Account
+
+
+ + + actions.setUsername(e.target.value)} + autocomplete="username" autocapitalize="none" onKeyDown={(e) => keyDown(e)} prefix={} /> + + +
+ + + actions.setPassword(e.target.value)} + autocomplete="new-password" onKeyDown={(e) => keyDown(e)} prefix={} /> + + + + actions.setConfirm(e.target.value)} + autocomplete="new-password" onKeyDown={(e) => keyDown(e)} prefix={} /> + + +
+
+ +
+
+ +
+ +
+ +
+
+
+ ); +}; + diff --git a/net/web/src/access/createAccount/CreateAccount.styled.js b/net/web/src/access/createAccount/CreateAccount.styled.js new file mode 100644 index 00000000..9263de7c --- /dev/null +++ b/net/web/src/access/createAccount/CreateAccount.styled.js @@ -0,0 +1,62 @@ +import styled from 'styled-components'; +import Colors from 'constants/Colors'; + +export const CreateAccountWrapper = styled.div` + max-width: 400px; + width: 90%; + height: 90%; + display: flex; + flex-direction: column; + + .app-title { + font-size: 24px; + display: flex; + align-items: flex-start; + justify-content: center; + flex: 1; + color: ${Colors.grey}; + + .settings { + color: ${Colors.grey}; + position: absolute; + top: 0px; + right: 0px; + font-size: 20px; + cursor: pointer; + margin: 16px; + } + } + + .form-title { + font-size: 32px; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + flex: 1; + } + + .form-form { + flex: 2; + + .form-space { + height: 16px; + } + + .form-button { + display: flex; + align-items: center; + justify-content: center; + + .form-login { + width: 50%; + } + } + } + + .form-submit { + background-color: #444444; + } +`; + + diff --git a/net/web/src/access/createAccount/useCreateAccount.hook.js b/net/web/src/access/createAccount/useCreateAccount.hook.js new file mode 100644 index 00000000..60a63047 --- /dev/null +++ b/net/web/src/access/createAccount/useCreateAccount.hook.js @@ -0,0 +1,111 @@ +import { useContext, useState, useEffect, useRef } from 'react'; +import { AppContext } from 'context/AppContext'; +import { useNavigate, useLocation } from "react-router-dom"; + +export function useCreateAccount() { + + const [checked, setChecked] = useState(true); + const [state, setState] = useState({ + username: '', + password: '', + confirm: '', + busy: false, + validatetatus: 'success', + help: '', + }); + + const navigate = useNavigate(); + const { search } = useLocation(); + const app = useContext(AppContext); + const debounce = useRef(null); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })); + } + + const usernameSet = (name) => { + setChecked(false) + clearTimeout(debounce.current) + debounce.current = setTimeout(async () => { + if (app.actions?.username && name !== '') { + try { + let valid = await app.actions.username(name, state.token) + if (!valid) { + updateState({ validateStatus: 'error', help: 'Username is not available' }) + } + else { + updateState({ validateStatus: 'success', help: '' }) + } + setChecked(true) + } + catch(err) { + console.log(err); + } + } + else { + updateState({ validateStatus: 'success', help: '' }); + setChecked(true); + } + }, 500) + } + + const actions = { + setUsername: (username) => { + updateState({ username }); + usernameSet(username); + }, + setPassword: (password) => { + updateState({ password }); + }, + setConfirm: (confirm) => { + updateState({ confirm }); + }, + isDisabled: () => { + if (state.username === '' || state.password === '' || state.password !== state.confirm || !checked || + state.validateStatus === 'error') { + return true + } + return false + }, + onSettings: () => { + navigate('/admin'); + }, + onCreateAccount: async () => { + if (!state.busy && state.username !== '' && state.password !== '' && state.password === state.confirm) { + updateState({ busy: true }) + try { + await app.actions.create(state.username, state.password) + } + catch (err) { + console.log(err); + updateState({ busy: false }) + throw new Error('create failed: check with your admin'); + } + updateState({ busy: false }) + } + }, + onLogin: () => { + navigate('/login'); + }, + }; + + useEffect(() => { + if (app) { + if (app.state) { + if (app.state.access) { + navigate('/session') + } + } + else { + let params = new URLSearchParams(search); + let token = params.get("add"); + if (token) { + updateState({ token }); + } + } + } + }, [app, navigate, search]) + + return { state, actions }; +} + diff --git a/net/web/src/access/login/Login.jsx b/net/web/src/access/login/Login.jsx index b003a2f2..bdb1e44c 100644 --- a/net/web/src/access/login/Login.jsx +++ b/net/web/src/access/login/Login.jsx @@ -1,7 +1,6 @@ -import React, { useState } from 'react'; import { Button, Modal, Form, Input } from 'antd'; import { SettingOutlined, LockOutlined, UserOutlined } from '@ant-design/icons'; -import { LoginWrapper, SubmitButton } from './Login.styled'; +import { LoginWrapper } from './Login.styled'; import { useLogin } from './useLogin.hook'; export function Login() { @@ -40,12 +39,12 @@ export function Login() { actions.setUsername(e.target.value)} - autocapitalize="none" onKeyDown={(e) => keyDown(e)} prefix={} /> + autocomplete="username" autocapitalize="none" onKeyDown={(e) => keyDown(e)} prefix={} /> actions.setPassword(e.target.value)} - onKeyDown={(e) => keyDown(e)} prefix={} /> + autocomplete="current-password" onKeyDown={(e) => keyDown(e)} prefix={} />
diff --git a/net/web/src/access/login/useLogin.hook.js b/net/web/src/access/login/useLogin.hook.js index 75909e6e..2e4bc752 100644 --- a/net/web/src/access/login/useLogin.hook.js +++ b/net/web/src/access/login/useLogin.hook.js @@ -1,6 +1,6 @@ import { useContext, useState, useEffect } from 'react'; import { AppContext } from 'context/AppContext'; -import { useNavigate, useLocation, useParams } from "react-router-dom"; +import { useNavigate, useLocation } from "react-router-dom"; export function useLogin() { @@ -37,7 +37,7 @@ export function useLogin() { navigate('/admin'); }, onLogin: async () => { - if (!state.busy && state.username != '' && state.password != '') { + if (!state.busy && state.username !== '' && state.password !== '') { updateState({ busy: true }) try { await app.actions.login(state.username, state.password) @@ -45,7 +45,7 @@ export function useLogin() { catch (err) { console.log(err); updateState({ busy: false }) - throw 'login failed: check your username and password'; + throw new Error('login failed: check your username and password'); } updateState({ busy: false }) } @@ -92,7 +92,7 @@ export function useLogin() { count(); } } - }, [app]) + }, [app, navigate, search]) return { state, actions }; } diff --git a/net/web/src/admin/Admin.jsx b/net/web/src/admin/Admin.jsx index 3f31a7eb..f9cf6a89 100644 --- a/net/web/src/admin/Admin.jsx +++ b/net/web/src/admin/Admin.jsx @@ -1,4 +1,3 @@ -import React, { useContext, useEffect } from 'react'; export function Admin() { return <> diff --git a/net/web/src/root/Root.jsx b/net/web/src/root/Root.jsx index bb71ece2..d07ebbd3 100644 --- a/net/web/src/root/Root.jsx +++ b/net/web/src/root/Root.jsx @@ -16,7 +16,7 @@ export function Root() { navigate('/login'); } } - }, [app]); + }, [app, navigate]); return <> } diff --git a/net/web/src/session/Session.jsx b/net/web/src/session/Session.jsx index 7cf4c689..9451c342 100644 --- a/net/web/src/session/Session.jsx +++ b/net/web/src/session/Session.jsx @@ -1,4 +1,3 @@ -import React, { useContext, useEffect } from 'react'; export function Session() { return <>