@qlover/create-app 0.7.6 → 0.7.7

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.
Files changed (91) hide show
  1. package/CHANGELOG.md +237 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/next-app/.env.template +22 -0
  5. package/dist/templates/next-app/.prettierignore +58 -0
  6. package/dist/templates/next-app/README.md +36 -0
  7. package/dist/templates/next-app/build/generateLocales.ts +25 -0
  8. package/dist/templates/next-app/config/IOCIdentifier.ts +45 -0
  9. package/dist/templates/next-app/config/Identifier/common.error.ts +34 -0
  10. package/dist/templates/next-app/config/Identifier/common.ts +62 -0
  11. package/dist/templates/next-app/config/Identifier/index.ts +10 -0
  12. package/dist/templates/next-app/config/Identifier/page.about.ts +181 -0
  13. package/dist/templates/next-app/config/Identifier/page.executor.ts +272 -0
  14. package/dist/templates/next-app/config/Identifier/page.home.ts +63 -0
  15. package/dist/templates/next-app/config/Identifier/page.identifiter.ts +39 -0
  16. package/dist/templates/next-app/config/Identifier/page.jsonStorage.ts +72 -0
  17. package/dist/templates/next-app/config/Identifier/page.login.ts +165 -0
  18. package/dist/templates/next-app/config/Identifier/page.register.ts +147 -0
  19. package/dist/templates/next-app/config/Identifier/page.request.ts +182 -0
  20. package/dist/templates/next-app/config/common.ts +34 -0
  21. package/dist/templates/next-app/config/i18n/PageI18nInterface.ts +51 -0
  22. package/dist/templates/next-app/config/i18n/i18nConfig.ts +12 -0
  23. package/dist/templates/next-app/config/i18n/index.ts +3 -0
  24. package/dist/templates/next-app/config/i18n/loginI18n.ts +42 -0
  25. package/dist/templates/next-app/config/theme.ts +23 -0
  26. package/dist/templates/next-app/docs/env.md +94 -0
  27. package/dist/templates/next-app/eslint.config.mjs +181 -0
  28. package/dist/templates/next-app/next.config.ts +21 -0
  29. package/dist/templates/next-app/package.json +58 -0
  30. package/dist/templates/next-app/plugins/eslint-plugin-testid.mjs +94 -0
  31. package/dist/templates/next-app/plugins/generateLocalesPlugin.ts +33 -0
  32. package/dist/templates/next-app/postcss.config.mjs +5 -0
  33. package/dist/templates/next-app/public/file.svg +1 -0
  34. package/dist/templates/next-app/public/globe.svg +1 -0
  35. package/dist/templates/next-app/public/locales/en/common.json +183 -0
  36. package/dist/templates/next-app/public/locales/zh/common.json +183 -0
  37. package/dist/templates/next-app/public/next.svg +1 -0
  38. package/dist/templates/next-app/public/vercel.svg +1 -0
  39. package/dist/templates/next-app/public/window.svg +1 -0
  40. package/dist/templates/next-app/src/app/[locale]/favicon.ico +0 -0
  41. package/dist/templates/next-app/src/app/[locale]/layout.tsx +44 -0
  42. package/dist/templates/next-app/src/app/[locale]/login/FeatureItem.tsx +13 -0
  43. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +115 -0
  44. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +73 -0
  45. package/dist/templates/next-app/src/app/[locale]/not-found.tsx +24 -0
  46. package/dist/templates/next-app/src/app/[locale]/page.tsx +106 -0
  47. package/dist/templates/next-app/src/base/cases/AppConfig.ts +15 -0
  48. package/dist/templates/next-app/src/base/cases/InversifyContainer.ts +33 -0
  49. package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +25 -0
  50. package/dist/templates/next-app/src/base/port/UserServiceInterface.ts +11 -0
  51. package/dist/templates/next-app/src/base/services/I18nService.ts +115 -0
  52. package/dist/templates/next-app/src/base/services/UserService.ts +23 -0
  53. package/dist/templates/next-app/src/core/IOC.ts +58 -0
  54. package/dist/templates/next-app/src/core/IocRegisterImpl.ts +100 -0
  55. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +98 -0
  56. package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +47 -0
  57. package/dist/templates/next-app/src/core/bootstraps/IocIdentifierTest.ts +26 -0
  58. package/dist/templates/next-app/src/core/bootstraps/PrintBootstrap.ts +18 -0
  59. package/dist/templates/next-app/src/core/globals.ts +21 -0
  60. package/dist/templates/next-app/src/i18n/request.ts +22 -0
  61. package/dist/templates/next-app/src/i18n/routing.ts +30 -0
  62. package/dist/templates/next-app/src/middleware.ts +22 -0
  63. package/dist/templates/next-app/src/server/getServerI18n.ts +26 -0
  64. package/dist/templates/next-app/src/styles/css/antd-themes/_default.css +239 -0
  65. package/dist/templates/next-app/src/styles/css/antd-themes/dark.css +178 -0
  66. package/dist/templates/next-app/src/styles/css/antd-themes/index.css +3 -0
  67. package/dist/templates/next-app/src/styles/css/antd-themes/no-context.css +34 -0
  68. package/dist/templates/next-app/src/styles/css/antd-themes/pink.css +204 -0
  69. package/dist/templates/next-app/src/styles/css/index.css +6 -0
  70. package/dist/templates/next-app/src/styles/css/page.css +19 -0
  71. package/dist/templates/next-app/src/styles/css/tailwind.css +5 -0
  72. package/dist/templates/next-app/src/styles/css/themes/_default.css +29 -0
  73. package/dist/templates/next-app/src/styles/css/themes/dark.css +29 -0
  74. package/dist/templates/next-app/src/styles/css/themes/index.css +3 -0
  75. package/dist/templates/next-app/src/styles/css/themes/pink.css +29 -0
  76. package/dist/templates/next-app/src/styles/css/zIndex.css +9 -0
  77. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +42 -0
  78. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +25 -0
  79. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +45 -0
  80. package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +52 -0
  81. package/dist/templates/next-app/src/uikit/components/LocaleLink.tsx +51 -0
  82. package/dist/templates/next-app/src/uikit/components/NextIntlProvider.tsx +21 -0
  83. package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +86 -0
  84. package/dist/templates/next-app/src/uikit/context/IOCContext.ts +6 -0
  85. package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +28 -0
  86. package/dist/templates/next-app/src/uikit/hook/useIOC.ts +37 -0
  87. package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +11 -0
  88. package/dist/templates/next-app/src/uikit/hook/useStore.ts +15 -0
  89. package/dist/templates/next-app/tailwind.config.ts +8 -0
  90. package/dist/templates/next-app/tsconfig.json +36 -0
  91. package/package.json +1 -1
