@qlover/create-app 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/configs/_common/package.json.template +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/react-app/.env +1 -2
- package/templates/react-app/README.md +70 -8
- package/templates/react-app/config/app.router.json +13 -13
- package/templates/react-app/config/common.ts +8 -0
- package/templates/react-app/config/i18n.ts +3 -1
- package/templates/react-app/config/{Identifier.I18n.ts → identifier/I18n.ts} +321 -3
- package/templates/react-app/index.html +1 -1
- package/templates/react-app/public/locales/en/common.json +54 -8
- package/templates/react-app/public/locales/zh/common.json +54 -8
- package/templates/react-app/public/router-root/logo.svg +1 -0
- package/templates/react-app/src/App.tsx +6 -3
- package/templates/react-app/src/base/cases/PublicAssetsPath.ts +17 -0
- package/templates/react-app/src/base/port/LoginInterface.ts +8 -0
- package/templates/react-app/src/base/port/StoreInterface.ts +58 -0
- package/templates/react-app/src/base/services/I18nService.ts +15 -9
- package/templates/react-app/src/{uikit/controllers/RouterController.ts → base/services/RouteService.ts} +12 -12
- package/templates/react-app/src/{uikit/controllers/UserController.ts → base/services/UserService.ts} +21 -11
- package/templates/react-app/src/core/bootstrap.ts +1 -1
- package/templates/react-app/src/core/registers/RegisterCommon.ts +11 -1
- package/templates/react-app/src/core/registers/RegisterControllers.ts +3 -12
- package/templates/react-app/src/pages/auth/Layout.tsx +5 -5
- package/templates/react-app/src/pages/auth/Login.tsx +50 -29
- package/templates/react-app/src/pages/auth/Register.tsx +238 -1
- package/templates/react-app/src/pages/base/About.tsx +1 -1
- package/templates/react-app/src/pages/base/ErrorIdentifier.tsx +2 -2
- package/templates/react-app/src/pages/base/Executor.tsx +4 -4
- package/templates/react-app/src/pages/base/Home.tsx +1 -1
- package/templates/react-app/src/pages/base/JSONStorage.tsx +3 -3
- package/templates/react-app/src/pages/base/Request.tsx +4 -4
- package/templates/react-app/src/pages/base/components/BaseHeader.tsx +8 -2
- package/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +7 -7
- package/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +3 -3
- package/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +1 -1
- package/templates/react-app/src/uikit/controllers/ExecutorController.ts +6 -3
- package/templates/react-app/src/uikit/controllers/JSONStorageController.ts +6 -3
- package/templates/react-app/src/uikit/controllers/RequestController.ts +3 -4
- package/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +15 -0
- package/templates/react-app/src/uikit/hooks/useStore.ts +12 -0
- package/templates/react-app/src/uikit/providers/BaseRouteProvider.tsx +7 -1
- package/templates/react-app/src/uikit/providers/ProcessProvider.tsx +7 -7
- package/templates/react-app/vite.config.ts +20 -11
- /package/templates/react-app/config/{Identifier.Error.ts → identifier/Error.ts} +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { SliceStore } from '@qlover/slice-store-react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Store State Interface
|
|
5
|
+
*
|
|
6
|
+
* This interface is used to define the state of a store.
|
|
7
|
+
* It is used to define the state of a store.
|
|
8
|
+
*
|
|
9
|
+
* must implement the copyWith method
|
|
10
|
+
*/
|
|
11
|
+
export interface StoreStateInterface {
|
|
12
|
+
// You can define your own properties here
|
|
13
|
+
// ...
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Store Interface
|
|
18
|
+
*
|
|
19
|
+
* This class is used to define the store interface.
|
|
20
|
+
* It is used to define the store interface.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* class ChatStoreState implements StoreStateInterface {
|
|
25
|
+
* isChatRunning: boolean = false;
|
|
26
|
+
*
|
|
27
|
+
* copyWith(state: { isChatRunning?: boolean }): this {
|
|
28
|
+
* return Object.assign(new ChatStoreState(), this, state);
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* export class ChatStore extends StoreInterface<ChatStoreState> {
|
|
33
|
+
* constructor() {
|
|
34
|
+
* super(() => new ChatStoreState());
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export abstract class StoreInterface<
|
|
40
|
+
T extends StoreStateInterface
|
|
41
|
+
> extends SliceStore<T> {
|
|
42
|
+
/**
|
|
43
|
+
* Constructor
|
|
44
|
+
*
|
|
45
|
+
* @param stateFactory - The factory function to create the initial state
|
|
46
|
+
*/
|
|
47
|
+
constructor(protected stateFactory: () => T) {
|
|
48
|
+
super(stateFactory);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Reset the state of the store
|
|
53
|
+
*/
|
|
54
|
+
resetState(): void {
|
|
55
|
+
// Create a new instance of initial state
|
|
56
|
+
this.emit(this.stateFactory());
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -4,22 +4,22 @@ import LanguageDetector from 'i18next-browser-languagedetector';
|
|
|
4
4
|
import HttpApi from 'i18next-http-backend';
|
|
5
5
|
import merge from 'lodash/merge';
|
|
6
6
|
import i18nConfig from '@config/i18n';
|
|
7
|
+
import type { BootstrapExecutorPlugin } from '@qlover/corekit-bridge';
|
|
7
8
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from '
|
|
11
|
-
|
|
9
|
+
StoreInterface,
|
|
10
|
+
StoreStateInterface
|
|
11
|
+
} from '@/base/port/StoreInterface';
|
|
12
12
|
const { supportedLngs, fallbackLng } = i18nConfig;
|
|
13
13
|
|
|
14
14
|
export type I18nServiceLocale = (typeof supportedLngs)[number];
|
|
15
15
|
|
|
16
|
-
export class I18nServiceState {
|
|
16
|
+
export class I18nServiceState implements StoreStateInterface {
|
|
17
17
|
loading: boolean = false;
|
|
18
18
|
constructor(public language: I18nServiceLocale) {}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export class I18nService
|
|
22
|
-
extends
|
|
22
|
+
extends StoreInterface<I18nServiceState>
|
|
23
23
|
implements BootstrapExecutorPlugin
|
|
24
24
|
{
|
|
25
25
|
readonly pluginName = 'I18nService';
|
|
@@ -57,9 +57,15 @@ export class I18nService
|
|
|
57
57
|
const pathLanguageDetector = {
|
|
58
58
|
name: 'pathLanguageDetector',
|
|
59
59
|
lookup: () => {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
const paths = this.pathname.split('/');
|
|
61
|
+
|
|
62
|
+
for (const path of paths) {
|
|
63
|
+
if (I18nService.isValidLanguage(path)) {
|
|
64
|
+
return path;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return fallbackLng;
|
|
63
69
|
},
|
|
64
70
|
cacheUserLanguage() {
|
|
65
71
|
// no cache, because we get language from URL
|
|
@@ -4,32 +4,32 @@ import type { UIDependenciesInterface } from '@/base/port/UIDependenciesInterfac
|
|
|
4
4
|
import type { LoggerInterface } from '@qlover/logger';
|
|
5
5
|
import { I18nService } from '@/base/services/I18nService';
|
|
6
6
|
|
|
7
|
-
export type
|
|
7
|
+
export type RouterServiceDependencies = {
|
|
8
8
|
navigate: NavigateFunction;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export type
|
|
11
|
+
export type RouterServiceOptions = {
|
|
12
12
|
config: {
|
|
13
13
|
routes: RouteConfigValue[];
|
|
14
14
|
};
|
|
15
15
|
logger: LoggerInterface;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
export type
|
|
18
|
+
export type RouterServiceState = {
|
|
19
19
|
routes: RouteConfigValue[];
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
export class
|
|
23
|
-
implements UIDependenciesInterface<
|
|
22
|
+
export class RouteService
|
|
23
|
+
implements UIDependenciesInterface<RouterServiceDependencies>
|
|
24
24
|
{
|
|
25
25
|
/**
|
|
26
26
|
* @override
|
|
27
27
|
*/
|
|
28
|
-
dependencies?:
|
|
28
|
+
dependencies?: RouterServiceDependencies;
|
|
29
29
|
|
|
30
|
-
state:
|
|
30
|
+
state: RouterServiceState;
|
|
31
31
|
|
|
32
|
-
constructor(private options:
|
|
32
|
+
constructor(private options: RouterServiceOptions) {
|
|
33
33
|
this.state = {
|
|
34
34
|
routes: options.config.routes
|
|
35
35
|
};
|
|
@@ -49,7 +49,7 @@ export class RouterController
|
|
|
49
49
|
return navigate;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
composePath(path: string): string {
|
|
53
53
|
const targetLang = I18nService.getCurrentLanguage();
|
|
54
54
|
return `/${targetLang}${path}`;
|
|
55
55
|
}
|
|
@@ -57,11 +57,11 @@ export class RouterController
|
|
|
57
57
|
/**
|
|
58
58
|
* @override
|
|
59
59
|
*/
|
|
60
|
-
setDependencies(dependencies: Partial<
|
|
60
|
+
setDependencies(dependencies: Partial<RouterServiceDependencies>): void {
|
|
61
61
|
this.dependencies = Object.assign(
|
|
62
62
|
this.dependencies || {},
|
|
63
63
|
dependencies
|
|
64
|
-
) as
|
|
64
|
+
) as RouterServiceDependencies;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
getRoutes(): RouteConfigValue[] {
|
|
@@ -73,7 +73,7 @@ export class RouterController
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
goto(path: string, options?: NavigateOptions): void {
|
|
76
|
-
path =
|
|
76
|
+
path = this.composePath(path);
|
|
77
77
|
this.logger.debug('Goto path => ', path);
|
|
78
78
|
this.navigate?.(path, options);
|
|
79
79
|
}
|
package/templates/react-app/src/{uikit/controllers/UserController.ts → base/services/UserService.ts}
RENAMED
|
@@ -3,17 +3,17 @@ import type {
|
|
|
3
3
|
UserApiGetUserInfoTransaction,
|
|
4
4
|
UserApiLoginTransaction
|
|
5
5
|
} from '@/base/apis/userApi/UserApiType';
|
|
6
|
-
import {
|
|
6
|
+
import { RouteService } from './RouteService';
|
|
7
7
|
import { ThreadUtil, type StorageTokenInterface } from '@qlover/corekit-bridge';
|
|
8
8
|
import { inject, injectable } from 'inversify';
|
|
9
9
|
import { IOCIdentifier } from '@/core/IOC';
|
|
10
|
-
import { LoginInterface } from '@/base/port/LoginInterface';
|
|
10
|
+
import { LoginInterface, RegisterFormData } from '@/base/port/LoginInterface';
|
|
11
11
|
import { UserApi } from '@/base/apis/userApi/UserApi';
|
|
12
12
|
import { AppError } from '@/base/cases/AppError';
|
|
13
|
-
import { LOCAL_NO_USER_TOKEN } from '@config/Identifier
|
|
14
|
-
import {
|
|
13
|
+
import { LOCAL_NO_USER_TOKEN } from '@config/Identifier/Error';
|
|
14
|
+
import { StoreInterface, StoreStateInterface } from '../port/StoreInterface';
|
|
15
15
|
|
|
16
|
-
class
|
|
16
|
+
class UserServiceState implements StoreStateInterface {
|
|
17
17
|
success: boolean = false;
|
|
18
18
|
userInfo: UserApiGetUserInfoTransaction['response']['data'] = {
|
|
19
19
|
name: '',
|
|
@@ -23,22 +23,22 @@ class UserControllerState {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
@injectable()
|
|
26
|
-
export class
|
|
27
|
-
extends
|
|
26
|
+
export class UserService
|
|
27
|
+
extends StoreInterface<UserServiceState>
|
|
28
28
|
implements ExecutorPlugin, LoginInterface
|
|
29
29
|
{
|
|
30
|
-
readonly pluginName = '
|
|
30
|
+
readonly pluginName = 'UserService';
|
|
31
31
|
|
|
32
32
|
constructor(
|
|
33
33
|
@inject(UserApi) private userApi: UserApi,
|
|
34
|
-
@inject(
|
|
34
|
+
@inject(RouteService) private routerController: RouteService,
|
|
35
35
|
@inject(IOCIdentifier.FeApiToken)
|
|
36
36
|
private userToken: StorageTokenInterface<string>
|
|
37
37
|
) {
|
|
38
|
-
super(() => new
|
|
38
|
+
super(() => new UserServiceState());
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
setState(state: Partial<
|
|
41
|
+
setState(state: Partial<UserServiceState>): void {
|
|
42
42
|
this.emit({ ...this.state, ...state });
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -111,4 +111,14 @@ export class UserController
|
|
|
111
111
|
isAuthenticated(): boolean {
|
|
112
112
|
return this.state.success;
|
|
113
113
|
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @override
|
|
117
|
+
*/
|
|
118
|
+
async register(params: RegisterFormData): Promise<unknown> {
|
|
119
|
+
return this.login({
|
|
120
|
+
username: params.username,
|
|
121
|
+
password: params.password
|
|
122
|
+
});
|
|
123
|
+
}
|
|
114
124
|
}
|
|
@@ -4,7 +4,7 @@ import { IOC } from './IOC';
|
|
|
4
4
|
import * as globals from '@/core/globals';
|
|
5
5
|
import { IocRegister } from './registers';
|
|
6
6
|
import { BootstrapsRegistry } from './bootstraps';
|
|
7
|
-
import { GLOBAL_NO_WINDOW } from '@config/Identifier
|
|
7
|
+
import { GLOBAL_NO_WINDOW } from '@config/Identifier/Error';
|
|
8
8
|
|
|
9
9
|
export default async function startup({
|
|
10
10
|
root,
|
|
@@ -17,8 +17,10 @@ import {
|
|
|
17
17
|
import mockDataJson from '@config/feapi.mock.json';
|
|
18
18
|
import { RequestStatusCatcher } from '@/base/cases/RequestStatusCatcher';
|
|
19
19
|
import themeConfig from '@config/theme.json';
|
|
20
|
-
import { localJsonStorage } from '../globals';
|
|
20
|
+
import { localJsonStorage, logger } from '../globals';
|
|
21
21
|
import { I18nService } from '@/base/services/I18nService';
|
|
22
|
+
import { RouteService } from '@/base/services/RouteService';
|
|
23
|
+
import { base as baseRoutes } from '@config/app.router.json';
|
|
22
24
|
|
|
23
25
|
export class RegisterCommon implements InversifyRegisterInterface {
|
|
24
26
|
register(
|
|
@@ -64,6 +66,14 @@ export class RegisterCommon implements InversifyRegisterInterface {
|
|
|
64
66
|
})
|
|
65
67
|
);
|
|
66
68
|
|
|
69
|
+
container.bind(
|
|
70
|
+
RouteService,
|
|
71
|
+
new RouteService({
|
|
72
|
+
config: baseRoutes,
|
|
73
|
+
logger
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
|
|
67
77
|
container.bind(I18nService, new I18nService(options.pathname));
|
|
68
78
|
}
|
|
69
79
|
}
|
|
@@ -1,24 +1,15 @@
|
|
|
1
|
-
import { localJsonStorage
|
|
2
|
-
import { RouterController } from '@/uikit/controllers/RouterController';
|
|
1
|
+
import { localJsonStorage } from '../globals';
|
|
3
2
|
import { JSONStorageController } from '@/uikit/controllers/JSONStorageController';
|
|
4
3
|
import { ProcesserService } from '@/base/services/ProcesserService';
|
|
5
|
-
import {
|
|
6
|
-
import { base as baseRoutes } from '@config/app.router.json';
|
|
4
|
+
import { UserService } from '@/base/services/UserService';
|
|
7
5
|
import { InversifyRegisterInterface } from '../IOC';
|
|
8
6
|
import { InversifyContainer } from '../IOC';
|
|
9
|
-
|
|
10
7
|
export class RegisterControllers implements InversifyRegisterInterface {
|
|
11
8
|
register(container: InversifyContainer): void {
|
|
12
|
-
const routerController = new RouterController({
|
|
13
|
-
config: baseRoutes,
|
|
14
|
-
logger
|
|
15
|
-
});
|
|
16
|
-
|
|
17
9
|
const jsonStorageController = new JSONStorageController(localJsonStorage);
|
|
18
10
|
|
|
19
|
-
container.bind(RouterController, routerController);
|
|
20
11
|
container.bind(JSONStorageController, jsonStorageController);
|
|
21
12
|
|
|
22
|
-
container.get(ProcesserService).use(container.get(
|
|
13
|
+
container.get(ProcesserService).use(container.get(UserService));
|
|
23
14
|
}
|
|
24
15
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { IOC } from '@/core/IOC';
|
|
2
|
-
import {
|
|
2
|
+
import { UserService } from '@/base/services/UserService';
|
|
3
3
|
import { Navigate, Outlet } from 'react-router-dom';
|
|
4
|
-
import {
|
|
4
|
+
import { useStore } from '@/uikit/hooks/useStore';
|
|
5
5
|
|
|
6
6
|
export default function Layout() {
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const userService = IOC(UserService);
|
|
8
|
+
useStore(userService, (state) => state.success);
|
|
9
9
|
|
|
10
10
|
// If user is authenticated, redirect to home page
|
|
11
|
-
if (
|
|
11
|
+
if (userService.isAuthenticated()) {
|
|
12
12
|
return <Navigate to="/" replace />;
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -3,9 +3,11 @@ import { Form, Input, Button } from 'antd';
|
|
|
3
3
|
import { UserOutlined, LockOutlined, GoogleOutlined } from '@ant-design/icons';
|
|
4
4
|
import { IOC } from '@/core/IOC';
|
|
5
5
|
import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { RouteService } from '@/base/services/RouteService';
|
|
7
|
+
import { UserService } from '@/base/services/UserService';
|
|
8
|
+
import { useStore } from '@/uikit/hooks/useStore';
|
|
9
|
+
import * as i18nKeys from '@config/Identifier/I18n';
|
|
10
|
+
import LocaleLink from '@/uikit/components/LocaleLink';
|
|
9
11
|
|
|
10
12
|
interface LoginFormData {
|
|
11
13
|
email: string;
|
|
@@ -14,19 +16,19 @@ interface LoginFormData {
|
|
|
14
16
|
|
|
15
17
|
export default function Login() {
|
|
16
18
|
const { t } = useBaseRoutePage();
|
|
17
|
-
const
|
|
19
|
+
const userService = IOC(UserService);
|
|
18
20
|
const AppConfig = IOC('AppConfig');
|
|
19
|
-
|
|
21
|
+
useStore(userService);
|
|
20
22
|
const [loading, setLoading] = useState(false);
|
|
21
23
|
|
|
22
24
|
const handleLogin = async (values: LoginFormData) => {
|
|
23
25
|
try {
|
|
24
26
|
setLoading(true);
|
|
25
|
-
await
|
|
27
|
+
await userService.login({
|
|
26
28
|
username: values.email,
|
|
27
29
|
password: values.password
|
|
28
30
|
});
|
|
29
|
-
IOC(
|
|
31
|
+
IOC(RouteService).replaceToHome();
|
|
30
32
|
} catch (error) {
|
|
31
33
|
console.error(error);
|
|
32
34
|
} finally {
|
|
@@ -45,19 +47,21 @@ export default function Login() {
|
|
|
45
47
|
</span>
|
|
46
48
|
</div>
|
|
47
49
|
<h1 className="text-4xl font-bold text-text mb-4">
|
|
48
|
-
|
|
50
|
+
{t(i18nKeys.LOGIN_WELCOME)}
|
|
49
51
|
</h1>
|
|
50
52
|
<p className="text-text-secondary text-lg mb-8">
|
|
51
|
-
|
|
52
|
-
accelerate your knowledge journey.
|
|
53
|
+
{t(i18nKeys.LOGIN_SUBTITLE)}
|
|
53
54
|
</p>
|
|
54
55
|
<div className="space-y-4">
|
|
56
|
+
<FeatureItem icon="🎯" text={t(i18nKeys.LOGIN_FEATURE_AI_PATHS)} />
|
|
55
57
|
<FeatureItem
|
|
56
58
|
icon="🎯"
|
|
57
|
-
text=
|
|
59
|
+
text={t(i18nKeys.LOGIN_FEATURE_SMART_RECOMMENDATIONS)}
|
|
60
|
+
/>
|
|
61
|
+
<FeatureItem
|
|
62
|
+
icon="📊"
|
|
63
|
+
text={t(i18nKeys.LOGIN_FEATURE_PROGRESS_TRACKING)}
|
|
58
64
|
/>
|
|
59
|
-
<FeatureItem icon="🎯" text="Smart content recommendations" />
|
|
60
|
-
<FeatureItem icon="📊" text="Real-time progress tracking" />
|
|
61
65
|
</div>
|
|
62
66
|
</div>
|
|
63
67
|
|
|
@@ -65,10 +69,10 @@ export default function Login() {
|
|
|
65
69
|
<div className="w-full lg:w-1/2 p-8 sm:p-12 flex items-center justify-center">
|
|
66
70
|
<div className="w-full max-w-[420px]">
|
|
67
71
|
<h2 className="text-2xl font-semibold mb-2 text-text">
|
|
68
|
-
{t(
|
|
72
|
+
{t(i18nKeys.LOGIN_TITLE)}
|
|
69
73
|
</h2>
|
|
70
74
|
<p className="text-text-secondary mb-8">
|
|
71
|
-
|
|
75
|
+
{t(i18nKeys.LOGIN_SUBTITLE)}
|
|
72
76
|
</p>
|
|
73
77
|
|
|
74
78
|
<Form
|
|
@@ -83,11 +87,14 @@ export default function Login() {
|
|
|
83
87
|
>
|
|
84
88
|
<Form.Item
|
|
85
89
|
name="email"
|
|
86
|
-
rules={[
|
|
90
|
+
rules={[
|
|
91
|
+
{ required: true, message: t(i18nKeys.LOGIN_EMAIL_REQUIRED) }
|
|
92
|
+
]}
|
|
87
93
|
>
|
|
88
94
|
<Input
|
|
89
95
|
prefix={<UserOutlined className="text-text-tertiary" />}
|
|
90
|
-
placeholder={t(
|
|
96
|
+
placeholder={t(i18nKeys.LOGIN_EMAIL)}
|
|
97
|
+
title={t(i18nKeys.LOGIN_EMAIL_TITLE)}
|
|
91
98
|
className="h-12 text-base bg-secondary border-border"
|
|
92
99
|
/>
|
|
93
100
|
</Form.Item>
|
|
@@ -95,19 +102,24 @@ export default function Login() {
|
|
|
95
102
|
<Form.Item
|
|
96
103
|
name="password"
|
|
97
104
|
rules={[
|
|
98
|
-
{ required: true, message:
|
|
105
|
+
{ required: true, message: t(i18nKeys.LOGIN_PASSWORD_REQUIRED) }
|
|
99
106
|
]}
|
|
100
107
|
>
|
|
101
108
|
<Input.Password
|
|
102
109
|
prefix={<LockOutlined />}
|
|
103
|
-
placeholder={t(
|
|
110
|
+
placeholder={t(i18nKeys.LOGIN_PASSWORD)}
|
|
111
|
+
title={t(i18nKeys.LOGIN_PASSWORD_TITLE)}
|
|
104
112
|
className="h-12 text-base"
|
|
105
113
|
/>
|
|
106
114
|
</Form.Item>
|
|
107
115
|
|
|
108
116
|
<div className="flex justify-end">
|
|
109
|
-
<a
|
|
110
|
-
|
|
117
|
+
<a
|
|
118
|
+
href="#"
|
|
119
|
+
className="text-brand hover:text-brand-hover"
|
|
120
|
+
title={t(i18nKeys.LOGIN_FORGOT_PASSWORD_TITLE)}
|
|
121
|
+
>
|
|
122
|
+
{t(i18nKeys.LOGIN_FORGOT_PASSWORD)}
|
|
111
123
|
</a>
|
|
112
124
|
</div>
|
|
113
125
|
|
|
@@ -116,27 +128,36 @@ export default function Login() {
|
|
|
116
128
|
type="primary"
|
|
117
129
|
htmlType="submit"
|
|
118
130
|
loading={loading}
|
|
131
|
+
title={t(i18nKeys.LOGIN_BUTTON_TITLE)}
|
|
119
132
|
className="w-full h-12 text-base"
|
|
120
133
|
>
|
|
121
|
-
{t(
|
|
134
|
+
{t(i18nKeys.LOGIN_BUTTON)}
|
|
122
135
|
</Button>
|
|
123
136
|
</Form.Item>
|
|
124
137
|
|
|
125
138
|
<div className="text-center text-text-tertiary my-4">
|
|
126
|
-
|
|
139
|
+
{t(i18nKeys.LOGIN_CONTINUE_WITH)}
|
|
127
140
|
</div>
|
|
128
141
|
|
|
129
|
-
<Button
|
|
130
|
-
|
|
142
|
+
<Button
|
|
143
|
+
icon={<GoogleOutlined />}
|
|
144
|
+
className="w-full h-12 text-base"
|
|
145
|
+
title={t(i18nKeys.LOGIN_WITH_GOOGLE_TITLE)}
|
|
146
|
+
>
|
|
147
|
+
{t(i18nKeys.LOGIN_WITH_GOOGLE)}
|
|
131
148
|
</Button>
|
|
132
149
|
|
|
133
150
|
<div className="text-center mt-6">
|
|
134
151
|
<span className="text-text-tertiary">
|
|
135
|
-
|
|
152
|
+
{t(i18nKeys.LOGIN_NO_ACCOUNT)}{' '}
|
|
136
153
|
</span>
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
154
|
+
<LocaleLink
|
|
155
|
+
href="/register"
|
|
156
|
+
className="text-brand hover:text-brand-hover"
|
|
157
|
+
title={t(i18nKeys.LOGIN_CREATE_ACCOUNT_TITLE)}
|
|
158
|
+
>
|
|
159
|
+
{t(i18nKeys.LOGIN_CREATE_ACCOUNT)}
|
|
160
|
+
</LocaleLink>
|
|
140
161
|
</div>
|
|
141
162
|
</Form>
|
|
142
163
|
</div>
|