@qlover/create-app 0.7.13 → 0.7.15

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 (93) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/next-app/README.en.md +131 -0
  5. package/dist/templates/next-app/README.md +115 -20
  6. package/dist/templates/next-app/config/IOCIdentifier.ts +14 -1
  7. package/dist/templates/next-app/config/Identifier/index.ts +1 -0
  8. package/dist/templates/next-app/config/Identifier/page.admin.ts +48 -0
  9. package/dist/templates/next-app/config/i18n/admin18n.ts +33 -0
  10. package/dist/templates/next-app/config/i18n/index.ts +3 -1
  11. package/dist/templates/next-app/docs/en/api.md +387 -0
  12. package/dist/templates/next-app/docs/en/component.md +544 -0
  13. package/dist/templates/next-app/docs/en/database.md +496 -0
  14. package/dist/templates/next-app/docs/en/development-guide.md +727 -0
  15. package/dist/templates/next-app/docs/en/env.md +563 -0
  16. package/dist/templates/next-app/docs/en/i18n.md +287 -0
  17. package/dist/templates/next-app/docs/en/index.md +166 -0
  18. package/dist/templates/next-app/docs/en/page.md +457 -0
  19. package/dist/templates/next-app/docs/en/project-structure.md +177 -0
  20. package/dist/templates/next-app/docs/en/router.md +427 -0
  21. package/dist/templates/next-app/docs/en/theme.md +532 -0
  22. package/dist/templates/next-app/docs/en/validator.md +478 -0
  23. package/dist/templates/next-app/docs/zh/api.md +387 -0
  24. package/dist/templates/next-app/docs/zh/component.md +544 -0
  25. package/dist/templates/next-app/docs/zh/database.md +496 -0
  26. package/dist/templates/next-app/docs/zh/development-guide.md +727 -0
  27. package/dist/templates/next-app/docs/zh/env.md +563 -0
  28. package/dist/templates/next-app/docs/zh/i18n.md +287 -0
  29. package/dist/templates/next-app/docs/zh/index.md +166 -0
  30. package/dist/templates/next-app/docs/zh/page.md +457 -0
  31. package/dist/templates/next-app/docs/zh/project-structure.md +177 -0
  32. package/dist/templates/next-app/docs/zh/router.md +427 -0
  33. package/dist/templates/next-app/docs/zh/theme.md +532 -0
  34. package/dist/templates/next-app/docs/zh/validator.md +476 -0
  35. package/dist/templates/next-app/migrations/schema/UserSchema.ts +2 -2
  36. package/dist/templates/next-app/next.config.ts +1 -1
  37. package/dist/templates/next-app/package.json +3 -1
  38. package/dist/templates/next-app/public/locales/en.json +8 -1
  39. package/dist/templates/next-app/public/locales/zh.json +8 -1
  40. package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +1 -1
  41. package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +14 -16
  42. package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +10 -3
  43. package/dist/templates/next-app/src/app/[locale]/layout.tsx +1 -1
  44. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +1 -1
  45. package/dist/templates/next-app/src/app/[locale]/page.tsx +2 -3
  46. package/dist/templates/next-app/src/app/[locale]/register/page.tsx +1 -1
  47. package/dist/templates/next-app/src/app/api/ai/completions/route.ts +32 -0
  48. package/dist/templates/next-app/src/base/cases/AppConfig.ts +3 -0
  49. package/dist/templates/next-app/src/base/cases/ChatAction.ts +21 -0
  50. package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +36 -0
  51. package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +1 -3
  52. package/dist/templates/next-app/src/base/services/AdminUserService.ts +1 -1
  53. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +1 -1
  54. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +23 -1
  55. package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +2 -2
  56. package/dist/templates/next-app/src/base/types/PageProps.ts +1 -1
  57. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +1 -0
  58. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +1 -0
  59. package/dist/templates/next-app/src/core/globals.ts +2 -0
  60. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +4 -1
  61. package/dist/templates/next-app/src/{base/cases → server}/PageParams.ts +1 -1
  62. package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +31 -0
  63. package/dist/templates/next-app/src/server/port/DBTableInterface.ts +1 -1
  64. package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +6 -4
  65. package/dist/templates/next-app/src/server/services/AIService.ts +43 -0
  66. package/dist/templates/next-app/src/server/services/ApiUserService.ts +1 -1
  67. package/dist/templates/next-app/src/server/{SupabaseBridge.ts → sqlBridges/SupabaseBridge.ts} +16 -11
  68. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +4 -4
  69. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +1 -1
  70. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +32 -25
  71. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +12 -26
  72. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +37 -5
  73. package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +17 -0
  74. package/dist/templates/next-app/src/uikit/components/ClientSeo.tsx +36 -0
  75. package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +5 -6
  76. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +2 -0
  77. package/dist/templates/next-app/src/uikit/components/With.tsx +17 -0
  78. package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +30 -0
  79. package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +65 -0
  80. package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +59 -0
  81. package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +28 -0
  82. package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +19 -0
  83. package/package.json +1 -1
  84. package/dist/templates/next-app/docs/env.md +0 -94
  85. package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +0 -21
  86. package/dist/templates/next-app/src/base/port/DBMigrationInterface.ts +0 -92
  87. package/dist/templates/next-app/src/base/port/MigrationApiInterface.ts +0 -3
  88. package/dist/templates/next-app/src/base/port/ServerApiResponseInterface.ts +0 -6
  89. package/dist/templates/next-app/src/base/services/migrations/MigrationsApi.ts +0 -43
  90. package/dist/templates/next-app/config/i18n/{HomeI18n .ts → HomeI18n.ts} +0 -0
  91. package/dist/templates/next-app/{build → make}/generateLocales.ts +2 -2
  92. /package/dist/templates/next-app/src/{base → server}/port/PaginationInterface.ts +0 -0
  93. /package/dist/templates/next-app/src/{base → server}/port/ParamsHandlerInterface.ts +0 -0
