@nocobase/portal-template-default 1.0.1 → 1.0.3

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 (186) hide show
  1. package/README.MD +22 -3
  2. package/package.json +4 -2
  3. package/registry/nocobase-acl/components/role-options.ts +7 -0
  4. package/registry/nocobase-acl/components/role-switcher.tsx +9 -4
  5. package/registry/nocobase-acl/demo/boundary-api.tsx +87 -16
  6. package/registry/nocobase-acl/demo/components.tsx +32 -11
  7. package/registry/nocobase-acl/demo/index.tsx +45 -14
  8. package/registry/nocobase-acl/demo/lazy-route.tsx +2 -7
  9. package/registry/nocobase-acl/demo/prompt-generator.tsx +52 -33
  10. package/registry/nocobase-acl/demo/role-switcher-prompt-generator.tsx +28 -9
  11. package/registry/nocobase-acl/extension.tsx +1 -1
  12. package/registry/nocobase-acl/tests/acl-regression.mjs +7 -6
  13. package/registry/nocobase-ai/components/chat/chat-messages.tsx +3 -4
  14. package/registry/nocobase-ai/components/chat/conversation-list.tsx +2 -4
  15. package/registry/nocobase-ai/components/tools/business-report-dialog.tsx +16 -19
  16. package/registry/nocobase-ai/components/tools/chart-renderer.tsx +3 -14
  17. package/registry/nocobase-ai/components/tools/echarts-preview.tsx +2 -5
  18. package/registry/nocobase-ai/demo/configuration-gate.tsx +9 -12
  19. package/registry/nocobase-ai/extension.tsx +2 -7
  20. package/registry/nocobase-i18n/README.md +53 -0
  21. package/registry/nocobase-i18n/components/index.ts +1 -0
  22. package/registry/nocobase-i18n/components/language-switcher.tsx +138 -0
  23. package/registry/nocobase-i18n/demo/index.tsx +209 -0
  24. package/registry/nocobase-i18n/demo/prompt-generator.tsx +155 -0
  25. package/registry/nocobase-i18n/extension.tsx +46 -0
  26. package/registry/nocobase-i18n/i18n-provider.ts +9 -0
  27. package/registry/nocobase-i18n/index.ts +6 -0
  28. package/registry/nocobase-i18n/locale-store.ts +134 -0
  29. package/registry/nocobase-i18n/locales/en-US.ts +89 -0
  30. package/registry/nocobase-i18n/locales/zh-CN.ts +85 -0
  31. package/registry/nocobase-i18n/provider.tsx +32 -0
  32. package/registry/nocobase-i18n/runtime.ts +118 -0
  33. package/registry/nocobase-i18n/server-resources.ts +184 -0
  34. package/registry/nocobase-i18n/tests/i18n-regression.mjs +42 -0
  35. package/registry.config.json +34 -14
  36. package/scripts/registry.mjs +5 -0
  37. package/src/App.tsx +27 -86
  38. package/src/app/extension.ts +3 -1
  39. package/src/app/extensions.tsx +21 -0
  40. package/src/components/access-control/acl-gate.tsx +4 -10
  41. package/src/components/app-shell/breadcrumb.tsx +39 -3
  42. package/src/components/app-shell/document-title-handler.tsx +11 -20
  43. package/src/components/app-shell/header.tsx +18 -3
  44. package/src/components/app-shell/loading-overlay.tsx +2 -4
  45. package/src/components/app-shell/loading-state.tsx +15 -0
  46. package/src/components/app-shell/sidebar.tsx +75 -47
  47. package/src/components/data-table/data-table-pagination.tsx +27 -7
  48. package/src/components/data-table/data-table-styles.ts +57 -0
  49. package/src/components/data-table/data-table.tsx +14 -59
  50. package/src/components/resources/resource-label.ts +101 -0
  51. package/src/components/resources/views/create-view.tsx +17 -14
  52. package/src/components/resources/views/edit-view.tsx +17 -15
  53. package/src/components/resources/views/list-view.tsx +20 -10
  54. package/src/components/resources/views/show-view.tsx +20 -18
  55. package/src/extensions/nocobase-acl/README.md +19 -0
  56. package/src/extensions/nocobase-acl/components/acl-boundary.tsx +106 -0
  57. package/src/extensions/nocobase-acl/components/index.ts +3 -0
  58. package/src/extensions/nocobase-acl/components/role-options.ts +36 -0
  59. package/src/extensions/nocobase-acl/components/role-switcher.tsx +135 -0
  60. package/src/extensions/nocobase-acl/demo/boundary-api.tsx +186 -0
  61. package/src/extensions/nocobase-acl/demo/components.tsx +231 -0
  62. package/src/extensions/nocobase-acl/demo/index.tsx +505 -0
  63. package/src/extensions/nocobase-acl/demo/lazy-route.tsx +10 -0
  64. package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +134 -0
  65. package/src/extensions/nocobase-acl/demo/role-switcher-prompt-generator.tsx +198 -0
  66. package/src/extensions/nocobase-acl/demo/scenario-section.tsx +37 -0
  67. package/src/extensions/nocobase-acl/extension.tsx +71 -0
  68. package/src/extensions/nocobase-acl/index.ts +1 -0
  69. package/src/extensions/nocobase-ai/README.md +70 -0
  70. package/src/extensions/nocobase-ai/adapters/react-hook-form.ts +20 -0
  71. package/src/extensions/nocobase-ai/assets/nocobase-ai-chat.svg +50 -0
  72. package/src/extensions/nocobase-ai/components/ai-root-provider.tsx +32 -0
  73. package/src/extensions/nocobase-ai/components/chat/ai-employee-avatar.tsx +31 -0
  74. package/src/extensions/nocobase-ai/components/chat/chat-attachment.tsx +92 -0
  75. package/src/extensions/nocobase-ai/components/chat/chat-compact.tsx +90 -0
  76. package/src/extensions/nocobase-ai/components/chat/chat-composer.tsx +429 -0
  77. package/src/extensions/nocobase-ai/components/chat/chat-empty-state.tsx +51 -0
  78. package/src/extensions/nocobase-ai/components/chat/chat-header.tsx +88 -0
  79. package/src/extensions/nocobase-ai/components/chat/chat-history-dialog.tsx +77 -0
  80. package/src/extensions/nocobase-ai/components/chat/chat-message.tsx +252 -0
  81. package/src/extensions/nocobase-ai/components/chat/chat-messages.tsx +173 -0
  82. package/src/extensions/nocobase-ai/components/chat/chat-window.tsx +118 -0
  83. package/src/extensions/nocobase-ai/components/chat/conversation-list.tsx +343 -0
  84. package/src/extensions/nocobase-ai/components/chat/markdown-message.tsx +165 -0
  85. package/src/extensions/nocobase-ai/components/chat/model-select-options.tsx +30 -0
  86. package/src/extensions/nocobase-ai/components/chat/reasoning-panel.tsx +29 -0
  87. package/src/extensions/nocobase-ai/components/chat/sub-agent-conversation.tsx +142 -0
  88. package/src/extensions/nocobase-ai/components/chat/tool-call-card.tsx +316 -0
  89. package/src/extensions/nocobase-ai/components/chat/user-prompt-editor.tsx +99 -0
  90. package/src/extensions/nocobase-ai/components/chat/work-context-chip.tsx +114 -0
  91. package/src/extensions/nocobase-ai/components/index.ts +59 -0
  92. package/src/extensions/nocobase-ai/components/page-elements/ai-form.tsx +44 -0
  93. package/src/extensions/nocobase-ai/components/page-elements/page-element-provider.tsx +469 -0
  94. package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +31 -0
  95. package/src/extensions/nocobase-ai/components/surfaces/chat-inline.tsx +18 -0
  96. package/src/extensions/nocobase-ai/components/surfaces/chat-page.tsx +18 -0
  97. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +54 -0
  98. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +74 -0
  99. package/src/extensions/nocobase-ai/components/surfaces/chat-surface-actions.tsx +54 -0
  100. package/src/extensions/nocobase-ai/components/tools/builtin-tool-renderers.tsx +31 -0
  101. package/src/extensions/nocobase-ai/components/tools/business-report-dialog.tsx +364 -0
  102. package/src/extensions/nocobase-ai/components/tools/business-report-renderer.tsx +122 -0
  103. package/src/extensions/nocobase-ai/components/tools/business-report-utils.tsx +282 -0
  104. package/src/extensions/nocobase-ai/components/tools/chart-renderer.tsx +99 -0
  105. package/src/extensions/nocobase-ai/components/tools/echarts-preview.tsx +59 -0
  106. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-advanced.ts +18 -0
  107. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-common.ts +20 -0
  108. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-components.ts +34 -0
  109. package/src/extensions/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +24 -0
  110. package/src/extensions/nocobase-ai/components/tools/echarts-runtime.ts +138 -0
  111. package/src/extensions/nocobase-ai/components/tools/sub-agent-renderer.tsx +67 -0
  112. package/src/extensions/nocobase-ai/components/tools/suggestions-renderer.tsx +59 -0
  113. package/src/extensions/nocobase-ai/components/tools/tool-renderer-provider.tsx +55 -0
  114. package/src/extensions/nocobase-ai/components/tools/tool-renderer-utils.ts +18 -0
  115. package/src/extensions/nocobase-ai/components/tools/workflow-renderer.tsx +108 -0
  116. package/src/extensions/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +73 -0
  117. package/src/extensions/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +149 -0
  118. package/src/extensions/nocobase-ai/demo/configuration-gate.tsx +49 -0
  119. package/src/extensions/nocobase-ai/demo/container-showcase.tsx +210 -0
  120. package/src/extensions/nocobase-ai/demo/floating.tsx +134 -0
  121. package/src/extensions/nocobase-ai/demo/index.tsx +346 -0
  122. package/src/extensions/nocobase-ai/demo/interaction-showcase.tsx +151 -0
  123. package/src/extensions/nocobase-ai/demo/page-context-prompt-generator.tsx +355 -0
  124. package/src/extensions/nocobase-ai/demo/page-context.tsx +884 -0
  125. package/src/extensions/nocobase-ai/demo/page-element-showcase.tsx +197 -0
  126. package/src/extensions/nocobase-ai/demo/prompt-card.tsx +41 -0
  127. package/src/extensions/nocobase-ai/demo/prompt-generator.tsx +452 -0
  128. package/src/extensions/nocobase-ai/demo/shortcut.tsx +1207 -0
  129. package/src/extensions/nocobase-ai/demo/tool-cards.tsx +530 -0
  130. package/src/extensions/nocobase-ai/extension.tsx +158 -0
  131. package/src/extensions/nocobase-ai/global-ai-chat.tsx +213 -0
  132. package/src/extensions/nocobase-ai/index.ts +3 -0
  133. package/src/extensions/nocobase-ai/providers/ai-provider.tsx +323 -0
  134. package/src/extensions/nocobase-ai/providers/avatars.ts +817 -0
  135. package/src/extensions/nocobase-ai/providers/chat-context.tsx +109 -0
  136. package/src/extensions/nocobase-ai/providers/chat-controller.ts +84 -0
  137. package/src/extensions/nocobase-ai/providers/chat-message-utils.ts +198 -0
  138. package/src/extensions/nocobase-ai/providers/chat-provider.tsx +871 -0
  139. package/src/extensions/nocobase-ai/providers/chat-reducer.ts +147 -0
  140. package/src/extensions/nocobase-ai/providers/chat-task-utils.ts +105 -0
  141. package/src/extensions/nocobase-ai/providers/chat-transport.ts +184 -0
  142. package/src/extensions/nocobase-ai/providers/form-registry.tsx +271 -0
  143. package/src/extensions/nocobase-ai/providers/frontend-tool-registry.tsx +231 -0
  144. package/src/extensions/nocobase-ai/providers/index.ts +61 -0
  145. package/src/extensions/nocobase-ai/providers/model.ts +32 -0
  146. package/src/extensions/nocobase-ai/providers/page-context.tsx +136 -0
  147. package/src/extensions/nocobase-ai/providers/stream-coalescer.ts +60 -0
  148. package/src/extensions/nocobase-ai/providers/stream-event-utils.ts +82 -0
  149. package/src/extensions/nocobase-ai/providers/stream-parser.ts +61 -0
  150. package/src/extensions/nocobase-ai/providers/sub-agent-stream.ts +316 -0
  151. package/src/extensions/nocobase-ai/providers/types.ts +183 -0
  152. package/src/extensions/nocobase-ai/providers/ui-message-stream.ts +463 -0
  153. package/src/extensions/nocobase-ai/providers/use-automatic-tool-approval.ts +94 -0
  154. package/src/extensions/nocobase-ai/providers/use-chat-attachments.ts +206 -0
  155. package/src/extensions/nocobase-ai/providers/use-chat-message-actions.ts +554 -0
  156. package/src/extensions/nocobase-ai/providers/use-chat-runtime.ts +210 -0
  157. package/src/extensions/nocobase-ai/providers/use-chat-work-context.ts +93 -0
  158. package/src/extensions/nocobase-ai/providers/use-conversation-catalog.ts +108 -0
  159. package/src/extensions/nocobase-ai/providers/use-conversation-history.ts +178 -0
  160. package/src/extensions/nocobase-ai/services/index.ts +7 -0
  161. package/src/extensions/nocobase-ai/services/nocobase-ai-service.ts +454 -0
  162. package/src/extensions/nocobase-ai/services/types.ts +82 -0
  163. package/src/index.tsx +2 -3
  164. package/src/lib/i18n.ts +161 -0
  165. package/src/lib/nocobase/client.ts +19 -2
  166. package/src/locales/en-US.ts +82 -0
  167. package/src/locales/index.ts +13 -0
  168. package/src/locales/zh-CN.ts +79 -0
  169. package/src/pages/users/create.tsx +98 -0
  170. package/src/pages/users/edit.tsx +92 -0
  171. package/src/pages/users/form-context.ts +57 -0
  172. package/src/pages/users/form-fields.tsx +211 -0
  173. package/src/pages/users/list.tsx +205 -0
  174. package/src/pages/users/show.tsx +187 -0
  175. package/src/pages/users/types.ts +20 -0
  176. package/src/providers/constants.ts +1 -0
  177. package/src/pages/blog-posts/create.tsx +0 -177
  178. package/src/pages/blog-posts/edit.tsx +0 -197
  179. package/src/pages/blog-posts/list.tsx +0 -131
  180. package/src/pages/blog-posts/show.tsx +0 -81
  181. package/src/pages/categories/create.tsx +0 -73
  182. package/src/pages/categories/edit.tsx +0 -73
  183. package/src/pages/categories/index.ts +0 -4
  184. package/src/pages/categories/list.tsx +0 -86
  185. package/src/pages/categories/show.tsx +0 -37
  186. /package/src/pages/{blog-posts → users}/index.ts +0 -0
