@qlover/create-app 0.7.7 → 0.7.8

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 (46) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/next-app/build/generateLocales.ts +1 -1
  5. package/dist/templates/next-app/config/IOCIdentifier.ts +15 -2
  6. package/dist/templates/next-app/config/Identifier/common.error.ts +7 -0
  7. package/dist/templates/next-app/package.json +1 -1
  8. package/dist/templates/next-app/public/locales/{en/common.json → en.json} +2 -1
  9. package/dist/templates/next-app/public/locales/{zh/common.json → zh.json} +2 -1
  10. package/dist/templates/next-app/src/app/[locale]/layout.tsx +8 -20
  11. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +6 -7
  12. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +24 -11
  13. package/dist/templates/next-app/src/app/[locale]/page.tsx +14 -1
  14. package/dist/templates/next-app/src/base/cases/DialogHandler.ts +92 -0
  15. package/dist/templates/next-app/src/base/cases/NavigateBridge.ts +16 -0
  16. package/dist/templates/next-app/src/base/cases/PageParams.ts +74 -0
  17. package/dist/templates/next-app/src/base/cases/RouterService.ts +35 -0
  18. package/dist/templates/next-app/src/base/cases/ServerAuth.ts +17 -0
  19. package/dist/templates/next-app/src/base/cases/ServerErrorHandler.ts +27 -0
  20. package/dist/templates/next-app/src/base/port/IOCInterface.ts +24 -0
  21. package/dist/templates/next-app/src/base/port/ParamsHandlerInterface.ts +11 -0
  22. package/dist/templates/next-app/src/base/port/RouterInterface.ts +11 -0
  23. package/dist/templates/next-app/src/base/port/ServerAuthInterface.ts +3 -0
  24. package/dist/templates/next-app/src/base/port/ServerInterface.ts +12 -0
  25. package/dist/templates/next-app/src/base/types/PageProps.ts +9 -0
  26. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +2 -39
  27. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +78 -0
  28. package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +37 -0
  29. package/dist/templates/next-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +20 -23
  30. package/dist/templates/next-app/src/core/globals.ts +3 -0
  31. package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +52 -0
  32. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +63 -0
  33. package/dist/templates/next-app/src/i18n/request.ts +1 -2
  34. package/dist/templates/next-app/src/i18n/routing.ts +3 -3
  35. package/dist/templates/next-app/src/middleware.ts +6 -3
  36. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +9 -4
  37. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +13 -1
  38. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +5 -0
  39. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +34 -0
  40. package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +16 -3
  41. package/dist/templates/next-app/src/uikit/context/IOCContext.ts +9 -2
  42. package/package.json +1 -1
  43. package/dist/templates/next-app/plugins/eslint-plugin-testid.mjs +0 -94
  44. package/dist/templates/next-app/plugins/generateLocalesPlugin.ts +0 -33
  45. package/dist/templates/next-app/src/core/IOC.ts +0 -58
  46. package/dist/templates/next-app/src/server/getServerI18n.ts +0 -26
package/CHANGELOG.md CHANGED
@@ -1,5 +1,96 @@
1
1
  # @qlover/create-app
2
2
 