@@ -0,0 +1,32 @@
1
+ import { ExecutorError } from '@qlover/fe-corekit';
2
+ import { NextResponse } from 'next/server';
3
+ import { BootstrapServer } from '@/core/bootstraps/BootstrapServer';
4
+ import { AppErrorApi } from '@/server/AppErrorApi';
5
+ import { AppSuccessApi } from '@/server/AppSuccessApi';
6
+ import { AIService } from '@/server/services/AIService';
7
+
8
+ export async function GET() {
9
+ const server = new BootstrapServer();
10
+
11
+ const result = await server.execNoError(async ({ parameters: { IOC } }) => {
12
+ // const requestBody = await req.json();
13
+
14
+ const result = await IOC(AIService).completions([
15
+ {
16
+ role: 'user',
17
+ content: 'hello'
18
+ }
19
+ ]);
20
+
21
+ return result;
22
+ });
23
+
24
+ if (result instanceof ExecutorError) {
25
+ console.log(result);
26
+ return NextResponse.json(new AppErrorApi(result.id, result.message), {
27
+ status: 400
28
+ });
29
+ }
30
+
31
+ return NextResponse.json(new AppSuccessApi(result));
32
+ }
@@ -31,4 +31,7 @@ export class AppConfig implements EnvConfigInterface {
31
31
  * @example '1 year'
32
32
  */
33
33
  readonly jwtExpiresIn: StringValue = '30 days';
34
+
35
+ readonly openaiBaseUrl: string = process.env.CEREBRAS_BASE_URL!;
36
+ readonly openaiApiKey: string = process.env.CEREBRAS_API_KEY!;
34
37
  }
