@qlover/create-app 0.7.8 → 0.7.10
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 +140 -0
- package/dist/index.cjs +3 -3
- package/dist/index.js +3 -3
- package/dist/templates/next-app/.env.template +8 -4
- package/dist/templates/next-app/config/IOCIdentifier.ts +4 -1
- package/dist/templates/next-app/config/Identifier/api.ts +20 -0
- package/dist/templates/next-app/config/Identifier/index.ts +2 -0
- package/dist/templates/next-app/config/Identifier/page.home.ts +7 -0
- package/dist/templates/next-app/config/Identifier/page.login.ts +2 -2
- package/dist/templates/next-app/config/Identifier/page.register.ts +43 -22
- package/dist/templates/next-app/config/Identifier/validator.ts +34 -0
- package/dist/templates/next-app/config/i18n/HomeI18n .ts +22 -0
- package/dist/templates/next-app/config/i18n/index.ts +1 -0
- package/dist/templates/next-app/config/i18n/register18n.ts +44 -0
- package/dist/templates/next-app/config/theme.ts +1 -0
- package/dist/templates/next-app/migrations/schema/UserSchema.ts +13 -0
- package/dist/templates/next-app/migrations/sql/1694244000000.sql +10 -0
- package/dist/templates/next-app/package.json +15 -6
- package/dist/templates/next-app/public/locales/en.json +17 -2
- package/dist/templates/next-app/public/locales/zh.json +17 -2
- package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +21 -0
- package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +10 -0
- package/dist/templates/next-app/src/app/[locale]/layout.tsx +1 -7
- package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +28 -16
- package/dist/templates/next-app/src/app/[locale]/login/page.tsx +10 -17
- package/dist/templates/next-app/src/app/[locale]/page.tsx +94 -102
- package/dist/templates/next-app/src/app/[locale]/register/RegisterForm.tsx +176 -0
- package/dist/templates/next-app/src/app/[locale]/register/page.tsx +79 -0
- package/dist/templates/next-app/src/app/api/user/login/route.ts +50 -0
- package/dist/templates/next-app/src/app/api/user/logout/route.ts +27 -0
- package/dist/templates/next-app/src/app/api/user/register/route.ts +50 -0
- package/dist/templates/next-app/src/base/cases/AppConfig.ts +19 -0
- package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +35 -0
- package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +70 -0
- package/dist/templates/next-app/src/base/cases/RouterService.ts +4 -0
- package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +67 -0
- package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +48 -0
- package/dist/templates/next-app/src/base/port/AppApiInterface.ts +14 -0
- package/dist/templates/next-app/src/base/port/AppUserApiInterface.ts +15 -0
- package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +18 -0
- package/dist/templates/next-app/src/base/port/DBMigrationInterface.ts +92 -0
- package/dist/templates/next-app/src/base/port/DBTableInterface.ts +3 -0
- package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +3 -2
- package/dist/templates/next-app/src/base/port/MigrationApiInterface.ts +3 -0
- package/dist/templates/next-app/src/base/port/ServerApiResponseInterface.ts +6 -0
- package/dist/templates/next-app/src/base/port/UserServiceInterface.ts +3 -2
- package/dist/templates/next-app/src/base/services/I18nService.ts +9 -45
- package/dist/templates/next-app/src/base/services/UserService.ts +9 -8
- package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +63 -0
- package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +72 -0
- package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +48 -0
- package/dist/templates/next-app/src/base/services/appApi/AppUserType.ts +51 -0
- package/dist/templates/next-app/src/base/services/migrations/MigrationsApi.ts +43 -0
- package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +30 -5
- package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +4 -3
- package/dist/templates/next-app/src/server/AppErrorApi.ts +10 -0
- package/dist/templates/next-app/src/server/AppSuccessApi.ts +7 -0
- package/dist/templates/next-app/src/server/PasswordEncrypt.ts +12 -0
- package/dist/templates/next-app/src/server/ServerAuth.ts +50 -0
- package/dist/templates/next-app/src/server/SupabaseBridge.ts +124 -0
- package/dist/templates/next-app/src/server/UserCredentialToken.ts +49 -0
- package/dist/templates/next-app/src/server/port/CrentialTokenInterface.ts +5 -0
- package/dist/templates/next-app/src/server/port/ServerInterface.ts +22 -0
- package/dist/templates/next-app/src/server/port/UserAuthInterface.ts +9 -0
- package/dist/templates/next-app/src/server/port/UserRepositoryInterface.ts +15 -0
- package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +8 -0
- package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +23 -0
- package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +63 -0
- package/dist/templates/next-app/src/server/services/UserService.ts +105 -0
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +79 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/_default.css +12 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/dark.css +26 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/pink.css +16 -0
- package/dist/templates/next-app/src/styles/css/page.css +4 -3
- package/dist/templates/next-app/src/styles/css/themes/_default.css +1 -0
- package/dist/templates/next-app/src/styles/css/themes/dark.css +1 -0
- package/dist/templates/next-app/src/styles/css/themes/pink.css +1 -0
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +6 -11
- package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +27 -0
- package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +8 -1
- package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +49 -21
- package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +20 -10
- package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +92 -48
- package/dist/templates/next-app/tsconfig.json +3 -1
- package/package.json +2 -2
- package/dist/templates/next-app/src/base/cases/ServerAuth.ts +0 -17
- package/dist/templates/next-app/src/base/port/ServerAuthInterface.ts +0 -3
- package/dist/templates/next-app/src/base/port/ServerInterface.ts +0 -12
- /package/dist/templates/next-app/src/{app/[locale]/login → uikit/components}/FeatureItem.tsx +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 用户未找到
|
|
3
|
+
* @localZh 用户未找到
|
|
4
|
+
* @localEn User not found
|
|
5
|
+
*/
|
|
6
|
+
export const API_USER_NOT_FOUND = 'api__user__not_found';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @description 用户已存在
|
|
10
|
+
* @localZh 用户已存在
|
|
11
|
+
* @localEn User already exists
|
|
12
|
+
*/
|
|
13
|
+
export const API_USER_ALREADY_EXISTS = 'api__user__already_exists';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @description 响应不正确
|
|
17
|
+
* @localZh 响应不正确
|
|
18
|
+
* @localEn Response not correct
|
|
19
|
+
*/
|
|
20
|
+
export const API_RESPONSE_NOT_OK = 'RESPONSE_NOT_OK';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './api';
|
|
1
2
|
export * from './common';
|
|
2
3
|
export * from './common.error';
|
|
3
4
|
export * from './page.about';
|
|
@@ -8,3 +9,4 @@ export * from './page.jsonStorage';
|
|
|
8
9
|
export * from './page.login';
|
|
9
10
|
export * from './page.register';
|
|
10
11
|
export * from './page.request';
|
|
12
|
+
export * from './validator';
|
|
@@ -12,6 +12,13 @@ export const PAGE_HOME_TITLE = 'page__home__title';
|
|
|
12
12
|
*/
|
|
13
13
|
export const PAGE_HOME_DESCRIPTION = 'page__home__description';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @description Home page keywords
|
|
17
|
+
* @localZh 现代前端实用库, 实用工具, 组件
|
|
18
|
+
* @localEn Modern frontend utility library, practical tools, components
|
|
19
|
+
*/
|
|
20
|
+
export const PAGE_HOME_KEYWORDS = 'page__home__keywords';
|
|
21
|
+
|
|
15
22
|
/**
|
|
16
23
|
* @description Home page welcome message
|
|
17
24
|
* @localZh 欢迎来到主页
|
|
@@ -97,8 +97,8 @@ export const LOGIN_NO_ACCOUNT = 'login__no_account';
|
|
|
97
97
|
export const LOGIN_CREATE_ACCOUNT = 'login__create_account';
|
|
98
98
|
/**
|
|
99
99
|
* @description Login page email validation message
|
|
100
|
-
* @localZh
|
|
101
|
-
* @localEn Please input
|
|
100
|
+
* @localZh 请输入正确的邮箱!
|
|
101
|
+
* @localEn Please input a valid email!
|
|
102
102
|
*/
|
|
103
103
|
export const LOGIN_EMAIL_REQUIRED = 'login__email_required';
|
|
104
104
|
/**
|
|
@@ -13,17 +13,56 @@ export const PAGE_REGISTER_TITLE = 'page__register__title';
|
|
|
13
13
|
export const PAGE_REGISTER_DESCRIPTION = 'page__register__description';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* @description Register page
|
|
17
|
-
* @localZh
|
|
18
|
-
* @localEn Create Account
|
|
16
|
+
* @description Register page content
|
|
17
|
+
* @localZh 创建账号页面内容
|
|
18
|
+
* @localEn Create Account Page Content
|
|
19
19
|
*/
|
|
20
|
-
export const
|
|
20
|
+
export const PAGE_REGISTER_CONTENT = 'page__register__content';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @description Register page keywords
|
|
24
|
+
* @localZh 创建账号页面关键词
|
|
25
|
+
* @localEn Create Account Page Keywords
|
|
26
|
+
*/
|
|
27
|
+
export const PAGE_REGISTER_KEYWORDS = 'page__register__keywords';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @description Register page welcome
|
|
31
|
+
* @localZh 欢迎来到我们的平台
|
|
32
|
+
* @localEn Welcome to our platform
|
|
33
|
+
*/
|
|
34
|
+
export const REGISTER_WELCOME = 'register__welcome';
|
|
35
|
+
|
|
21
36
|
/**
|
|
22
37
|
* @description Register page subtitle
|
|
23
38
|
* @localZh 开始您的学习之旅
|
|
24
39
|
* @localEn Start your learning journey
|
|
25
40
|
*/
|
|
26
41
|
export const REGISTER_SUBTITLE = 'register__subtitle';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @description Register page feature ai paths
|
|
45
|
+
* @localZh AI路径
|
|
46
|
+
* @localEn AI Paths
|
|
47
|
+
*/
|
|
48
|
+
export const REGISTER_FEATURE_AI_PATHS = 'register__feature__ai_paths';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @description Register page feature smart recommendations
|
|
52
|
+
* @localZh 智能推荐
|
|
53
|
+
* @localEn Smart Recommendations
|
|
54
|
+
*/
|
|
55
|
+
export const REGISTER_FEATURE_SMART_RECOMMENDATIONS =
|
|
56
|
+
'register__feature__smart_recommendations';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @description Register page feature progress tracking
|
|
60
|
+
* @localZh 进度跟踪
|
|
61
|
+
* @localEn Progress Tracking
|
|
62
|
+
*/
|
|
63
|
+
export const REGISTER_FEATURE_PROGRESS_TRACKING =
|
|
64
|
+
'register__feature__progress_tracking';
|
|
65
|
+
|
|
27
66
|
/**
|
|
28
67
|
* @description Register page username field
|
|
29
68
|
* @localZh 用户名
|
|
@@ -121,24 +160,6 @@ export const REGISTER_HAVE_ACCOUNT = 'register__have_account';
|
|
|
121
160
|
* @localEn Sign in
|
|
122
161
|
*/
|
|
123
162
|
export const REGISTER_LOGIN_LINK = 'register__login_link';
|
|
124
|
-
/**
|
|
125
|
-
* @description Register page feature item - Personalized Learning
|
|
126
|
-
* @localZh 个性化学习体验
|
|
127
|
-
* @localEn Personalized Learning Experience
|
|
128
|
-
*/
|
|
129
|
-
export const REGISTER_FEATURE_PERSONALIZED = 'register__feature__personalized';
|
|
130
|
-
/**
|
|
131
|
-
* @description Register page feature item - Expert Support
|
|
132
|
-
* @localZh 专家支持和指导
|
|
133
|
-
* @localEn Expert Support and Guidance
|
|
134
|
-
*/
|
|
135
|
-
export const REGISTER_FEATURE_SUPPORT = 'register__feature__support';
|
|
136
|
-
/**
|
|
137
|
-
* @description Register page feature item - Learning Community
|
|
138
|
-
* @localZh 活跃的学习社区
|
|
139
|
-
* @localEn Active Learning Community
|
|
140
|
-
*/
|
|
141
|
-
export const REGISTER_FEATURE_COMMUNITY = 'register__feature__community';
|
|
142
163
|
/**
|
|
143
164
|
* @description Register page terms agreement required message
|
|
144
165
|
* @localZh 请同意服务条款和隐私政策
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Validator for login params
|
|
3
|
+
* @localZh 不是一个有效的登录参数
|
|
4
|
+
* @localEn Not a valid login parameter
|
|
5
|
+
*/
|
|
6
|
+
export const V_LOGIN_PARAMS_REQUIRED = 'v_login_params_required';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @description Invalid email format validation message
|
|
10
|
+
* @localZh 邮箱格式无效的验证消息
|
|
11
|
+
* @localEn Invalid email format validation message
|
|
12
|
+
*/
|
|
13
|
+
export const V_EMAIL_INVALID = 'validator_email_invalid';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @description Password minimum length validation message
|
|
17
|
+
* @localZh 密码最小长度验证消息(6)
|
|
18
|
+
* @localEn Password minimum length validation message(6)
|
|
19
|
+
*/
|
|
20
|
+
export const V_PASSWORD_MIN_LENGTH = 'validator_password_min_length';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @description Password maximum length validation message
|
|
24
|
+
* @localZh 密码最大长度验证消息(50)
|
|
25
|
+
* @localEn Password maximum length validation message(50)
|
|
26
|
+
*/
|
|
27
|
+
export const V_PASSWORD_MAX_LENGTH = 'validator_password_max_length';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @description Password whitespace validation message
|
|
31
|
+
* @localZh 密码不能包含空格的验证消息
|
|
32
|
+
* @localEn Password cannot contain whitespace characters validation message
|
|
33
|
+
*/
|
|
34
|
+
export const V_PASSWORD_SPECIAL_CHARS = 'validator_password_special_chars';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as i18nKeys from '../Identifier/page.home';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Home page i18n interface
|
|
5
|
+
*
|
|
6
|
+
* @description
|
|
7
|
+
* - welcome: welcome message
|
|
8
|
+
*/
|
|
9
|
+
export type HomeI18nInterface = typeof homeI18n;
|
|
10
|
+
|
|
11
|
+
export const homeI18n = Object.freeze({
|
|
12
|
+
// basic meta properties
|
|
13
|
+
title: i18nKeys.PAGE_HOME_TITLE,
|
|
14
|
+
description: i18nKeys.PAGE_HOME_DESCRIPTION,
|
|
15
|
+
content: i18nKeys.PAGE_HOME_DESCRIPTION,
|
|
16
|
+
keywords: i18nKeys.PAGE_HOME_KEYWORDS,
|
|
17
|
+
|
|
18
|
+
welcome: i18nKeys.HOME_WELCOME,
|
|
19
|
+
getStartedTitle: i18nKeys.HOME_GET_STARTED_TITLE,
|
|
20
|
+
getStartedDescription: i18nKeys.HOME_GET_STARTED_DESCRIPTION,
|
|
21
|
+
getStartedButton: i18nKeys.HOME_GET_STARTED_BUTTON
|
|
22
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as i18nKeys from '../Identifier/page.register';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Register page i18n interface
|
|
5
|
+
*
|
|
6
|
+
* @description
|
|
7
|
+
* - welcome: welcome message
|
|
8
|
+
*/
|
|
9
|
+
export type RegisterI18nInterface = typeof register18n;
|
|
10
|
+
|
|
11
|
+
export const register18n = Object.freeze({
|
|
12
|
+
// basic meta properties
|
|
13
|
+
title: i18nKeys.PAGE_REGISTER_TITLE,
|
|
14
|
+
description: i18nKeys.PAGE_REGISTER_DESCRIPTION,
|
|
15
|
+
content: i18nKeys.PAGE_REGISTER_CONTENT,
|
|
16
|
+
keywords: i18nKeys.PAGE_REGISTER_KEYWORDS,
|
|
17
|
+
|
|
18
|
+
// register page
|
|
19
|
+
welcome: i18nKeys.REGISTER_WELCOME,
|
|
20
|
+
subtitle: i18nKeys.REGISTER_SUBTITLE,
|
|
21
|
+
feature_ai_paths: i18nKeys.REGISTER_FEATURE_AI_PATHS,
|
|
22
|
+
feature_smart_recommendations:
|
|
23
|
+
i18nKeys.REGISTER_FEATURE_SMART_RECOMMENDATIONS,
|
|
24
|
+
feature_progress_tracking: i18nKeys.REGISTER_FEATURE_PROGRESS_TRACKING,
|
|
25
|
+
|
|
26
|
+
// register form
|
|
27
|
+
username: i18nKeys.REGISTER_USERNAME,
|
|
28
|
+
username_required: i18nKeys.REGISTER_USERNAME_REQUIRED,
|
|
29
|
+
email: i18nKeys.REGISTER_EMAIL,
|
|
30
|
+
email_required: i18nKeys.REGISTER_EMAIL_REQUIRED,
|
|
31
|
+
password: i18nKeys.REGISTER_PASSWORD,
|
|
32
|
+
password_required: i18nKeys.REGISTER_PASSWORD_REQUIRED,
|
|
33
|
+
confirm_password_required: i18nKeys.REGISTER_CONFIRM_PASSWORD_REQUIRED,
|
|
34
|
+
password_mismatch: i18nKeys.REGISTER_PASSWORD_MISMATCH,
|
|
35
|
+
button: i18nKeys.REGISTER_BUTTON,
|
|
36
|
+
terms_prefix: i18nKeys.REGISTER_TERMS_PREFIX,
|
|
37
|
+
terms_link: i18nKeys.REGISTER_TERMS_LINK,
|
|
38
|
+
terms_and: i18nKeys.REGISTER_TERMS_AND,
|
|
39
|
+
privacy_link: i18nKeys.REGISTER_PRIVACY_LINK,
|
|
40
|
+
have_account: i18nKeys.REGISTER_HAVE_ACCOUNT,
|
|
41
|
+
confirm_password: i18nKeys.REGISTER_CONFIRM_PASSWORD,
|
|
42
|
+
terms_required: i18nKeys.REGISTER_TERMS_REQUIRED,
|
|
43
|
+
login_link: i18nKeys.REGISTER_LOGIN_LINK
|
|
44
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS fe_users (
|
|
2
|
+
id BIGSERIAL PRIMARY KEY,
|
|
3
|
+
role TEXT NOT NULL,
|
|
4
|
+
email TEXT NOT NULL,
|
|
5
|
+
password TEXT NOT NULL,
|
|
6
|
+
credential_token TEXT,
|
|
7
|
+
email_confirmed_at TIMESTAMPTZ,
|
|
8
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
9
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
10
|
+
);
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
|
-
"dev": "cross-env APP_ENV=localhost next dev --turbopack",
|
|
7
|
-
"dev:staging": "cross-env APP_ENV=staging next dev --turbopack",
|
|
8
|
-
"dev:prod": "cross-env APP_ENV=production next dev --turbopack",
|
|
6
|
+
"dev": "cross-env APP_ENV=localhost next dev --turbopack --port 3100",
|
|
7
|
+
"dev:staging": "cross-env APP_ENV=staging next dev --turbopack --port 3100",
|
|
8
|
+
"dev:prod": "cross-env APP_ENV=production next dev --turbopack --port 3100",
|
|
9
9
|
"build": "cross-env APP_ENV=localhost next build --turbopack",
|
|
10
10
|
"build:staging": "cross-env APP_ENV=staging next build --turbopack",
|
|
11
11
|
"build:prod": "cross-env APP_ENV=production next build --turbopack",
|
|
12
|
-
"start": "next start",
|
|
12
|
+
"start": "next start --port 3101",
|
|
13
13
|
"lint": "eslint .",
|
|
14
14
|
"lint:fix": "eslint . --ext .ts,.tsx --fix",
|
|
15
15
|
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,scss,md}\"",
|
|
@@ -25,26 +25,34 @@
|
|
|
25
25
|
"@qlover/corekit-bridge": "^1.6.4",
|
|
26
26
|
"@qlover/fe-corekit": "^2.1.0",
|
|
27
27
|
"@qlover/slice-store-react": "^1.4.1",
|
|
28
|
+
"@supabase/auth-helpers-nextjs": "^0.10.0",
|
|
29
|
+
"@supabase/ssr": "^0.7.0",
|
|
30
|
+
"@supabase/supabase-js": "^2.57.2",
|
|
31
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
28
32
|
"antd": "^5.27.1",
|
|
29
33
|
"clsx": "^2.1.1",
|
|
30
34
|
"inversify": "^7.8.1",
|
|
35
|
+
"jsonwebtoken": "^9.0.2",
|
|
31
36
|
"lodash": "^4.17.21",
|
|
32
37
|
"next": "15.5.0",
|
|
33
38
|
"next-intl": "^4.3.5",
|
|
34
39
|
"next-themes": "^0.4.6",
|
|
35
40
|
"react": "19.1.0",
|
|
36
|
-
"react-dom": "19.1.0"
|
|
41
|
+
"react-dom": "19.1.0",
|
|
42
|
+
"zod": "^4.1.8"
|
|
37
43
|
},
|
|
38
44
|
"devDependencies": {
|
|
39
45
|
"@brain-toolkit/ts2locales": "^0.2.3",
|
|
40
46
|
"@eslint/eslintrc": "^3",
|
|
47
|
+
"@qlover/env-loader": "^0.3.0",
|
|
48
|
+
"@qlover/eslint-plugin": "latest",
|
|
41
49
|
"@tailwindcss/postcss": "^4",
|
|
42
50
|
"@types/lodash": "^4.17.20",
|
|
43
51
|
"@types/node": "^20",
|
|
44
52
|
"@types/react": "^19",
|
|
45
53
|
"@types/react-dom": "^19",
|
|
46
|
-
"@qlover/eslint-plugin": "latest",
|
|
47
54
|
"cross-env": "^7.0.3",
|
|
55
|
+
"dotenv-expand": "^12.0.3",
|
|
48
56
|
"eslint": "^9",
|
|
49
57
|
"eslint-config-next": "15.5.0",
|
|
50
58
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -52,6 +60,7 @@
|
|
|
52
60
|
"eslint-plugin-prettier": "^5.5.4",
|
|
53
61
|
"eslint-plugin-unused-imports": "^4.2.0",
|
|
54
62
|
"prettier": "^3.6.2",
|
|
63
|
+
"supabase": "^2.40.7",
|
|
55
64
|
"tailwindcss": "^4",
|
|
56
65
|
"typescript": "^5"
|
|
57
66
|
}
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"login__with_google": "Sign in with Google",
|
|
115
115
|
"login__no_account": "Don't have an account?",
|
|
116
116
|
"login__create_account": "Create one here",
|
|
117
|
-
"login__email_required": "Please input
|
|
117
|
+
"login__email_required": "Please input a valid email!",
|
|
118
118
|
"login__password_required": "Please input your password!",
|
|
119
119
|
"login__feature__ai_paths": "AI-powered personalized learning paths",
|
|
120
120
|
"login__feature__smart_recommendations": "Smart content recommendations",
|
|
@@ -180,5 +180,20 @@
|
|
|
180
180
|
"page__request__stop_api_catch": "Stop API Catch Result",
|
|
181
181
|
"page__login__content": "Login Page Content",
|
|
182
182
|
"page__login__keywords": "Login Page Keywords",
|
|
183
|
-
"err__server__auth__error": "Server auth error"
|
|
183
|
+
"err__server__auth__error": "Server auth error",
|
|
184
|
+
"page__home__keywords": "Modern frontend utility library, practical tools, components",
|
|
185
|
+
"api__user__not_found": "User not found",
|
|
186
|
+
"api__user__already_exists": "User already exists",
|
|
187
|
+
"RESPONSE_NOT_OK": "Response not correct",
|
|
188
|
+
"v_login_params_required": "Not a valid login parameter",
|
|
189
|
+
"validator_email_invalid": "Invalid email format validation message",
|
|
190
|
+
"validator_password_min_length": "Password minimum length validation message(6)",
|
|
191
|
+
"validator_password_max_length": "Password maximum length validation message(50)",
|
|
192
|
+
"validator_password_special_chars": "Password cannot contain whitespace characters validation message",
|
|
193
|
+
"page__register__content": "Create Account Page Content",
|
|
194
|
+
"page__register__keywords": "Create Account Page Keywords",
|
|
195
|
+
"register__welcome": "Welcome to our platform",
|
|
196
|
+
"register__feature__ai_paths": "AI Paths",
|
|
197
|
+
"register__feature__smart_recommendations": "Smart Recommendations",
|
|
198
|
+
"register__feature__progress_tracking": "Progress Tracking"
|
|
184
199
|
}
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"login__with_google": "使用 Google 登录",
|
|
115
115
|
"login__no_account": "还没有账号?",
|
|
116
116
|
"login__create_account": "在此创建",
|
|
117
|
-
"login__email_required": "
|
|
117
|
+
"login__email_required": "请输入正确的邮箱!",
|
|
118
118
|
"login__password_required": "请输入您的密码!",
|
|
119
119
|
"login__feature__ai_paths": "AI驱动的个性化学习路径",
|
|
120
120
|
"login__feature__smart_recommendations": "智能内容推荐",
|
|
@@ -180,5 +180,20 @@
|
|
|
180
180
|
"page__request__stop_api_catch": "停止 API 捕获结果",
|
|
181
181
|
"page__login__content": "登录页面内容",
|
|
182
182
|
"page__login__keywords": "登录页面关键词",
|
|
183
|
-
"err__server__auth__error": "服务器认证错误"
|
|
183
|
+
"err__server__auth__error": "服务器认证错误",
|
|
184
|
+
"page__home__keywords": "现代前端实用库, 实用工具, 组件",
|
|
185
|
+
"api__user__not_found": "用户未找到",
|
|
186
|
+
"api__user__already_exists": "用户已存在",
|
|
187
|
+
"RESPONSE_NOT_OK": "响应不正确",
|
|
188
|
+
"v_login_params_required": "不是一个有效的登录参数",
|
|
189
|
+
"validator_email_invalid": "邮箱格式无效的验证消息",
|
|
190
|
+
"validator_password_min_length": "密码最小长度验证消息(6)",
|
|
191
|
+
"validator_password_max_length": "密码最大长度验证消息(50)",
|
|
192
|
+
"validator_password_special_chars": "密码不能包含空格的验证消息",
|
|
193
|
+
"page__register__content": "创建账号页面内容",
|
|
194
|
+
"page__register__keywords": "创建账号页面关键词",
|
|
195
|
+
"register__welcome": "欢迎来到我们的平台",
|
|
196
|
+
"register__feature__ai_paths": "AI路径",
|
|
197
|
+
"register__feature__smart_recommendations": "智能推荐",
|
|
198
|
+
"register__feature__progress_tracking": "进度跟踪"
|
|
184
199
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PageParams } from '@/base/cases/PageParams';
|
|
2
|
+
import type { PageLayoutProps } from '@/base/types/PageProps';
|
|
3
|
+
import '@/styles/css/index.css';
|
|
4
|
+
|
|
5
|
+
export default async function AdminLayout({
|
|
6
|
+
children,
|
|
7
|
+
params
|
|
8
|
+
}: PageLayoutProps) {
|
|
9
|
+
const pageParams = new PageParams(await params!);
|
|
10
|
+
const locale = pageParams.getLocale();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
data-testid="AdminRootLayout"
|
|
15
|
+
lang={locale}
|
|
16
|
+
className="min-h-screen bg-primary text-text"
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -2,7 +2,6 @@ import { NextIntlClientProvider } from 'next-intl';
|
|
|
2
2
|
import { themeConfig } from '@config/theme';
|
|
3
3
|
import { PageParams } from '@/base/cases/PageParams';
|
|
4
4
|
import type { PageLayoutProps } from '@/base/types/PageProps';
|
|
5
|
-
import { BaseHeader } from '@/uikit/components/BaseHeader';
|
|
6
5
|
import { ComboProvider } from '@/uikit/components/ComboProvider';
|
|
7
6
|
import '@/styles/css/index.css';
|
|
8
7
|
|
|
@@ -19,12 +18,7 @@ export default async function RootLayout({
|
|
|
19
18
|
<html data-testid="RootLayout" lang={locale} suppressHydrationWarning>
|
|
20
19
|
<body>
|
|
21
20
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
|
22
|
-
<ComboProvider themeConfig={themeConfig}>
|
|
23
|
-
<div className="flex flex-col min-h-screen">
|
|
24
|
-
<BaseHeader showLogoutButton />
|
|
25
|
-
<div className="flex flex-col">{children}</div>
|
|
26
|
-
</div>
|
|
27
|
-
</ComboProvider>
|
|
21
|
+
<ComboProvider themeConfig={themeConfig}>{children}</ComboProvider>
|
|
28
22
|
</NextIntlClientProvider>
|
|
29
23
|
</body>
|
|
30
24
|
</html>
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { UserOutlined, LockOutlined, GoogleOutlined } from '@ant-design/icons';
|
|
4
4
|
import { Form, Input, Button } from 'antd';
|
|
5
|
+
import { useTranslations } from 'next-intl';
|
|
5
6
|
import { useState } from 'react';
|
|
6
7
|
import { I } from '@config/IOCIdentifier';
|
|
8
|
+
import { LoginValidator } from '@/server/validators/LoginValidator';
|
|
7
9
|
import { LocaleLink } from '@/uikit/components/LocaleLink';
|
|
8
10
|
import { useIOC } from '@/uikit/hook/useIOC';
|
|
9
11
|
import type { LoginI18nInterface } from '@config/i18n/loginI18n';
|
|
@@ -15,7 +17,10 @@ interface LoginFormData {
|
|
|
15
17
|
|
|
16
18
|
export function LoginForm(props: { tt: LoginI18nInterface }) {
|
|
17
19
|
const { tt } = props;
|
|
20
|
+
const t = useTranslations();
|
|
18
21
|
const userService = useIOC(I.UserServiceInterface);
|
|
22
|
+
const logger = useIOC(I.Logger);
|
|
23
|
+
const appConfig = useIOC(I.AppConfig);
|
|
19
24
|
const routerService = useIOC(I.RouterServiceInterface);
|
|
20
25
|
const [loading, setLoading] = useState(false);
|
|
21
26
|
|
|
@@ -25,7 +30,7 @@ export function LoginForm(props: { tt: LoginI18nInterface }) {
|
|
|
25
30
|
await userService.login(values);
|
|
26
31
|
routerService.gotoHome();
|
|
27
32
|
} catch (error) {
|
|
28
|
-
|
|
33
|
+
logger.error(error);
|
|
29
34
|
} finally {
|
|
30
35
|
setLoading(false);
|
|
31
36
|
}
|
|
@@ -38,39 +43,50 @@ export function LoginForm(props: { tt: LoginI18nInterface }) {
|
|
|
38
43
|
onFinish={handleLogin}
|
|
39
44
|
layout="vertical"
|
|
40
45
|
className="space-y-4"
|
|
46
|
+
validateTrigger="onSubmit"
|
|
47
|
+
initialValues={{
|
|
48
|
+
email: appConfig.testLoginEmail,
|
|
49
|
+
password: appConfig.testLoginPassword
|
|
50
|
+
}}
|
|
41
51
|
>
|
|
42
52
|
<Form.Item
|
|
43
53
|
name="email"
|
|
44
|
-
rules={[{ required: true, message: tt.emailRequired }]}
|
|
54
|
+
rules={[{ required: true, type: 'email', message: tt.emailRequired }]}
|
|
45
55
|
>
|
|
46
56
|
<Input
|
|
47
57
|
prefix={<UserOutlined className="text-text-tertiary" />}
|
|
48
58
|
placeholder={tt.email}
|
|
49
59
|
title={tt.emailTitle}
|
|
50
|
-
className="h-12 text-base bg-secondary border-border"
|
|
51
|
-
autoComplete="off"
|
|
60
|
+
className="h-12 text-base bg-secondary border-c-border"
|
|
52
61
|
/>
|
|
53
62
|
</Form.Item>
|
|
54
63
|
|
|
55
64
|
<Form.Item
|
|
56
65
|
name="password"
|
|
57
|
-
rules={[
|
|
66
|
+
rules={[
|
|
67
|
+
{ required: true, message: tt.passwordRequired },
|
|
68
|
+
{
|
|
69
|
+
validator(_, value) {
|
|
70
|
+
const validator = new LoginValidator();
|
|
71
|
+
const result = validator.validatePassword(value);
|
|
72
|
+
if (result != null) {
|
|
73
|
+
return Promise.reject(t(result.message));
|
|
74
|
+
}
|
|
75
|
+
return Promise.resolve();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
]}
|
|
58
79
|
>
|
|
59
80
|
<Input.Password
|
|
60
81
|
prefix={<LockOutlined />}
|
|
61
82
|
placeholder={tt.password}
|
|
62
83
|
title={tt.passwordTitle}
|
|
63
84
|
className="h-12 text-base"
|
|
64
|
-
autoComplete="new-password"
|
|
65
85
|
/>
|
|
66
86
|
</Form.Item>
|
|
67
87
|
|
|
68
88
|
<div className="flex justify-end">
|
|
69
|
-
<LocaleLink
|
|
70
|
-
href="#"
|
|
71
|
-
className="text-brand hover:text-brand-hover"
|
|
72
|
-
title={tt.forgotPasswordTitle}
|
|
73
|
-
>
|
|
89
|
+
<LocaleLink href="#" title={tt.forgotPasswordTitle}>
|
|
74
90
|
{tt.forgotPassword}
|
|
75
91
|
</LocaleLink>
|
|
76
92
|
</div>
|
|
@@ -101,11 +117,7 @@ export function LoginForm(props: { tt: LoginI18nInterface }) {
|
|
|
101
117
|
|
|
102
118
|
<div className="text-center mt-6">
|
|
103
119
|
<span className="text-text-tertiary">{tt.noAccount} </span>
|
|
104
|
-
<LocaleLink
|
|
105
|
-
href="/register"
|
|
106
|
-
className="text-brand hover:text-brand-hover"
|
|
107
|
-
title={tt.createAccountTitle}
|
|
108
|
-
>
|
|
120
|
+
<LocaleLink href="/register" title={tt.createAccountTitle}>
|
|
109
121
|
{tt.createAccount}
|
|
110
122
|
</LocaleLink>
|
|
111
123
|
</div>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { notFound } from 'next/navigation';
|
|
2
2
|
import { loginI18n, i18nConfig } from '@config/i18n';
|
|
3
3
|
import { PageParams, type PageParamsType } from '@/base/cases/PageParams';
|
|
4
|
-
import { ServerAuth } from '@/base/cases/ServerAuth';
|
|
5
4
|
import type { PageParamsProps } from '@/base/types/PageProps';
|
|
6
5
|
import { BootstrapServer } from '@/core/bootstraps/BootstrapServer';
|
|
7
6
|
import { redirect } from '@/i18n/routing';
|
|
8
|
-
import {
|
|
7
|
+
import { ServerAuth } from '@/server/ServerAuth';
|
|
8
|
+
import { BaseLayout } from '@/uikit/components/BaseLayout';
|
|
9
|
+
import { FeatureItem } from '@/uikit/components/FeatureItem';
|
|
9
10
|
import { LoginForm } from './LoginForm';
|
|
10
11
|
import type { Metadata } from 'next';
|
|
11
12
|
|
|
@@ -29,9 +30,7 @@ export async function generateMetadata({
|
|
|
29
30
|
}): Promise<Metadata> {
|
|
30
31
|
const pageParams = new PageParams(await params);
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return tt;
|
|
33
|
+
return await pageParams.getI18nInterface(loginI18n);
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
export default async function LoginPage(props: PageParamsProps) {
|
|
@@ -44,25 +43,20 @@ export default async function LoginPage(props: PageParamsProps) {
|
|
|
44
43
|
|
|
45
44
|
const server = new BootstrapServer();
|
|
46
45
|
|
|
47
|
-
if (await
|
|
46
|
+
if (await server.getIOC(ServerAuth).hasAuth()) {
|
|
48
47
|
return redirect({ href: '/', locale: params.locale! });
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
const tt = await pageParams.getI18nInterface(loginI18n);
|
|
52
51
|
|
|
53
52
|
return (
|
|
54
|
-
<
|
|
53
|
+
<BaseLayout
|
|
55
54
|
data-testid="LoginPage"
|
|
56
|
-
|
|
55
|
+
mainProps={{
|
|
56
|
+
className: 'text-xs1 bg-primary flex min-h-screen'
|
|
57
|
+
}}
|
|
57
58
|
>
|
|
58
|
-
{/* Left side - Brand section */}
|
|
59
59
|
<div className="hidden lg:flex bg-secondary lg:w-1/2 p-12 flex-col">
|
|
60
|
-
<div className="flex items-center gap-3 mb-12">
|
|
61
|
-
<div className="w-10 h-10 bg-brand rounded-lg"></div>
|
|
62
|
-
<span className="text-2xl font-semibold text-text">
|
|
63
|
-
{'AppConfig.appName'}
|
|
64
|
-
</span>
|
|
65
|
-
</div>
|
|
66
60
|
<h1 className="text-4xl font-bold text-text mb-4">{tt.welcome}</h1>
|
|
67
61
|
<p className="text-text-secondary text-lg mb-8">{tt.subtitle}</p>
|
|
68
62
|
<div className="space-y-4">
|
|
@@ -72,7 +66,6 @@ export default async function LoginPage(props: PageParamsProps) {
|
|
|
72
66
|
</div>
|
|
73
67
|
</div>
|
|
74
68
|
|
|
75
|
-
{/* Right side - Login form */}
|
|
76
69
|
<div className="w-full lg:w-1/2 p-8 sm:p-12 flex items-center justify-center">
|
|
77
70
|
<div className="w-full max-w-[420px]">
|
|
78
71
|
<h2 className="text-2xl font-semibold mb-2 text-text">{tt.title}</h2>
|
|
@@ -81,6 +74,6 @@ export default async function LoginPage(props: PageParamsProps) {
|
|
|
81
74
|
<LoginForm tt={tt} />
|
|
82
75
|
</div>
|
|
83
76
|
</div>
|
|
84
|
-
</
|
|
77
|
+
</BaseLayout>
|
|
85
78
|
);
|
|
86
79
|
}
|