package/README.MD CHANGED
@@ -14,7 +14,15 @@ NOCOBASE_API_URL=https://develop.v2.test.nocobase.com/nocobase/api/__app/cms
14
14
 
15
15
  In development, the app auto-detects the proxy target from `NOCOBASE_API_URL` when that value is an absolute URL. The configured API URL can stay on the original sub-app path such as `/nocobase/api/__app/cms`.
16
16
 
17
- Refine resource names must match NocoBase collection names. For example, the supplied Swagger document exposes `collectionCategories`, so use that name when registering a corresponding Refine resource.
17
+ Application resource names must match NocoBase collection names. The included
18
+ Users example targets the default `users` collection, so it works without a
19
+ project-specific business collection.
20
+
21
+ Application-owned React translations live in `src/locales`. The entry registers
22
+ an `app` namespace for business pages and augments the default `starter`
23
+ namespace for shared controls. Feature-specific Registry translations remain
24
+ inside their own extension namespace and are not mixed into application locale
25
+ files.
18
26
 
19
27
  ## Available Scripts
20
28
 
@@ -48,6 +56,8 @@ Shadcn Registry item fields, each item has a local `source` mapping used to
48
56
  generate its standard `files` array. The same mapping lets the starter's
49
57
  `postinstall` hook copy a locally authored extension only when its target is
