Skip to content

Commit 8a418f1

Browse files
committed
feat(platform): add login route
1 parent 4c36143 commit 8a418f1

15 files changed

Lines changed: 352 additions & 53 deletions

File tree

.github/workflows/main.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,24 @@ jobs:
5353
project: './dist/packages/site'
5454
login: ${{ secrets.SURGE_LOGIN }}
5555
token: ${{ secrets.SURGE_TOKEN }}
56+
build-platform:
57+
needs: [lint, test]
58+
runs-on: ubuntu-latest
59+
steps:
60+
- uses: actions/checkout@v2
61+
with:
62+
fetch-depth: 0
63+
- uses: nrwl/nx-set-shas@v2
64+
- uses: actions/setup-node@v2
65+
with:
66+
node-version: '16.13'
67+
cache: 'yarn'
68+
- run: yarn install --frozen-lockfile
69+
- run: yarn nx build platform --configuration=production --skip-nx-cache
70+
- run: cp ./dist/packages/platform/index.html ./dist/packages/platform/200.html
71+
- uses: dswistowski/surge-sh-action@v1
72+
with:
73+
domain: 'rd-platform.surge.sh'
74+
project: './dist/packages/platform'
75+
login: ${{ secrets.SURGE_LOGIN }}
76+
token: ${{ secrets.SURGE_TOKEN }}

commitlint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const fs = require('fs');
44

55
const message = process.env['HUSKY_GIT_PARAMS'];
66
const types = ['feat', 'fix', 'chore', 'docs', 'style', 'refactor', 'perf', 'test'];
7-
const scopes = ['hooks', 'icons', 'site', 'ui', 'utils', 'vscode-extension'];
7+
const scopes = ['hooks', 'icons', 'platform', 'site', 'ui', 'utils', 'vscode-extension'];
88

99
function parseMessage(message) {
1010
const PATTERN = /^(\w*)(?:\((.*)\))?!?: (.*)$/;

packages/platform/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"keywords": [
77
"react",
88
"devui",
9+
"admin",
910
"platform"
1011
],
1112
"homepage": "https://github.com/DevCloudFE/react-devui/tree/main/packages/platform#readme",
Lines changed: 171 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,191 @@
11
import type { UserState } from '../../config/state';
22

3-
import { useState } from 'react';
3+
import { useEffect, useState } from 'react';
44
import { useTranslation } from 'react-i18next';
55
import { useNavigate } from 'react-router-dom';
66

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';
811

912
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';
1115

1216
export default function Login(): JSX.Element | null {
1317
const { t } = useTranslation();
1418
const createHttp = useHttp();
15-
const [loading, setLoading] = useState(false);
19+
const [loginloading, setLoginLoading] = useState(false);
1620
const init = useInit();
1721
const navigate = useNavigate();
1822
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+
);
1994

2095
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+
})}
40177
>
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>
43189
</div>
44190
);
45191
}
33.1 KB
Loading

packages/platform/src/i18n/resources.json

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,23 @@
2020
"Logout": "Logout"
2121
},
2222
"login": {
23-
"Login": "Login"
23+
"Login": "Login",
24+
"description": "Elegant Design & Ultimate Performance",
25+
"Account Login": "Account Login",
26+
"Phone Login": "Phone Login",
27+
"Username": "Username: 'admin'",
28+
"Password": "Password: any input",
29+
"Please enter your name": "Please enter your name!",
30+
"Please enter your password": "Please enter your password!",
31+
"Remember me": "Remember me",
32+
"Forgot Password": "Forgot Password?",
33+
"Phone number": "Phone number",
34+
"Verification code": "Verification code",
35+
"Get code": "Get code",
36+
"Please enter your phone number": "Please enter your phone number!",
37+
"Please enter verification code": "Please enter verification code!",
38+
"Terms": "Terms",
39+
"Privacy": "Privacy"
2440
}
2541
}
2642
},
@@ -58,7 +74,23 @@
5874
"Logout": "退出登录"
5975
},
6076
"login": {
61-
"Login": "登 录"
77+
"Login": "登 录",
78+
"description": "优雅的设计 & 极致的性能",
79+
"Account Login": "账号密码登录",
80+
"Phone Login": "手机号登录",
81+
"Username": "用户名:'admin'",
82+
"Password": "密码:任意输入",
83+
"Please enter your name": "请输入用户名!",
84+
"Please enter your password": "请输入密码!",
85+
"Remember me": "记住我",
86+
"Forgot Password": "忘记密码?",
87+
"Phone number": "手机号",
88+
"Verification code": "验证码",
89+
"Get code": "获取验证码",
90+
"Please enter your phone number": "请输入手机号!",
91+
"Please enter verification code": "请输入验证码!",
92+
"Terms": "条款",
93+
"Privacy": "隐私"
6294
}
6395
}
6496
},

packages/platform/src/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="utf-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
66
<title>Platform</title>
7-
<meta name="keywords" content="react,devui,platform" />
7+
<meta name="keywords" content="react,devui,admin,platform" />
88
<meta name="description" content="An out-of-box solution for enterprise applications as a React boilerplate." />
99
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg" />
1010

packages/platform/src/styles/_app.scss

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@use 'sass:map';
2+
13
*,
24
*::before,
35
*::after {
@@ -10,11 +12,24 @@ body {
1012
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
1113
font-weight: 400;
1214
color: var(--d-text-color);
13-
/* stylelint-disable-next-line declaration-property-value-allowed-list */
14-
background-color: #fff;
15+
background-color: map.get($colors, 'white');
1516
text-size-adjust: 100%;
16-
/* stylelint-disable-next-line declaration-property-value-allowed-list */
17-
-webkit-tap-highlight-color: rgb(0 0 0 / 0%);
1817

1918
@include font-size(1rem);
2019
}
20+
21+
.app-link {
22+
font: inherit;
23+
color: var(--#{$variable-prefix}color-primary);
24+
text-decoration: none;
25+
transition: color var(--#{$variable-prefix}animation-duration-fast) linear;
26+
27+
&:hover,
28+
&:focus {
29+
color: var(--#{$variable-prefix}color-primary-lighter);
30+
}
31+
32+
&:active {
33+
color: var(--#{$variable-prefix}color-primary-darker);
34+
}
35+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
@import 'routes/layout';
2+
3+
@import 'routes/login';

0 commit comments

Comments
 (0)