@nocobase/portal-template-default 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/.env.example +5 -0
  2. package/.github/workflows/publish.yml +106 -0
  3. package/.gitignore +33 -0
  4. package/AGENTS.md +19 -0
  5. package/README.MD +61 -180
  6. package/package.json +3 -5
  7. package/scripts/build-html.mjs +12 -0
  8. package/src/App.css +2 -0
  9. package/src/App.tsx +35 -186
  10. package/src/app/development.tsx +154 -0
  11. package/src/app/extension.ts +5 -0
  12. package/src/app/extensions.tsx +18 -4
  13. package/src/app/routes.tsx +65 -0
  14. package/src/components/access-control/acl-bootstrap.tsx +11 -3
  15. package/src/components/access-control/acl-gate.tsx +7 -6
  16. package/src/components/access-control/can-access.tsx +17 -0
  17. package/src/components/access-control/navigate-to-accessible-resource.tsx +8 -3
  18. package/src/components/app-shell/sidebar.tsx +21 -5
  19. package/src/components/auth/auto-redirect-provider.tsx +2 -1
  20. package/src/extensions/nocobase-acl/README.md +74 -0
  21. package/{registry → src/extensions}/nocobase-acl/components/acl-boundary.tsx +22 -25
  22. package/{registry → src/extensions}/nocobase-acl/components/role-switcher.tsx +26 -23
  23. package/src/extensions/nocobase-acl/demo/acl-preview-provider.tsx +81 -0
  24. package/{registry → src/extensions}/nocobase-acl/demo/boundary-api.tsx +58 -0
  25. package/{registry → src/extensions}/nocobase-acl/demo/index.tsx +243 -105
  26. package/{registry → src/extensions}/nocobase-acl/demo/prompt-generator.tsx +3 -1
  27. package/src/extensions/nocobase-acl/extension.tsx +73 -0
  28. package/{registry → src/extensions}/nocobase-ai/README.md +21 -5
  29. package/{registry → src/extensions}/nocobase-ai/components/chat/chat-composer.tsx +3 -1
  30. package/{registry → src/extensions}/nocobase-ai/components/chat/chat-messages.tsx +38 -3
  31. package/{registry → src/extensions}/nocobase-ai/components/chat/tool-call-card.tsx +1 -9
  32. package/{registry → src/extensions}/nocobase-ai/components/index.ts +5 -0
  33. package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +17 -0
  34. package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +35 -0
  35. package/src/extensions/nocobase-ai/components/surfaces/chat-surface.tsx +108 -0
  36. package/{registry → src/extensions}/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +2 -2
  37. package/{registry → src/extensions}/nocobase-ai/demo/configuration-gate.tsx +4 -1
  38. package/{registry → src/extensions}/nocobase-ai/demo/container-showcase.tsx +2 -2
  39. package/{registry → src/extensions}/nocobase-ai/demo/floating.tsx +45 -15
  40. package/{registry → src/extensions}/nocobase-ai/demo/index.tsx +116 -74
  41. package/{registry → src/extensions}/nocobase-ai/demo/prompt-generator.tsx +3 -3
  42. package/{registry → src/extensions}/nocobase-ai/demo/tool-cards.tsx +2 -3
  43. package/src/extensions/nocobase-ai/extension.tsx +160 -0
  44. package/{registry → src/extensions}/nocobase-ai/global-ai-chat.tsx +12 -28
  45. package/{registry → src/extensions}/nocobase-ai/providers/ai-provider.tsx +14 -1
  46. package/{registry → src/extensions}/nocobase-ai/providers/chat-context.tsx +1 -0
  47. package/{registry → src/extensions}/nocobase-ai/providers/chat-provider.tsx +29 -8
  48. package/{registry → src/extensions}/nocobase-ai/providers/use-chat-attachments.ts +6 -1
  49. package/{registry → src/extensions}/nocobase-ai/providers/use-chat-work-context.ts +4 -1
  50. package/{registry → src/extensions}/nocobase-route-surfaces/README.md +2 -1
  51. package/{registry → src/extensions}/nocobase-route-surfaces/demo/index.tsx +33 -7
  52. package/{registry → src/extensions}/nocobase-route-surfaces/demo/scenarios.ts +20 -14
  53. package/{registry → src/extensions}/nocobase-route-surfaces/extension.tsx +55 -52
  54. package/src/{pages/users → extensions/nocobase-users-example}/create.tsx +4 -4
  55. package/src/{pages/users → extensions/nocobase-users-example}/edit.tsx +5 -9
  56. package/src/extensions/nocobase-users-example/extension.tsx +109 -0
  57. package/src/{pages/users → extensions/nocobase-users-example}/form-context.ts +11 -9
  58. package/src/extensions/nocobase-users-example/layout.tsx +16 -0
  59. package/src/{pages/users → extensions/nocobase-users-example}/list.tsx +1 -1
  60. package/src/extensions/nocobase-users-example/locales/en-US.ts +77 -0
  61. package/src/extensions/nocobase-users-example/locales/index.ts +8 -0
  62. package/src/extensions/nocobase-users-example/locales/zh-CN.ts +73 -0
  63. package/src/extensions/nocobase-users-example/optional-ai.ts +67 -0
  64. package/src/{pages/users → extensions/nocobase-users-example}/role-detail.tsx +4 -15
  65. package/src/{pages/users → extensions/nocobase-users-example}/routes.ts +1 -4
  66. package/src/extensions/nocobase-users-example/show.tsx +266 -0
  67. package/src/lib/nocobase/acl/context.ts +32 -0
  68. package/src/lib/nocobase/acl/evaluator.ts +186 -0
  69. package/src/lib/nocobase/acl/hooks.ts +40 -0
  70. package/src/lib/nocobase/acl/index.ts +4 -1
  71. package/src/lib/nocobase/acl/menu.ts +6 -6
  72. package/src/lib/nocobase/acl/record-permissions.ts +37 -7
  73. package/src/lib/nocobase/acl/store-provider.tsx +14 -0
  74. package/src/lib/nocobase/acl/store.ts +92 -86
  75. package/src/lib/nocobase/acl/types.ts +52 -32
  76. package/src/lib/nocobase/auth-session.ts +128 -0
  77. package/src/lib/nocobase/client.ts +48 -50
  78. package/src/locales/en-US.ts +0 -78
  79. package/src/locales/index.ts +2 -7
  80. package/src/locales/zh-CN.ts +0 -74
  81. package/src/providers/access-control.ts +43 -8
  82. package/src/providers/auth-callback.ts +1 -0
  83. package/src/providers/auth.ts +24 -26
  84. package/src/providers/constants.ts +0 -4
  85. package/src/providers/data.ts +14 -26
  86. package/src/providers/runtime-config.ts +40 -0
  87. package/src/vite-env.d.ts +9 -0
  88. package/tests/auth-session-regression.mjs +101 -0
  89. package/tsconfig.json +2 -1
  90. package/vite.config.ts +9 -1
  91. package/dist/assets/CoordinateSystem-C8P-Cleg.js +0 -1
  92. package/dist/assets/LegendVisualProvider-Bdgkckq-.js +0 -1
  93. package/dist/assets/LineDraw-C0m1GONT.js +0 -1
  94. package/dist/assets/SymbolDraw-CCospe5G.js +0 -1
  95. package/dist/assets/VisualMapping-Cbv1v7Cd.js +0 -1
  96. package/dist/assets/ai-employee-shortcut-B253WWow.js +0 -1
  97. package/dist/assets/calendar-B3dXXyIv.js +0 -1
  98. package/dist/assets/chat-inline-xpQ9iq4p.js +0 -1
  99. package/dist/assets/chat-side-panel-layout-BVWsp5rr.js +0 -1
  100. package/dist/assets/components-ChhJaNRc.js +0 -20
  101. package/dist/assets/configuration-gate-Dwdq_lxf.js +0 -1
  102. package/dist/assets/createClipPathFromCoordSys-uEftn1Mq.js +0 -1
  103. package/dist/assets/createSeriesDataSimply-CI7l7Ufv.js +0 -1
  104. package/dist/assets/customGraphicKeyframeAnimation-DE3jSfMd.js +0 -3
  105. package/dist/assets/echarts-preview-C-cNUtJF.js +0 -1
  106. package/dist/assets/echarts-runtime-CEgJbv68.js +0 -58
  107. package/dist/assets/echarts-runtime-advanced-Buhm2BHz.js +0 -1
  108. package/dist/assets/echarts-runtime-common-DLAdBAIa.js +0 -1
  109. package/dist/assets/echarts-runtime-components-ypnyLkYx.js +0 -1
  110. package/dist/assets/echarts-runtime-hierarchy-ClStquCF.js +0 -1
  111. package/dist/assets/floating-BarTmn-b.js +0 -13
  112. package/dist/assets/index-BJIcYTdc.js +0 -53
  113. package/dist/assets/index-C6xNKxpg.js +0 -656
  114. package/dist/assets/index-D4JQi1TM.js +0 -54
  115. package/dist/assets/index-YA7-AUO8.css +0 -1
  116. package/dist/assets/index-g8zWVpP9.js +0 -23
  117. package/dist/assets/labelHelper-C137W5gU.js +0 -1
  118. package/dist/assets/nocobase-ai-chat-VTMaZFQB.svg +0 -50
  119. package/dist/assets/page-context-Cxx11gzg.js +0 -73
  120. package/dist/assets/prompt-card-BsYMW_rq.js +0 -1
  121. package/dist/assets/sectorHelper-Fqgz3uEk.js +0 -1
  122. package/dist/assets/shortcut-xtghDP63.js +0 -47
  123. package/dist/assets/static.browser-CfQQzERe.js +0 -30
  124. package/dist/assets/switch-DrxImI8F.js +0 -1
  125. package/dist/assets/tool-cards-BGfsY8JF.js +0 -41
  126. package/dist/favicon.ico +0 -0
  127. package/dist/index.html +0 -40
  128. package/dist/index.raw.html +0 -46
  129. package/dist/logo-dark.png +0 -0
  130. package/dist/logo-mark-dark.png +0 -0
  131. package/dist/logo-mark.png +0 -0
  132. package/dist/logo.png +0 -0
  133. package/registry/nocobase-acl/README.md +0 -19
  134. package/registry/nocobase-acl/extension.tsx +0 -71
  135. package/registry/nocobase-acl/tests/acl-regression.mjs +0 -314
  136. package/registry/nocobase-ai/components/surfaces/chat-dialog.tsx +0 -31
  137. package/registry/nocobase-ai/components/surfaces/chat-side-panel.tsx +0 -74
  138. package/registry/nocobase-ai/extension.tsx +0 -158
  139. package/registry/nocobase-ai/tests/business-report-regression.mjs +0 -70
  140. package/registry/nocobase-ai/tests/form-filler-regression.mjs +0 -129
  141. package/registry/nocobase-ai/tests/frontend-tool-regression.mjs +0 -128
  142. package/registry/nocobase-ai/tests/page-context-regression.mjs +0 -48
  143. package/registry/nocobase-ai/tests/stream-regression.mjs +0 -207
  144. package/registry/nocobase-auth-cas/README.md +0 -6
  145. package/registry/nocobase-auth-cas/cas-sign-in-button.tsx +0 -41
  146. package/registry/nocobase-auth-cas/demo.tsx +0 -20
  147. package/registry/nocobase-auth-cas/extension.tsx +0 -46
  148. package/registry/nocobase-auth-cas/index.ts +0 -1
  149. package/registry/nocobase-auth-cas/use-cas-sign-in.ts +0 -29
  150. package/registry/nocobase-auth-dingtalk/README.md +0 -7
  151. package/registry/nocobase-auth-dingtalk/auto-login-provider.tsx +0 -154
  152. package/registry/nocobase-auth-dingtalk/demo.tsx +0 -23
  153. package/registry/nocobase-auth-dingtalk/dingtalk-sign-in-button.tsx +0 -45
  154. package/registry/nocobase-auth-dingtalk/extension.tsx +0 -55
  155. package/registry/nocobase-auth-dingtalk/index.ts +0 -4
  156. package/registry/nocobase-auth-dingtalk/use-dingtalk-sign-in.ts +0 -64
  157. package/registry/nocobase-auth-ldap/README.md +0 -6
  158. package/registry/nocobase-auth-ldap/demo.tsx +0 -21
  159. package/registry/nocobase-auth-ldap/extension.tsx +0 -46
  160. package/registry/nocobase-auth-ldap/index.ts +0 -1
  161. package/registry/nocobase-auth-ldap/ldap-sign-in-form.tsx +0 -69
  162. package/registry/nocobase-auth-ldap/use-ldap-sign-in.ts +0 -5
  163. package/registry/nocobase-auth-oidc/README.md +0 -11
  164. package/registry/nocobase-auth-oidc/auto-redirect-provider.tsx +0 -13
  165. package/registry/nocobase-auth-oidc/demo.tsx +0 -20
  166. package/registry/nocobase-auth-oidc/extension.tsx +0 -51
  167. package/registry/nocobase-auth-oidc/index.ts +0 -1
  168. package/registry/nocobase-auth-oidc/oidc-sign-in-button.tsx +0 -44
  169. package/registry/nocobase-auth-oidc/use-oidc-sign-in.ts +0 -58
  170. package/registry/nocobase-auth-saml/README.md +0 -6
  171. package/registry/nocobase-auth-saml/auto-redirect-provider.tsx +0 -13
  172. package/registry/nocobase-auth-saml/demo.tsx +0 -20
  173. package/registry/nocobase-auth-saml/extension.tsx +0 -51
  174. package/registry/nocobase-auth-saml/index.ts +0 -1
  175. package/registry/nocobase-auth-saml/saml-sign-in-button.tsx +0 -43
  176. package/registry/nocobase-auth-saml/use-saml-sign-in.ts +0 -51
  177. package/registry/nocobase-auth-sms/README.md +0 -7
  178. package/registry/nocobase-auth-sms/demo.tsx +0 -25
  179. package/registry/nocobase-auth-sms/extension.tsx +0 -46
  180. package/registry/nocobase-auth-sms/index.ts +0 -1
  181. package/registry/nocobase-auth-sms/sms-sign-in-form.tsx +0 -107
  182. package/registry/nocobase-auth-sms/use-sms-sign-in.ts +0 -88
  183. package/registry/nocobase-auth-wecom/README.md +0 -6
  184. package/registry/nocobase-auth-wecom/auto-login-provider.tsx +0 -21
  185. package/registry/nocobase-auth-wecom/demo.tsx +0 -23
  186. package/registry/nocobase-auth-wecom/extension.tsx +0 -49
  187. package/registry/nocobase-auth-wecom/index.ts +0 -1
  188. package/registry/nocobase-auth-wecom/use-wecom-sign-in.ts +0 -62
  189. package/registry/nocobase-auth-wecom/wecom-sign-in-button.tsx +0 -70
  190. package/registry/nocobase-i18n/README.md +0 -44
  191. package/registry/nocobase-i18n/components/index.ts +0 -1
  192. package/registry/nocobase-i18n/components/language-switcher.tsx +0 -138
  193. package/registry/nocobase-i18n/demo/index.tsx +0 -209
  194. package/registry/nocobase-i18n/demo/prompt-generator.tsx +0 -132
  195. package/registry/nocobase-i18n/extension.tsx +0 -45
  196. package/registry/nocobase-i18n/index.ts +0 -7
  197. package/registry/nocobase-i18n/locales/en-US.ts +0 -39
  198. package/registry/nocobase-i18n/locales/index.ts +0 -8
  199. package/registry/nocobase-i18n/locales/zh-CN.ts +0 -35
  200. package/registry/nocobase-i18n/provider.tsx +0 -44
  201. package/registry/nocobase-i18n/server-resources.ts +0 -166
  202. package/registry/nocobase-i18n/tests/i18n-regression.mjs +0 -51
  203. package/registry.config.json +0 -302
  204. package/scripts/registry.mjs +0 -164
  205. package/src/components/access-control/resource-access-guard.tsx +0 -21
  206. package/src/components/access-control/use-acl-runtime.ts +0 -28
  207. package/src/lib/nocobase/acl/action.ts +0 -147
  208. package/src/pages/users/layout.tsx +0 -15
  209. package/src/pages/users/show.tsx +0 -278
  210. /package/{registry → src/extensions}/nocobase-acl/components/index.ts +0 -0
  211. /package/{registry → src/extensions}/nocobase-acl/components/role-options.ts +0 -0
  212. /package/{registry → src/extensions}/nocobase-acl/demo/components.tsx +0 -0
  213. /package/{registry → src/extensions}/nocobase-acl/demo/lazy-route.tsx +0 -0
  214. /package/{registry → src/extensions}/nocobase-acl/demo/role-switcher-prompt-generator.tsx +0 -0
  215. /package/{registry → src/extensions}/nocobase-acl/demo/scenario-section.tsx +0 -0
  216. /package/{registry → src/extensions}/nocobase-acl/index.ts +0 -0
  217. /package/{registry → src/extensions}/nocobase-ai/adapters/react-hook-form.ts +0 -0
  218. /package/{registry → src/extensions}/nocobase-ai/assets/nocobase-ai-chat.svg +0 -0
  219. /package/{registry → src/extensions}/nocobase-ai/components/ai-root-provider.tsx +0 -0
  220. /package/{registry → src/extensions}/nocobase-ai/components/chat/ai-employee-avatar.tsx +0 -0
  221. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-attachment.tsx +0 -0
  222. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-compact.tsx +0 -0
  223. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-empty-state.tsx +0 -0
  224. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-header.tsx +0 -0
  225. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-history-dialog.tsx +0 -0
  226. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-message.tsx +0 -0
  227. /package/{registry → src/extensions}/nocobase-ai/components/chat/chat-window.tsx +0 -0
  228. /package/{registry → src/extensions}/nocobase-ai/components/chat/conversation-list.tsx +0 -0
  229. /package/{registry → src/extensions}/nocobase-ai/components/chat/markdown-message.tsx +0 -0
  230. /package/{registry → src/extensions}/nocobase-ai/components/chat/model-select-options.tsx +0 -0
  231. /package/{registry → src/extensions}/nocobase-ai/components/chat/reasoning-panel.tsx +0 -0
  232. /package/{registry → src/extensions}/nocobase-ai/components/chat/sub-agent-conversation.tsx +0 -0
  233. /package/{registry → src/extensions}/nocobase-ai/components/chat/user-prompt-editor.tsx +0 -0
  234. /package/{registry → src/extensions}/nocobase-ai/components/chat/work-context-chip.tsx +0 -0
  235. /package/{registry → src/extensions}/nocobase-ai/components/page-elements/ai-form.tsx +0 -0
  236. /package/{registry → src/extensions}/nocobase-ai/components/page-elements/page-element-provider.tsx +0 -0
  237. /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-inline.tsx +0 -0
  238. /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-page.tsx +0 -0
  239. /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +0 -0
  240. /package/{registry → src/extensions}/nocobase-ai/components/surfaces/chat-surface-actions.tsx +0 -0
  241. /package/{registry → src/extensions}/nocobase-ai/components/tools/builtin-tool-renderers.tsx +0 -0
  242. /package/{registry → src/extensions}/nocobase-ai/components/tools/business-report-dialog.tsx +0 -0
  243. /package/{registry → src/extensions}/nocobase-ai/components/tools/business-report-renderer.tsx +0 -0
  244. /package/{registry → src/extensions}/nocobase-ai/components/tools/business-report-utils.tsx +0 -0
  245. /package/{registry → src/extensions}/nocobase-ai/components/tools/chart-renderer.tsx +0 -0
  246. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-preview.tsx +0 -0
  247. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-advanced.ts +0 -0
  248. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-common.ts +0 -0
  249. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-components.ts +0 -0
  250. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +0 -0
  251. /package/{registry → src/extensions}/nocobase-ai/components/tools/echarts-runtime.ts +0 -0
  252. /package/{registry → src/extensions}/nocobase-ai/components/tools/sub-agent-renderer.tsx +0 -0
  253. /package/{registry → src/extensions}/nocobase-ai/components/tools/suggestions-renderer.tsx +0 -0
  254. /package/{registry → src/extensions}/nocobase-ai/components/tools/tool-renderer-provider.tsx +0 -0
  255. /package/{registry → src/extensions}/nocobase-ai/components/tools/tool-renderer-utils.ts +0 -0
  256. /package/{registry → src/extensions}/nocobase-ai/components/tools/workflow-renderer.tsx +0 -0
  257. /package/{registry → src/extensions}/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +0 -0
  258. /package/{registry → src/extensions}/nocobase-ai/demo/interaction-showcase.tsx +0 -0
  259. /package/{registry → src/extensions}/nocobase-ai/demo/page-context-prompt-generator.tsx +0 -0
  260. /package/{registry → src/extensions}/nocobase-ai/demo/page-context.tsx +0 -0
  261. /package/{registry → src/extensions}/nocobase-ai/demo/page-element-showcase.tsx +0 -0
  262. /package/{registry → src/extensions}/nocobase-ai/demo/prompt-card.tsx +0 -0
  263. /package/{registry → src/extensions}/nocobase-ai/demo/shortcut.tsx +0 -0
  264. /package/{registry → src/extensions}/nocobase-ai/index.ts +0 -0
  265. /package/{registry → src/extensions}/nocobase-ai/providers/avatars.ts +0 -0
  266. /package/{registry → src/extensions}/nocobase-ai/providers/chat-controller.ts +0 -0
  267. /package/{registry → src/extensions}/nocobase-ai/providers/chat-message-utils.ts +0 -0
  268. /package/{registry → src/extensions}/nocobase-ai/providers/chat-reducer.ts +0 -0
  269. /package/{registry → src/extensions}/nocobase-ai/providers/chat-task-utils.ts +0 -0
  270. /package/{registry → src/extensions}/nocobase-ai/providers/chat-transport.ts +0 -0
  271. /package/{registry → src/extensions}/nocobase-ai/providers/form-registry.tsx +0 -0
  272. /package/{registry → src/extensions}/nocobase-ai/providers/frontend-tool-registry.tsx +0 -0
  273. /package/{registry → src/extensions}/nocobase-ai/providers/index.ts +0 -0
  274. /package/{registry → src/extensions}/nocobase-ai/providers/model.ts +0 -0
  275. /package/{registry → src/extensions}/nocobase-ai/providers/page-context.tsx +0 -0
  276. /package/{registry → src/extensions}/nocobase-ai/providers/stream-coalescer.ts +0 -0
  277. /package/{registry → src/extensions}/nocobase-ai/providers/stream-event-utils.ts +0 -0
  278. /package/{registry → src/extensions}/nocobase-ai/providers/stream-parser.ts +0 -0
  279. /package/{registry → src/extensions}/nocobase-ai/providers/sub-agent-stream.ts +0 -0
  280. /package/{registry → src/extensions}/nocobase-ai/providers/types.ts +0 -0
  281. /package/{registry → src/extensions}/nocobase-ai/providers/ui-message-stream.ts +0 -0
  282. /package/{registry → src/extensions}/nocobase-ai/providers/use-automatic-tool-approval.ts +0 -0
  283. /package/{registry → src/extensions}/nocobase-ai/providers/use-chat-message-actions.ts +0 -0
  284. /package/{registry → src/extensions}/nocobase-ai/providers/use-chat-runtime.ts +0 -0
  285. /package/{registry → src/extensions}/nocobase-ai/providers/use-conversation-catalog.ts +0 -0
  286. /package/{registry → src/extensions}/nocobase-ai/providers/use-conversation-history.ts +0 -0
  287. /package/{registry → src/extensions}/nocobase-ai/services/index.ts +0 -0
  288. /package/{registry → src/extensions}/nocobase-ai/services/nocobase-ai-service.ts +0 -0
  289. /package/{registry → src/extensions}/nocobase-ai/services/types.ts +0 -0
  290. /package/{registry → src/extensions}/nocobase-route-surfaces/demo/lazy-route.tsx +0 -0
  291. /package/{registry → src/extensions}/nocobase-route-surfaces/demo/prompt-generator.tsx +0 -0
  292. /package/{registry → src/extensions}/nocobase-route-surfaces/index.ts +0 -0
  293. /package/{registry → src/extensions}/nocobase-route-surfaces/route-dialog.tsx +0 -0
  294. /package/{registry → src/extensions}/nocobase-route-surfaces/route-drawer.tsx +0 -0
  295. /package/{registry → src/extensions}/nocobase-route-surfaces/route-page.tsx +0 -0
  296. /package/{registry → src/extensions}/nocobase-route-surfaces/route-surface-context.ts +0 -0
  297. /package/{registry → src/extensions}/nocobase-route-surfaces/use-refine-unsaved-changes.tsx +0 -0
  298. /package/{registry → src/extensions}/nocobase-route-surfaces/use-route-surface-close.ts +0 -0
  299. /package/{registry → src/extensions}/nocobase-route-surfaces/use-route-surface-state.ts +0 -0
  300. /package/src/{pages/users → extensions/nocobase-users-example}/form-fields.tsx +0 -0
  301. /package/src/{pages/users → extensions/nocobase-users-example}/index.ts +0 -0
  302. /package/src/{pages/users → extensions/nocobase-users-example}/role-badges.tsx +0 -0
  303. /package/src/{pages/users → extensions/nocobase-users-example}/role-utils.ts +0 -0
  304. /package/src/{pages/users → extensions/nocobase-users-example}/types.ts +0 -0