50
58
  missing. Existing installed source is never compared, updated, or overwritten.
59
+ Optional entries can set `source.install` to `false`; they remain available to
60
+ Registry builds and local previews but are not added by `postinstall`.
51
61
 
52
62
  You can run the same missing-only installation explicitly with:
53
63
 
@@ -55,7 +65,16 @@ You can run the same missing-only installation explicitly with:
55
65
  pnpm registry:install
56
66
  ```
57
67
 
58
- This repository currently authors four incremental NocoBase AI entries:
68
+ This repository authors optional NocoBase integration entries. Internationalization
69
+ is delivered as one cohesive Registry entry:
70
+
71
+ - `i18n` provides frontend-owned UI resources, selected server-generated
72
+ namespaces such as `lm-collections`, the application translation provider,
73
+ locale persistence, `X-Locale` synchronization, legacy `{{t(...)}}`
74
+ compatibility, the language switcher, and the lazy-loaded showcase with its
75
+ prompt generator.
76
+
77
+ The AI integration is available through four incremental entries:
59
78
 
60
79
  - `ai-runtime` provides AI services, providers, transports, and
61
80
  controllers. Its NocoBase requests use the starter's shared
@@ -64,7 +83,7 @@ This repository currently authors four incremental NocoBase AI entries:
64
83
  runtime entry.
65
84
  - `ai-react-hook-form` adds the optional React Hook Form adapter for the fixed
66
85
  Form filler Tool.
67
- - `ai` adds the Refine extension integration and Demo pages and depends on the
86
+ - `ai` adds the application extension integration and Demo pages and depends on the
68
87
  chat entry.
69
88
 
70
89
  To refresh the local preview from the canonical Registry source, run:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/portal-template-default",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "devDependencies": {
6
6
  "@ai-sdk/react": "2.0.0",
@@ -29,8 +29,8 @@
29
29
  "echarts-for-react": "3.0.2",
30
30
  "embla-carousel-react": "^8.6.0",
31
31
  "eventsource-parser": "3.1.0",
32
- "input-otp": "^1.4.2",
33
32
  "ignore": "^7.0.6",
33
+ "input-otp": "^1.4.2",
34
34
  "lucide-react": "^0.487.0",
35
35
  "next-themes": "^0.4.6",
36
36
  "postcss": "^8.5.3",
@@ -60,6 +60,7 @@
60
60
  "eslint-plugin-react-hooks": "^5.2.0",
61
61
  "eslint-plugin-react-refresh": "^0.4.19",
62
62
  "globals": "^16.0.0",
63
+ "i18next": "^25.3.2",
63
64
  "tailwindcss": "^4.1.4",
64
65
  "typescript": "^5.8.3",
65
66
  "typescript-eslint": "^8.30.1",
@@ -80,6 +81,7 @@
80
81
  "registry:preview": "node ./scripts/registry.mjs preview",
81
82
  "registry:build": "node ./scripts/registry.mjs build && shadcn build ./registry.json -o ./public/r",
82
83
  "test:acl": "node registry/nocobase-acl/tests/acl-regression.mjs",
84
+ "test:i18n": "node registry/nocobase-i18n/tests/i18n-regression.mjs",
83
85
  "start": "refine start",
84
86
  "refine": "refine"
85
87
  },
@@ -1,8 +1,15 @@
1
1
  import type { Role, RoleMode } from "@/lib/nocobase/acl";
2
+ import { resolveTranslatableText } from "@/lib/i18n";
2
3
 
3
4
  export const UNION_ROLE = "__union__";
4
5
  export const ANONYMOUS_ROLE = "anonymous";
5
6
 
7
+ export function resolveRoleTitle(role?: Role) {
8
+ return resolveTranslatableText(role?.title || role?.name || "Role", {
9
+ ns: "starter",
10
+ });
11
+ }
12
+
6
13
  export function getRoleOptions({
7
14
  roles,
8
15
  roleMode,
@@ -18,7 +18,7 @@ import {
18
18
  type Role,
19
19
  } from "@/lib/nocobase/acl";
20
20
  import { cn } from "@/lib/utils";
21
- import { getRoleOptions, UNION_ROLE } from "./role-options";
21
+ import { getRoleOptions, resolveRoleTitle, UNION_ROLE } from "./role-options";
22
22
 
23
23
  export type RoleSwitcherProps = {
24
24
  className?: string;
@@ -82,7 +82,9 @@ export function RoleSwitcher({
82
82
  .then(() => window.location.reload())
83
83
  .catch((reason) => {
84
84
  setError(
85
- reason instanceof Error ? reason.message : "Unable to switch role"
85
+ reason instanceof Error
86
+ ? reason.message
87
+ : "Unable to switch role"
86
88
  );
87
89
  setSwitching(false);
88
90
  });
@@ -120,11 +122,14 @@ function RoleOption({
120
122
  return (
121
123
  <>
122
124
  {showSeparator ? <SelectSeparator /> : null}
123
- <SelectItem value={role.name}>{role.title || role.name}</SelectItem>
125
+ <SelectItem value={role.name}>{resolveRoleTitle(role)}</SelectItem>
124
126
  </>
125
127
  );
126
128
  }
127
129
 
128
130
  function getRoleTitle(roles: Role[], roleName?: string) {
129
- return roles.find((role) => role.name === roleName)?.title || roleName || "Role";
131
+ return resolveRoleTitle(
132
+ roles.find((role) => role.name === roleName) ??
133
+ (roleName ? { name: roleName } : undefined)
134
+ );
130
135
  }
@@ -8,30 +8,95 @@ import {
8
8
  TableRow,
9
9
  } from "@/components/ui/table";
10
10
 
11
- type PropRow = [name: string, type: string, defaultValue: string, description: string];
11
+ type PropRow = [
12
+ name: string,
13
+ type: string,
14
+ defaultValue: string,
15
+ description: string
16
+ ];
12
17
 
13
18
  const pageProps: PropRow[] = [
14
- ["anyOf", "AclPermission[]", "undefined", "Allows the page when any listed permission is available."],
15
- ["allOf", "AclPermission[]", "undefined", "Requires every listed permission before rendering the page."],
16
- ["fallback", "ReactNode", "AccessDenied", "Replaces the complete page when its permission test fails."],
19
+ [
20
+ "anyOf",
21
+ "AclPermission[]",
22
+ "undefined",
23
+ "Allows the page when any listed permission is available.",
24
+ ],
25
+ [
26
+ "allOf",
27
+ "AclPermission[]",
28
+ "undefined",
29
+ "Requires every listed permission before rendering the page.",
30
+ ],
31
+ [
32
+ "fallback",
33
+ "ReactNode",
34
+ "AccessDenied",
35
+ "Replaces the complete page when its permission test fails.",
36
+ ],
17
37
  ["children", "ReactNode", "required", "The protected page content."],
18
38
  ];
19
39
 
20
40
  const regionProps: PropRow[] = [
21
- ["resource", "string", "required", "NocoBase collection or ACL resource name."],
22
- ["action", "string", "required", "Refine action such as list, show, create, edit, or delete."],
23
- ["id", "BaseKey", "undefined", "Optional record ID for record-scoped permission checks."],
24
- ["dataSourceKey", "string", "undefined", "Selects a non-main NocoBase data source."],
25
- ["fallback", '"hidden" | "forbidden" | ReactNode', '"hidden"', "Hides the region or replaces it with a local denied state."],
41
+ [
42
+ "resource",
43
+ "string",
44
+ "required",
45
+ "NocoBase collection or ACL resource name.",
46
+ ],
47
+ [
48
+ "action",
49
+ "string",
50
+ "required",
51
+ "Resource action such as list, show, create, edit, or delete.",
52
+ ],
53
+ [
54
+ "id",
55
+ "BaseKey",
56
+ "undefined",
57
+ "Optional record ID for record-scoped permission checks.",
58
+ ],
59
+ [
60
+ "dataSourceKey",
61
+ "string",
62
+ "undefined",
63
+ "Selects a non-main NocoBase data source.",
64
+ ],
65
+ [
66
+ "fallback",
67
+ '"hidden" | "forbidden" | ReactNode',
68
+ '"hidden"',
69
+ "Hides the region or replaces it with a local denied state.",
70
+ ],
26
71
  ["children", "ReactNode", "required", "The protected page region."],
27
72
  ];
28
73
 
29
74
  const fieldProps: PropRow[] = [
30
- ["resource", "string", "required", "NocoBase collection containing the field."],
31
- ["action", "string", "required", "The action whose field whitelist should be checked."],
75
+ [
76
+ "resource",
77
+ "string",
78
+ "required",
79
+ "NocoBase collection containing the field.",
80
+ ],
81
+ [
82
+ "action",
83
+ "string",
84
+ "required",
85
+ "The action whose field whitelist should be checked.",
86
+ ],
32
87
  ["field", "string", "required", "Exact NocoBase field name."],
33
- ["dataSourceKey", "string", "undefined", "Selects a non-main NocoBase data source."],
34
- ["fallback", "ReactNode", "null", "Optional read-only replacement for a denied field."],
88
+ [
89
+ "dataSourceKey",
90
+ "string",
91
+ "undefined",
92
+ "Selects a non-main NocoBase data source.",
93
+ ],
94
+ [
95
+ "fallback",
96
+ "ReactNode",
97
+ "null",
98
+ "Optional read-only replacement for a denied field.",
99
+ ],
35
100
  ["children", "ReactNode", "required", "The protected field or field group."],
36
101
  ];
37
102
 
@@ -99,9 +164,15 @@ function ComponentApi({
99
164
  <TableBody>
100
165
  {rows.map(([name, type, defaultValue, propDescription]) => (
101
166
  <TableRow key={name}>
102
- <TableCell className="font-mono text-xs font-medium">{name}</TableCell>
103
- <TableCell className="font-mono text-xs text-muted-foreground">{type}</TableCell>
104
- <TableCell className="font-mono text-xs text-muted-foreground">{defaultValue}</TableCell>
167
+ <TableCell className="font-mono text-xs font-medium">
168
+ {name}
169
+ </TableCell>
170
+ <TableCell className="font-mono text-xs text-muted-foreground">
171
+ {type}
172
+ </TableCell>
173
+ <TableCell className="font-mono text-xs text-muted-foreground">
174
+ {defaultValue}
175
+ </TableCell>
105
176
  <TableCell className="min-w-72 whitespace-normal text-muted-foreground">
106
177
  {propDescription}
107
178
  </TableCell>
@@ -19,7 +19,12 @@ import {
19
19
  import { RoleSwitcher } from "../components";
20
20
  import { RoleSwitcherPromptGenerator } from "./role-switcher-prompt-generator";
21
21
 
22
- type PropRow = [name: string, type: string, defaultValue: string, description: string];
22
+ type PropRow = [
23
+ name: string,
24
+ type: string,
25
+ defaultValue: string,
26
+ description: string
27
+ ];
23
28
 
24
29
  const roleSwitcherProps: PropRow[] = [
25
30
  [
@@ -49,11 +54,20 @@ const roleSwitcherProps: PropRow[] = [
49
54
  ];
50
55
 
51
56
  const roleBehaviors = [
52
- ["Multiple assigned roles", "Renders a Select using roles from the current Refine identity."],
57
+ [
58
+ "Multiple assigned roles",
59
+ "Renders a Select using roles from the signed-in identity.",
60
+ ],
53
61
  ["allow-use-union", "Prepends Full permissions using the __union__ role."],
54
- ["only-use-union", "Shows Full permissions as the current non-interactive role."],
62
+ [
63
+ "only-use-union",
64
+ "Shows Full permissions as the current non-interactive role.",
65
+ ],
55
66
  ["allowAnonymous", "Appends Anonymous to the available role options."],
56
- ["One available role", "Returns null, or shows the current-role badge when showWhenUnavailable is true."],
67
+ [
68
+ "One available role",
69
+ "Returns null, or shows the current-role badge when showWhenUnavailable is true.",
70
+ ],
57
71
  ];
58
72
 
59
73
  export function AclComponentsPage() {
@@ -64,8 +78,8 @@ export function AclComponentsPage() {
64
78
  Role switcher
65
79
  </h1>
66
80
  <p className="max-w-3xl text-sm leading-6 text-muted-foreground">
67
- A reusable role selector backed by the Starter&apos;s NocoBase ACL store
68
- and current Refine identity.
81
+ A reusable role selector backed by the Starter&apos;s NocoBase ACL
82
+ store and signed-in identity.
69
83
  </p>
70
84
  </header>
71
85
 
@@ -146,14 +160,15 @@ export function AclComponentsPage() {
146
160
  {roleBehaviors.map(([condition, behavior]) => (
147
161
  <TableRow key={condition}>
148
162
  <TableCell className="font-medium">{condition}</TableCell>
149
- <TableCell className="text-muted-foreground">{behavior}</TableCell>
163
+ <TableCell className="text-muted-foreground">
164
+ {behavior}
165
+ </TableCell>
150
166
  </TableRow>
151
167
  ))}
152
168
  </TableBody>
153
169
  </Table>
154
170
  </Card>
155
171
  </section>
156
-
157
172
  </div>
158
173
  );
159
174
  }
@@ -173,9 +188,15 @@ function PropsTable({ rows }: { rows: PropRow[] }) {
173
188
  <TableBody>
174
189
  {rows.map(([name, type, defaultValue, description]) => (
175
190
  <TableRow key={name}>
176
- <TableCell className="font-mono text-xs font-medium">{name}</TableCell>
177
- <TableCell className="font-mono text-xs text-muted-foreground">{type}</TableCell>
178
- <TableCell className="font-mono text-xs text-muted-foreground">{defaultValue}</TableCell>
191
+ <TableCell className="font-mono text-xs font-medium">
192
+ {name}
193
+ </TableCell>
194
+ <TableCell className="font-mono text-xs text-muted-foreground">
195
+ {type}
196
+ </TableCell>
197
+ <TableCell className="font-mono text-xs text-muted-foreground">
198
+ {defaultValue}
199
+ </TableCell>
179
200
  <TableCell className="min-w-72 whitespace-normal text-muted-foreground">
180
201
  {description}
181
202
  </TableCell>
@@ -70,7 +70,7 @@ export function AclPatternsPage() {
70
70
  prompt={{
71
71
  title: "Resource navigation",
72
72
  description:
73
- "Generate Refine resources, sidebar behavior, and guarded routes backed by NocoBase collections.",
73
+ "Generate application resources, sidebar behavior, and guarded routes backed by NocoBase collections.",
74
74
  defaultScene: "User and role administration",
75
75
  defaultTarget:
76
76
  "Allow the Users resource but remove Roles from navigation and block its direct URL.",
@@ -98,7 +98,8 @@ export function AclPatternsPage() {
98
98
  description:
99
99
  "Generate a complete page whose availability depends on NocoBase permissions.",
100
100
  defaultScene: "User directory and account administration page",
101
- defaultTarget: "Require users:list before rendering the complete page.",
101
+ defaultTarget:
102
+ "Require users:list before rendering the complete page.",
102
103
  requirements: `- Wrap the complete business page with AclPage.
