@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
@@ -0,0 +1,205 @@
1
+ import { useGetLocale, useTranslate } from "@refinedev/core";
2
+ import { useTable } from "@refinedev/react-table";
3
+ import { createColumnHelper } from "@tanstack/react-table";
4
+ import { Eye, Pencil, Trash2 } from "lucide-react";
5
+ import { useMemo } from "react";
6
+
7
+ import { DataTable } from "@/components/data-table/data-table";
8
+ import { DeleteButton } from "@/components/resources/buttons/delete";
9
+ import { EditButton } from "@/components/resources/buttons/edit";
10
+ import { ShowButton } from "@/components/resources/buttons/show";
11
+ import { ListView } from "@/components/resources/views/list-view";
12
+ import { Badge } from "@/components/ui/badge";
13
+ import { useAIPageElementHandle } from "@/extensions/nocobase-ai";
14
+ import { resolveTranslatableText } from "@/lib/i18n";
15
+ import type { Role } from "@/lib/nocobase/acl";
16
+ import type { UserRecord } from "./types";
17
+
18
+ const getRoleLabel = (role: Role) =>
19
+ resolveTranslatableText(role.title || role.name, { ns: "starter" });
20
+
21
+ const isRootUser = (record: UserRecord) =>
22
+ record.roles?.some((role) => role.name === "root") ?? false;
23
+
24
+ export const UserList = () => {
25
+ const translate = useTranslate();
26
+ const getLocale = useGetLocale();
27
+ const locale = getLocale();
28
+
29
+ const columns = useMemo(() => {
30
+ const columnHelper = createColumnHelper<UserRecord>();
31
+
32
+ return [
33
+ columnHelper.accessor("nickname", {
34
+ id: "nickname",
35
+ header: translate("users.fields.nickname", { ns: "app" }, "Nickname"),
36
+ enableSorting: true,
37
+ cell: ({ row, getValue }) =>
38
+ getValue() || row.original.username || row.original.email || "-",
39
+ }),
40
+ columnHelper.accessor("username", {
41
+ id: "username",
42
+ header: translate("users.fields.username", { ns: "app" }, "Username"),
43
+ enableSorting: true,
44
+ cell: ({ getValue }) => getValue() || "-",
45
+ }),
46
+ columnHelper.accessor("email", {
47
+ id: "email",
48
+ header: translate("users.fields.email", { ns: "app" }, "Email"),
49
+ enableSorting: true,
50
+ cell: ({ getValue }) => getValue() || "-",
51
+ }),
52
+ columnHelper.accessor("phone", {
53
+ id: "phone",
54
+ header: translate("users.fields.phone", { ns: "app" }, "Phone"),
55
+ enableSorting: true,
56
+ cell: ({ getValue }) => getValue() || "-",
57
+ }),
58
+ columnHelper.accessor("roles", {
59
+ id: "roles",
60
+ header: translate("users.fields.roles", { ns: "app" }, "Roles"),
61
+ enableSorting: false,
62
+ cell: ({ getValue }) => {
63
+ const roles = getValue() ?? [];
64
+ return roles.length ? (
65
+ <div className="flex flex-wrap gap-1">
66
+ {roles.map((role) => (
67
+ <Badge key={role.name} variant="secondary">
68
+ {getRoleLabel(role)}
69
+ </Badge>
70
+ ))}
71
+ </div>
72
+ ) : (
73
+ "-"
74
+ );
75
+ },
76
+ }),
77
+ columnHelper.accessor("createdAt", {
78
+ id: "createdAt",
79
+ header: translate(
80
+ "users.fields.createdAt",
81
+ { ns: "app" },
82
+ "Created at"
83
+ ),
84
+ enableSorting: true,
85
+ cell: ({ getValue }) => {
86
+ const value = getValue();
87
+ return value
88
+ ? new Intl.DateTimeFormat(locale, {
89
+ dateStyle: "medium",
90
+ }).format(new Date(value))
91
+ : "-";
92
+ },
93
+ }),
94
+ columnHelper.display({
95
+ id: "actions",
96
+ header: translate("users.fields.actions", { ns: "app" }, "Actions"),
97
+ cell: ({ row }) => (
98
+ <div className="flex items-center gap-1">
99
+ <EditButton
100
+ recordItemId={row.original.id}
101
+ variant="ghost"
102
+ size="icon"
103
+ aria-label={translate(
104
+ "users.actions.edit",
105
+ { ns: "app" },
106
+ "Edit user"
107
+ )}
108
+ title={translate(
109
+ "users.actions.edit",
110
+ { ns: "app" },
111
+ "Edit user"
112
+ )}
113
+ >
114
+ <Pencil />
115
+ </EditButton>
116
+ <ShowButton
117
+ recordItemId={row.original.id}
118
+ variant="ghost"
119
+ size="icon"
120
+ aria-label={translate(
121
+ "users.actions.view",
122
+ { ns: "app" },
123
+ "View user"
124
+ )}
125
+ title={translate(
126
+ "users.actions.view",
127
+ { ns: "app" },
128
+ "View user"
129
+ )}
130
+ >
131
+ <Eye />
132
+ </ShowButton>
133
+ {isRootUser(row.original) ? null : (
134
+ <DeleteButton
135
+ recordItemId={row.original.id}
136
+ variant="ghost"
137
+ size="icon"
138
+ className="text-destructive hover:text-destructive"
139
+ aria-label={translate(
140
+ "users.actions.delete",
141
+ { ns: "app" },
142
+ "Delete user"
143
+ )}
144
+ title={translate(
145
+ "users.actions.delete",
146
+ { ns: "app" },
147
+ "Delete user"
148
+ )}
149
+ >
150
+ <Trash2 />
151
+ </DeleteButton>
152
+ )}
153
+ </div>
154
+ ),
155
+ enableSorting: false,
156
+ size: 144,
157
+ }),
158
+ ];
159
+ }, [locale, translate]);
160
+
161
+ const table = useTable<UserRecord>({
162
+ columns,
163
+ refineCoreProps: {
164
+ syncWithLocation: true,
165
+ meta: {
166
+ appends: ["roles"],
167
+ },
168
+ sorters: {
169
+ initial: [{ field: "createdAt", order: "desc" }],
170
+ },
171
+ },
172
+ });
173
+
174
+ const tableContext = useAIPageElementHandle({
175
+ id: "users-table",
176
+ title: translate("users.ai.table", { ns: "app" }, "Users table"),
177
+ kind: "table",
178
+ getContext: () => ({
179
+ resource: "users",
180
+ page: table.refineCore.currentPage,
181
+ pageSize: table.refineCore.pageSize,
182
+ total: table.refineCore.tableQuery.data?.total ?? 0,
183
+ rows: (table.refineCore.tableQuery.data?.data ?? []).map((record) => ({
184
+ id: record.id,
185
+ nickname: record.nickname,
186
+ username: record.username,
187
+ email: record.email,
188
+ phone: record.phone,
189
+ roles: record.roles?.map((role) => ({
190
+ name: role.name,
191
+ title: getRoleLabel(role),
192
+ })),
193
+ createdAt: record.createdAt,
194
+ })),
195
+ }),
196
+ });
197
+
198
+ return (
199
+ <ListView>
200
+ <div ref={tableContext.ref}>
201
+ <DataTable table={table} />
202
+ </div>
203
+ </ListView>
204
+ );
205
+ };
@@ -0,0 +1,187 @@
1
+ import {
2
+ useGetLocale,
3
+ useResourceParams,
4
+ useShow,
5
+ useTranslate,
6
+ } from "@refinedev/core";
7
+
8
+ import { ShowView } from "@/components/resources/views/show-view";
9
+ import { Badge } from "@/components/ui/badge";
10
+ import {
11
+ Card,
12
+ CardContent,
13
+ CardDescription,
14
+ CardHeader,
15
+ CardTitle,
16
+ } from "@/components/ui/card";
17
+ import { Separator } from "@/components/ui/separator";
18
+ import { useAIPageElementHandle } from "@/extensions/nocobase-ai";
19
+ import { resolveTranslatableText } from "@/lib/i18n";
20
+ import type { UserRecord } from "./types";
21
+
22
+ export const UserShow = () => {
23
+ const translate = useTranslate();
24
+ const getLocale = useGetLocale();
25
+ const locale = getLocale();
26
+ const { id } = useResourceParams();
27
+ const { result: record } = useShow<UserRecord>({
28
+ meta: {
29
+ appends: ["roles"],
30
+ },
31
+ });
32
+
33
+ const displayName =
34
+ record?.nickname ||
35
+ record?.username ||
36
+ record?.email ||
37
+ translate("users.detail.unnamed", { ns: "app" }, "Unnamed user");
38
+ const roleLabels =
39
+ record?.roles?.map((role) =>
40
+ resolveTranslatableText(role.title || role.name, { ns: "starter" })
41
+ ) ?? [];
42
+ const detailContext = useAIPageElementHandle({
43
+ id: `users-detail-${id ?? "current"}`,
44
+ title: `${translate(
45
+ "users.ai.detail",
46
+ { ns: "app" },
47
+ "User details"
48
+ )}: ${displayName}`,
49
+ kind: "detail",
50
+ getContext: () => ({
51
+ resource: "users",
52
+ record: {
53
+ id: record?.id,
54
+ nickname: record?.nickname,
55
+ username: record?.username,
56
+ email: record?.email,
57
+ phone: record?.phone,
58
+ roles: roleLabels,
59
+ createdAt: record?.createdAt,
60
+ updatedAt: record?.updatedAt,
61
+ },
62
+ }),
63
+ });
64
+
65
+ const formatDate = (value?: string) =>
66
+ value
67
+ ? new Intl.DateTimeFormat(locale, {
68
+ dateStyle: "medium",
69
+ timeStyle: "short",
70
+ }).format(new Date(value))
71
+ : "-";
72
+
73
+ return (
74
+ <ShowView title={displayName}>
75
+ <Card ref={detailContext.ref} className="max-w-4xl">
76
+ <CardHeader>
77
+ <CardTitle>{displayName}</CardTitle>
78
+ <CardDescription>ID: {record?.id ?? "-"}</CardDescription>
79
+ </CardHeader>
80
+ <CardContent className="space-y-5">
81
+ <DetailSection
82
+ title={translate(
83
+ "users.detail.identity",
84
+ { ns: "app" },
85
+ "Identity"
86
+ )}
87
+ items={[
88
+ [
89
+ translate("users.fields.nickname", { ns: "app" }, "Nickname"),
90
+ record?.nickname || "-",
91
+ ],
92
+ [
93
+ translate("users.fields.username", { ns: "app" }, "Username"),
94
+ record?.username || "-",
95
+ ],
96
+ ]}
97
+ />
98
+ <Separator />
99
+ <DetailSection
100
+ title={translate("users.detail.contact", { ns: "app" }, "Contact")}
101
+ items={[
102
+ [
103
+ translate("users.fields.email", { ns: "app" }, "Email"),
104
+ record?.email || "-",
105
+ ],
106
+ [
107
+ translate("users.fields.phone", { ns: "app" }, "Phone"),
108
+ record?.phone || "-",
109
+ ],
110
+ ]}
111
+ />
112
+ <Separator />
113
+ <section className="space-y-3">
114
+ <h3 className="text-sm font-medium">
115
+ {translate("users.detail.access", { ns: "app" }, "Access")}
116
+ </h3>
117
+ <div className="flex flex-wrap gap-2">
118
+ {roleLabels.length ? (
119
+ roleLabels.map((role, index) => (
120
+ <Badge key={`${role}-${index}`} variant="secondary">
121
+ {role}
122
+ </Badge>
123
+ ))
124
+ ) : (
125
+ <span className="text-sm text-muted-foreground">
126
+ {translate(
127
+ "users.detail.noRoles",
128
+ { ns: "app" },
129
+ "No assigned roles"
130
+ )}
131
+ </span>
132
+ )}
133
+ </div>
134
+ </section>
135
+ <Separator />
136
+ <DetailSection
137
+ title={translate(
138
+ "users.detail.timestamps",
139
+ { ns: "app" },
140
+ "Timestamps"
141
+ )}
142
+ items={[
143
+ [
144
+ translate(
145
+ "users.fields.createdAt",
146
+ { ns: "app" },
147
+ "Created at"
148
+ ),
149
+ formatDate(record?.createdAt),
150
+ ],
151
+ [
152
+ translate(
153
+ "users.fields.updatedAt",
154
+ { ns: "app" },
155
+ "Updated at"
156
+ ),
157
+ formatDate(record?.updatedAt),
158
+ ],
159
+ ]}
160
+ />
161
+ </CardContent>
162
+ </Card>
163
+ </ShowView>
164
+ );
165
+ };
166
+
167
+ function DetailSection({
168
+ title,
169
+ items,
170
+ }: {
171
+ title: string;
172
+ items: Array<[label: string, value: string | number]>;
173
+ }) {
174
+ return (
175
+ <section className="space-y-3">
176
+ <h3 className="text-sm font-medium">{title}</h3>
177
+ <dl className="grid gap-4 sm:grid-cols-2">
178
+ {items.map(([label, value]) => (
179
+ <div key={label} className="space-y-1">
180
+ <dt className="text-xs text-muted-foreground">{label}</dt>
181
+ <dd className="text-sm font-medium">{value}</dd>
182
+ </div>
183
+ ))}
184
+ </dl>
185
+ </section>
186
+ );
187
+ }
@@ -0,0 +1,20 @@
1
+ import type { Role } from "@/lib/nocobase/acl";
2
+
3
+ export type UserRecord = {
4
+ id: string | number;
5
+ nickname?: string;
6
+ username?: string;
7
+ email?: string;
8
+ phone?: string;
9
+ roles?: Role[];
10
+ createdAt?: string;
11
+ updatedAt?: string;
12
+ };
13
+
14
+ export type UserFormValues = {
15
+ nickname: string;
16
+ username: string;
17
+ email: string;
18
+ phone: string;
19
+ password?: string;
20
+ };
@@ -36,5 +36,6 @@ export const API_URL = toProxyRelativeUrl(rawApiUrl, proxyTarget);
36
36
  export const API_ORIGIN = getDefaultProxyTarget(rawApiUrl);