package/src/App.tsx CHANGED
@@ -1,92 +1,32 @@
1
- import { Refine, Authenticated, type ResourceProps } from "@refinedev/core";
1
+ import { Refine, type ResourceProps } from "@refinedev/core";
2
2
 
3
- import { BrowserRouter, Route, Routes, Outlet } from "react-router";
3
+ import { BrowserRouter } from "react-router";
4
4
  import routerProvider, {
5
- CatchAllNavigate,
6
5
  UnsavedChangesNotifier,
7
6
  } from "@refinedev/react-router";
8
- import {
9
- UserCreate,
10
- UserEdit,
11
- UserResourceLayout,
12
- RoleDetailRoute,
13
- userRoutes,
14
- UserShow,
15
- } from "./pages/users";
16
7
  import { dataProvider } from "./providers/data";
17
- import { Login } from "./pages/login";
18
- import { Register } from "./pages/register";
19
- import { ForgotPassword } from "./pages/forgot-password";
20
- import { ErrorComponent } from "./components/app-shell/error-component";
21
- import { Layout } from "./components/app-shell/layout";
22
8
  import { DocumentTitleHandler } from "./components/app-shell/document-title-handler";
23
9
  import { useNotificationProvider } from "./components/notifications/use-notification-provider";
24
10
  import { Toaster } from "./components/notifications/toaster";