@@ -0,0 +1,204 @@
1
+ [data-theme='pink'],
2
+ [data-theme='pink'] .fe-theme {
3
+ /* Antd 主色调相关变量 - 玫瑰色主题 */
4
+ --fe-color-primary: #f472b6; /* pink-400 */
5
+ --fe-color-primary-hover: #ec4899; /* pink-500 */
6
+ --fe-color-primary-active: #db2777; /* pink-600 */
7
+ --fe-color-primary-bg: rgba(
8
+ 244,
9
+ 114,
10
+ 182,
11
+ 0.1
12
+ ); /* pink-400 with 0.1 opacity */
13
+ --fe-color-primary-border: #f472b6; /* pink-400 */
14
+ --fe-color-primary-text: #f472b6; /* pink-400 */
15
+ --fe-color-primary-text-hover: #ec4899; /* pink-500 */
16
+ --fe-color-primary-text-active: #db2777; /* pink-600 */
17
+ --fe-color-primary-deprecated-bg: #f472b6; /* pink-400 */
18
+ --fe-color-primary-deprecated-border: #f472b6; /* pink-400 */
19
+
20
+ /* 状态色 */
21
+ --fe-color-success: #52c41a;
22
+ --fe-color-warning: #faad14;
23
+ /* 警告信息相关颜色 - 粉色主题 */
24
+ --fe-color-warning-bg: #fff7e6;
25
+ --fe-color-warning-bg-hover: #fff0d9;
26
+ --fe-color-warning-border: #ffd591;
27
+ --fe-color-warning-border-hover: #ffc069;
28
+ --fe-color-warning-hover: #ffc069;
29
+ --fe-color-warning-active: #d4770c;
30
+ --fe-color-warning-text-hover: #ffc53d;
31
+ --fe-color-warning-text: #faad14;
32
+ --fe-color-warning-text-active: #d4770c;
33
+ --fe-color-error: #fb7185; /* rose-400 for error in pink theme */
34
+ --fe-color-info: var(--fe-color-primary);
35
+ --fe-color-link: var(--fe-color-primary);
36
+
37
+ /* Antd 基础变量 */
38
+ --ant-color-bg-container: rgb(255 241 242);
39
+ --ant-color-bg-elevated: rgb(254 205 211);
40
+ --fe-color-text-heading: rgb(159 18 57); /* rose-800 用于粉色主题标题文本 */
41
+ --ant-color-text: rgba(190 18 60 / 0.85);
42
+ --ant-color-text-secondary: rgba(190 18 60 / 0.45);
43
+ --ant-color-text-tertiary: rgba(190 18 60 / 0.35);
44
+ --ant-color-text-quaternary: rgba(190 18 60 / 0.15);
45
+ --ant-color-text-placeholder: rgba(190 18 60 / 0.25);
46
+ --ant-color-border: rgb(254 205 211);
47
+
48
+ /* Antd 图标相关变量 */
49
+ --fe-color-icon: rgba(190, 18, 60, 0.45); /* 粉色主题下的图标颜色 */
50
+ --fe-color-icon-hover: rgba(190, 18, 60, 0.85); /* 粉色主题下图标悬停颜色 */
51
+ --fe-color-icon-active: var(--fe-color-primary); /* 使用主题粉色 */
52
+ --fe-color-icon-disabled: rgba(190, 18, 60, 0.25); /* 粉色主题下禁用状态 */
53
+
54
+ /* Antd Input 组件变量 */
55
+ .ant-input,
56
+ .ant-input-css-var {
57
+ /* Input 背景色 */
58
+ --fe-input-bg: var(--ant-color-bg-container);
59
+ --fe-input-hover-bg: var(--ant-color-bg-elevated);
60
+ --fe-input-active-bg: var(--ant-color-bg-elevated);
61
+ --fe-input-addon-bg: rgba(190 18 60, 0.02);
62
+
63
+ /* Input 边框颜色 */
64
+ --fe-input-border-color: var(--ant-color-border);
65
+ --fe-input-hover-border-color: rgb(var(--color-brand));
66
+ --fe-input-active-border-color: rgb(var(--color-brand));
67
+
68
+ /* Input 阴影效果 */
69
+ --fe-input-active-shadow: 0 0 0 2px rgba(var(--color-brand), 0.1);
70
+ }
71
+
72
+ /* Antd Button 组件变量 */
73
+ .ant-btn,
74
+ .ant-btn-css-var {
75
+ /* 玫瑰色主题 */
76
+ --fe-button-primary-color: #fff;
77
+ --fe-button-primary-bg: #f472b6; /* pink-400 */
78
+ --fe-button-primary-hover-bg: #ec4899; /* pink-500 */
79
+ --fe-button-primary-active-bg: #db2777; /* pink-600 */
80
+ --fe-button-primary-shadow: 0 2px 0 rgba(219, 39, 119, 0.15);
81
+
82
+ /* 默认按钮 */
83
+ --fe-button-default-color: rgb(190 18 60);
84
+ --fe-button-default-bg: rgb(255 241 242);
85
+ --fe-button-default-border-color: rgb(254 205 211);
86
+ --fe-button-default-hover-bg: rgb(255 241 242);
87
+ --fe-button-default-hover-color: #f472b6;
88
+ --fe-button-default-hover-border-color: #f472b6;
89
+ --fe-button-default-active-bg: rgb(255 241 242);
90
+ --fe-button-default-active-color: #db2777;
91
+ --fe-button-default-active-border-color: #db2777;
92
+ --fe-button-default-shadow: 0 2px 0 rgba(244, 114, 182, 0.1);
93
+
94
+ /* 其他状态 */
95
+ --fe-button-text-hover-bg: rgba(244, 114, 182, 0.08);
96
+ --fe-button-border-color-disabled: rgb(254 205 211);
97
+ }
98
+
99
+ /* Antd Select 组件变量 */
100
+ .ant-select,
101
+ .ant-select-css-var {
102
+ --fe-select-internal_fixed_item_margin: 2px;
103
+ --fe-select-z-index-popup: var(--zi-select);
104
+ --fe-select-option-selected-color: rgba(190, 18, 60, 0.88);
105
+ --fe-select-option-selected-font-weight: 600;
106
+ --fe-select-option-selected-bg: var(--fe-color-primary-bg);
107
+ --fe-select-option-active-bg: rgba(190, 18, 60, 0.04);
108
+ --fe-select-option-padding: 5px 12px;
109
+ --fe-select-option-font-size: 14px;
110
+ --fe-select-option-line-height: 1.5714285714285714;
111
+ --fe-select-option-height: 32px;
112
+ --fe-select-selector-bg: var(--fe-color-bg-container);
113
+ --fe-select-clear-bg: var(--fe-color-bg-container);
114
+ --fe-select-single-item-height-lg: 40px;
115
+ --fe-select-multiple-item-bg: rgba(190, 18, 60, 0.06);
116
+ --fe-select-multiple-item-border-color: transparent;
117
+ --fe-select-multiple-item-height: 24px;
118
+ --fe-select-multiple-item-height-sm: 16px;
119
+ --fe-select-multiple-item-height-lg: 32px;
120
+ --fe-select-multiple-selector-bg-disabled: rgba(190, 18, 60, 0.04);
121
+ --fe-select-multiple-item-color-disabled: rgba(190, 18, 60, 0.25);
122
+ --fe-select-multiple-item-border-color-disabled: transparent;
123
+ --fe-select-show-arrow-padding-inline-end: 18px;
124
+ --fe-select-hover-border-color: var(--fe-color-primary);
125
+ --fe-select-active-border-color: var(--fe-color-primary-active);
126
+ --fe-select-active-outline-color: var(--fe-color-primary-bg);
127
+ --fe-select-select-affix-padding: 4px;
128
+ }
129
+
130
+ /* Antd Tag 组件变量 */
131
+ .ant-tag,
132
+ .ant-tag-css-var {
133
+ --fe-tag-default-bg: var(--fe-color-bg-container);
134
+ --fe-tag-default-color: var(--fe-color-text);
135
+ --fe-tag-default-border-color: var(--fe-color-border);
136
+ --fe-tag-font-size: 12px;
137
+ --fe-tag-line-height: 20px;
138
+ --fe-tag-height: 22px;
139
+ --fe-tag-padding-horizontal: 7px;
140
+ --fe-tag-margin: 0 8px 0 0;
141
+ --fe-tag-border-radius: 4px;
142
+ }
143
+
144
+ /* Antd Progress 组件变量 */
145
+ .ant-progress,
146
+ .ant-progress-css-var {
147
+ --fe-progress-default-color: var(--fe-color-primary);
148
+ --fe-progress-remaining-color: var(--fe-color-bg-elevated);
149
+ --fe-progress-text-color: var(--fe-color-text);
150
+ --fe-progress-line-font-size: 14px;
151
+ --fe-progress-circle-font-size: 14px;
152
+ --fe-progress-circle-text-color: var(--fe-color-text);
153
+ --fe-progress-circle-trail-color: var(--fe-color-bg-elevated);
154
+ --fe-progress-circle-stroke-width: 6px;
155
+ --fe-progress-line-stroke-width: 8px;
156
+ }
157
+
158
+ /* Antd Message 组件变量 */
159
+ .ant-message,
160
+ .ant-message-css-var {
161
+ --fe-message-z-index-popup: var(--zi-message);
162
+ --fe-message-content-bg: var(--ant-color-bg-container);
163
+ --fe-message-content-padding: 9px 12px;
164
+ --fe-message-notice-content-padding: 10px 16px;
165
+ --fe-message-notice-content-bg: var(--ant-color-bg-container);
166
+ --fe-message-notice-content-shadow:
167
+ 0 6px 16px 0 rgba(244, 114, 182, 0.08),
168
+ 0 3px 6px -4px rgba(244, 114, 182, 0.12),
169
+ 0 9px 28px 8px rgba(244, 114, 182, 0.05);
170
+ --fe-message-success-color: #52c41a;
171
+ --fe-message-error-color: #fb7185; /* rose-400 for error in pink theme */
172
+ --fe-message-warning-color: #faad14;
173
+ --fe-message-info-color: var(--fe-color-primary);
174
+ --fe-message-loading-color: var(--fe-color-primary);
175
+ }
176
+
177
+ /* Antd Modal 组件变量 */
178
+ .ant-modal,
179
+ .ant-modal-css-var {
180
+ --fe-modal-footer-bg: transparent;
181
+ --fe-modal-header-bg: var(--ant-color-bg-container);
182
+ --fe-modal-title-line-height: var(--fe-line-height);
183
+ --fe-modal-title-font-size: 16px;
184
+ --fe-modal-content-bg: var(--ant-color-bg-container);
185
+ --fe-modal-title-color: var(--ant-color-text);
186
+ --fe-modal-content-padding: 20px 24px;
187
+ --fe-modal-header-padding: 16px 24px;
188
+ --fe-modal-header-border-bottom: 1px solid var(--ant-color-border);
189
+ --fe-modal-header-margin-bottom: 8px;
190
+ --fe-modal-body-padding: 0px;
191
+ --fe-modal-footer-padding: 10px 16px;
192
+ --fe-modal-footer-border-top: 1px solid var(--ant-color-border);
193
+ --fe-modal-footer-border-radius: var(--fe-border-radius);
194
+ --fe-modal-footer-margin-top: 12px;
195
+ --fe-modal-confirm-body-padding: 32px 32px 24px;
196
+ --fe-modal-confirm-icon-margin-inline-end: 12px;
197
+ --fe-modal-confirm-btns-margin-top: 12px;
198
+ --fe-modal-box-shadow:
199
+ 0 6px 16px 0 rgba(244, 114, 182, 0.08),
200
+ 0 3px 6px -4px rgba(244, 114, 182, 0.12),
201
+ 0 9px 28px 8px rgba(244, 114, 182, 0.05);
202
+ --fe-modal-mask-bg: rgba(244, 114, 182, 0.45);
203
+ }
204
+ }
@@ -0,0 +1,6 @@
1
+ @import './tailwind.css';
2
+ @import './zIndex.css';
3
+
4
+ @import './antd-themes/index.css';
5
+
6
+ @import './page.css';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * This file is used to define the page styles for the app.
3
+ *
4
+ * @example --color-primary
5
+ * ```tsx
6
+ * <div className="bg-primary text-primary border-text-tertiary"></div>
7
+ * ```
8
+ */
9
+ @theme {
10
+ --color-primary: rgba(var(--color-bg-base));
11
+ --color-secondary: rgba(var(--color-bg-secondary));
12
+ --color-elevated: rgba(var(--color-bg-elevated));
13
+ --color-text: rgba(var(--text-primary));
14
+ --color-text-secondary: rgba(var(--text-secondary));
15
+ --color-text-tertiary: rgba(var(--text-tertiary));
16
+ --color-border: rgba(var(--color-border));
17
+ --color-brand: rgba(var(--color-brand));
18
+ --color-brand-hover: rgba(var(--color-brand-hover));
19
+ }
@@ -0,0 +1,5 @@
1
+ @layer theme, base, antd, components, utilities;
2
+
3
+ @import 'tailwindcss';
4
+
5
+ @import './themes/index.css';
@@ -0,0 +1,29 @@
1
+ /* theme variables - for tailwind */
2
+ @layer base {
3
+ :root {
4
+ /* 基础背景色 */
5
+ --color-bg-base: 255 255 255;
6
+ --color-bg-secondary: 241 245 249; /* slate-100 */
7
+ --color-bg-elevated: 248 250 252; /* slate-50 */
8
+
9
+ /* 文字颜色 */
10
+ --text-primary: 15 23 42; /* slate-900 */
11
+ --text-secondary: 71 85 105; /* slate-600 */
12
+ --text-tertiary: 100 116 139; /* slate-500 */
13
+
14
+ /* 边框颜色 */
15
+ --color-border: 226 232 240; /* slate-200 */
16
+
17
+ /* 品牌色 */
18
+ --color-brand: 37 99 235; /* blue-600 */
19
+ --color-brand-hover: 59 130 246; /* blue-500 */
20
+
21
+ /* 登录页特定样式 */
22
+ --login-card-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
23
+ --login-input-bg: var(--fe-color-bg-container);
24
+ --login-input-border: var(--fe-color-border);
25
+ --login-button-text: rgb(255 255 255);
26
+ --login-social-button-bg: var(--fe-color-bg-container);
27
+ --login-social-button-border: var(--fe-color-border);
28
+ }
29
+ }
@@ -0,0 +1,29 @@
1
+ /* theme variables - for tailwind */
2
+ @layer base {
3
+ [data-theme='dark'] {
4
+ /* 基础背景色 */
5
+ --color-bg-base: 15 23 42; /* slate-900 */
6
+ --color-bg-secondary: 30 41 59; /* slate-800 */
7
+ --color-bg-elevated: 51 65 85; /* slate-700 */
8
+
9
+ /* 文字颜色 */
10
+ --text-primary: 255 255 255;
11
+ --text-secondary: 148 163 184; /* slate-400 */
12
+ --text-tertiary: 100 116 139; /* slate-500 */
13
+
14
+ /* 边框颜色 */
15
+ --color-border: 51 65 85; /* slate-700 */
16
+
17
+ /* 品牌色 */
18
+ --color-brand: 37 99 235; /* blue-600 */
19
+ --color-brand-hover: 59 130 246; /* blue-500 */
20
+
21
+ /* 登录页特定样式 */
22
+ --login-card-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
23
+ --login-input-bg: var(--fe-color-bg-container);
24
+ --login-input-border: var(--fe-color-border);
25
+ --login-button-text: rgb(255 255 255);
26
+ --login-social-button-bg: var(--fe-color-bg-container);
27
+ --login-social-button-border: var(--fe-color-border);
28
+ }
29
+ }
@@ -0,0 +1,3 @@
1
+ @import './_default.css';
2
+ @import './dark.css';
3
+ @import './pink.css';
@@ -0,0 +1,29 @@
1
+ /* theme variables - for tailwind */
2
+ @layer base {
3
+ [data-theme='pink'] {
4
+ /* 基础背景色 */
5
+ --color-bg-base: 255 241 242; /* rose-50 */
6
+ --color-bg-secondary: 255 228 230; /* rose-100 */
7
+ --color-bg-elevated: 254 205 211; /* rose-200 */
8
+
9
+ /* 文字颜色 */
10
+ --text-primary: 190 18 60; /* rose-700 */
11
+ --text-secondary: 225 29 72; /* rose-600 */
12
+ --text-tertiary: 244 63 94; /* rose-500 */
13
+
14
+ /* 边框颜色 */
15
+ --color-border: 254 205 211; /* rose-200 */
16
+
17
+ /* 品牌色 */
18
+ --color-brand: 225 29 72; /* rose-600 */
19
+ --color-brand-hover: 244 63 94; /* rose-500 */
20
+
21
+ /* 登录页特定样式 */
22
+ --login-card-shadow: 0 4px 12px rgba(244 63 94 / 0.15);
23
+ --login-input-bg: var(--ant-color-bg-container);
24
+ --login-input-border: var(--ant-color-border);
25
+ --login-button-text: rgb(255 255 255);
26
+ --login-social-button-bg: var(--ant-color-bg-container);
27
+ --login-social-button-border: var(--ant-color-border);
28
+ }
29
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This file is used to define the z-index values for the app.
3
+ */
4
+ :root {
5
+ /* antd select popup zindex */
6
+ --zi-select: 1050;
7
+ /* antd message zindex */
8
+ --zi-message: 2010;
9
+ }
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import { AppConfig } from '@/base/cases/AppConfig';
5
+ import { IOC } from '@/core/IOC';
6
+ import { LanguageSwitcher } from './LanguageSwitcher';
7
+ import { ThemeSwitcher } from './ThemeSwitcher';
8
+
9
+ export function BaseHeader() {
10
+ return (
11
+ <header
12
+ data-testid="base-header"
13
+ className="h-14 bg-secondary border-b border-border sticky top-0 z-50"
14
+ >
15
+ <div className="flex items-center justify-between h-full px-4 mx-auto max-w-7xl">
16
+ <div className="flex items-center">
17
+ <Link
18
+ href="/"
19
+ className="flex items-center hover:opacity-80 transition-opacity"
20
+ >
21
+ {/* <img
22
+ data-testid="base-header-logo"
23
+ src={IOC(PublicAssetsPath).getPath('/logo.svg')}
24
+ alt="logo"
25
+ className="h-8 w-auto"
26
+ /> */}
27
+ <span
28
+ data-testid="base-header-app-name"
29
+ className="ml-2 text-lg font-semibold text-text"
30
+ >
31
+ {IOC(AppConfig).appName}
32
+ </span>
33
+ </Link>
34
+ </div>
35
+ <div className="flex items-center gap-4">
36
+ <LanguageSwitcher />
37
+ <ThemeSwitcher />
38
+ </div>
39
+ </div>
40
+ </header>
41
+ );
42
+ }
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+ import '@ant-design/v5-patch-for-react-19';
3
+ import { useEffect } from 'react';
4
+ import { BootstrapClient } from '@/core/bootstraps/BootstrapClient';
5
+ import { IOCContext } from '../context/IOCContext';
6
+
7
+ export function BootstrapsProvider(props: { children: React.ReactNode }) {
8
+ const IOC = BootstrapClient.createSingletonIOC();
9
+
10
+ useEffect(() => {
11
+ if (typeof window !== 'undefined') {
12
+ BootstrapClient.main({
13
+ root: window,
14
+ pathname: window.location.pathname,
15
+ IOC: IOC
16
+ });
17
+ }
18
+ }, [IOC]);
19
+
20
+ return (
21
+ <IOCContext.Provider data-testid="BootstrapsProvider" value={IOC}>
22
+ {props.children}
23
+ </IOCContext.Provider>
24
+ );
25
+ }
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+ import '@ant-design/v5-patch-for-react-19';
3
+ import { AntdRegistry } from '@ant-design/nextjs-registry';
4
+ import { AntdThemeProvider } from '@brain-toolkit/antd-theme-override/react';
5
+ import { ThemeProvider } from 'next-themes';
6
+ import { BootstrapsProvider } from './BootstrapsProvider';
7
+ import type { CommonThemeConfig } from '@config/theme';
8
+
9
+ /**
10
+ * CommonProvider is a provider for the common components
11
+ *
12
+ * - IOCProvider
13
+ * - BootstrapsProvider
14
+ * - ThemeProvider
15
+ * - AntdProvider
16
+ *
17
+ * @param param0
18
+ * @returns
19
+ */
20
+ export function ComboProvider(props: {
21
+ themeConfig: CommonThemeConfig;
22
+ children: React.ReactNode;
23
+ }) {
24
+ const { themeConfig, children } = props;
25
+
26
+ return (
27
+ <AntdThemeProvider
28
+ data-testid="ComboProvider"
29
+ theme={themeConfig.antdTheme}
30
+ >
31
+ <ThemeProvider
32
+ themes={themeConfig.supportedThemes as unknown as string[]}
33
+ attribute={themeConfig.domAttribute}
34
+ defaultTheme={themeConfig.defaultTheme}
35
+ enableSystem
36
+ enableColorScheme={false}
37
+ storageKey={themeConfig.storageKey}
38
+ >
39
+ <BootstrapsProvider>
40
+ <AntdRegistry>{children}</AntdRegistry>
41
+ </BootstrapsProvider>
42
+ </ThemeProvider>
43
+ </AntdThemeProvider>
44
+ );
45
+ }
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+
3
+ import { Select } from 'antd';
4
+ import { useLocale } from 'next-intl';
5
+ import { useCallback } from 'react';
6
+ import { i18nConfig } from '@config/i18n';
7
+ import { IOCIdentifier } from '@config/IOCIdentifier';
8
+ import type { I18nServiceLocale } from '@/base/port/I18nServiceInterface';
9
+ import { usePathname, useRouter } from '@/i18n/routing';
10
+ import { useIOC } from '../hook/useIOC';
11
+ import { useStore } from '../hook/useStore';
12
+ import type { LocaleType } from '@config/i18n';
13
+
14
+ export function LanguageSwitcher() {
15
+ const i18nService = useIOC(IOCIdentifier.I18nServiceInterface);
16
+ const { loading } = useStore(i18nService);
17
+ const pathname = usePathname(); // current pathname, aware of i18n
18
+
19
+ const router = useRouter(); // i18n-aware router instance
20
+ const currentLocale = useLocale() as LocaleType; // currently active locale
21
+
22
+ const handleLanguageChange = useCallback(
23
+ async (value: string) => {
24
+ // Set a persistent cookie with the user's preferred locale (valid for 1 year)
25
+ document.cookie = `NEXT_LOCALE=${value}; path=/; max-age=31536000; SameSite=Lax`;
26
+ // Route to the same page in the selected locale
27
+ router.replace(pathname, { locale: value });
28
+
29
+ try {
30
+ await i18nService.changeLanguage(value as I18nServiceLocale);
31
+ } catch (error) {
32
+ console.error('Failed to change language:', error);
33
+ }
34
+ },
35
+ [i18nService, pathname, router]
36
+ );
37
+
38
+ return (
39
+ <Select
40
+ data-testid="LanguageSwitcher"
41
+ loading={loading}
42
+ value={currentLocale}
43
+ onChange={handleLanguageChange}
44
+ options={i18nConfig.supportedLngs.map((lang) => ({
45
+ value: lang,
46
+ label:
47
+ i18nConfig.localeNames[lang as keyof typeof i18nConfig.localeNames]
48
+ }))}
49
+ className="w-24"
50
+ />
51
+ );
52
+ }
@@ -0,0 +1,51 @@
1
+ import Link from 'next/link';
2
+ import { useLocaleRoutes } from '@config/common';
3
+ import { i18nConfig } from '@config/i18n';
4
+ import type { LinkProps } from 'next/link';
5
+ import type { ReactNode } from 'react';
6
+
7
+ interface LocaleLinkProps
8
+ extends Omit<LinkProps, 'href'>,
9
+ React.HTMLAttributes<HTMLAnchorElement> {
10
+ href:
11
+ | string
12
+ | {
13
+ pathname: string;
14
+ query?: Record<string, string>;
15
+ hash?: string;
16
+ };
17
+ locale?: string;
18
+ title: string;
19
+ children: ReactNode;
20
+ defaultLocale?: string;
21
+ className?: string;
22
+ }
23
+
24
+ export const LocaleLink: React.FC<LocaleLinkProps> = ({
25
+ href,
26
+ locale,
27
+ children,
28
+ defaultLocale,
29
+ ...props
30
+ }) => {
31
+ locale = locale || i18nConfig.fallbackLng;
32
+
33
+ const isDefaultLocale = locale === defaultLocale;
34
+ const shouldAddLocale = useLocaleRoutes && !isDefaultLocale;
35
+
36
+ let localizedHref: typeof href;
37
+ if (typeof href === 'string') {
38
+ localizedHref = shouldAddLocale ? `/${locale}${href}` : href;
39
+ } else {
40
+ localizedHref = {
41
+ ...href,
42
+ pathname: shouldAddLocale ? `/${locale}${href.pathname}` : href.pathname
43
+ };
44
+ }
45
+
46
+ return (
47
+ <Link data-testid="locale-link" {...props} href={localizedHref}>
48
+ {children}
49
+ </Link>
50
+ );
51
+ };
@@ -0,0 +1,21 @@
1
+ import { NextIntlClientProvider, useMessages } from 'next-intl';
2
+ import type { ReactNode } from 'react';
3
+
4
+ type Props = {
5
+ children: ReactNode;
6
+ locale: string;
7
+ };
8
+
9
+ export function NextIntlProvider({ children, locale }: Props) {
10
+ const messages = useMessages();
11
+
12
+ return (
13
+ <NextIntlClientProvider
14
+ data-testid="NextIntlProvider"
15
+ locale={locale}
16
+ messages={messages}
17
+ >
18
+ {children}
19
+ </NextIntlClientProvider>
20
+ );
21
+ }
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+
3
+ import {
4
+ BulbOutlined,
5
+ BulbFilled,
6
+ HeartFilled,
7
+ HeartOutlined,
8
+ DesktopOutlined
9
+ } from '@ant-design/icons';
10
+ import { Select } from 'antd';
11
+ import { clsx } from 'clsx';
12
+ import { useTheme } from 'next-themes';
13
+ import { themeConfig } from '@config/theme';
14
+ import { useMountedClient } from '../hook/useMountedClient';
15
+
16
+ const { supportedThemes } = themeConfig;
17
+
18
+ const colorMap: Record<
19
+ string,
20
+ { i18nkey: string; colors: string[]; icons: React.ElementType[] }
21
+ > = {
22
+ system: {
23
+ i18nkey: 'System',
24
+ colors: ['text-text', 'text-text-secondary'],
25
+ icons: [DesktopOutlined, DesktopOutlined]
26
+ },
27
+ light: {
28
+ i18nkey: 'Light',
29
+ colors: ['text-text', 'text-text-secondary'],
30
+ icons: [BulbFilled, BulbOutlined]
31
+ },
32
+ dark: {
33
+ i18nkey: 'Dark',
34
+ colors: ['text-[#9333ea]', 'text-[#a855f7]'],
35
+ icons: [BulbFilled, BulbOutlined]
36
+ },
37
+ pink: {
38
+ i18nkey: 'Pink',
39
+ colors: ['text-[#f472b6]', 'text-[#ec4899]'],
40
+ icons: [HeartFilled, HeartOutlined]
41
+ }
42
+ };
43
+
44
+ export function ThemeSwitcher() {
45
+ const { theme, resolvedTheme, setTheme } = useTheme();
46
+ const mounted = useMountedClient();
47
+
48
+ const themeOptions = ['system', ...supportedThemes!].map((themeName) => {
49
+ const { i18nkey, colors, icons } = colorMap[themeName] || colorMap.light;
50
+ const [currentColor, normalColor] = colors;
51
+ const [CurrentIcon, NormalIcon] = icons;
52
+
53
+ const isCurrentTheme =
54
+ theme === themeName ||
55
+ (themeName === resolvedTheme && theme === 'system');
56
+
57
+ return {
58
+ key: themeName,
59
+ value: themeName,
60
+ label: (
61
+ <div
62
+ className={clsx(
63
+ 'flex items-center gap-2',
64
+ isCurrentTheme ? currentColor : normalColor
65
+ )}
66
+ >
67
+ {isCurrentTheme ? <CurrentIcon /> : <NormalIcon />}
68
+ <span>{i18nkey}</span>
69
+ </div>
70
+ )
71
+ };
72
+ });
73
+
74
+ return (
75
+ <Select
76
+ data-testid="ThemeSwitcher"
77
+ loading={!mounted}
78
+ value={mounted ? theme : themeOptions[0]?.key}
79
+ onChange={setTheme}
80
+ options={themeOptions}
81
+ style={{ width: 120 }}
82
+ className="min-w-40 max-w-full"
83
+ disabled={!mounted}
84
+ />
85
+ );
86
+ }
@@ -0,0 +1,6 @@
1
+ 'use client';
2
+
3
+ import { createContext } from 'react';
4
+ import type { IOC } from '@/core/IOC';
5
+
6
+ export const IOCContext = createContext<typeof IOC | null>(null);