@@ -0,0 +1,21 @@
1
+ import { injectable } from 'inversify';
2
+ import {
3
+ ChatActionInterface,
4
+ type MessageInterface,
5
+ type ChatStateInterface
6
+ } from '@/uikit/components/chat/ChatActionInterface';
7
+
8
+ class ChatState implements ChatStateInterface {
9
+ messages: MessageInterface[] = [];
10
+ }
11
+
12
+ @injectable()
13
+ export class ChatAction extends ChatActionInterface<ChatStateInterface> {
14
+ constructor() {
15
+ super(() => new ChatState());
16
+ }
17
+
18
+ focus(): void {
19
+ console.log('focus');
20
+ }
21
+ }
@@ -0,0 +1,36 @@
1
+ import { injectable } from 'inversify';
2
+
3
+ import type { FocusBarStateInterface } from '@/uikit/components/chat/FocusBarActionInterface';
4
+ import { FocusBarActionInterface } from '@/uikit/components/chat/FocusBarActionInterface';
5
+ import { RequestState } from './RequestState';
6
+
7
+ class FocusBarState implements FocusBarStateInterface {
8
+ showHistoryArea = false;
9
+ inputValue = '';
10
+ sendState = new RequestState();
11
+ }
12
+
13
+ @injectable()
14
+ export class FocusBarAction extends FocusBarActionInterface<FocusBarStateInterface> {
15
+ constructor() {
16
+ super(() => new FocusBarState());
17
+ }
18
+
19
+ sendMessage(_message: string): Promise<void> {
20
+ return new Promise((resolve) => {
21
+ this.emit(this.cloneState({ sendState: new RequestState(true) }));
22
+ setTimeout(() => {
23
+ this.emit(this.cloneState({ sendState: new RequestState().end() }));
24
+ resolve();
25
+ }, 1000);
26
+ });
27
+ }
28
+
29
+ setInputValue(value: string): void {
30
+ this.emit(this.cloneState({ inputValue: value }));
31
+ }
32
+
33
+ clearInput(): void {
34
+ this.emit(this.cloneState({ inputValue: '' }));
35
+ }
36
+ }
@@ -1,6 +1,6 @@
1
1
  import { StoreInterface } from '@qlover/corekit-bridge';
2
2
  import { RequestState } from '../cases/RequestState';
3
- import type { PaginationInterface } from './PaginationInterface';
3
+ import type { PaginationInterface } from '../../server/port/PaginationInterface';
4
4
  import type { StoreStateInterface } from '@qlover/corekit-bridge';
5
5
 