25
11
  import { ThemeProvider } from "./components/theme/theme-provider";
26
12
  import { TooltipProvider } from "./components/ui/tooltip";
27
13
  import { BrandLogo } from "./components/app-shell/brand";
28
- import {
29
- AppExtensionProviders,
30
- AppAuthRuntimeProviders,
31
- extensionResources,
32
- extensionRouteElements,
33
- } from "./app/extensions";
14
+ import { AppAuthRuntimeProviders, extensionResources } from "./app/extensions";
34
15
  import "./App.css";
35
16
  import { authProvider } from "./providers/auth";
36
17
  import { accessControlProvider } from "./providers/access-control";
37
- import { AclBootstrap } from "./components/access-control/acl-bootstrap";
38
- import { ResourceAccessGuard } from "./components/access-control/resource-access-guard";
39
- import { NavigateToAccessibleResource } from "./components/access-control/navigate-to-accessible-resource";
40
- import { KeyRound, PanelsTopLeft, UsersRound } from "lucide-react";
41
18
  import { i18nProvider } from "./providers/i18n";
42
19
  import { SystemSettingsProvider } from "./providers/system-settings";
43
- import { AuthDemoPage } from "./components/auth/demo";
44
20
  import { getPortalBase } from "./providers/runtime-config";
21
+ import { AclStoreProvider, aclStore } from "./lib/nocobase/acl";
22
+ import { AppRoutes } from "./app/routes";
45
23
 