103
104
  - Use anyOf when one accessible collection is enough, or allOf when every permission is required.
104
105
  - Provide a useful page-level fallback instead of rendering an empty screen.
@@ -151,7 +152,7 @@ export function AclPatternsPage() {
151
152
  defaultTarget:
152
153
  "Allow all actions for one user record and show a view-only row when update and delete are denied.",
153
154
  requirements: `- Use the Starter dataProvider so X-With-ACL-Meta captures NocoBase allowedActions from list, get, and getMany responses.
154
- - Use Refine Create, Show, Edit, and Delete buttons; do not manually compare user IDs or role names.
155
+ - Use the built-in Create, Show, Edit, and Delete buttons; do not manually compare user IDs or role names.
155
156
  - Collection-level denial hides the action everywhere.
156
157
  - Record-level allowedActions must hide or disable only the affected row action.
157
158
  - Keep 403 responses authenticated and let NocoBase perform the final action validation.`,
@@ -177,7 +178,7 @@ export function AclPatternsPage() {
177
178
  defaultTarget:
178
179
  "Allow nickname editing, make email read-only, and hide phone when those fields are denied.",
179
180
  requirements: `- Wrap each protected field or field group with AclField.
180
- - Pass the collection resource, Refine action, and exact field name.
181
+ - Pass the collection resource, application action, and exact field name.
181
182
  - Choose whether a denied field is hidden, replaced by read-only output, or shown with a custom fallback.
182
183
  - Do not infer field access from the overall edit permission.
183
184
  - Submit only values the current UI is allowed to edit; NocoBase still performs final field validation.`,
@@ -231,9 +232,13 @@ function NavigationPreview({ showUsers }: { showUsers: boolean }) {
231
232
  return (
232
233
  <div className="grid min-h-48 grid-cols-[150px_minmax(0,1fr)] overflow-hidden rounded-xl border bg-background">
233
234
  <aside className="border-r bg-muted/35 p-3">
234
- <div className="mb-3 text-xs font-semibold text-muted-foreground">Workspace</div>
235
+ <div className="mb-3 text-xs font-semibold text-muted-foreground">
236
+ Workspace
237
+ </div>
235
238
  <NavItem icon={<LayoutPanelTop />} label="Dashboard" />
236
- {showUsers ? <NavItem icon={<UsersRound />} label="Users" active /> : null}
239
+ {showUsers ? (
240
+ <NavItem icon={<UsersRound />} label="Users" active />
241
+ ) : null}
237
242
  <NavItem icon={<UserRoundCog />} label="Roles" />
238
243
  </aside>
239
244
  <div className="flex items-center justify-center p-5 text-center">
@@ -241,7 +246,9 @@ function NavigationPreview({ showUsers }: { showUsers: boolean }) {
241
246
  <div>
242
247
  <UsersRound className="mx-auto size-7 text-primary" />
243
248
  <div className="mt-2 font-medium">Users</div>
244
- <div className="mt-1 text-xs text-muted-foreground">Route is available</div>
249
+ <div className="mt-1 text-xs text-muted-foreground">
250
+ Route is available
251
+ </div>
245
252
  </div>
246
253
  ) : (
247
254
  <div>
@@ -372,15 +379,27 @@ function ActionsPreview({ allowed }: { allowed: boolean }) {
372
379
  <td className="py-3 text-muted-foreground">{user.role}</td>
373
380
  <td className="py-3">
374
381
  <div className="flex justify-end gap-1">
375
- <Button variant="ghost" size="icon-sm" aria-label="View user">
382
+ <Button
383
+ variant="ghost"
384
+ size="icon-sm"
385
+ aria-label="View user"
386
+ >
376
387
  <Eye />
377
388
  </Button>
378
389
  {allowed ? (
379
390
  <>
380
- <Button variant="ghost" size="icon-sm" aria-label="Edit user">
391
+ <Button
392
+ variant="ghost"
393
+ size="icon-sm"
394
+ aria-label="Edit user"
395
+ >
381
396
  <Pencil />
382
397
  </Button>
383
- <Button variant="ghost" size="icon-sm" aria-label="Delete user">
398
+ <Button
399
+ variant="ghost"
400
+ size="icon-sm"
401
+ aria-label="Delete user"
402
+ >
384
403
  <Trash2 />
385
404
  </Button>
386
405
  </>
@@ -412,7 +431,9 @@ function ProfileFormPreview({ restricted }: { restricted: boolean }) {
412
431
  {restricted ? (
413
432
  <div className="rounded-md border bg-muted/45 px-3 py-2 text-sm text-muted-foreground">
414
433
  {user.email}
415
- <Badge className="ml-2" variant="outline">Read only</Badge>
434
+ <Badge className="ml-2" variant="outline">
435
+ Read only
436
+ </Badge>
416
437
  </div>
417
438
  ) : (
418
439
  <Input defaultValue={user.email} />
@@ -429,7 +450,13 @@ function ProfileFormPreview({ restricted }: { restricted: boolean }) {
429
450
  );
430
451
  }
431
452
 
432
- function DemoField({ label, children }: { label: string; children: React.ReactNode }) {
453
+ function DemoField({
454
+ label,
455
+ children,
456
+ }: {
457
+ label: string;
458
+ children: React.ReactNode;
459
+ }) {
433
460
  return (
434
461
  <label className="block space-y-2">
435
462
  <span className="text-xs font-medium">{label}</span>
@@ -453,7 +480,9 @@ function CompactUsersTable({ limit = 3 }: { limit?: number }) {
453
480
  <tr key={user.id} className="border-b last:border-0">
454
481
  <td className="py-3">
455
482
  <div className="font-medium">{user.name}</div>
456
- <div className="text-xs text-muted-foreground">{user.username}</div>
483
+ <div className="text-xs text-muted-foreground">
484
+ {user.username}
485
+ </div>
457
486
  </td>
458
487
  <td className="py-3 text-muted-foreground">{user.email}</td>
459
488
  </tr>
@@ -468,7 +497,9 @@ function RoleRow({ title, name }: { title: string; name: string }) {
468
497
  return (
469
498
  <div className="rounded-lg border px-3 py-2">
470
499
  <div className="text-sm font-medium">{title}</div>
471
- <div className="mt-0.5 font-mono text-xs text-muted-foreground">{name}</div>
500
+ <div className="mt-0.5 font-mono text-xs text-muted-foreground">
501
+ {name}
502
+ </div>
472
503
  </div>
473
504
  );
474
505
  }
@@ -1,14 +1,9 @@
1
1
  import { Suspense, type ReactNode } from "react";
2
+ import { LoadingState } from "@/components/app-shell/loading-state";
2
3
 
3
4
  export function LazyAclRoute({ children }: { children: ReactNode }) {
4
5
  return (
5
- <Suspense
6
- fallback={
7
- <div className="flex min-h-80 items-center justify-center text-sm text-muted-foreground">
8
- Loading access control demo…
9
- </div>
10
- }
11
- >
6
+ <Suspense fallback={<LoadingState className="min-h-80" />}>
12
7
  {children}
13
8
  </Suspense>
14
9
  );
@@ -1,7 +1,14 @@
1
- import { Check, Copy, Sparkles } from "lucide-react";
1
+ import { Check, ChevronDown, Copy, Sparkles } from "lucide-react";
2
2
  import { useMemo, useState } from "react";
3
3
 
4
4
  import { Button } from "@/components/ui/button";
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from "@/components/ui/card";
5
12
  import { Input } from "@/components/ui/input";
6
13
  import { Label } from "@/components/ui/label";
7
14
  import { Textarea } from "@/components/ui/textarea";
@@ -25,7 +32,7 @@ export function AclScenarioPromptGenerator({
25
32
  const [copyError, setCopyError] = useState(false);
26
33
 
27
34
  const prompt = useMemo(
28
- () => `Build a complete NocoBase ACL scene in the Refine Starter.
35
+ () => `Build a complete NocoBase ACL scene in the NocoBase Admin Starter.
29
36
 
30
37
  Business scene
31
38
  - Page or feature: ${scene}
@@ -41,7 +48,7 @@ Implementation contract
41
48
  - NocoBase roles:check is the source of truth. Do not create a second permission store or hard-code role names.
42
49
  - Keep backend ACL enforcement in place; frontend checks only control presentation and navigation.
43
50
  - Import reusable components from the installed local entry point at @/extensions/nocobase-acl when page composition is needed.
44
- - Resource actions use Refine names: list, show, create, edit, delete. Let the Starter map them to NocoBase list/get/create/update/destroy.
51
+ - Resource actions use the application names list, show, create, edit, and delete. Let the Starter map them to NocoBase list/get/create/update/destroy.
45
52
  - Preserve dataSourceKey when the collection belongs to a non-main data source.
46
53
  - Show an understandable forbidden or hidden state where appropriate.
47
54
  - Deliver complete React component code, resource metadata, route integration, and the visible sample UI.`,
@@ -56,33 +63,44 @@ Implementation contract
56
63
  <span className="ml-auto text-xs font-normal text-muted-foreground">
57
64
  {config.title}
58
65
  </span>
66
+ <ChevronDown className="size-4 text-muted-foreground transition-transform group-open:rotate-180" />
59
67
  </summary>
60
- <div className="grid gap-4 border-t p-4 lg:grid-cols-[minmax(0,320px)_minmax(0,1fr)]">
61
- <div className="space-y-4">
62
- <p className="text-xs leading-5 text-muted-foreground">
63
- {config.description}
64
- </p>
65
- <div className="space-y-2">
66
- <Label htmlFor={`${config.title}-scene`}>Business scene</Label>
67
- <Input
68
- id={`${config.title}-scene`}
69
- value={scene}
70
- onChange={(event) => setScene(event.target.value)}
71
- />
72
- </div>
73
- <div className="space-y-2">
74
- <Label htmlFor={`${config.title}-target`}>Permission target</Label>
75
- <Textarea
76
- id={`${config.title}-target`}
77
- value={target}
78
- onChange={(event) => setTarget(event.target.value)}
79
- className="min-h-24"
80
- />
81
- </div>
82
- </div>
83
- <div className="min-w-0 overflow-hidden rounded-lg border bg-background">
84
- <div className="flex items-center justify-between gap-3 border-b px-3 py-2">
85
- <span className="text-xs font-medium">Generated prompt</span>
68
+ <div className="grid items-start gap-5 border-t p-4 xl:grid-cols-[360px_minmax(0,1fr)]">
69
+ <Card className="gap-0 py-0">
70
+ <CardHeader className="border-b py-4">
71
+ <CardTitle className="text-base">Scenario settings</CardTitle>
72
+ <CardDescription>{config.description}</CardDescription>
73
+ </CardHeader>
74
+ <CardContent className="space-y-5 py-5">
75
+ <div className="space-y-2">
76
+ <Label htmlFor={`${config.title}-scene`}>Business scene</Label>
77
+ <Input
78
+ id={`${config.title}-scene`}
79
+ value={scene}
80
+ onChange={(event) => setScene(event.target.value)}
81
+ />
82
+ </div>
83
+ <div className="space-y-2">
84
+ <Label htmlFor={`${config.title}-target`}>
85
+ Permission target
86
+ </Label>
87
+ <Textarea
88
+ id={`${config.title}-target`}
89
+ value={target}
90
+ onChange={(event) => setTarget(event.target.value)}
91
+ className="min-h-24"
92
+ />
93
+ </div>
94
+ </CardContent>
95
+ </Card>
96
+ <Card className="min-w-0 gap-0 overflow-hidden py-0">
97
+ <div className="flex items-center justify-between gap-3 border-b px-4 py-3">
98
+ <div>
99
+ <div className="text-sm font-medium">Generated prompt</div>
100
+ <div className="text-xs text-muted-foreground">
101
+ Updates as you change the business scene and permission target.
102
+ </div>
103
+ </div>
86
104
  <Button
87
105
  size="sm"
88
106
  onClick={async () => {
@@ -101,14 +119,15 @@ Implementation contract
101
119
  </Button>
102
120
  </div>
103
121
  {copyError ? (
104
- <p className="border-b px-3 py-2 text-xs text-destructive">
105
- Clipboard access failed. Select the prompt text and copy it manually.
122
+ <p className="border-b px-4 py-2 text-xs text-destructive">
123
+ Clipboard access failed. Select the prompt text and copy it
124
+ manually.
106
125
  </p>
107
126
  ) : null}
108
- <pre className="max-h-80 overflow-auto whitespace-pre-wrap p-4 font-mono text-xs leading-5 text-muted-foreground">
127
+ <pre className="max-h-[520px] overflow-auto whitespace-pre-wrap bg-muted/25 p-5 font-mono text-xs leading-5 text-muted-foreground">
109
128
  {prompt}
110
129
  </pre>
111
- </div>
130
+ </Card>
112
131
  </div>
113
132
  </details>
114
133
  );