3
+ ## 0.7.8
4
+
5
+ ### Patch Changes
6
+
7
+ #### ✨ Features
8
+
9
+ - **next-app:** enhance internationalization support with new localization files and refactor layout structure ([9d73643](https://github.com/qlover/fe-base/commit/9d7364337c18bac196a554b50e2342a51e15154d)) ([#506](https://github.com/qlover/fe-base/pull/506))
10
+ - Added English and Chinese localization files for common application messages to improve internationalization.
11
+ - Refactored layout.tsx to streamline locale handling and message retrieval using the new PageParams class.
12
+ - Removed the obsolete getServerI18n function to simplify the localization setup.
13
+ - Updated LoginPage and related components to utilize the new i18n structure for better translation handling.
14
+
15
+ These changes aim to enhance the user experience by providing comprehensive localization support and improving the overall structure of the application.
16
+
17
+ - **next-app:** enhance IOC integration and introduce new service interfaces ([688ec8e](https://github.com/qlover/fe-base/commit/688ec8e11f94fd92a13257dbdd7ba6359f4a2efc)) ([#506](https://github.com/qlover/fe-base/pull/506))
18
+ - Added UserService interface to IOCIdentifier for improved dependency management.
19
+ - Updated IOCIdentifierMap to include UserService, facilitating better service registration.
20
+ - Refactored LoginForm to utilize the useIOC hook for accessing UserService, enhancing component modularity.
21
+ - Introduced new IOCInterface for defining IOC registration options and container interactions.
22
+ - Created ClientIOC and ServerIOC classes for managing client and server-side IOC functionalities, improving overall architecture.
23
+
24
+ These changes aim to streamline dependency injection and enhance the modularity of the application, providing a clearer structure for service interactions.
25
+
26
+ - **next-app:** refactor layout and enhance parameter handling with BootstrapServer ([3bf30c4](https://github.com/qlover/fe-base/commit/3bf30c4aee2b44a717a67ab26f1a7f36b8dc19e9)) ([#506](https://github.com/qlover/fe-base/pull/506))
27
+ - Updated RootLayout to utilize BootstrapServer for locale and message retrieval, improving internationalization support.
28
+ - Refactored PageParams to implement ParamsHandlerInterface, streamlining parameter management.
29
+ - Introduced BootstrapServer class for better server-side parameter handling and integration with IOC.
30
+ - Created ParamsHandlerInterface to standardize parameter handling across the application.
31
+
32
+ These changes aim to enhance the modularity and maintainability of the application while improving the internationalization experience.
33
+
34
+ - **next-app:** enhance localization and error handling in application ([1b1c7ab](https://github.com/qlover/fe-base/commit/1b1c7ab534657a88d14524ce25432bc4042ec26c)) ([#506](https://github.com/qlover/fe-base/pull/506))
35
+ - Updated generateLocales function to output locale files in a simplified format.
36
+ - Refactored IOCIdentifier to replace Logger type with LoggerInterface for better type safety.
37
+ - Added SERVER_AUTH_ERROR identifier to common error messages for improved error handling.
38
+ - Integrated new error messages into English and Chinese localization files.
39
+ - Removed obsolete common.json files to streamline localization structure.
40
+ - Introduced ServerAuthPlugin and ServerErrorHandler for enhanced server-side authentication and error management.
41
+
42
+ These changes aim to improve the application's internationalization capabilities and error handling processes, providing a more robust user experience.
43
+
44
+ - **next-app:** refactor authentication handling and streamline server integration ([c17bc24](https://github.com/qlover/fe-base/commit/c17bc242067e4a01a50c89832c9060e130ca5291)) ([#506](https://github.com/qlover/fe-base/pull/506))
45
+ - Replaced ServerAuthPlugin with a new ServerAuth class for improved authentication management.
46
+ - Updated Home page to utilize the new ServerAuth class for checking user authentication and redirecting to the login page if necessary.
47
+ - Removed obsolete ServerAuthPlugin file to clean up the codebase.
48
+ - Introduced ServerInterface and ServerAuthInterface for better abstraction and type safety in server interactions.
49
+
50
+ These changes aim to enhance the authentication flow and improve the overall structure of the server-side logic in the application.
51
+
52
+ - **next-app:** enhance routing and dialog handling with new services and components ([944ded9](https://github.com/qlover/fe-base/commit/944ded9c93223d4230d1911552ba17e90a23fd57)) ([#506](https://github.com/qlover/fe-base/pull/506))
53
+ - Introduced RouterService and NavigateBridge for improved navigation management within the application.
54
+ - Added DialogHandler for standardized dialog and notification handling using Ant Design components.
55
+ - Updated IOCIdentifier to include new services and interfaces for better dependency management.
56
+ - Refactored LoginForm to utilize the new RouterService for navigation after login.
57
+ - Enhanced BaseHeader to conditionally display a logout button, improving user experience.
58
+ - Integrated new components and services into the application structure for better modularity and maintainability.
59
+
60
+ These changes aim to streamline navigation and dialog interactions, enhancing the overall user experience and application architecture.
61
+
62
+ - **next-app:** refactor page properties and enhance server integration ([ce49da5](https://github.com/qlover/fe-base/commit/ce49da573ccd621559d3d5cbcea448db93998e66)) ([#506](https://github.com/qlover/fe-base/pull/506))
63
+ - Renamed PageProps to PageParamsProps for clarity in parameter handling.
64
+ - Updated Home and LoginPage components to utilize the new PageParamsProps interface for improved type safety.
65
+ - Enhanced BootstrapServer to include a method for retrieving internationalization interfaces, streamlining localization support.
66
+ - Added error handling in LoginPage to manage missing parameters effectively.
67
+
68
+ These changes aim to improve the structure and clarity of page properties while enhancing server-side integration for better localization management.
69
+
70
+ - **next-app:** refactor server integration and enhance parameter handling ([bd3cd12](https://github.com/qlover/fe-base/commit/bd3cd12cec6f0dc1b2b660e977f5e81cb18951ab)) ([#506](https://github.com/qlover/fe-base/pull/506))
71
+ - Updated layout and page components to utilize the new PageParams class for improved locale and message retrieval.
72
+ - Refactored BootstrapServer to streamline server initialization and removed obsolete methods for better clarity.
73
+ - Enhanced LoginPage to utilize PageParams for internationalization interface retrieval, improving localization support.
74
+
75
+ These changes aim to improve the structure and maintainability of server interactions while enhancing the internationalization experience across the application.
76
+
77
+ #### 🐞 Bug Fixes
78
+
79
+ - **next-app:** update eslint-plugin dependency to latest version ([2fea1ab](https://github.com/qlover/fe-base/commit/2fea1ab9c94cc52ffe34e82d2980baf99de55aae)) ([#506](https://github.com/qlover/fe-base/pull/506))
80
+ - Changed the @qlover/eslint-plugin dependency in package.json from a local file reference to the latest version. This update ensures that the project uses the most recent features and fixes available in the eslint-plugin package.
81
+
82
+ This change aims to improve code quality and maintainability by leveraging the latest enhancements in the eslint-plugin.
83
+
84
+ #### ♻️ Refactors
85
+
86
+ - **next-app:** remove obsolete ESLint plugin and streamline localization setup ([d1c81b4](https://github.com/qlover/fe-base/commit/d1c81b4185131bad25f497e0004ebc48001ac9fe)) ([#506](https://github.com/qlover/fe-base/pull/506))
87
+ - Deleted the custom eslint-plugin-testid as it has been replaced by @qlover/eslint-plugin for better testability.
88
+ - Removed the generateLocalesPlugin as its functionality is now integrated into the build process.
89
+ - Added English and Chinese localization files for common application messages to enhance internationalization support.
90
+ - Updated the i18n request configuration to dynamically load the appropriate locale files based on the selected language.
91
+
92
+ These changes aim to simplify the localization setup and improve the overall structure of the internationalization implementation.
93
+
3
94
  ## 0.7.7
4
95
 
5
96
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -8,4 +8,4 @@ ${t}`,dn=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),_n=Objec
8
8
  `))this.#e+=Math.max(1,Math.ceil(Ye(D,{countAnsiEscapeCodes:!0})/t))}get isEnabled(){return this.#a&&!this.#F}set isEnabled(t){if(typeof t!="boolean")throw new TypeError("The `isEnabled` option must be a boolean");this.#a=t}get isSilent(){return this.#F}set isSilent(t){if(typeof t!="boolean")throw new TypeError("The `isSilent` option must be a boolean");this.#F=t}frame(){let t=Date.now();(this.#i===-1||t-this.#c>=this.interval)&&(this.#i=++this.#i%this.#D.frames.length,this.#c=t);let{frames:r}=this.#D,u=r[this.#i];this.color&&(u=d[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",D=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+D+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#n;t++)t>0&&this.#u.moveCursor(0,-1),this.#u.clearLine(1);return(this.#l||this.lastIndent!==this.#l)&&this.#u.cursorTo(this.#l),this.lastIndent=this.#l,this.#n=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#n=this.#e,this)}start(t){return t&&(this.text=t),this.#F?this:this.#a?this.isSpinning?this:(this.#t.hideCursor&&$e.hide(this.#u),this.#t.discardStdin&&V.default.stdin.isTTY&&(this.#r=!0,Je.start()),this.render(),this.#f=setInterval(this.render.bind(this),this.interval),this):(this.text&&this.#u.write(`- ${this.text}
9
9
  `),this)}stop(){return this.#a?(clearInterval(this.#f),this.#f=void 0,this.#i=0,this.clear(),this.#t.hideCursor&&$e.show(this.#u),this.#t.discardStdin&&V.default.stdin.isTTY&&this.#r&&(Je.stop(),this.#r=!1),this):this}succeed(t){return this.stopAndPersist({symbol:H.success,text:t})}fail(t){return this.stopAndPersist({symbol:H.error,text:t})}warn(t){return this.stopAndPersist({symbol:H.warning,text:t})}info(t){return this.stopAndPersist({symbol:H.info,text:t})}stopAndPersist(t={}){if(this.#F)return this;let r=t.prefixText??this.#s,u=this.#E(r," "),i=t.symbol??" ",D=t.text??this.text,s=typeof D=="string"?(i?" ":"")+D:"",a=t.suffixText??this.#o,f=this.#g(a," "),p=u+i+s+f+`
10
10
  `;return this.stop(),this.#u.write(p),this}};function tr(e){return new Ze(e)}async function rr(e,t){let r=typeof e=="function",u=typeof e.then=="function";if(!r&&!u)throw new TypeError("Parameter `action` must be a Function or a Promise");let{successText:i,failText:D}=typeof t=="object"?t:{successText:void 0,failText:void 0},o=tr(t).start();try{let a=await(r?e(o):e);return o.succeed(i===void 0?void 0:typeof i=="string"?i:i(a)),a}catch(s){throw o.fail(D===void 0?void 0:typeof D=="string"?D:D(s)),s}}var HD=require("fs");var P=require("path"),me=require("fs"),cr=h(lr(),1),Dt=require("fs");var de=require("fs"),X=class{static ensureDir(t){(0,de.existsSync)(t)||(0,de.mkdirSync)(t,{recursive:!0})}};var{copyFile:rs,stat:us}=Dt.promises,_e=class e{constructor(t,r=e.IGNORE_FILE){this.ignoreTargetPath=t;this.ignoreFile=r}static IGNORE_FILE=".gitignore.template";getIg(t=this.ignoreTargetPath){let r=(0,P.join)(t,this.ignoreFile);if(!(0,me.existsSync)(r))return;let D=(0,me.readFileSync)(r,"utf8").split(`
11
- `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,cr.default)().add(D)}async copyFiles(t,r,u,i){let D=await Dt.promises.readdir(t);await Promise.all(D.map(async o=>{let s=(0,P.join)(t,o),a=(0,P.join)(r,o);if(u&&u.ignores(o))return;if(X.ensureDir((0,P.dirname)(a)),(await us(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await rs(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){X.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var L=require("fs"),kD=h(LD(),1),xe=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return(0,L.readFileSync)(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){(0,L.writeFileSync)(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,kD.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return(0,L.existsSync)(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var $D=["pack-app"],ye=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!(0,HD.existsSync)(r))throw new Error("template path not exit");this.ora=rr,this.context=new UD.FeScriptContext(t),this.subPackages=["node-lib","react-app"],this.copyer=new _e((0,v.join)(this.context.options.configsRootPath,"_common")),this.compose=new xe}get logger(){return this.context.logger}async steps(t){try{return await WD.default.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return $D.includes(t)}async getGeneratorContext(){let t=Ot(this.subPackages,$D),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=wt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=(0,v.join)(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:(0,v.join)(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:(0,v.join)(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=(0,v.join)(D,o),a=(0,v.join)(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var bt={name:"@qlover/create-app",version:"0.7.7",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function Ul(){let e=new YD.Command;return e.version(bt.version,"-v, --version","Show version").description(bt.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Wl(){let{dryRun:e,verbose:t,...r}=Ul(),u=(0,vt.resolve)("./templates"),i=(0,vt.resolve)("./configs");(0,xt.existsSync)(u)||(console.error("Template is empty!"),process.exit(1)),(0,xt.existsSync)(i)||(console.error("Configs is empty!"),process.exit(1)),await new ye({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Wl().catch(e=>{console.error(e),process.exit(1)});
11
+ `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,cr.default)().add(D)}async copyFiles(t,r,u,i){let D=await Dt.promises.readdir(t);await Promise.all(D.map(async o=>{let s=(0,P.join)(t,o),a=(0,P.join)(r,o);if(u&&u.ignores(o))return;if(X.ensureDir((0,P.dirname)(a)),(await us(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await rs(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){X.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var L=require("fs"),kD=h(LD(),1),xe=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return(0,L.readFileSync)(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){(0,L.writeFileSync)(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,kD.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return(0,L.existsSync)(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var $D=["pack-app"],ye=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!(0,HD.existsSync)(r))throw new Error("template path not exit");this.ora=rr,this.context=new UD.FeScriptContext(t),this.subPackages=["node-lib","react-app"],this.copyer=new _e((0,v.join)(this.context.options.configsRootPath,"_common")),this.compose=new xe}get logger(){return this.context.logger}async steps(t){try{return await WD.default.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return $D.includes(t)}async getGeneratorContext(){let t=Ot(this.subPackages,$D),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=wt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=(0,v.join)(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:(0,v.join)(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:(0,v.join)(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=(0,v.join)(D,o),a=(0,v.join)(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var bt={name:"@qlover/create-app",version:"0.7.8",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function Ul(){let e=new YD.Command;return e.version(bt.version,"-v, --version","Show version").description(bt.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Wl(){let{dryRun:e,verbose:t,...r}=Ul(),u=(0,vt.resolve)("./templates"),i=(0,vt.resolve)("./configs");(0,xt.existsSync)(u)||(console.error("Template is empty!"),process.exit(1)),(0,xt.existsSync)(i)||(console.error("Configs is empty!"),process.exit(1)),await new ye({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Wl().catch(e=>{console.error(e),process.exit(1)});
package/dist/index.js CHANGED
@@ -8,4 +8,4 @@ ${t}`,cn=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),fn=Objec
8
8
  `))this.#e+=Math.max(1,Math.ceil(Le(D,{countAnsiEscapeCodes:!0})/t))}get isEnabled(){return this.#a&&!this.#F}set isEnabled(t){if(typeof t!="boolean")throw new TypeError("The `isEnabled` option must be a boolean");this.#a=t}get isSilent(){return this.#F}set isSilent(t){if(typeof t!="boolean")throw new TypeError("The `isSilent` option must be a boolean");this.#F=t}frame(){let t=Date.now();(this.#i===-1||t-this.#c>=this.interval)&&(this.#i=++this.#i%this.#D.frames.length,this.#c=t);let{frames:r}=this.#D,u=r[this.#i];this.color&&(u=E[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",D=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+D+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#n;t++)t>0&&this.#u.moveCursor(0,-1),this.#u.clearLine(1);return(this.#l||this.lastIndent!==this.#l)&&this.#u.cursorTo(this.#l),this.lastIndent=this.#l,this.#n=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#n=this.#e,this)}start(t){return t&&(this.text=t),this.#F?this:this.#a?this.isSpinning?this:(this.#t.hideCursor&&je.hide(this.#u),this.#t.discardStdin&&ce.stdin.isTTY&&(this.#r=!0,We.start()),this.render(),this.#f=setInterval(this.render.bind(this),this.interval),this):(this.text&&this.#u.write(`- ${this.text}
9
9
  `),this)}stop(){return this.#a?(clearInterval(this.#f),this.#f=void 0,this.#i=0,this.clear(),this.#t.hideCursor&&je.show(this.#u),this.#t.discardStdin&&ce.stdin.isTTY&&this.#r&&(We.stop(),this.#r=!1),this):this}succeed(t){return this.stopAndPersist({symbol:k.success,text:t})}fail(t){return this.stopAndPersist({symbol:k.error,text:t})}warn(t){return this.stopAndPersist({symbol:k.warning,text:t})}info(t){return this.stopAndPersist({symbol:k.info,text:t})}stopAndPersist(t={}){if(this.#F)return this;let r=t.prefixText??this.#s,u=this.#E(r," "),i=t.symbol??" ",D=t.text??this.text,s=typeof D=="string"?(i?" ":"")+D:"",a=t.suffixText??this.#o,f=this.#g(a," "),p=u+i+s+f+`
10
10
  `;return this.stop(),this.#u.write(p),this}};function zt(e){return new He(e)}async function Kt(e,t){let r=typeof e=="function",u=typeof e.then=="function";if(!r&&!u)throw new TypeError("Parameter `action` must be a Function or a Promise");let{successText:i,failText:D}=typeof t=="object"?t:{successText:void 0,failText:void 0},o=zt(t).start();try{let a=await(r?e(o):e);return o.succeed(i===void 0?void 0:typeof i=="string"?i:i(a)),a}catch(s){throw o.fail(D===void 0?void 0:typeof D=="string"?D:D(s)),s}}import{existsSync as Hl}from"fs";var Dr=ue(ir(),1);import{dirname as Jn,join as Ze}from"path";import{existsSync as Zn,readFileSync as Qn}from"fs";import{promises as nr}from"fs";import{existsSync as Kn,mkdirSync as Xn}from"fs";var H=class{static ensureDir(t){Kn(t)||Xn(t,{recursive:!0})}};var{copyFile:es,stat:ts}=nr,Ce=class e{constructor(t,r=e.IGNORE_FILE){this.ignoreTargetPath=t;this.ignoreFile=r}static IGNORE_FILE=".gitignore.template";getIg(t=this.ignoreTargetPath){let r=Ze(t,this.ignoreFile);if(!Zn(r))return;let D=Qn(r,"utf8").split(`
11
- `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,Dr.default)().add(D)}async copyFiles(t,r,u,i){let D=await nr.readdir(t);await Promise.all(D.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(Jn(a)),(await ts(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await es(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var ID=ue(qD(),1);import{readFileSync as Ll,writeFileSync as kl,existsSync as $l}from"fs";var me=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return Ll(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){kl(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,ID.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return $l(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var jD=["pack-app"],Be=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!Hl(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Ul(t),this.subPackages=["node-lib","react-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new me}get logger(){return this.context.logger}async steps(t){try{return await Wl.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return jD.includes(t)}async getGeneratorContext(){let t=_t(this.subPackages,jD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=mt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=N(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:N(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:N(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=N(D,o),a=N(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var Et={name:"@qlover/create-app",version:"0.7.7",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function zl(){let e=new Vl;return e.version(Et.version,"-v, --version","Show version").description(Et.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Kl(){let{dryRun:e,verbose:t,...r}=zl(),u=ND("./templates"),i=ND("./configs");GD(u)||(console.error("Template is empty!"),process.exit(1)),GD(i)||(console.error("Configs is empty!"),process.exit(1)),await new Be({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Kl().catch(e=>{console.error(e),process.exit(1)});
11
+ `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,Dr.default)().add(D)}async copyFiles(t,r,u,i){let D=await nr.readdir(t);await Promise.all(D.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(Jn(a)),(await ts(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await es(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var ID=ue(qD(),1);import{readFileSync as Ll,writeFileSync as kl,existsSync as $l}from"fs";var me=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return Ll(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){kl(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,ID.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return $l(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var jD=["pack-app"],Be=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!Hl(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Ul(t),this.subPackages=["node-lib","react-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new me}get logger(){return this.context.logger}async steps(t){try{return await Wl.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return jD.includes(t)}async getGeneratorContext(){let t=_t(this.subPackages,jD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=mt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=N(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(s,a)=>(this.logger.debug("copyCallback",s,a),this.compose.composeConfigFile(t,s,a)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}await this.copyer.copyPaths({sourcePath:N(D,u),targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:N(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=N(D,o),a=N(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var Et={name:"@qlover/create-app",version:"0.7.8",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function zl(){let e=new Vl;return e.version(Et.version,"-v, --version","Show version").description(Et.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function Kl(){let{dryRun:e,verbose:t,...r}=zl(),u=ND("./templates"),i=ND("./configs");GD(u)||(console.error("Template is empty!"),process.exit(1)),GD(i)||(console.error("Configs is empty!"),process.exit(1)),await new Be({dryRun:e,verbose:t,options:{...r,templateRootPath:u,configsRootPath:i}}).generate()}Kl().catch(e=>{console.error(e),process.exit(1)});
@@ -15,7 +15,7 @@ export async function generateLocales() {
15
15
  source: path,
16
16
  // You can use namespace
17
17
  // target: `./public/locales/{{lng}}/{{${name}}}.json`
18
- target: `./public/locales/{{lng}}/common.json`
18
+ target: `./public/locales/{{lng}}.json`
19
19
  }));
20
20
 
21
21
  const ts2Locale = new Ts2Locales(locales);
@@ -1,8 +1,11 @@
1
1
  import type { AppConfig } from '@/base/cases/AppConfig';
2
+ import type { DialogHandler } from '@/base/cases/DialogHandler';
3
+ import type { RouterService } from '@/base/cases/RouterService';
2
4
  import type { I18nService } from '@/base/services/I18nService';
5
+ import type { UserService } from '@/base/services/UserService';
3
6
  import type * as CorekitBridge from '@qlover/corekit-bridge';
4
7
  import type * as FeCorekit from '@qlover/fe-corekit';
5
- import type * as Logger from '@qlover/logger';
8
+ import type { LoggerInterface } from '@qlover/logger';
6
9
 
7
10
  /**
8
11
  * IOC identifier
@@ -15,6 +18,8 @@ export const IOCIdentifier = Object.freeze({
15
18
  LocalStorage: 'LocalStorage',
16
19
  LocalStorageEncrypt: 'LocalStorageEncrypt',
17
20
  CookieStorage: 'CookieStorage',
21
+ UserServiceInterface: 'UserServiceInterface',
22
+ RouterServiceInterface: 'RouterServiceInterface',
18
23
  I18nServiceInterface: 'I18nServiceInterface'
19
24
  });
20
25
 
@@ -30,7 +35,7 @@ export const I = IOCIdentifier;
30
35
  */
31
36
  export interface IOCIdentifierMap {
32
37
  [IOCIdentifier.JSONSerializer]: FeCorekit.JSONSerializer;
33
- [IOCIdentifier.Logger]: Logger.Logger;
38
+ [IOCIdentifier.Logger]: LoggerInterface;
34
39
  [IOCIdentifier.LocalStorage]: FeCorekit.SyncStorage<
35
40
  unknown,
36
41
  FeCorekit.ObjectStorageOptions
@@ -41,5 +46,13 @@ export interface IOCIdentifierMap {
41
46
  >;
42
47
  [IOCIdentifier.CookieStorage]: CorekitBridge.CookieStorage;
43
48
  [IOCIdentifier.AppConfig]: AppConfig;
49
+ [IOCIdentifier.UserServiceInterface]: UserService;
50
+ [IOCIdentifier.RouterServiceInterface]: RouterService;
44
51
  [IOCIdentifier.I18nServiceInterface]: I18nService;
52
+ [IOCIdentifier.DialogHandler]: DialogHandler;
53
+ }
54
+
55
+ export interface IOCIdentifierMapServer {
56
+ [IOCIdentifier.AppConfig]: AppConfig;
57
+ [IOCIdentifier.Logger]: LoggerInterface;
45
58
  }
@@ -32,3 +32,10 @@ export const RES_NO_TOKEN = 'err__response__no__token';
32
32
  * @localEn Not found component
33
33
  */
34
34
  export const NOT_FOUND_COMPONENT = 'err__not__found__component';
35
+
36
+ /**
37
+ * @description 服务器认证错误
38
+ * @localZh 服务器认证错误
39
+ * @localEn Server auth error
40
+ */
41
+ export const SERVER_AUTH_ERROR = 'err__server__auth__error';
@@ -43,7 +43,7 @@
43
43
  "@types/node": "^20",
44
44
  "@types/react": "^19",
45
45
  "@types/react-dom": "^19",
46
- "@qlover/eslint-plugin": "file:../../../packages/eslint-plugin",
46
+ "@qlover/eslint-plugin": "latest",
47
47
  "cross-env": "^7.0.3",
48
48
  "eslint": "^9",
49
49
  "eslint-config-next": "15.5.0",
@@ -179,5 +179,6 @@
179
179
  "page__request__trigger_api_catch": "Trigger API Catch Result",
180
180
  "page__request__stop_api_catch": "Stop API Catch Result",
181
181
  "page__login__content": "Login Page Content",
182
- "page__login__keywords": "Login Page Keywords"
182
+ "page__login__keywords": "Login Page Keywords",
183
+ "err__server__auth__error": "Server auth error"
183
184
  }
@@ -179,5 +179,6 @@
179
179
  "page__request__trigger_api_catch": "触发 API 捕获结果",
180
180
  "page__request__stop_api_catch": "停止 API 捕获结果",
181
181
  "page__login__content": "登录页面内容",
182
- "page__login__keywords": "登录页面关键词"
182
+ "page__login__keywords": "登录页面关键词",
183
+ "err__server__auth__error": "服务器认证错误"
183
184
  }
@@ -1,39 +1,27 @@
1
- import { notFound } from 'next/navigation';
2
1
  import { NextIntlClientProvider } from 'next-intl';
3
- import { getMessages } from 'next-intl/server';
4
- import { i18nConfig } from '@config/i18n';
5
2
  import { themeConfig } from '@config/theme';
3
+ import { PageParams } from '@/base/cases/PageParams';
4
+ import type { PageLayoutProps } from '@/base/types/PageProps';
6
5
  import { BaseHeader } from '@/uikit/components/BaseHeader';
7
6
  import { ComboProvider } from '@/uikit/components/ComboProvider';
8
- import type { LocaleType } from '@config/i18n';
9
7
  import '@/styles/css/index.css';
10
8
 
11
9
  export default async function RootLayout({
12
10
  children,
13
11
  params
14
- }: {
15
- children: React.ReactNode;
16
- params: Promise<{ locale: string }>;
17
- }) {
18
- // Extract the locale from the route params (async for Next.js App Router)
19
- const { locale } = await params;
20
-
21
- // Validate that the requested locale is supported
22
- if (!i18nConfig.supportedLngs.includes(locale as LocaleType)) {
23
- notFound();
24
- }
25
-
26
- // Load the translation messages for the selected locale
27
- const messages = await getMessages({ locale });
12
+ }: PageLayoutProps) {
13
+ const pageParams = new PageParams(await params!);
14
+ const locale = pageParams.getLocale();
15
+ const messages = await pageParams.getI18nMessages();
28
16
 
29
17
  // TODO: suppressHydrationWarning 暂时解决 hydration 问题
30
18
  return (
31
19
  <html data-testid="RootLayout" lang={locale} suppressHydrationWarning>
32
20
  <body>
33
- <NextIntlClientProvider messages={messages}>
21
+ <NextIntlClientProvider locale={locale} messages={messages}>
34
22
  <ComboProvider themeConfig={themeConfig}>
35
23
  <div className="flex flex-col min-h-screen">
36
- <BaseHeader />
24
+ <BaseHeader showLogoutButton />
37
25
  <div className="flex flex-col">{children}</div>
38
26
  </div>
39
27
  </ComboProvider>
@@ -3,9 +3,9 @@
3
3
  import { UserOutlined, LockOutlined, GoogleOutlined } from '@ant-design/icons';
4
4
  import { Form, Input, Button } from 'antd';
5
5
  import { useState } from 'react';
6
- import { UserService } from '@/base/services/UserService';
7
- import { IOC } from '@/core/IOC';
6
+ import { I } from '@config/IOCIdentifier';
8
7
  import { LocaleLink } from '@/uikit/components/LocaleLink';
8
+ import { useIOC } from '@/uikit/hook/useIOC';
9
9
  import type { LoginI18nInterface } from '@config/i18n/loginI18n';
10
10
 
11
11
  interface LoginFormData {
@@ -15,16 +15,15 @@ interface LoginFormData {
15
15
 
16
16
  export function LoginForm(props: { tt: LoginI18nInterface }) {
17
17
  const { tt } = props;
18
- const userService = IOC(UserService);
18
+ const userService = useIOC(I.UserServiceInterface);
19
+ const routerService = useIOC(I.RouterServiceInterface);
19
20
  const [loading, setLoading] = useState(false);
20
21
 
21
22
  const handleLogin = async (values: LoginFormData) => {
22
23
  try {
23
24
  setLoading(true);
24
- await userService.login({
25
- email: values.email,
26
- password: values.password
27
- });
25
+ await userService.login(values);
26
+ routerService.gotoHome();
28
27
  } catch (error) {
29
28
  console.error('Login error:', error);
30
29
  } finally {
@@ -1,6 +1,10 @@
1
+ import { notFound } from 'next/navigation';
1
2
  import { loginI18n, i18nConfig } from '@config/i18n';
2
- import { getServerI18n } from '@/server/getServerI18n';
3
- import { useI18nInterface } from '@/uikit/hook/useI18nInterface';
3
+ import { PageParams, type PageParamsType } from '@/base/cases/PageParams';
4
+ import { ServerAuth } from '@/base/cases/ServerAuth';
5
+ import type { PageParamsProps } from '@/base/types/PageProps';
6
+ import { BootstrapServer } from '@/core/bootstraps/BootstrapServer';
7
+ import { redirect } from '@/i18n/routing';
4
8
  import { FeatureItem } from './FeatureItem';
5
9
  import { LoginForm } from './LoginForm';
6
10
  import type { Metadata } from 'next';
@@ -21,21 +25,30 @@ export const dynamic = 'auto'; // Enable static generation when possible, fallba
21
25
  export async function generateMetadata({
22
26
  params
23
27
  }: {
24
- params: Promise<{ locale: string }>;
28
+ params: Promise<PageParamsType>;
25
29
  }): Promise<Metadata> {
26
- const { locale } = await params;
30
+ const pageParams = new PageParams(await params);
27
31
 
28
- const tt = await getServerI18n({
29
- locale,
30
- i18nInterface: loginI18n
31
- });
32
+ const tt = await pageParams.getI18nInterface(loginI18n);
32
33
 
33
- // Return localized SEO metadata
34
34
  return tt;
35
35
  }
36
36
 
37
- export default function LoginPage() {
38
- const tt = useI18nInterface(loginI18n);
37
+ export default async function LoginPage(props: PageParamsProps) {
38
+ if (!props.params) {
39
+ return notFound();
40
+ }
41
+
42
+ const params = await props.params;
43
+ const pageParams = new PageParams(params);
44
+
45
+ const server = new BootstrapServer();
46
+
47
+ if (await new ServerAuth(server).hasAuth()) {
48
+ return redirect({ href: '/', locale: params.locale! });
49
+ }
50
+
51
+ const tt = await pageParams.getI18nInterface(loginI18n);
39
52
 
40
53
  return (
41
54
  <div
@@ -1,6 +1,19 @@
1
1
  import Image from 'next/image';
2
+ import { PageParams } from '@/base/cases/PageParams';
3
+ import { ServerAuth } from '@/base/cases/ServerAuth';
4
+ import type { PageParamsProps } from '@/base/types/PageProps';
5
+ import { BootstrapServer } from '@/core/bootstraps/BootstrapServer';
6
+ import { redirect } from '@/i18n/routing';
7
+
8
+ export default async function Home({ params }: PageParamsProps) {
9
+ const server = new BootstrapServer();
10
+ const pageParams = new PageParams(await params!);
11
+ const locale = pageParams.getLocale();
12
+
13
+ if (!(await new ServerAuth(server).hasAuth())) {
14
+ return redirect({ href: '/login', locale });
15
+ }
2
16
 
3
- export default async function Home() {
4
17
  return (
5
18
  <div
6
19
  data-testid="Home"
@@ -0,0 +1,92 @@
1
+ import type {
2
+ AntdStaticApiInterface,
3
+ MessageApi,
4
+ ModalApi,
5
+ NotificationApi
6
+ } from '@brain-toolkit/antd-theme-override/react';
7
+ import type {
8
+ UIDialogInterface,
9
+ NotificationOptions
10
+ } from '@qlover/corekit-bridge';
11
+ import type { ModalFuncProps } from 'antd';
12
+
13
+ export interface DialogHandlerOptions
14
+ extends NotificationOptions,
15
+ ModalFuncProps {
16
+ content: string;
17
+ }
18
+
19
+ /**
20
+ * Dialog Handler Implementation
21
+ *
22
+ * Implements the InteractionHubInterface using Ant Design components.
23
+ * Provides concrete implementations for displaying notifications and confirmation dialogs.
24
+ *
25
+ * Features:
26
+ * - Uses Ant Design's message component for notifications
27
+ * - Uses Ant Design's Modal component for confirmations
28
+ * - Supports customizable display durations
29
+ * - Handles error objects appropriately
30
+ *
31
+ * @example
32
+ * const dialog = new DialogHandler();
33
+ * dialog.success('Data saved successfully');
34
+ * dialog.confirm({
35
+ * title: 'Confirm Delete',
36
+ * content: 'Are you sure you want to delete this item?',
37
+ * onOk: () => handleDelete(),
38
+ * });
39
+ */
40
+ export class DialogHandler
41
+ implements UIDialogInterface<DialogHandlerOptions>, AntdStaticApiInterface
42
+ {
43
+ protected antds: {
44
+ message?: MessageApi;
45
+ modal?: ModalApi;
46
+ notification?: NotificationApi;
47
+ } = {};
48
+
49
+ setMessage(message: MessageApi): void {
50
+ this.antds.message = message;
51
+ }
52
+
53
+ setModal(modal: ModalApi): void {
54
+ this.antds.modal = modal;
55
+ }
56
+
57
+ setNotification(notification: NotificationApi): void {
58
+ this.antds.notification = notification;
59
+ }
60
+
61
+ /**
62
+ * Formats error message from various error types
63
+ */
64
+ protected formatErrorMessage(error: unknown): string {
65
+ if (error instanceof Error) return error.message;
66
+ if (typeof error === 'string') return error;
67
+ return 'An unknown error occurred';
68
+ }
69
+
70
+ public success(msg: string, options?: NotificationOptions): void {
71
+ this.antds.message?.success({ content: msg, ...options });
72
+ }
73
+
74
+ public error(msg: string, options?: NotificationOptions): void {
75
+ this.antds.message?.error({
76
+ content: options?.error ? this.formatErrorMessage(options.error) : msg,
77
+ ...options
78
+ });
79
+ }
80
+
81
+ public info(msg: string, options?: NotificationOptions): void {
82
+ this.antds.message?.info({ content: msg, ...options });
83
+ }
84
+
85
+ public warn(msg: string, options?: NotificationOptions): void {
86
+ this.antds.message?.warning({ content: msg, ...options });
87
+ }
88
+
89
+ public confirm(options: DialogHandlerOptions): void {
90
+ this.antds.modal?.confirm(options);
91
+ }
92
+ }
@@ -0,0 +1,16 @@
1
+ import { injectable } from 'inversify';
2
+ import type { UIBridgeInterface } from '@qlover/corekit-bridge';
3
+ import type { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
4
+
5
+ @injectable()
6
+ export class NavigateBridge implements UIBridgeInterface<AppRouterInstance> {
7
+ protected navigate: AppRouterInstance | null = null;
8
+
9
+ setUIBridge(ui: AppRouterInstance): void {
10
+ this.navigate = ui;
11
+ }
12
+
13
+ getUIBridge(): AppRouterInstance | null {
14
+ return this.navigate;
15
+ }
16
+ }
@@ -0,0 +1,74 @@
1
+ import { notFound } from 'next/navigation';
2
+ import { getMessages, getTranslations } from 'next-intl/server';
3
+ import { i18nConfig } from '@config/i18n';
4
+ import type { ParamsHandlerInterface as ParamsHandlerInterface } from '../port/ParamsHandlerInterface';
5
+ import type { LocaleType, PageI18nInterface } from '@config/i18n';
6
+
7
+ export interface PageWithParams {
8
+ params?: Promise<PageParamsType>;
9
+ }
10
+
11
+ export interface PageParamsType {
12
+ locale?: string;
13
+ }
14
+
15
+ /**
16
+ * Handler Page Params
17
+ */
18
+ export class PageParams implements ParamsHandlerInterface {
19
+ private locale: string | null;
20
+
21
+ constructor(protected readonly params: PageParamsType) {
22
+ this.locale = this.params.locale || i18nConfig.fallbackLng;
23
+ }
24
+
25
+ public getLocale(defaultLocale?: string): string {
26
+ if (this.locale) {
27
+ return this.locale;
28
+ }
29
+
30
+ this.locale = this.params.locale || defaultLocale || i18nConfig.fallbackLng;
31
+
32
+ return this.locale;
33
+ }
34
+
35
+ public getI18nWithNotFound(): string {
36
+ const locale = this.getLocale();
37
+
38
+ if (!i18nConfig.supportedLngs.includes(locale as LocaleType)) {
39
+ notFound();
40
+ }
41
+
42
+ return locale;
43
+ }
44
+
45
+ public async getI18nMessages(): Promise<Record<string, string>> {
46
+ const locale = this.getLocale();
47
+
48
+ const messages = await getMessages({ locale });
49
+
50
+ return messages;
51
+ }
52
+
53
+ public async getI18nInterface<T extends PageI18nInterface>(
54
+ i18nInterface: T,
55
+ namespace?: string
56
+ ): Promise<T> {
57
+ // Load translation messages from the HomePage namespace
58
+ const t = await getTranslations({
59
+ locale: this.getLocale(),
60
+ namespace: namespace
61
+ });
62
+
63
+ const result = Object.fromEntries(
64
+ Object.entries(i18nInterface).map(([key, value]) => {
65
+ if (typeof value === 'string') {
66
+ return [key, t(value)];
67
+ }
68
+ return [key, value];
69
+ })
70
+ ) as T;
71
+
72
+ return result;
73
+ }
74
+ }