46
- const coreResources: ResourceProps[] = [
47
- {
48
- name: "users",
49
- list: userRoutes.list,
50
- create: userRoutes.create,
51
- edit: userRoutes.edit,
52
- show: userRoutes.show,
53
- meta: {
54
- label: "Users",
55
- singularLabel: "User",
56
- i18nKey: "resources.users",
57
- i18nSingularKey: "resources.user",
58
- i18nOptions: { ns: "app" },
59
- descriptionI18nKey: "resources.users.description",
60
- icon: <UsersRound />,
61
- description:
62
- "Manage the people who can sign in and work in this NocoBase application.",
63
- canCreate: true,
64
- canDelete: true,
65
- acl: {
66
- type: "collection",
67
- },
68
- },
69
- },
70
- {
71
- name: "auth-components",
72
- meta: {
73
- label: "Authentication",
74
- icon: <KeyRound />,
75
- description: "NocoBase authentication UI and integration patterns.",
76
- acl: { type: "authenticated" },
77
- },
78
- },
79
- {
80
- name: "auth-patterns",
81
- list: "/auth",
82
- meta: {
83
- parent: "auth-components",
84
- label: "Login composition",
85
- icon: <PanelsTopLeft />,
86
- acl: { type: "authenticated" },
87
- },
88
- },
89
- ];
24
+ const getResourcePriority = (resource: ResourceProps) =>
25
+ typeof resource.meta?.priority === "number" ? resource.meta.priority : 100;
26
+
27
+ const appResources = [...extensionResources].sort(
28
+ (left, right) => getResourcePriority(left) - getResourcePriority(right)
29
+ );
90
30
 