6
6
  export interface AdminPageListParams {
@@ -34,8 +34,6 @@ export abstract class AdminPageInterface<
34
34
  try {
35
35
  const result = await this.fetchList(this.state.listParams);
36
36
 
37
- console.log('jj result', result);
38
-
39
37
  this.emit(
40
38
  this.cloneState({
41
39
  initState: new RequestState(false, result).end()
@@ -1,4 +1,5 @@
1
1
  import { inject, injectable } from 'inversify';
2
+ import type { PaginationInterface } from '@/server/port/PaginationInterface';
2
3
  import {
3
4
  AdminPageInterface,
4
5
  type AdminPageListParams,
@@ -6,7 +7,6 @@ import {
6
7
  } from '../port/AdminPageInterface';
7
8
  import { AdminUserApi } from './adminApi/AdminUserApi';
8
9
  import { RequestState } from '../cases/RequestState';
9
- import type { PaginationInterface } from '../port/PaginationInterface';
10
10
 
11
11
  @injectable()
12
12
  export class AdminUserService extends AdminPageInterface<AdminPageState> {
@@ -1,6 +1,6 @@
1
1
  import { inject, injectable } from 'inversify';
2
2
  import type { AdminPageListParams } from '@/base/port/AdminPageInterface';
3
- import type { PaginationInterface } from '@/base/port/PaginationInterface';
3
+ import type { PaginationInterface } from '@/server/port/PaginationInterface';
4
4
  import {
5
5
  AppApiRequester,
6
6
  type AppApiConfig,
@@ -6,10 +6,13 @@ import {
6
6
  } from '@qlover/fe-corekit';
7
7
  import type { AppApiErrorInterface } from '@/base/port/AppApiInterface';
8
8
  import type { AppApiConfig } from './AppApiRequester';
9
+ import type { LoggerInterface } from '@qlover/logger';
9
10
 
10
11
  export class AppApiPlugin implements ExecutorPlugin {
11
12
  readonly pluginName = 'AppApiPlugin';
12
13
 
14
+ constructor(protected logger: LoggerInterface) {}
15
+
13
16
  isAppApiErrorInterface(value: unknown): value is AppApiErrorInterface {
14
17
  return (
15
18
  typeof value === 'object' &&
@@ -21,8 +24,16 @@ export class AppApiPlugin implements ExecutorPlugin {
21
24
  );
22
25
  }
23
26
 
24
- onSuccess(context: ExecutorContext<unknown>): void | Promise<void> {
27
+ onSuccess(context: ExecutorContext<AppApiConfig>): void | Promise<void> {
25
28
  const response = context.returnValue;
29
+ const { parameters } = context;
30
+
31
+ this.logger.info(
32
+ `%c[AppApi ${parameters.method} ${parameters.url}]%c - ${new Date().toLocaleString()}`,
33
+ 'color: #0f0;',
34
+ 'color: inherit;',
35
+ response
36
+ );
26
37
 
27
38
  if (this.isAppApiErrorInterface(response)) {
28
39
  throw new Error(response.message || response.id);
@@ -34,6 +45,8 @@ export class AppApiPlugin implements ExecutorPlugin {
34
45
  ): Promise<ExecutorError | void> {
35
46
  const { error, parameters } = context;
36
47
 
48
+ this.loggerError(parameters, error);
49
+
37
50
  if (error instanceof RequestError && parameters.responseType === 'json') {
38
51
  // @ts-expect-error response is not defined in Error
39
52
  let response = error?.response;
@@ -60,4 +73,13 @@ export class AppApiPlugin implements ExecutorPlugin {
60
73
  return {};
61
74
  }
62
75
  }
76
+
77
+ protected loggerError(config: AppApiConfig, error: unknown): void {
78
+ this.logger.error(
79
+ `%c[AppApi ${config.method} ${config.url}]%c - ${new Date().toLocaleString()}`,
80
+ 'color: #f00;',
81
+ 'color: inherit;',
82
+ error
83
+ );
84
+ }
63
85
  }
@@ -3,6 +3,7 @@ import { FetchURLPlugin } from '@qlover/fe-corekit';
3
3
  import { DialogErrorPlugin } from '@/base/cases/DialogErrorPlugin';
4
4
  import { RequestEncryptPlugin } from '@/base/cases/RequestEncryptPlugin';
5
5
  import { StringEncryptor } from '@/base/cases/StringEncryptor';
6
+ import { I } from '@config/IOCIdentifier';
6
7
  import { AppApiPlugin } from './AppApiPlugin';
7
8
  import { AppApiRequester } from './AppApiRequester';
8
9
  import type { AppApiConfig } from './AppApiRequester';
@@ -27,9 +28,8 @@ export class AppUserApiBootstrap implements BootstrapExecutorPlugin {
27
28
  requestDataSerializer: this.requestDataSerializer.bind(this)
28
29
  })
29
30
  );
30
- appUserApi.usePlugin(new AppApiPlugin());
31
+ appUserApi.usePlugin(new AppApiPlugin(ioc.get(I.Logger)));
31
32
  appUserApi.usePlugin(ioc.get(DialogErrorPlugin));
32
- console.log('jj AppUserApiBootstrap success');
33
33
  }
34
34
 
35
35
  protected requestDataSerializer(
@@ -1,4 +1,4 @@
1
- import type { PageWithParams } from '@/base/cases/PageParams';
1
+ import type { PageWithParams } from '@/server/PageParams';
2
2
 
3
3
  export interface PageParamsProps extends PageWithParams {
4
4
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
@@ -1,3 +1,4 @@
1
+ import 'reflect-metadata';
1
2
  import { Bootstrap } from '@qlover/corekit-bridge';
2
3
  import { isObject } from 'lodash';
3
4
  import { browserGlobalsName } from '@config/common';
@@ -1,3 +1,4 @@
1
+ import 'reflect-metadata';
1
2
  import {
2
3
  type ServiceIdentifier,
3
4
  type BootstrapContextValue,
@@ -1,3 +1,5 @@
1
+ import 'reflect-metadata';
2
+
1
3
  // ! global variables, don't import any dependencies and don't have side effects
2
4
  import { ColorFormatter, ConsoleHandler, Logger } from '@qlover/corekit-bridge';
3
5
  import { JSONSerializer } from '@qlover/fe-corekit';
@@ -7,6 +7,7 @@ import {
7
7
  type IOCRegisterInterface
8
8
  } from '@qlover/corekit-bridge';
9
9
  import type { IocRegisterOptions } from '@/base/port/IOCInterface';
10
+ import { SupabaseBridge } from '@/server/sqlBridges/SupabaseBridge';
10
11
  import { IOCIdentifier as I } from '@config/IOCIdentifier';
11
12
 
12
13
  export class ServerIOCRegister
@@ -45,7 +46,9 @@ export class ServerIOCRegister
45
46
  );
46
47
  }
47
48
 
48
- protected registerImplement(_ioc: IOCContainerInterface): void {}
49
+ protected registerImplement(ioc: IOCContainerInterface): void {
50
+ ioc.bind(I.DBBridgeInterface, ioc.get(SupabaseBridge));
51
+ }
49
52
 
50
53
  protected registerCommon(_ioc: IOCContainerInterface): void {}
51
54
 
@@ -2,7 +2,7 @@ import { notFound } from 'next/navigation';
2
2
  import { getMessages, getTranslations } from 'next-intl/server';
3
3
  import { i18nConfig } from '@config/i18n';
4
4
  import type { LocaleType, PageI18nInterface } from '@config/i18n';
5
- import type { ParamsHandlerInterface as ParamsHandlerInterface } from '../port/ParamsHandlerInterface';
5
+ import type { ParamsHandlerInterface as ParamsHandlerInterface } from './port/ParamsHandlerInterface';
6
6
 
7
7
  export interface PageWithParams {
8
8
  params?: Promise<PageParamsType>;
@@ -0,0 +1,31 @@
1
+ export type WhereOperation = '=' | '!=' | '>' | '<' | '>=' | '<=';
2
+ export type Where = [string, WhereOperation, string | number];
3
+
4
+ export interface BridgeEvent {
5
+ table: string;
6
+ fields?: string | string[];
7
+ where?: Where[];
8
+ data?: unknown;
9
+ page?: number;
10
+ pageSize?: number;
11
+ }
12
+
13
+ export interface PaginationInfo {
14
+ total: number;
15
+ page: number;
16
+ pageSize: number;
17
+ }
18
+
19
+ export interface DBBridgeResponse<T> {
20
+ error?: unknown;
21
+ data: T;
22
+ pagination?: PaginationInfo;
23
+ }
24
+
25
+ export interface DBBridgeInterface {
26
+ add(event: BridgeEvent): Promise<DBBridgeResponse<unknown>>;
27
+ update(event: BridgeEvent): Promise<DBBridgeResponse<unknown>>;
28
+ delete(event: BridgeEvent): Promise<DBBridgeResponse<unknown>>;
29
+ get(event: BridgeEvent): Promise<DBBridgeResponse<unknown>>;
30
+ pagination(event: BridgeEvent): Promise<DBBridgeResponse<unknown[]>>;
31
+ }
@@ -1,4 +1,4 @@
1
- import type { PaginationInterface } from '@/base/port/PaginationInterface';
1
+ import type { PaginationInterface } from '@/server/port/PaginationInterface';
2
2
 
3
3
  export interface DBTableInterface {
4
4
  readonly name: string;
@@ -1,9 +1,9 @@
1
1
  import { inject, injectable } from 'inversify';
2
2
  import { isEmpty, last } from 'lodash';
3
- import type { DBBridgeInterface } from '@/base/port/DBBridgeInterface';
4
- import type { PaginationInterface } from '@/base/port/PaginationInterface';
3
+ import type { DBBridgeInterface } from '@/server/port/DBBridgeInterface';
4
+ import type { PaginationInterface } from '@/server/port/PaginationInterface';
5
5
  import type { UserSchema } from '@migrations/schema/UserSchema';
6
- import { SupabaseBridge } from '../SupabaseBridge';
6
+ import { I } from '@config/IOCIdentifier';
7
7
  import type { UserRepositoryInterface } from '../port/UserRepositoryInterface';
8
8
 
9
9
  @injectable()
@@ -21,7 +21,9 @@ export class UserRepository implements UserRepositoryInterface {
21
21
  'updated_at'
22
22
  ];
23
23
 
24
- constructor(@inject(SupabaseBridge) protected dbBridge: DBBridgeInterface) {}
24
+ constructor(
25
+ @inject(I.DBBridgeInterface) protected dbBridge: DBBridgeInterface
26
+ ) {}
25
27
 
26
28
  getAll(): Promise<unknown> {
27
29
  return this.dbBridge.get({ table: this.name });
@@ -0,0 +1,43 @@
1
+ import { inject, injectable } from 'inversify';
2
+ import OpenAI from 'openai';
3
+ import type { AppConfig } from '@/base/cases/AppConfig';
4
+ import { I } from '@config/IOCIdentifier';
5
+ import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
6
+
7
+ @injectable()
8
+ export class AIService {
9
+ protected apiKey: string;
10
+ protected baseUrl: string;
11
+ protected client: OpenAI;
12
+
13
+ constructor(@inject(I.AppConfig) appConfig: AppConfig) {
14
+ this.apiKey = appConfig.openaiApiKey;
15
+ this.baseUrl = appConfig.openaiBaseUrl;
16
+
17
+ console.log(this.apiKey, this.baseUrl);
18
+ this.client = new OpenAI({
19
+ apiKey: this.apiKey,
20
+ baseURL: this.baseUrl
21
+ });
22
+ }
23
+
24
+ async completions(messages: ChatCompletionMessageParam[]): Promise<unknown> {
25
+ const url = `${this.baseUrl}/chat/completions`;
26
+
27
+ const response = await fetch(url, {
28
+ method: 'POST',
29
+ body: JSON.stringify({
30
+ messages: messages,
31
+ model: 'claude-sonnet-4-20250514'
32
+ }),
33
+ headers: {
34
+ Authorization: `token ${this.apiKey}`,
35
+ 'Content-Type': 'application/json',
36
+ Accept: 'application/json'
37
+ },
38
+ mode: 'cors'
39
+ });
40
+
41
+ return await response.json();
42
+ }
43
+ }
@@ -1,5 +1,5 @@
1
1
  import { inject, injectable } from 'inversify';
2
- import type { PaginationInterface } from '@/base/port/PaginationInterface';
2
+ import type { PaginationInterface } from '@/server/port/PaginationInterface';
3
3
  import type { UserSchema } from '@migrations/schema/UserSchema';
4
4
  import { UserRepository } from '../repositorys/UserRepository';
5
5
  import { PaginationValidator } from '../validators/PaginationValidator';
@@ -9,8 +9,9 @@ import type { AppConfig } from '@/base/cases/AppConfig';
9
9
  import type {
10
10
  BridgeEvent,
11
11
  DBBridgeInterface,
12
+ DBBridgeResponse,
12
13
  Where
13
- } from '@/base/port/DBBridgeInterface';
14
+ } from '@/server/port/DBBridgeInterface';
14
15
  import { I } from '@config/IOCIdentifier';
15
16
  import type { LoggerInterface } from '@qlover/logger';
16
17
  import type { PostgrestFilterBuilder } from '@supabase/postgrest-js';
@@ -24,6 +25,9 @@ const whereHandlerMaps = {
24
25
  '<=': 'lte'
25
26
  };
26
27
 
28
+ export type SupabaseBridgeResponse<T> = DBBridgeResponse<T> &
29
+ PostgrestResponse<T>;
30
+
27
31
  @injectable()
28
32
  export class SupabaseBridge implements DBBridgeInterface {
29
33
  protected supabase: SupabaseClient;
@@ -42,19 +46,20 @@ export class SupabaseBridge implements DBBridgeInterface {
42
46
  return this.supabase;
43
47
  }
44
48
 
45
- async execSql(sql: string): Promise<PostgrestSingleResponse<unknown>> {
49
+ async execSql(sql: string): Promise<SupabaseBridgeResponse<unknown>> {
46
50
  const res = await this.supabase.rpc('exec_sql', { sql });
47
51
  return this.catch(res);
48
52
  }
49
53
 
50
54
  protected async catch(
51
55
  result: PostgrestSingleResponse<unknown>
52
- ): Promise<PostgrestSingleResponse<unknown>> {
56
+ ): Promise<SupabaseBridgeResponse<unknown>> {
53
57
  if (result.error) {
54
58
  this.logger.info(result);
55
59
  throw new Error(result.error.message);
56
60
  }
57
- return result;
61
+
62
+ return result as SupabaseBridgeResponse<unknown>;
58
63
  }
59
64
 
60
65
  protected handleWhere(
@@ -79,7 +84,7 @@ export class SupabaseBridge implements DBBridgeInterface {
79
84
  }
80
85
  }
81
86
 
82
- async add(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>> {
87
+ async add(event: BridgeEvent): Promise<DBBridgeResponse<unknown>> {
83
88
  const { table, data } = event;
84
89
  if (!data) {
85
90
  throw new Error('Data is required for add operation');
@@ -91,7 +96,7 @@ export class SupabaseBridge implements DBBridgeInterface {
91
96
  return this.catch(res);
92
97
  }
93
98
 
94
- async update(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>> {
99
+ async update(event: BridgeEvent): Promise<DBBridgeResponse<unknown>> {
95
100
  const { table, data, where } = event;
96
101
  if (!data) {
97
102
  throw new Error('Data is required for update operation');
@@ -104,7 +109,7 @@ export class SupabaseBridge implements DBBridgeInterface {
104
109
  return this.catch(await handler);
105
110
  }
106
111
 
107
- async delete(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>> {
112
+ async delete(event: BridgeEvent): Promise<DBBridgeResponse<unknown>> {
108
113
  const { table, where } = event;
109
114
  const handler = this.supabase.from(table).delete();
110
115
 
@@ -113,7 +118,7 @@ export class SupabaseBridge implements DBBridgeInterface {
113
118
  return this.catch(await handler);
114
119
  }
115
120
 
116
- async get(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>> {
121
+ async get(event: BridgeEvent): Promise<SupabaseBridgeResponse<unknown>> {
117
122
  const { table, fields = '*', where } = event;
118
123
  const selectFields = Array.isArray(fields) ? fields.join(',') : fields;
119
124
  const handler = this.supabase.from(table).select(selectFields);
@@ -123,7 +128,7 @@ export class SupabaseBridge implements DBBridgeInterface {
123
128
  return this.catch(await handler);
124
129
  }
125
130
 
126
- async pagination(event: BridgeEvent): Promise<PostgrestResponse<unknown>> {
131
+ async pagination(event: BridgeEvent): Promise<DBBridgeResponse<unknown[]>> {
127
132
  const { table, fields = '*', where, page = 1, pageSize = 10 } = event;
128
133
  const selectFields = Array.isArray(fields) ? fields.join(',') : fields;
129
134
 
@@ -145,7 +150,7 @@ export class SupabaseBridge implements DBBridgeInterface {
145
150
  const result = await this.catch(await handler);
146
151
 
147
152
  if (result.error) {
148
- return result as PostgrestResponse<unknown>;
153
+ return result as DBBridgeResponse<unknown[]>;
149
154
  }
150
155
 
151
156
  return {
@@ -154,6 +159,6 @@ export class SupabaseBridge implements DBBridgeInterface {
154
159
  count: countResult.count,
155
160
  status: result.status,
156
161
  statusText: result.statusText
157
- };
162
+ } as DBBridgeResponse<unknown[]>;
158
163
  }
159
164
  }
@@ -17,13 +17,13 @@ export interface LoginValidatorData {
17
17
  password: string;
18
18
  }
19
19
 
20
- const emailSchema = z.email({ error: V_EMAIL_INVALID });
20
+ const emailSchema = z.string().email({ message: V_EMAIL_INVALID });
21
21
 
22
22
  const passwordSchema = z
23
23
  .string()
24
- .min(6, { error: V_PASSWORD_MIN_LENGTH })
25
- .max(50, { error: V_PASSWORD_MAX_LENGTH })
26
- .regex(/^\S+$/, { error: V_PASSWORD_SPECIAL_CHARS });
24
+ .min(6, { message: V_PASSWORD_MIN_LENGTH })
25
+ .max(50, { message: V_PASSWORD_MAX_LENGTH })
26
+ .regex(/^\S+$/, { message: V_PASSWORD_SPECIAL_CHARS });
27
27
 
28
28
  interface ExtendedExecutorError extends ExecutorError {
29
29
  issues?: ValidationFaildResult[];
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import type { PaginationInterface } from '@/base/port/PaginationInterface';
2
+ import type { PaginationInterface } from '@/server/port/PaginationInterface';
3
3
  import { API_PAGE_INVALID } from '@config/Identifier';
4
4
  import {
5
5
  type ValidationFaildResult,
@@ -3,18 +3,20 @@
3
3
  import {
4
4
  DashboardOutlined,
5
5
  UserOutlined,
6
- MenuFoldOutlined,
7
- MenuUnfoldOutlined
6
+ VerticalAlignBottomOutlined,
7
+ VerticalAlignTopOutlined
8
8
  } from '@ant-design/icons';
9
9
  import { Layout, Menu } from 'antd';
10
10
  import { clsx } from 'clsx';
11
- import React, { useCallback, useMemo, type HTMLAttributes } from 'react';
11
+ import React, { useMemo, type HTMLAttributes } from 'react';
12
12
  import { AdminPageManager } from '@/base/cases/AdminPageManager';
13
13
  import { BaseHeader } from './BaseHeader';
14
+ import { LanguageSwitcher } from './LanguageSwitcher';
14
15
  import { LocaleLink } from './LocaleLink';
16
+ import { LogoutButton } from './LogoutButton';
17
+ import { ThemeSwitcher } from './ThemeSwitcher';
15
18
  import { useIOC } from '../hook/useIOC';
16
19
  import { useStore } from '../hook/useStore';
17
- import type { RenderLeftFunction } from './BaseHeader';
18
20
  import type { ItemType } from 'antd/es/menu/interface';
19
21
 
20
22
  const { Sider } = Layout;
@@ -33,7 +35,6 @@ export function AdminLayout(props: AdminLayoutProps) {
33
35
  const { children, className, mainClassName, ...rest } = props;
34
36
 
35
37
  const page = useIOC(AdminPageManager);
36
-
37
38
  const collapsedSidebar = useStore(page, page.selectors.collapsedSidebar);
38
39
  const navItems = useStore(page, page.selectors.navItems);
39
40
 
@@ -60,40 +61,46 @@ export function AdminLayout(props: AdminLayoutProps) {
60
61
  });
61
62
  }, [navItems]);
62
63
 
63
- const renderHeaderLeft: RenderLeftFunction = useCallback(
64
- ({ defaultElement }) => (
65
- <div data-testid="AdminLayoutHeader" className="flex items-center">
66
- <span
67
- className="text-text hover:text-text-hover cursor-pointer text-md transition-colors"
68
- onClick={() => page.toggleSidebar()}
69
- >
70
- {collapsedSidebar ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
71
- </span>
72
-
73
- {defaultElement}
74
- </div>
75
- ),
76
- [collapsedSidebar, page]
77
- );
64
+ const rightActions = useMemo(() => {
65
+ return [
66
+ <LanguageSwitcher key="language-switcher" />,
67
+ <ThemeSwitcher key="theme-switcher" />,
68
+ <LogoutButton key="logout-button" />
69
+ ];
70
+ }, []);
78
71
 
79
72
  return (
80
73
  <Layout data-testid="AdminLayout" className={className} {...rest}>
81
- <div className="overflow-auto h-screen sticky top-0 bottom-0 scrollbar-thin scrollbar-gutter-stable">
74
+ <div className="overflow-y-auto overflow-x-hidden h-screen sticky top-0 bottom-0 scrollbar-thin scrollbar-gutter-stable">
82
75
  <Sider
83
- className="h-full"
76
+ className="h-full relative"
84
77
  onCollapse={() => page.toggleSidebar()}
85
78
  collapsed={collapsedSidebar}
79
+ collapsedWidth={46}
86
80
  >
87
- <div className="demo-logo-vertical" />
88
81
  <Menu mode="inline" items={sidebarItems} />
82
+
83
+ <div
84
+ data-testid="ToggleSidebarButton"
85
+ className="absolute w-2 right-0 top-0 bottom-0 bg-secondary cursor-pointer hover:bg-elevated flex items-center justify-center"
86
+ onClick={() => page.toggleSidebar()}
87
+ >
88
+ <span className="text-text scale-75 rotate-x-90">
89
+ {collapsedSidebar ? (
90
+ <VerticalAlignTopOutlined />
91
+ ) : (
92
+ <VerticalAlignBottomOutlined />
93
+ )}
94
+ </span>
95
+ </div>
89
96
  </Sider>
90
97
  </div>
91
98
 
92
99
  <Layout>
93
100
  <BaseHeader
94
101
  href="/admin"
95
- showLogoutButton
96
- renderLeft={renderHeaderLeft}
102
+ className="max-w-full pl-0"
103
+ rightActions={rightActions}
97
104
  />
98
105
  <main
99
106
  className={clsx('p-2 bg-primary text-text flex-1', mainClassName)}