@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/src/App.tsx CHANGED
@@ -5,18 +5,7 @@ import routerProvider, {
5
5
  CatchAllNavigate,
6
6
  UnsavedChangesNotifier,
7
7
  } from "@refinedev/react-router";
8
- import {
9
- BlogPostList,
10
- BlogPostCreate,
11
- BlogPostEdit,
12
- BlogPostShow,
13
- } from "./pages/blog-posts";
14
- import {
15
- CategoryList,
16
- CategoryCreate,
17
- CategoryEdit,
18
- CategoryShow,
19
- } from "./pages/categories";
8
+ import { UserCreate, UserEdit, UserList, UserShow } from "./pages/users";
20
9
  import { dataProvider } from "./providers/data";
21
10
  import { Login } from "./pages/login";
22
11
  import { Register } from "./pages/register";
@@ -31,6 +20,7 @@ import { TooltipProvider } from "./components/ui/tooltip";
31
20
  import { BrandLogo } from "./components/app-shell/brand";
32
21
  import {
33
22
  AppExtensionProviders,
23
+ extensionI18nProvider,
34
24
  extensionResources,
35
25
  extensionRouteElements,
36
26
  } from "./app/extensions";
@@ -40,38 +30,25 @@ import { accessControlProvider } from "./providers/access-control";
40
30
  import { AclBootstrap } from "./components/access-control/acl-bootstrap";
41
31
  import { ResourceAccessGuard } from "./components/access-control/resource-access-guard";
42
32
  import { NavigateToAccessibleResource } from "./components/access-control/navigate-to-accessible-resource";
43
- import { FileText, Tags } from "lucide-react";
33
+ import { UsersRound } from "lucide-react";
44
34
 