37
37
  export const NOCOBASE_TOKEN_KEY = "nocobase-auth-token";
38
38
  export const NOCOBASE_ROLE_KEY = "nocobase-auth-role";
39
+ export const NOCOBASE_LOCALE_KEY = "nocobase-locale";
39
40
  export const NOCOBASE_AUTHENTICATOR =
40
41
  import.meta.env.NOCOBASE_AUTHENTICATOR ?? "basic";
@@ -1,177 +0,0 @@
1
- import { useForm } from "@refinedev/react-hook-form";
2
- import { useSelect } from "@refinedev/core";
3
- import { useNavigate } from "react-router";
4
- import { Textarea } from "@/components/ui/textarea";
5
-
6
- import { CreateView } from "@/components/resources/views/create-view";
7
- import { Button } from "@/components/ui/button";
8
- import { Input } from "@/components/ui/input";
9
- import {
10
- Select,
11
- SelectContent,
12
- SelectItem,
13
- SelectTrigger,
14
- SelectValue,
15
- } from "@/components/ui/select";
16
- import {
17
- Form,
18
- FormControl,
19
- FormField,
20
- FormItem,
21
- FormLabel,
22
- FormMessage,
23
- } from "@/components/ui/form";
24
-
25
- export const BlogPostCreate = () => {
26
- const navigate = useNavigate();
27
-
28
- const {
29
- refineCore: { onFinish },
30
- ...form
31
- } = useForm({
32
- refineCoreProps: {},
33
- });
34
-
35
- const { options: categoryOptions } = useSelect({
36
- resource: "categories",
37
- optionLabel: "title",
38
- optionValue: "id",
39
- pagination: {
40
- currentPage: 1,
41
- pageSize: 100,
42
- },
43
- });
44
-
45
- function onSubmit(values: Record<string, string>) {
46
- onFinish(values);
47
- }
48
-
49
- return (
50
- <CreateView>
51
- <Form {...form}>
52
- <form onSubmit={form.handleSubmit(onSubmit)} className="resource-form">
53
- <FormField
54
- control={form.control}
55
- name="title"
56
- rules={{ required: "Title is required" }}
57
- render={({ field }) => (
58
- <FormItem>
59
- <FormLabel>Title</FormLabel>
60
- <FormControl
61
- render={<Input
62
- {...field}
63
- value={field.value || ""}
64
- placeholder="Enter title"
65
- />}
66
- />
67
- <FormMessage />
68
- </FormItem>
69
- )}
70
- />
71
-
72
- <FormField
73
- control={form.control}
74
- name="content"
75
- rules={{ required: "Content is required" }}
76
- render={({ field }) => (
77
- <FormItem>
78
- <FormLabel>Content</FormLabel>
79
- <FormControl
80
- render={<Textarea
81
- {...field}
82
- value={field.value || ""}
83
- placeholder="Enter content"
84
- rows={10}
85
- />}
86
- />
87
- <FormMessage />
88
- </FormItem>
89
- )}
90
- />
91
-
92
- <FormField
93
- control={form.control}
94
- name="categoryId"
95
- rules={{ required: "Category is required" }}
96
- render={({ field }) => (
97
- <FormItem>
98
- <FormLabel>Category</FormLabel>
99
- <Select
100
- items={categoryOptions?.map((option) => ({
101
- label: option.label,
102
- value: option.value.toString(),
103
- }))}
104
- onValueChange={field.onChange}
105
- value={field.value?.toString() || ""}
106
- >
107
- <FormControl
108
- render={<SelectTrigger>
109
- <SelectValue placeholder="Select a category" />
110
- </SelectTrigger>}
111
- />
112
- <SelectContent>
113
- {categoryOptions?.map((option) => (
114
- <SelectItem key={option.value} value={option.value.toString()}>
115
- {option.label}
116
- </SelectItem>
117
- ))}
118
- </SelectContent>
119
- </Select>
120
- <FormMessage />
121
- </FormItem>
122
- )}
123
- />
124
-
125
- <FormField
126
- control={form.control}
127
- name="status"
128
- rules={{ required: "Status is required" }}
129
- render={({ field }) => (
130
- <FormItem>
131
- <FormLabel>Status</FormLabel>
132
- <Select
133
- items={{
134
- draft: "Draft",
135
- published: "Published",
136
- rejected: "Rejected",
137
- }}
138
- onValueChange={field.onChange}
139
- defaultValue={"draft"}
140
- >
141
- <FormControl
142
- render={<SelectTrigger>
143
- <SelectValue placeholder="Select status" />
144
- </SelectTrigger>}
145
- />
146
- <SelectContent>
147
- <SelectItem value="draft">Draft</SelectItem>
148
- <SelectItem value="published">Published</SelectItem>
149
- <SelectItem value="rejected">Rejected</SelectItem>
150
- </SelectContent>
151
- </Select>
152
- <FormMessage />
153
- </FormItem>
154
- )}
155
- />
156
-
157
- <div className="flex gap-2">
158
- <Button
159
- type="submit"
160
- {...form.saveButtonProps}
161
- disabled={form.formState.isSubmitting}
162
- >
163
- {form.formState.isSubmitting ? "Creating..." : "Create"}
164
- </Button>
165
- <Button
166
- type="button"
167
- variant="outline"
168
- onClick={() => navigate(-1)}
169
- >
170
- Cancel
171
- </Button>
172
- </div>
173
- </form>
174
- </Form>
175
- </CreateView>
176
- );
177
- };