91
31
  const basename = getPortalBase().replace(/\/+$/, "");
92
32
 
@@ -97,123 +37,32 @@ function App() {
97
37
  <ThemeProvider>
98
38
  <TooltipProvider>
99
39
  <SystemSettingsProvider>
100
- <Refine
101
- dataProvider={dataProvider}
102
- notificationProvider={useNotificationProvider()}
103
- routerProvider={routerProvider}
104
- authProvider={authProvider}
105
- accessControlProvider={accessControlProvider}
106
- i18nProvider={i18nProvider}
107
- resources={[...coreResources, ...extensionResources]}
108
- options={{
109
- syncWithLocation: true,
110
- warnWhenUnsavedChanges: true,
111
- disableTelemetry: true,
112
- title: {
113
- text: "NocoBase",
114
- icon: <BrandLogo className="size-14 rounded-2xl" />,
115
- },
116
- }}
117
- >
118
- <Routes>
119
- <Route
120
- element={
121
- <Authenticated
122
- key="authenticated-inner"
123
- fallback={<CatchAllNavigate to="/login" />}
124
- >
125
- <AclBootstrap>
126
- <AppExtensionProviders>
127
- <Layout>
128
- <Outlet />
129
- </Layout>
130
- </AppExtensionProviders>
131
- </AclBootstrap>
132
- </Authenticated>
133
- }
134
- >
135
- <Route index element={<NavigateToAccessibleResource />} />
136
- <Route path="/auth" element={<AuthDemoPage />} />
137
- <Route
138
- path="/users"
139
- element={<UserResourceLayout />}
40
+ <AclStoreProvider store={aclStore}>
41
+ <Refine
42
+ dataProvider={dataProvider}
43
+ notificationProvider={useNotificationProvider()}
44
+ routerProvider={routerProvider}
45
+ authProvider={authProvider}
46
+ accessControlProvider={accessControlProvider}
47
+ i18nProvider={i18nProvider}
48
+ resources={appResources}
49
+ options={{
50
+ syncWithLocation: true,
51
+ warnWhenUnsavedChanges: true,
52
+ disableTelemetry: true,
53
+ title: {
54
+ text: "NocoBase",
55
+ icon: <BrandLogo className="size-14 rounded-2xl" />,
56
+ },
57
+ }}
140
58
  >
141
- <Route
142
- path="create"
143
- element={
144
- <ResourceAccessGuard resource="users" action="create">
145
- <UserCreate />
146
- </ResourceAccessGuard>
147
- }
148
- />
149
- <Route
150
- path="edit/:id"
151
- element={
152
- <ResourceAccessGuard resource="users" action="edit">
153
- <UserEdit />
154
- </ResourceAccessGuard>
155
- }
156
- />
157
- <Route
158
- path="roles/:roleName"
159
- element={
160
- <ResourceAccessGuard resource="roles" action="show">
161
- <RoleDetailRoute returnTo="list" />
162
- </ResourceAccessGuard>
163
- }
164
- />
165
- <Route
166
- path="show/:id"
167
- element={
168
- <ResourceAccessGuard resource="users" action="show">
169
- <UserShow />
170
- </ResourceAccessGuard>
171
- }
172
- >
173
- <Route
174
- path="edit"
175
- element={
176
- <ResourceAccessGuard resource="users" action="edit">
177
- <UserEdit returnTo="show" />
178
- </ResourceAccessGuard>
179
- }
180
- />
181
- <Route
182
- path="roles/:roleName"
183
- element={
184
- <ResourceAccessGuard resource="roles" action="show">
185
- <RoleDetailRoute returnTo="show" />
186
- </ResourceAccessGuard>
187
- }
188
- />
189
- </Route>
190
- </Route>
191
- {extensionRouteElements}
192
- <Route path="*" element={<ErrorComponent />} />
193
- </Route>
194
- <Route
195
- element={
196
- <Authenticated
197
- key="authenticated-outer"
198
- fallback={<Outlet />}
199
- >
200
- <AclBootstrap>
201
- <NavigateToAccessibleResource />
202
- </AclBootstrap>
203
- </Authenticated>
204
- }
205
- >
206
- <Route path="/login" element={<Login />} />
207
- <Route path="/signin" element={<Login />} />
208
- <Route path="/register" element={<Register />} />
209
- <Route path="/forgot-password" element={<ForgotPassword />} />
210
- </Route>
211
- </Routes>
59
+ <AppRoutes />
212
60
 
213
- <Toaster />
214
- <UnsavedChangesNotifier />
215
- <DocumentTitleHandler appName="NocoBase" />
216
- </Refine>
61
+ <Toaster />
62
+ <UnsavedChangesNotifier />
63
+ <DocumentTitleHandler appName="NocoBase" />
64
+ </Refine>
65
+ </AclStoreProvider>
217
66
  </SystemSettingsProvider>
218
67
  </TooltipProvider>
219
68
  </ThemeProvider>
@@ -0,0 +1,154 @@
1
+ import type { ResourceProps, TreeMenuItem } from "@refinedev/core";
2
+ import { KeyRound, PanelsTopLeft } from "lucide-react";
3
+ import type { ReactElement } from "react";
4
+ import { Navigate, Outlet, Route, useLocation } from "react-router";
5
+
6
+ import { AuthDemoPage } from "@/components/auth/demo";
7
+ import { Header } from "@/components/app-shell/header";
8
+ import { SidebarNavigation } from "@/components/app-shell/sidebar";
9
+ import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
10
+ import { cn } from "@/lib/utils";
11
+ import type { AppExtension } from "./extension";
12
+
13
+ const coreDevelopmentResources: ResourceProps[] = [
14
+ {
15
+ name: "auth-components",
16
+ meta: {
17
+ label: "Authentication",
18
+ icon: <KeyRound />,
19
+ description: "NocoBase authentication UI and integration patterns.",
20
+ },
21
+ },
22
+ {
23
+ name: "auth-patterns",
24
+ list: "auth",
25
+ meta: {
26
+ parent: "auth-components",
27
+ label: "Login composition",
28
+ icon: <PanelsTopLeft />,
29
+ description:
30
+ "Dynamic login and application-owned customization patterns.",
31
+ },
32
+ },
33
+ ];
34
+
35
+ const toDevelopmentPath = (path: string) =>
36
+ `/dev/${path.replace(/^\/+/, "")}`.replace(/\/+$/, "");
37
+
38
+ function collectDevelopmentMenu(extensions: AppExtension[]) {
39
+ const resources = [
40
+ ...coreDevelopmentResources,
41
+ ...extensions.flatMap((extension) => extension.dev?.resources ?? []),
42
+ ];
43
+ const childrenByParent = new Map<string, ResourceProps[]>();
44
+ const roots: ResourceProps[] = [];
45
+
46
+ for (const resource of resources) {
47
+ const parent = resource.meta?.parent;
48
+ if (typeof parent === "string") {
49
+ const children = childrenByParent.get(parent) ?? [];
50
+ children.push(resource);
51
+ childrenByParent.set(parent, children);
52
+ } else {
53
+ roots.push(resource);
54
+ }
55
+ }
56
+
57
+ const toMenuItem = (
58
+ resource: ResourceProps,
59
+ parentKey?: string
60
+ ): TreeMenuItem => {
61
+ const key = parentKey ? `${parentKey}/${resource.name}` : resource.name;
62
+ const children = childrenByParent
63
+ .get(resource.name)
64
+ ?.map((child) => toMenuItem(child, key));
65
+
66
+ return {
67
+ name: resource.name,
68
+ key,
69
+ route:
70
+ typeof resource.list === "string"
71
+ ? toDevelopmentPath(resource.list)
72
+ : undefined,
73
+ meta: resource.meta,
74
+ children: children ?? [],
75
+ };
76
+ };
77
+
78
+ return roots.map((resource) => toMenuItem(resource));
79
+ }
80
+
81
+ function findFirstRoute(items: TreeMenuItem[]): string | undefined {
82
+ for (const item of items) {
83
+ if (item.route) return item.route;
84
+ const childRoute = item.children && findFirstRoute(item.children);
85
+ if (childRoute) return childRoute;
86
+ }
87
+ }
88
+
89
+ function findSelectedKey(items: TreeMenuItem[], pathname: string) {
90
+ const matches: TreeMenuItem[] = [];
91
+
92
+ const visit = (menuItems: TreeMenuItem[]) => {
93
+ for (const item of menuItems) {
94
+ if (
95
+ item.route &&
96
+ (pathname === item.route || pathname.startsWith(`${item.route}/`))
97
+ ) {
98
+ matches.push(item);
99
+ }
100
+ if (item.children) visit(item.children);
101
+ }
102
+ };
103
+
104
+ visit(items);
105
+ return matches.sort(
106
+ (left, right) => (right.route?.length ?? 0) - (left.route?.length ?? 0)
107
+ )[0]?.key;
108
+ }
109
+
110
+ export function createDevelopmentRoute(
111
+ extensions: AppExtension[]
112
+ ): ReactElement {
113
+ const menuItems = collectDevelopmentMenu(extensions);
114
+ const firstRoute = findFirstRoute(menuItems) ?? "/";
115
+ const routes = extensions
116
+ .map((extension) => extension.dev?.routes)
117
+ .filter((route): route is ReactElement => Boolean(route));
118
+
119
+ return (
120
+ <Route
121
+ key="registry-development"
122
+ path="/dev"
123
+ element={<DevelopmentLayout menuItems={menuItems} />}
124
+ >
125
+ <Route index element={<Navigate to={firstRoute} replace />} />
126
+ <Route path="auth" element={<AuthDemoPage />} />
127
+ {routes}
128
+ </Route>
129
+ );
130
+ }
131
+
132
+ function DevelopmentLayout({ menuItems }: { menuItems: TreeMenuItem[] }) {
133
+ const { pathname } = useLocation();
134
+
135
+ return (
136
+ <SidebarProvider>
137
+ <SidebarNavigation
138
+ menuItems={menuItems}
139
+ selectedKey={findSelectedKey(menuItems, pathname)}
140
+ />
141
+ <SidebarInset className="bg-muted/25">
142
+ <Header />
143
+ <main
144
+ className={cn(
145
+ "@container/main mx-auto flex w-full max-w-[1600px] flex-1 flex-col",
146
+ "relative px-4 py-5 md:p-6 lg:px-8 lg:py-7"
147
+ )}
148
+ >
149
+ <Outlet />
150
+ </main>
151
+ </SidebarInset>
152
+ </SidebarProvider>
153
+ );
154
+ }
@@ -4,8 +4,13 @@ import type { AuthenticatorAdapter } from "@/components/auth/types";
4
4
 
5
5
  export type AppExtension = {
6
6
  id: string;
7
+ priority?: number;
7
8
  resources?: ResourceProps[];
8
9
  routes?: ReactElement;
10
+ dev?: {
11
+ resources?: ResourceProps[];
12
+ routes?: ReactElement;
13
+ };
9
14
  Provider?: ComponentType<PropsWithChildren>;
10
15
  AuthRuntimeProvider?: ComponentType<PropsWithChildren>;
11
16
  authRuntimePriority?: number;
@@ -1,15 +1,25 @@
1
- import { Suspense, type PropsWithChildren, type ReactNode } from "react";
1
+ import {
2
+ Suspense,
3
+ type PropsWithChildren,
4
+ type ReactElement,
5
+ type ReactNode,
6
+ } from "react";
2
7
  import type { AppExtension } from "./extension";
3
8
  import { LoadingState } from "@/components/app-shell/loading-state";
9
+ import { createDevelopmentRoute } from "./development";
4
10
 
5
11
  const extensionModules = import.meta.glob<{ default: AppExtension }>(
6
- "../extensions/*/extension.tsx",
12
+ "@/extensions/*/extension.tsx",
7
13
  { eager: true }
8
14
  );
9
15
 
10
16
  export const appExtensions = Object.values(extensionModules)
11
17
  .map((module) => module.default)
12
- .sort((left, right) => left.id.localeCompare(right.id));
18
+ .sort(
19
+ (left, right) =>
20
+ (left.priority ?? 100) - (right.priority ?? 100) ||
21
+ left.id.localeCompare(right.id)
22
+ );
13
23
 
14
24
  export const extensionResources = appExtensions.flatMap(
15
25
  (extension) => extension.resources ?? []
@@ -17,7 +27,11 @@ export const extensionResources = appExtensions.flatMap(
17
27
 
18
28
  export const extensionRouteElements = appExtensions
19
29
  .map((extension) => extension.routes)
20
- .filter((routes): routes is NonNullable<typeof routes> => Boolean(routes));
30
+ .filter((routes): routes is ReactElement => Boolean(routes));
31
+
32
+ export const extensionStandaloneRouteElements = import.meta.env.DEV
33
+ ? [createDevelopmentRoute(appExtensions)]
34
+ : [];
21
35
 
22
36
  export const extensionUserMenuItems = appExtensions
23
37
  .filter((extension) => extension.UserMenuItems)
@@ -0,0 +1,65 @@
1
+ import { Authenticated } from "@refinedev/core";
2
+ import { CatchAllNavigate } from "@refinedev/react-router";
3
+ import { Outlet, Route, Routes } from "react-router";
4
+
5
+ import { AclBootstrap } from "@/components/access-control/acl-bootstrap";
6
+ import { NavigateToAccessibleResource } from "@/components/access-control/navigate-to-accessible-resource";
7
+ import { ErrorComponent } from "@/components/app-shell/error-component";
8
+ import { Layout } from "@/components/app-shell/layout";
9
+ import { ForgotPassword } from "@/pages/forgot-password";
10
+ import { Login } from "@/pages/login";
11
+ import { Register } from "@/pages/register";
12
+ import {
13
+ AppExtensionProviders,
14
+ extensionRouteElements,
15
+ extensionStandaloneRouteElements,
16
+ } from "./extensions";
17
+
18
+ export function AppRoutes() {
19
+ return (
20
+ <Routes>
21
+ <Route
22
+ element={
23
+ <Authenticated
24
+ key="authenticated-inner"
25
+ fallback={<CatchAllNavigate to="/login" />}
26
+ >
27
+ <AclBootstrap>
28
+ <AppExtensionProviders>
29
+ <Outlet />
30
+ </AppExtensionProviders>
31
+ </AclBootstrap>
32
+ </Authenticated>
33
+ }
34
+ >
35
+ {extensionStandaloneRouteElements}
36
+ <Route
37
+ element={
38
+ <Layout>
39
+ <Outlet />
40
+ </Layout>
41
+ }
42
+ >
43
+ <Route index element={<NavigateToAccessibleResource />} />
44
+ {extensionRouteElements}
45
+ <Route path="*" element={<ErrorComponent />} />
46
+ </Route>
47
+ </Route>
48
+
49
+ <Route
50
+ element={
51
+ <Authenticated key="authenticated-outer" fallback={<Outlet />}>
52
+ <AclBootstrap>
53
+ <NavigateToAccessibleResource />
54
+ </AclBootstrap>
55
+ </Authenticated>
56
+ }
57
+ >
58
+ <Route path="/login" element={<Login />} />
59
+ <Route path="/signin" element={<Login />} />
60
+ <Route path="/register" element={<Register />} />
61
+ <Route path="/forgot-password" element={<ForgotPassword />} />
62
+ </Route>
63
+ </Routes>
64
+ );
65
+ }
@@ -1,9 +1,17 @@
1
- import type { PropsWithChildren } from "react";
1
+ import { useEffect, type PropsWithChildren } from "react";
2
2
 
3
+ import { useAclState, useAclStore } from "@/lib/nocobase/acl";
3
4
  import { AclGate } from "./acl-gate";
4
- import { useAclRuntime } from "./use-acl-runtime";
5
5
 
6
6
  export function AclBootstrap({ children }: PropsWithChildren) {
7
- useAclRuntime();
7
+ const store = useAclStore();
8
+ const state = useAclState();
9
+
10
+ useEffect(() => {
11
+ if (state.status === "idle") {
12
+ store.load();
13
+ }
14
+ }, [state.status, store]);
15
+
8
16
  return <AclGate>{children}</AclGate>;
9
17
  }
@@ -3,26 +3,27 @@ import type { PropsWithChildren } from "react";
3
3
 
4
4
  import { Button } from "@/components/ui/button";
5
5
  import { LoadingState } from "@/components/app-shell/loading-state";
6
- import { loadAcl, useAclSnapshot } from "@/lib/nocobase/acl";
6
+ import { useAclState, useAclStore } from "@/lib/nocobase/acl";
7
7
 
8
8
  export function AclGate({ children }: PropsWithChildren) {
9
- const snapshot = useAclSnapshot();
9
+ const store = useAclStore();
10
+ const state = useAclState();
10
11
 
11
- if (snapshot.status === "ready") return children;
12
+ if (state.status === "ready") return children;
12
13
 
13
- if (snapshot.status === "error") {
14
+ if (state.status === "error") {
14
15
  return (
15
16
  <div className="flex min-h-screen items-center justify-center bg-background p-6">
16
17
  <div className="max-w-md text-center">
17
18
  <h1 className="text-xl font-semibold">Unable to load permissions</h1>
18
19
  <p className="mt-2 text-sm leading-6 text-muted-foreground">
19
- {snapshot.error?.message ??
20
+ {state.error.message ??
20
21
  "The current role permissions could not be loaded."}
21
22
  </p>
22
23
  <Button
23
24
  className="mt-5"
24
25
  variant="outline"
25
- onClick={() => void loadAcl({ force: true }).catch(() => undefined)}
26
+ onClick={() => store.retry()}
26
27
  >
27
28
  <RotateCcw />
28
29
  Retry
@@ -0,0 +1,17 @@
1
+ import type { PropsWithChildren, ReactNode } from "react";
2
+
3
+ import { useCanAccess, type AclAccessRequest } from "@/lib/nocobase/acl";
4
+
5
+ export type CanAccessProps = PropsWithChildren<
6
+ AclAccessRequest & {
7
+ fallback?: ReactNode;
8
+ }
9
+ >;
10
+
11
+ export function CanAccess({
12
+ children,
13
+ fallback = null,
14
+ ...request
15
+ }: CanAccessProps) {
16
+ return useCanAccess(request) ? children : fallback;
17
+ }
@@ -4,14 +4,19 @@ import { Navigate } from "react-router";
4
4
  import {
5
5
  filterMenuItemsByAcl,
6
6
  findFirstAccessibleRoute,
7
- useAclSnapshot,
7
+ useAclState,
8
8
  } from "@/lib/nocobase/acl";
9
9
  import { AccessDenied } from "./access-denied";
10
10
 
11
11
  export function NavigateToAccessibleResource() {
12
12
  const { menuItems } = useMenu();
13
- const acl = useAclSnapshot();
14
- const route = findFirstAccessibleRoute(filterMenuItemsByAcl(menuItems, acl));
13
+ const state = useAclState();
14
+ const route =
15
+ state.status === "ready"
16
+ ? findFirstAccessibleRoute(
17
+ filterMenuItemsByAcl(menuItems, state.permissions)
18
+ )
19
+ : undefined;
15
20
 
16
21
  return route ? <Navigate to={route} replace /> : <AccessDenied />;
17
22
  }
@@ -31,18 +31,34 @@ import { Button } from "@/components/ui/button";
31
31
  import { ChevronRight, ListIcon, ShieldCheck } from "lucide-react";
32
32
  import { cn } from "@/lib/utils";
33
33
  import { Brand } from "@/components/app-shell/brand";
34
- import { filterMenuItemsByAcl, useAclSnapshot } from "@/lib/nocobase/acl";
34
+ import { filterMenuItemsByAcl, useAclState } from "@/lib/nocobase/acl";
35
35
  import { getResourceLabel } from "@/components/resources/resource-label";
36
36
 
37
37
  export function Sidebar() {
38
- const { open } = useShadcnSidebar();
39
38
  const { menuItems, selectedKey } = useMenu();
40
- const acl = useAclSnapshot();
39
+ const acl = useAclState();
41
40
  const allowedMenuItems = React.useMemo(
42
- () => filterMenuItemsByAcl(menuItems, acl),
41
+ () =>
42
+ acl.status === "ready"
43
+ ? filterMenuItemsByAcl(menuItems, acl.permissions)
44
+ : [],
43
45
  [acl, menuItems]
44
46
  );
45
47
 
48
+ return (
49
+ <SidebarNavigation menuItems={allowedMenuItems} selectedKey={selectedKey} />
50
+ );
51
+ }
52
+
53
+ export function SidebarNavigation({
54
+ menuItems,
55
+ selectedKey,
56
+ }: {
57
+ menuItems: TreeMenuItem[];
58
+ selectedKey?: string;
59
+ }) {
60
+ const { open } = useShadcnSidebar();
61
+
46
62
  return (
47
63
  <ShadcnSidebar
48
64
  collapsible="icon"
@@ -64,7 +80,7 @@ export function Sidebar() {
64
80
  }
65
81
  )}
66
82
  >
67
- {allowedMenuItems.map((item: TreeMenuItem) => (
83
+ {menuItems.map((item: TreeMenuItem) => (
68
84
  <SidebarItem
69
85
  key={item.key || item.name}
70
86
  item={item}