45
35
  const coreResources: ResourceProps[] = [
46
36
  {
47
- name: "blog_posts",
48
- list: "/blog-posts",
49
- create: "/blog-posts/create",
50
- edit: "/blog-posts/edit/:id",
51
- show: "/blog-posts/show/:id",
37
+ name: "users",
38
+ list: "/users",
39
+ create: "/users/create",
40
+ edit: "/users/edit/:id",
41
+ show: "/users/show/:id",
52
42
  meta: {
53
- label: "Blog posts",
54
- icon: <FileText />,
43
+ label: "Users",
44
+ singularLabel: "User",
45
+ i18nKey: "resources.users",
46
+ i18nSingularKey: "resources.user",
47
+ i18nOptions: { ns: "app" },
48
+ descriptionI18nKey: "resources.users.description",
49
+ icon: <UsersRound />,
55
50
  description:
56
- "Create and publish content on a reliable NocoBase data foundation.",
57
- canCreate: true,
58
- canDelete: true,
59
- acl: {
60
- type: "collection",
61
- },
62
- },
63
- },
64
- {
65
- name: "categories",
66
- list: "/categories",
67
- create: "/categories/create",
68
- edit: "/categories/edit/:id",
69
- show: "/categories/show/:id",
70
- meta: {
71
- label: "Categories",
72
- icon: <Tags />,
73
- description:
74
- "Organize reusable structures while NocoBase keeps the underlying data consistent.",
51
+ "Manage the people who can sign in and work in this NocoBase application.",
75
52
  canCreate: true,
76
53
  canDelete: true,
77
54
  acl: {
@@ -94,6 +71,7 @@ function App() {
94
71
  routerProvider={routerProvider}
95
72
  authProvider={authProvider}
96
73
  accessControlProvider={accessControlProvider}
74
+ i18nProvider={extensionI18nProvider}
97
75
  resources={[...coreResources, ...extensionResources]}
98
76
  options={{
99
77
  syncWithLocation: true,
@@ -122,74 +100,37 @@ function App() {
122
100
  </Authenticated>
123
101
  }
124
102
  >
125
- <Route
126
- index
127
- element={<NavigateToAccessibleResource />}
128
- />
129
- <Route path="/blog-posts">
130
- <Route
131
- index
132
- element={
133
- <ResourceAccessGuard resource="blog_posts" action="list">
134
- <BlogPostList />
135
- </ResourceAccessGuard>
136
- }
137
- />
138
- <Route
139
- path="create"
140
- element={
141
- <ResourceAccessGuard resource="blog_posts" action="create">
142
- <BlogPostCreate />
143
- </ResourceAccessGuard>
144
- }
145
- />
146
- <Route
147
- path="edit/:id"
148
- element={
149
- <ResourceAccessGuard resource="blog_posts" action="edit">
150
- <BlogPostEdit />
151
- </ResourceAccessGuard>
152
- }
153
- />
154
- <Route
155
- path="show/:id"
156
- element={
157
- <ResourceAccessGuard resource="blog_posts" action="show">
158
- <BlogPostShow />
159
- </ResourceAccessGuard>
160
- }
161
- />
162
- </Route>
163
- <Route path="/categories">
103
+ <Route index element={<NavigateToAccessibleResource />} />
104
+ <Route path="/users">
164
105
  <Route
165
106
  index
166
107
  element={
167
- <ResourceAccessGuard resource="categories" action="list">
168
- <CategoryList />
108
+ <ResourceAccessGuard resource="users" action="list">
109
+ <UserList />
169
110
  </ResourceAccessGuard>
170
111
  }
171
112
  />
172
113
  <Route
173
114
  path="create"
174
115
  element={
175
- <ResourceAccessGuard resource="categories" action="create">
176
- <CategoryCreate />
116
+ <ResourceAccessGuard resource="users" action="create">
117
+ <UserCreate />
177
118
  </ResourceAccessGuard>
178
119
  }
179
120
  />
180
121
  <Route
181
122
  path="edit/:id"
182
123
  element={
183
- <ResourceAccessGuard resource="categories" action="edit">
184
- <CategoryEdit />
124
+ <ResourceAccessGuard resource="users" action="edit">
125
+ <UserEdit />
185
126
  </ResourceAccessGuard>
186
127
  }
187
128
  />
188
129
  <Route
189
130
  path="show/:id"
190
131
  element={
191
- <ResourceAccessGuard resource="categories" action="show">
192
- <CategoryShow />
132
+ <ResourceAccessGuard resource="users" action="show">
133
+ <UserShow />
193
134
  </ResourceAccessGuard>
194
135
  }
195
136
  />
@@ -1,4 +1,4 @@
1
- import type { ResourceProps } from "@refinedev/core";
1
+ import type { I18nProvider, ResourceProps } from "@refinedev/core";
2
2
  import type { ComponentType, PropsWithChildren, ReactElement } from "react";
3
3
 
4
4
  export type AppExtension = {
@@ -6,4 +6,6 @@ export type AppExtension = {
6
6
  resources?: ResourceProps[];
7
7
  routes?: ReactElement;
8
8
  Provider?: ComponentType<PropsWithChildren>;
9
+ UserMenuItems?: ComponentType;
10
+ i18nProvider?: I18nProvider;
9
11
  };
@@ -18,6 +18,27 @@ export const extensionRouteElements = appExtensions
18
18
  .map((extension) => extension.routes)
19
19
  .filter((routes): routes is NonNullable<typeof routes> => Boolean(routes));
20
20
 
21
+ const i18nExtensions = appExtensions.filter(
22
+ (extension) => extension.i18nProvider
23
+ );
24
+
25
+ if (i18nExtensions.length > 1) {
26
+ console.warn(
27
+ `Multiple app extensions provide i18nProvider (${i18nExtensions
28
+ .map((extension) => extension.id)
29
+ .join(", ")}). Using ${i18nExtensions[0].id}.`
30
+ );
31
+ }
32
+
33
+ export const extensionI18nProvider = i18nExtensions[0]?.i18nProvider;
34
+
35
+ export const extensionUserMenuItems = appExtensions
36
+ .filter((extension) => extension.UserMenuItems)
37
+ .map((extension) => ({
38
+ id: extension.id,
39
+ Component: extension.UserMenuItems!,
40
+ }));
41
+
21
42
  export function AppExtensionProviders({ children }: PropsWithChildren) {
22
43
  return appExtensions.reduceRight<ReactNode>((content, extension) => {
23
44
  const Provider = extension.Provider;
@@ -1,7 +1,8 @@
1
- import { Loader2, RotateCcw } from "lucide-react";
1
+ import { RotateCcw } from "lucide-react";
2
2
  import type { PropsWithChildren } from "react";
3
3
 
4
4
  import { Button } from "@/components/ui/button";
5
+ import { LoadingState } from "@/components/app-shell/loading-state";
5
6
  import { loadAcl, useAclSnapshot } from "@/lib/nocobase/acl";
6
7
 
7
8
  export function AclGate({ children }: PropsWithChildren) {
@@ -21,9 +22,7 @@ export function AclGate({ children }: PropsWithChildren) {
21
22
  <Button
22
23
  className="mt-5"
23
24
  variant="outline"
24
- onClick={() =>
25
- void loadAcl({ force: true }).catch(() => undefined)
26
- }
25
+ onClick={() => void loadAcl({ force: true }).catch(() => undefined)}
27
26
  >
28
27
  <RotateCcw />
29
28
  Retry
@@ -33,10 +32,5 @@ export function AclGate({ children }: PropsWithChildren) {
33
32
  );
34
33
  }
35
34
 
36
- return (
37
- <div className="flex min-h-screen items-center justify-center bg-background">
38
- <Loader2 className="size-7 animate-spin text-muted-foreground" />
39
- <span className="sr-only">Loading permissions…</span>
40
- </div>
41
- );
35
+ return <LoadingState className="min-h-screen bg-background" />;
42
36
  }
@@ -7,6 +7,8 @@ import {
7
7
  useBreadcrumb,
8
8
  useLink,
9
9
  useResourceParams,
10
+ useTranslate,
11
+ useUserFriendlyName,
10
12
  } from "@refinedev/core";
11
13
  import {
12
14
  BreadcrumbSeparator as ShadcnBreadcrumbSeparator,
@@ -15,11 +17,15 @@ import {
15
17
  BreadcrumbPage as ShadcnBreadcrumbPage,
16
18
  Breadcrumb as ShadcnBreadcrumb,
17
19
  } from "@/components/ui/breadcrumb";
20
+ import { resolveTranslatableText } from "@/lib/i18n";
21
+ import { getResourceLabel } from "@/components/resources/resource-label";
18
22
 
19
23
  export function Breadcrumb() {
20
24
  const Link = useLink();
21
25
  const { breadcrumbs } = useBreadcrumb();
22
26
  const { resources } = useResourceParams();
27
+ const translate = useTranslate();
28
+ const getUserFriendlyName = useUserFriendlyName();
23
29
  const rootRouteResource = matchResourceFromRoute("/", resources);
24
30
 
25
31
  const breadCrumbItems = useMemo(() => {
@@ -42,15 +48,45 @@ export function Breadcrumb() {
42
48
  });
43
49
 
44
50
  for (const { label, href } of breadcrumbs) {
51
+ const matchingResource = resources.find((resource) => {
52
+ const metaLabel = resource.meta?.label;
53
+ return (
54
+ label === metaLabel ||
55
+ label === resource.name ||
56
+ (href &&
57
+ [resource.list, resource.create, resource.edit, resource.show].some(
58
+ (route) => typeof route === "string" && route === href
59
+ ))
60
+ );
61
+ });
62
+ const displayLabel = matchingResource
63
+ ? getResourceLabel(
64
+ matchingResource,
65
+ "plural",
66
+ translate,
67
+ getUserFriendlyName
68
+ )
69
+ : resolveTranslatableText(label);
45
70
  list.push({
46
- key: `breadcrumb-item-${label}`,
71
+ key: `breadcrumb-item-${displayLabel}`,
47
72
  href: href ?? "",
48
- Component: href ? <Link to={href}>{label}</Link> : <span>{label}</span>,
73
+ Component: href ? (
74
+ <Link to={href}>{displayLabel}</Link>
75
+ ) : (
76
+ <span>{displayLabel}</span>
77
+ ),
49
78
  });
50
79
  }
51
80
 
52
81
  return list;
53
- }, [breadcrumbs, Link, rootRouteResource]);
82
+ }, [
83
+ breadcrumbs,
84
+ getUserFriendlyName,
85
+ Link,
86
+ resources,
87
+ rootRouteResource,
88
+ translate,
89
+ ]);
54
90
 
55
91
  return (
56
92
  <ShadcnBreadcrumb>
@@ -1,19 +1,14 @@
1
1
  import type { Action, IResourceItem } from "@refinedev/core";
2
- import {
3
- useParsed,
4
- useTranslate,
5
- useUserFriendlyName,
6
- } from "@refinedev/core";
2
+ import { useParsed, useTranslate } from "@refinedev/core";
7
3
  import { useLayoutEffect } from "react";
8
4
  import { useLocation } from "react-router";
5
+ import { useResourceLabel } from "@/components/resources/resource-label";
9
6
 
10
7
  export type ActionPrefixContext = {
11
8
  id?: string;
12
9
  };
13
10
 
14
- export type ActionPrefix =
15
- | string
16
- | ((context: ActionPrefixContext) => string);
11
+ export type ActionPrefix = string | ((context: ActionPrefixContext) => string);
17
12
 
18
13
  export type DocumentTitleContext = {
19
14
  action?: Action;
@@ -85,19 +80,14 @@ export function DocumentTitleHandler({
85
80
  const location = useLocation();
86
81
  const { action, id, params, pathname, resource } = useParsed();
87
82
  const translate = useTranslate();
88
- const getUserFriendlyName = useUserFriendlyName();
83
+ const identifier = resource?.identifier ?? resource?.name;
84
+ const resourceNameFallback = useResourceLabel(
85
+ resource,
86
+ action === "list" ? "plural" : "singular",
87
+ identifier
88
+ );
89
89
 
90
90
  useLayoutEffect(() => {
91
- const identifier = resource?.identifier ?? resource?.name;
92
- const resourceNameFallback =
93
- resource?.meta?.label ??
94
- (identifier
95
- ? getUserFriendlyName(
96
- identifier,
97
- action === "list" ? "plural" : "singular"
98
- )
99
- : undefined);
100
-
101
91
  const resourceName = resource?.name
102
92
  ? safeTranslate(
103
93
  translate,
@@ -155,12 +145,13 @@ export function DocumentTitleHandler({
155
145
  appName,
156
146
  defaultTitle,
157
147
  formatTitle,
158
- getUserFriendlyName,
159
148
  id,
149
+ identifier,
160
150
  location,
161
151
  params,
162
152
  pathname,
163
153
  resource,
154
+ resourceNameFallback,
164
155
  separator,
165
156
  translate,
166
157
  ]);
@@ -1,4 +1,8 @@
1
- import { useActiveAuthProvider, useLogout } from "@refinedev/core";
1
+ import {
2
+ useActiveAuthProvider,
3
+ useLogout,
4
+ useTranslate,
5
+ } from "@refinedev/core";
2
6
  import {
3
7
  DropdownMenu,
4
8
  DropdownMenuItem,
@@ -13,6 +17,7 @@ import { useSidebar, SidebarTrigger } from "@/components/ui/sidebar";
13
17
  import { LogOutIcon } from "lucide-react";
14
18
  import { cn } from "@/lib/utils";
15
19
  import { Brand } from "@/components/app-shell/brand";
20
+ import { extensionUserMenuItems } from "@/app/extensions";
16
21
 
17
22
  export const Header = () => {
18
23
  const { isMobile } = useSidebar();
@@ -21,6 +26,8 @@ export const Header = () => {
21
26
  };
22
27
 
23
28
  function DesktopHeader() {
29
+ const translate = useTranslate();
30
+
24
31
  return (
25
32
  <header
26
33
  className={cn(
@@ -44,7 +51,7 @@ function DesktopHeader() {
44
51
  <SidebarTrigger className="size-9 rounded-xl text-muted-foreground hover:text-foreground" />
45
52
  <div className="hidden h-5 w-px bg-border sm:block" />
46
53
  <span className="hidden text-sm font-medium text-muted-foreground sm:block">
47
- AI application workspace
54
+ {translate("shell.workspace", "AI application workspace")}
48
55
  </span>
49
56
  </div>
50
57
  <div className="flex items-center gap-2">
@@ -94,6 +101,7 @@ function MobileHeader() {
94
101
 
95
102
  const UserDropdown = () => {
96
103
  const { mutate: logout, isPending: isLoggingOut } = useLogout();
104
+ const translate = useTranslate();
97
105
 
98
106
  const authProvider = useActiveAuthProvider();
99
107
 
@@ -110,6 +118,9 @@ const UserDropdown = () => {
110
118
  <div className="px-2 py-2">
111
119
  <UserInfo />
112
120
  </div>
121
+ {extensionUserMenuItems.map(({ id, Component }) => (
122
+ <Component key={id} />
123
+ ))}
113
124
  <DropdownMenuSeparator />
114
125
  <DropdownMenuItem
115
126
  className="mt-1 min-h-9 cursor-pointer gap-2 px-2 text-muted-foreground focus:text-foreground"
@@ -118,7 +129,11 @@ const UserDropdown = () => {
118
129
  }}
119
130
  >
120
131
  <LogOutIcon />
121
- <span>{isLoggingOut ? "Signing out..." : "Sign out"}</span>
132
+ <span>
133
+ {isLoggingOut
134
+ ? translate("auth.signingOut", "Signing out...")
135
+ : translate("auth.signOut", "Sign out")}
136
+ </span>
122
137
  </DropdownMenuItem>
123
138
  </DropdownMenuContent>
124
139
  </DropdownMenu>
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
- import { Loader2 } from "lucide-react";
5
4
  import { cn } from "@/lib/utils";
5
+ import { LoadingState } from "@/components/app-shell/loading-state";
6
6
 
7
7
  interface LoadingOverlayProps extends React.HTMLAttributes<HTMLDivElement> {
8
8
  loading?: boolean;
@@ -25,9 +25,7 @@ export const LoadingOverlay = React.forwardRef<
25
25
  className
26
26
  )}
27
27
  >
28
- <div className="flex flex-col items-center gap-2">
29
- <Loader2 className="h-8 w-8 animate-spin text-primary" />
30
- </div>
28
+ <LoadingState />
31
29
  </div>
32
30
  </div>
33
31
  );
@@ -0,0 +1,15 @@
1
+ import { Loader2 } from "lucide-react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ export function LoadingState({ className }: { className?: string }) {
6
+ return (
7
+ <div
8
+ role="status"
9
+ className={cn("flex items-center justify-center", className)}
10
+ >
11
+ <Loader2 className="size-7 animate-spin text-primary" />
12
+ <span className="sr-only">Loading</span>
13
+ </div>
14
+ );
15
+ }