|
1 | 1 | import type { UserState } from '../../config/state'; |
2 | 2 |
|
3 | | -import { useState } from 'react'; |
| 3 | +import { useEffect, useState } from 'react'; |
4 | 4 | import { useTranslation } from 'react-i18next'; |
5 | 5 | import { useNavigate } from 'react-router-dom'; |
6 | 6 |
|
7 | | -import { DButton } from '@react-devui/ui'; |
| 7 | +import { useAsync } from '@react-devui/hooks'; |
| 8 | +import { LockOutlined, MobileOutlined, UserOutlined } from '@react-devui/icons'; |
| 9 | +import { DButton, DCheckbox, DForm, DInput, DTabs, FormControl, FormGroup, useForm, Validators } from '@react-devui/ui'; |
| 10 | +import { getClassName } from '@react-devui/utils'; |
8 | 11 |
|
9 | 12 | import { TOKEN } from '../../config/token'; |
10 | | -import { useHttp, useInit, usePrevRoute } from '../hooks'; |
| 13 | +import { AppLanguage } from '../components'; |
| 14 | +import { useDeviceQuery, useHttp, useInit, usePrevRoute } from '../hooks'; |
11 | 15 |
|
12 | 16 | export default function Login(): JSX.Element | null { |
13 | 17 | const { t } = useTranslation(); |
14 | 18 | const createHttp = useHttp(); |
15 | | - const [loading, setLoading] = useState(false); |
| 19 | + const [loginloading, setLoginLoading] = useState(false); |
16 | 20 | const init = useInit(); |
17 | 21 | const navigate = useNavigate(); |
18 | 22 | const from = usePrevRoute(); |
| 23 | + const async = useAsync(); |
| 24 | + const deviceMatched = useDeviceQuery(); |
| 25 | + |
| 26 | + const [loginType, setLoginType] = useState<'account' | 'phone'>('account'); |
| 27 | + const [remember, setRemember] = useState(true); |
| 28 | + const [codeDisabled, setCodeDisabled] = useState(0); |
| 29 | + useEffect(() => { |
| 30 | + if (codeDisabled > 0) { |
| 31 | + async.setTimeout(() => { |
| 32 | + setCodeDisabled((draft) => draft - 1); |
| 33 | + }, 1000); |
| 34 | + } |
| 35 | + }, [async, codeDisabled]); |
| 36 | + |
| 37 | + const accountForm = useForm( |
| 38 | + () => |
| 39 | + new FormGroup({ |
| 40 | + username: new FormControl('', [ |
| 41 | + Validators.required, |
| 42 | + (control) => { |
| 43 | + return !control.value || control.value === 'admin' ? null : { checkValue: true }; |
| 44 | + }, |
| 45 | + ]), |
| 46 | + password: new FormControl('', Validators.required), |
| 47 | + }) |
| 48 | + ); |
| 49 | + const phoneForm = useForm( |
| 50 | + () => |
| 51 | + new FormGroup({ |
| 52 | + phone: new FormControl('', [Validators.required]), |
| 53 | + code: new FormControl('', Validators.required), |
| 54 | + }) |
| 55 | + ); |
| 56 | + |
| 57 | + const loginSameNode = ( |
| 58 | + <> |
| 59 | + <DForm.Item> |
| 60 | + <DCheckbox dModel={remember} onModelChange={setRemember}> |
| 61 | + {t('routes.login.Remember me')} |
| 62 | + </DCheckbox> |
| 63 | + <a className="app-link" style={{ marginLeft: 'auto' }}> |
| 64 | + {t('routes.login.Forgot Password')} |
| 65 | + </a> |
| 66 | + </DForm.Item> |
| 67 | + <DForm.Item> |
| 68 | + <DButton |
| 69 | + type="submit" |
| 70 | + disabled={loginType === 'account' ? accountForm.form.invalid : phoneForm.form.invalid} |
| 71 | + onClick={() => { |
| 72 | + const [http] = createHttp(); |
| 73 | + setLoginLoading(true); |
| 74 | + http<{ user: UserState; token: string }>({ |
| 75 | + url: '/api/login', |
| 76 | + method: 'post', |
| 77 | + }).subscribe({ |
| 78 | + next: (res) => { |
| 79 | + setLoginLoading(false); |
| 80 | + TOKEN.token = res.token; |
| 81 | + init(res.user); |
| 82 | + navigate(from, { replace: true }); |
| 83 | + }, |
| 84 | + }); |
| 85 | + }} |
| 86 | + dLoading={loginloading} |
| 87 | + dBlock |
| 88 | + > |
| 89 | + {t('routes.login.Login')} |
| 90 | + </DButton> |
| 91 | + </DForm.Item> |
| 92 | + </> |
| 93 | + ); |
19 | 94 |
|
20 | 95 | return ( |
21 | | - <div> |
22 | | - Login |
23 | | - <DButton |
24 | | - onClick={() => { |
25 | | - const [http] = createHttp(); |
26 | | - setLoading(true); |
27 | | - http<{ user: UserState; token: string }>({ |
28 | | - url: '/api/login', |
29 | | - method: 'post', |
30 | | - }).subscribe({ |
31 | | - next: (res) => { |
32 | | - setLoading(false); |
33 | | - TOKEN.token = res.token; |
34 | | - init(res.user); |
35 | | - navigate(from, { replace: true }); |
36 | | - }, |
37 | | - }); |
38 | | - }} |
39 | | - dLoading={loading} |
| 96 | + <div className="app-login-route"> |
| 97 | + <AppLanguage className="app-login-route__lang" /> |
| 98 | + <div> |
| 99 | + {deviceMatched === 'desktop' && <img className="app-login-route__bg" src="/assets/login-bg.png" alt="bg" />} |
| 100 | + <div className="app-login-route__login-container"> |
| 101 | + <div className="app-login-route__title-container"> |
| 102 | + <img className="app-login-route__logo" src="/assets/logo.svg" alt="Logo" /> |
| 103 | + <span>React DevUI</span> |
| 104 | + </div> |
| 105 | + <div className="app-login-route__description">{t('routes.login.description')}</div> |
| 106 | + <DTabs |
| 107 | + className="app-login-route__tabs" |
| 108 | + dList={[ |
| 109 | + { |
| 110 | + id: 'account', |
| 111 | + title: t('routes.login.Account Login'), |
| 112 | + panel: ( |
| 113 | + <DForm dForm={accountForm} dLabelWidth={0}> |
| 114 | + <DForm.Item |
| 115 | + dFormControls={{ |
| 116 | + username: { |
| 117 | + required: t('routes.login.Please enter your name'), |
| 118 | + checkValue: t('routes.login.Username'), |
| 119 | + }, |
| 120 | + }} |
| 121 | + > |
| 122 | + {({ username }) => ( |
| 123 | + <DInput dFormControl={username} dPrefix={<UserOutlined />} dPlaceholder={t('routes.login.Username')} /> |
| 124 | + )} |
| 125 | + </DForm.Item> |
| 126 | + <DForm.Item dFormControls={{ password: t('routes.login.Please enter your password') }}> |
| 127 | + {({ password }) => ( |
| 128 | + <DInput |
| 129 | + dFormControl={password} |
| 130 | + dPrefix={<LockOutlined />} |
| 131 | + dPlaceholder={t('routes.login.Password')} |
| 132 | + dType="password" |
| 133 | + /> |
| 134 | + )} |
| 135 | + </DForm.Item> |
| 136 | + {loginSameNode} |
| 137 | + </DForm> |
| 138 | + ), |
| 139 | + }, |
| 140 | + { |
| 141 | + id: 'phone', |
| 142 | + title: t('routes.login.Phone Login'), |
| 143 | + panel: ( |
| 144 | + <DForm dForm={phoneForm} dLabelWidth={0}> |
| 145 | + <DForm.Item dFormControls={{ phone: t('routes.login.Please enter your phone number') }}> |
| 146 | + {({ phone }) => ( |
| 147 | + <DInput dFormControl={phone} dPrefix={<MobileOutlined />} dPlaceholder={t('routes.login.Phone number')} /> |
| 148 | + )} |
| 149 | + </DForm.Item> |
| 150 | + <DForm.Item dFormControls={{ code: t('routes.login.Please enter verification code') }} dSpan> |
| 151 | + {({ code }) => <DInput dFormControl={code} dPlaceholder={t('routes.login.Verification code')} />} |
| 152 | + </DForm.Item> |
| 153 | + <DForm.Item dLabelWidth={8} dSpan="auto"> |
| 154 | + <DButton |
| 155 | + disabled={codeDisabled > 0 || phoneForm.form.controls['phone'].invalid} |
| 156 | + onClick={() => { |
| 157 | + setCodeDisabled(60); |
| 158 | + }} |
| 159 | + > |
| 160 | + {codeDisabled > 0 ? `${codeDisabled}s` : t('routes.login.Get code')} |
| 161 | + </DButton> |
| 162 | + </DForm.Item> |
| 163 | + {loginSameNode} |
| 164 | + </DForm> |
| 165 | + ), |
| 166 | + }, |
| 167 | + ]} |
| 168 | + dActive={loginType} |
| 169 | + onActiveChange={setLoginType} |
| 170 | + /> |
| 171 | + </div> |
| 172 | + </div> |
| 173 | + <footer |
| 174 | + className={getClassName('app-login-route__footer', { |
| 175 | + 'app-login-route__footer--phone': deviceMatched === 'phone', |
| 176 | + })} |
40 | 177 | > |
41 | | - {t('login.Login')} |
42 | | - </DButton> |
| 178 | + <div> |
| 179 | + <span>© 2022 made with ❤ by </span> |
| 180 | + <a className="app-link" href="//github.com/xiejay97"> |
| 181 | + Xie Jay |
| 182 | + </a> |
| 183 | + </div> |
| 184 | + <div className="app-login-route__link-container"> |
| 185 | + <a className="app-link">{t('routes.login.Terms')}</a> |
| 186 | + <a className="app-link">{t('routes.login.Privacy')}</a> |
| 187 | + </div> |
| 188 | + </footer> |
43 | 189 | </div> |
44 | 190 | ); |
45 | 191 | } |
0 commit comments