@nocobase/portal-template-default 1.0.3 → 1.0.5
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.
- package/README.MD +10 -5
- package/package.json +17 -17
- package/registry/nocobase-ai/components/chat/chat-message.tsx +8 -6
- package/registry/nocobase-ai/components/chat/chat-messages.tsx +8 -3
- package/registry/nocobase-ai/components/chat/markdown-message.tsx +26 -0
- package/registry/nocobase-ai/components/chat/sub-agent-conversation.tsx +3 -3
- package/registry/nocobase-ai/demo/configuration-gate.tsx +83 -35
- package/registry/nocobase-i18n/README.md +23 -32
- package/registry/nocobase-i18n/components/language-switcher.tsx +1 -1
- package/registry/nocobase-i18n/demo/index.tsx +2 -2
- package/registry/nocobase-i18n/demo/prompt-generator.tsx +3 -2
- package/registry/nocobase-i18n/extension.tsx +3 -4
- package/registry/nocobase-i18n/index.ts +4 -3
- package/registry/nocobase-i18n/locales/en-US.ts +2 -52
- package/registry/nocobase-i18n/locales/index.ts +8 -0
- package/registry/nocobase-i18n/locales/zh-CN.ts +2 -52
- package/registry/nocobase-i18n/provider.tsx +18 -6
- package/registry/nocobase-i18n/server-resources.ts +13 -31
- package/registry/nocobase-i18n/tests/i18n-regression.mjs +14 -5
- package/registry/nocobase-route-surfaces/README.md +21 -0
- package/registry/nocobase-route-surfaces/demo/index.tsx +266 -0
- package/{src/extensions/nocobase-acl → registry/nocobase-route-surfaces}/demo/lazy-route.tsx +2 -1
- package/registry/nocobase-route-surfaces/demo/prompt-generator.tsx +90 -0
- package/registry/nocobase-route-surfaces/demo/scenarios.ts +95 -0
- package/registry/nocobase-route-surfaces/extension.tsx +119 -0
- package/registry/nocobase-route-surfaces/index.ts +7 -0
- package/registry/nocobase-route-surfaces/route-dialog.tsx +145 -0
- package/registry/nocobase-route-surfaces/route-drawer.tsx +186 -0
- package/registry/nocobase-route-surfaces/route-page.tsx +29 -0
- package/registry/nocobase-route-surfaces/route-surface-context.ts +13 -0
- package/registry/nocobase-route-surfaces/use-refine-unsaved-changes.tsx +106 -0
- package/registry/nocobase-route-surfaces/use-route-surface-close.ts +15 -0
- package/registry/nocobase-route-surfaces/use-route-surface-state.ts +65 -0
- package/registry.config.json +30 -3
- package/scripts/registry.mjs +0 -2
- package/src/App.tsx +65 -35
- package/src/app/extension.ts +1 -2
- package/src/app/extensions.tsx +0 -14
- package/src/components/app-shell/breadcrumb.tsx +67 -2
- package/src/components/app-shell/sidebar.tsx +2 -2
- package/src/components/resources/views/list-view.tsx +3 -2
- package/src/components/ui/sheet.tsx +22 -21
- package/src/lib/nocobase/client.ts +6 -1
- package/src/locales/en-US.ts +81 -0
- package/src/locales/zh-CN.ts +77 -0
- package/src/pages/users/create.tsx +76 -53
- package/src/pages/users/edit.tsx +87 -56
- package/src/pages/users/index.ts +3 -0
- package/src/pages/users/layout.tsx +15 -0
- package/src/pages/users/list.tsx +19 -21
- package/src/pages/users/role-badges.tsx +43 -0
- package/src/pages/users/role-detail.tsx +272 -0
- package/src/pages/users/role-utils.ts +6 -0
- package/src/pages/users/routes.ts +24 -0
- package/src/pages/users/show.tsx +195 -104
- package/src/pages/users/types.ts +13 -0
- package/src/providers/i18n/index.ts +3 -0
- package/{registry/nocobase-i18n → src/providers/i18n}/runtime.ts +63 -16
- package/src/providers/system-settings/context.ts +26 -0
- package/src/providers/system-settings/index.ts +2 -0
- package/src/providers/system-settings/provider.tsx +94 -0
- package/src/extensions/nocobase-acl/README.md +0 -19
- package/src/extensions/nocobase-acl/components/acl-boundary.tsx +0 -106
- package/src/extensions/nocobase-acl/components/index.ts +0 -3
- package/src/extensions/nocobase-acl/components/role-options.ts +0 -36
- package/src/extensions/nocobase-acl/components/role-switcher.tsx +0 -135
- package/src/extensions/nocobase-acl/demo/boundary-api.tsx +0 -186
- package/src/extensions/nocobase-acl/demo/components.tsx +0 -231
- package/src/extensions/nocobase-acl/demo/index.tsx +0 -505
- package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +0 -134
- package/src/extensions/nocobase-acl/demo/role-switcher-prompt-generator.tsx +0 -198
- package/src/extensions/nocobase-acl/demo/scenario-section.tsx +0 -37
- package/src/extensions/nocobase-acl/extension.tsx +0 -71
- package/src/extensions/nocobase-acl/index.ts +0 -1
- package/src/extensions/nocobase-ai/README.md +0 -70
- package/src/extensions/nocobase-ai/adapters/react-hook-form.ts +0 -20
- package/src/extensions/nocobase-ai/assets/nocobase-ai-chat.svg +0 -50
- package/src/extensions/nocobase-ai/components/ai-root-provider.tsx +0 -32
- package/src/extensions/nocobase-ai/components/chat/ai-employee-avatar.tsx +0 -31
- package/src/extensions/nocobase-ai/components/chat/chat-attachment.tsx +0 -92
- package/src/extensions/nocobase-ai/components/chat/chat-compact.tsx +0 -90
- package/src/extensions/nocobase-ai/components/chat/chat-composer.tsx +0 -429
- package/src/extensions/nocobase-ai/components/chat/chat-empty-state.tsx +0 -51
- package/src/extensions/nocobase-ai/components/chat/chat-header.tsx +0 -88
- package/src/extensions/nocobase-ai/components/chat/chat-history-dialog.tsx +0 -77
- package/src/extensions/nocobase-ai/components/chat/chat-message.tsx +0 -252
- package/src/extensions/nocobase-ai/components/chat/chat-messages.tsx +0 -173
- package/src/extensions/nocobase-ai/components/chat/chat-window.tsx +0 -118
- package/src/extensions/nocobase-ai/components/chat/conversation-list.tsx +0 -343
- package/src/extensions/nocobase-ai/components/chat/markdown-message.tsx +0 -165
- package/src/extensions/nocobase-ai/components/chat/model-select-options.tsx +0 -30
- package/src/extensions/nocobase-ai/components/chat/reasoning-panel.tsx +0 -29
- package/src/extensions/nocobase-ai/components/chat/sub-agent-conversation.tsx +0 -142
- package/src/extensions/nocobase-ai/components/chat/tool-call-card.tsx +0 -316
- package/src/extensions/nocobase-ai/components/chat/user-prompt-editor.tsx +0 -99
- package/src/extensions/nocobase-ai/components/chat/work-context-chip.tsx +0 -114
- package/src/extensions/nocobase-ai/components/index.ts +0 -59
- package/src/extensions/nocobase-ai/components/page-elements/ai-form.tsx +0 -44
- package/src/extensions/nocobase-ai/components/page-elements/page-element-provider.tsx +0 -469
- package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +0 -31
- package/src/extensions/nocobase-ai/components/surfaces/chat-inline.tsx +0 -18
- package/src/extensions/nocobase-ai/components/surfaces/chat-page.tsx +0 -18
- package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +0 -54
- package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +0 -74
- package/src/extensions/nocobase-ai/components/surfaces/chat-surface-actions.tsx +0 -54
- package/src/extensions/nocobase-ai/components/tools/builtin-tool-renderers.tsx +0 -31
- package/src/extensions/nocobase-ai/components/tools/business-report-dialog.tsx +0 -364
- package/src/extensions/nocobase-ai/components/tools/business-report-renderer.tsx +0 -122
- package/src/extensions/nocobase-ai/components/tools/business-report-utils.tsx +0 -282
- package/src/extensions/nocobase-ai/components/tools/chart-renderer.tsx +0 -99
- package/src/extensions/nocobase-ai/components/tools/echarts-preview.tsx +0 -59
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-advanced.ts +0 -18
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-common.ts +0 -20
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-components.ts +0 -34
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +0 -24
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime.ts +0 -138
- package/src/extensions/nocobase-ai/components/tools/sub-agent-renderer.tsx +0 -67
- package/src/extensions/nocobase-ai/components/tools/suggestions-renderer.tsx +0 -59
- package/src/extensions/nocobase-ai/components/tools/tool-renderer-provider.tsx +0 -55
- package/src/extensions/nocobase-ai/components/tools/tool-renderer-utils.ts +0 -18
- package/src/extensions/nocobase-ai/components/tools/workflow-renderer.tsx +0 -108
- package/src/extensions/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +0 -73
- package/src/extensions/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +0 -149
- package/src/extensions/nocobase-ai/demo/configuration-gate.tsx +0 -49
- package/src/extensions/nocobase-ai/demo/container-showcase.tsx +0 -210
- package/src/extensions/nocobase-ai/demo/floating.tsx +0 -134
- package/src/extensions/nocobase-ai/demo/index.tsx +0 -346
- package/src/extensions/nocobase-ai/demo/interaction-showcase.tsx +0 -151
- package/src/extensions/nocobase-ai/demo/page-context-prompt-generator.tsx +0 -355
- package/src/extensions/nocobase-ai/demo/page-context.tsx +0 -884
- package/src/extensions/nocobase-ai/demo/page-element-showcase.tsx +0 -197
- package/src/extensions/nocobase-ai/demo/prompt-card.tsx +0 -41
- package/src/extensions/nocobase-ai/demo/prompt-generator.tsx +0 -452
- package/src/extensions/nocobase-ai/demo/shortcut.tsx +0 -1207
- package/src/extensions/nocobase-ai/demo/tool-cards.tsx +0 -530
- package/src/extensions/nocobase-ai/extension.tsx +0 -158
- package/src/extensions/nocobase-ai/global-ai-chat.tsx +0 -213
- package/src/extensions/nocobase-ai/index.ts +0 -3
- package/src/extensions/nocobase-ai/providers/ai-provider.tsx +0 -323
- package/src/extensions/nocobase-ai/providers/avatars.ts +0 -817
- package/src/extensions/nocobase-ai/providers/chat-context.tsx +0 -109
- package/src/extensions/nocobase-ai/providers/chat-controller.ts +0 -84
- package/src/extensions/nocobase-ai/providers/chat-message-utils.ts +0 -198
- package/src/extensions/nocobase-ai/providers/chat-provider.tsx +0 -871
- package/src/extensions/nocobase-ai/providers/chat-reducer.ts +0 -147
- package/src/extensions/nocobase-ai/providers/chat-task-utils.ts +0 -105
- package/src/extensions/nocobase-ai/providers/chat-transport.ts +0 -184
- package/src/extensions/nocobase-ai/providers/form-registry.tsx +0 -271
- package/src/extensions/nocobase-ai/providers/frontend-tool-registry.tsx +0 -231
- package/src/extensions/nocobase-ai/providers/index.ts +0 -61
- package/src/extensions/nocobase-ai/providers/model.ts +0 -32
- package/src/extensions/nocobase-ai/providers/page-context.tsx +0 -136
- package/src/extensions/nocobase-ai/providers/stream-coalescer.ts +0 -60
- package/src/extensions/nocobase-ai/providers/stream-event-utils.ts +0 -82
- package/src/extensions/nocobase-ai/providers/stream-parser.ts +0 -61
- package/src/extensions/nocobase-ai/providers/sub-agent-stream.ts +0 -316
- package/src/extensions/nocobase-ai/providers/types.ts +0 -183
- package/src/extensions/nocobase-ai/providers/ui-message-stream.ts +0 -463
- package/src/extensions/nocobase-ai/providers/use-automatic-tool-approval.ts +0 -94
- package/src/extensions/nocobase-ai/providers/use-chat-attachments.ts +0 -206
- package/src/extensions/nocobase-ai/providers/use-chat-message-actions.ts +0 -554
- package/src/extensions/nocobase-ai/providers/use-chat-runtime.ts +0 -210
- package/src/extensions/nocobase-ai/providers/use-chat-work-context.ts +0 -93
- package/src/extensions/nocobase-ai/providers/use-conversation-catalog.ts +0 -108
- package/src/extensions/nocobase-ai/providers/use-conversation-history.ts +0 -178
- package/src/extensions/nocobase-ai/services/index.ts +0 -7
- package/src/extensions/nocobase-ai/services/nocobase-ai-service.ts +0 -454
- package/src/extensions/nocobase-ai/services/types.ts +0 -82
- /package/{registry/nocobase-i18n → src/providers/i18n}/i18n-provider.ts +0 -0
- /package/{registry/nocobase-i18n → src/providers/i18n}/locale-store.ts +0 -0
package/src/pages/users/show.tsx
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useGetLocale,
|
|
3
|
-
useResourceParams,
|
|
4
3
|
useShow,
|
|
5
4
|
useTranslate,
|
|
6
5
|
} from "@refinedev/core";
|
|
6
|
+
import { useNavigate, useOutlet, useParams } from "react-router";
|
|
7
|
+
import { Pencil, RotateCw } from "lucide-react";
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
CardContent,
|
|
13
|
-
CardDescription,
|
|
14
|
-
CardHeader,
|
|
15
|
-
CardTitle,
|
|
16
|
-
} from "@/components/ui/card";
|
|
9
|
+
import { LoadingState } from "@/components/app-shell/loading-state";
|
|
10
|
+
import { EditButton } from "@/components/resources/buttons/edit";
|
|
11
|
+
import { RefreshButton } from "@/components/resources/buttons/refresh";
|
|
12
|
+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
17
13
|
import { Separator } from "@/components/ui/separator";
|
|
14
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
15
|
+
import { RouteDrawer } from "@/extensions/nocobase-route-surfaces";
|
|
18
16
|
import { useAIPageElementHandle } from "@/extensions/nocobase-ai";
|
|
19
|
-
import {
|
|
17
|
+
import { RoleBadges } from "./role-badges";
|
|
18
|
+
import { resolveRoleLabel } from "./role-utils";
|
|
19
|
+
import { getUserRolePath, userRoutes } from "./routes";
|
|
20
20
|
import type { UserRecord } from "./types";
|
|
21
21
|
|
|
22
22
|
export const UserShow = () => {
|
|
23
23
|
const translate = useTranslate();
|
|
24
24
|
const getLocale = useGetLocale();
|
|
25
25
|
const locale = getLocale();
|
|
26
|
-
const
|
|
27
|
-
const {
|
|
26
|
+
const navigate = useNavigate();
|
|
27
|
+
const { id } = useParams<{ id: string }>();
|
|
28
|
+
const nestedDrawer = useOutlet();
|
|
29
|
+
const { result: record, query } = useShow<UserRecord>({
|
|
30
|
+
resource: "users",
|
|
31
|
+
id,
|
|
28
32
|
meta: {
|
|
29
33
|
appends: ["roles"],
|
|
30
34
|
},
|
|
@@ -35,10 +39,7 @@ export const UserShow = () => {
|
|
|
35
39
|
record?.username ||
|
|
36
40
|
record?.email ||
|
|
37
41
|
translate("users.detail.unnamed", { ns: "app" }, "Unnamed user");
|
|
38
|
-
const
|
|
39
|
-
record?.roles?.map((role) =>
|
|
40
|
-
resolveTranslatableText(role.title || role.name, { ns: "starter" })
|
|
41
|
-
) ?? [];
|
|
42
|
+
const roles = record?.roles ?? [];
|
|
42
43
|
const detailContext = useAIPageElementHandle({
|
|
43
44
|
id: `users-detail-${id ?? "current"}`,
|
|
44
45
|
title: `${translate(
|
|
@@ -55,7 +56,10 @@ export const UserShow = () => {
|
|
|
55
56
|
username: record?.username,
|
|
56
57
|
email: record?.email,
|
|
57
58
|
phone: record?.phone,
|
|
58
|
-
roles:
|
|
59
|
+
roles: roles.map((role) => ({
|
|
60
|
+
name: role.name,
|
|
61
|
+
title: resolveRoleLabel(role),
|
|
62
|
+
})),
|
|
59
63
|
createdAt: record?.createdAt,
|
|
60
64
|
updatedAt: record?.updatedAt,
|
|
61
65
|
},
|
|
@@ -71,96 +75,183 @@ export const UserShow = () => {
|
|
|
71
75
|
: "-";
|
|
72
76
|
|
|
73
77
|
return (
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
78
|
+
<RouteDrawer
|
|
79
|
+
title={
|
|
80
|
+
query.isLoading && !record ? (
|
|
81
|
+
<Skeleton className="h-6 w-40" />
|
|
82
|
+
) : (
|
|
83
|
+
displayName
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
description={translate(
|
|
87
|
+
"users.drawer.show.description",
|
|
88
|
+
{ ns: "app" },
|
|
89
|
+
"Review this user's identity, contact information, and roles."
|
|
90
|
+
)}
|
|
91
|
+
closeLabel={translate("buttons.close", "Close")}
|
|
92
|
+
closeTo={userRoutes.list}
|
|
93
|
+
nested={nestedDrawer}
|
|
94
|
+
actions={
|
|
95
|
+
record ? (
|
|
96
|
+
<>
|
|
97
|
+
<RefreshButton
|
|
98
|
+
resource="users"
|
|
99
|
+
recordItemId={record.id}
|
|
100
|
+
variant="outline"
|
|
101
|
+
size="icon-sm"
|
|
102
|
+
aria-label={translate("buttons.refresh", "Refresh")}
|
|
103
|
+
title={translate("buttons.refresh", "Refresh")}
|
|
104
|
+
>
|
|
105
|
+
<RotateCw />
|
|
106
|
+
</RefreshButton>
|
|
107
|
+
<EditButton
|
|
108
|
+
resource="users"
|
|
109
|
+
recordItemId={record.id}
|
|
110
|
+
variant="outline"
|
|
111
|
+
size="icon-sm"
|
|
112
|
+
aria-label={translate(
|
|
113
|
+
"users.actions.edit",
|
|
114
|
+
{ ns: "app" },
|
|
115
|
+
"Edit user"
|
|
116
|
+
)}
|
|
117
|
+
title={translate(
|
|
118
|
+
"users.actions.edit",
|
|
119
|
+
{ ns: "app" },
|
|
120
|
+
"Edit user"
|
|
121
|
+
)}
|
|
122
|
+
onClick={() => navigate("edit")}
|
|
123
|
+
>
|
|
124
|
+
<Pencil />
|
|
125
|
+
</EditButton>
|
|
126
|
+
</>
|
|
127
|
+
) : null
|
|
128
|
+
}
|
|
129
|
+
>
|
|
130
|
+
<div
|
|
131
|
+
ref={detailContext.ref}
|
|
132
|
+
className="min-h-0 flex-1 overflow-y-auto px-5 py-5"
|
|
133
|
+
>
|
|
134
|
+
{query.isLoading ? (
|
|
135
|
+
<LoadingState className="min-h-64" />
|
|
136
|
+
) : query.isError ? (
|
|
137
|
+
<Alert variant="destructive">
|
|
138
|
+
<AlertTitle>
|
|
139
|
+
{translate(
|
|
140
|
+
"users.detail.loadError.title",
|
|
141
|
+
{ ns: "app" },
|
|
142
|
+
"Unable to load user"
|
|
143
|
+
)}
|
|
144
|
+
</AlertTitle>
|
|
145
|
+
<AlertDescription>
|
|
146
|
+
{translate(
|
|
147
|
+
"users.detail.loadError.description",
|
|
148
|
+
{ ns: "app" },
|
|
149
|
+
"The user may no longer exist, or you may not have permission to view it."
|
|
150
|
+
)}
|
|
151
|
+
</AlertDescription>
|
|
152
|
+
</Alert>
|
|
153
|
+
) : (
|
|
154
|
+
<div className="space-y-5">
|
|
155
|
+
<DetailSection
|
|
156
|
+
title={translate(
|
|
157
|
+
"users.detail.identity",
|
|
158
|
+
{ ns: "app" },
|
|
159
|
+
"Identity"
|
|
160
|
+
)}
|
|
161
|
+
items={[
|
|
162
|
+
[
|
|
163
|
+
translate("users.fields.id", { ns: "app" }, "ID"),
|
|
164
|
+
record?.id ?? "-",
|
|
165
|
+
],
|
|
166
|
+
[
|
|
167
|
+
translate(
|
|
168
|
+
"users.fields.nickname",
|
|
169
|
+
{ ns: "app" },
|
|
170
|
+
"Nickname"
|
|
171
|
+
),
|
|
172
|
+
record?.nickname || "-",
|
|
173
|
+
],
|
|
174
|
+
[
|
|
175
|
+
translate(
|
|
176
|
+
"users.fields.username",
|
|
177
|
+
{ ns: "app" },
|
|
178
|
+
"Username"
|
|
179
|
+
),
|
|
180
|
+
record?.username || "-",
|
|
181
|
+
],
|
|
182
|
+
]}
|
|
183
|
+
/>
|
|
184
|
+
|
|
185
|
+
<Separator />
|
|
186
|
+
|
|
187
|
+
<DetailSection
|
|
188
|
+
title={translate(
|
|
189
|
+
"users.detail.contact",
|
|
190
|
+
{ ns: "app" },
|
|
191
|
+
"Contact"
|
|
192
|
+
)}
|
|
193
|
+
items={[
|
|
194
|
+
[
|
|
195
|
+
translate("users.fields.email", { ns: "app" }, "Email"),
|
|
196
|
+
record?.email || "-",
|
|
197
|
+
],
|
|
198
|
+
[
|
|
199
|
+
translate("users.fields.phone", { ns: "app" }, "Phone"),
|
|
200
|
+
record?.phone || "-",
|
|
201
|
+
],
|
|
202
|
+
]}
|
|
203
|
+
/>
|
|
204
|
+
|
|
205
|
+
<Separator />
|
|
206
|
+
|
|
207
|
+
<section className="space-y-3">
|
|
208
|
+
<h3 className="text-sm font-medium">
|
|
209
|
+
{translate("users.detail.access", { ns: "app" }, "Access")}
|
|
210
|
+
</h3>
|
|
211
|
+
<RoleBadges
|
|
212
|
+
roles={roles}
|
|
213
|
+
onSelect={(role) =>
|
|
214
|
+
id ? navigate(getUserRolePath(id, role.name)) : undefined
|
|
215
|
+
}
|
|
216
|
+
empty={translate(
|
|
127
217
|
"users.detail.noRoles",
|
|
128
218
|
{ ns: "app" },
|
|
129
219
|
"No assigned roles"
|
|
130
220
|
)}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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",
|
|
221
|
+
/>
|
|
222
|
+
</section>
|
|
223
|
+
|
|
224
|
+
<Separator />
|
|
225
|
+
|
|
226
|
+
<DetailSection
|
|
227
|
+
title={translate(
|
|
228
|
+
"users.detail.timestamps",
|
|
154
229
|
{ ns: "app" },
|
|
155
|
-
"
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
230
|
+
"Timestamps"
|
|
231
|
+
)}
|
|
232
|
+
items={[
|
|
233
|
+
[
|
|
234
|
+
translate(
|
|
235
|
+
"users.fields.createdAt",
|
|
236
|
+
{ ns: "app" },
|
|
237
|
+
"Created at"
|
|
238
|
+
),
|
|
239
|
+
formatDate(record?.createdAt),
|
|
240
|
+
],
|
|
241
|
+
[
|
|
242
|
+
translate(
|
|
243
|
+
"users.fields.updatedAt",
|
|
244
|
+
{ ns: "app" },
|
|
245
|
+
"Updated at"
|
|
246
|
+
),
|
|
247
|
+
formatDate(record?.updatedAt),
|
|
248
|
+
],
|
|
249
|
+
]}
|
|
250
|
+
/>
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
</RouteDrawer>
|
|
164
255
|
);
|
|
165
256
|
};
|
|
166
257
|
|
|
@@ -178,7 +269,7 @@ function DetailSection({
|
|
|
178
269
|
{items.map(([label, value]) => (
|
|
179
270
|
<div key={label} className="space-y-1">
|
|
180
271
|
<dt className="text-xs text-muted-foreground">{label}</dt>
|
|
181
|
-
<dd className="text-sm font-medium">{value}</dd>
|
|
272
|
+
<dd className="text-sm font-medium break-words">{value}</dd>
|
|
182
273
|
</div>
|
|
183
274
|
))}
|
|
184
275
|
</dl>
|
package/src/pages/users/types.ts
CHANGED
|
@@ -18,3 +18,16 @@ export type UserFormValues = {
|
|
|
18
18
|
phone: string;
|
|
19
19
|
password?: string;
|
|
20
20
|
};
|
|
21
|
+
|
|
22
|
+
export type RoleRecord = Role & {
|
|
23
|
+
id?: string | number;
|
|
24
|
+
description?: string;
|
|
25
|
+
default?: boolean;
|
|
26
|
+
hidden?: boolean;
|
|
27
|
+
allowConfigure?: boolean;
|
|
28
|
+
strategy?: {
|
|
29
|
+
actions?: string[];
|
|
30
|
+
};
|
|
31
|
+
createdAt?: string;
|
|
32
|
+
updatedAt?: string;
|
|
33
|
+
};
|
|
@@ -3,6 +3,7 @@ import { createInstance, type TOptions } from "i18next";
|
|
|
3
3
|
import {
|
|
4
4
|
getTranslationResources,
|
|
5
5
|
registerTranslationResources,
|
|
6
|
+
setTranslationResolver,
|
|
6
7
|
subscribeTranslationResources,
|
|
7
8
|
type TranslationOptions,
|
|
8
9
|
} from "@/lib/i18n";
|
|
@@ -14,15 +15,23 @@ import {
|
|
|
14
15
|
getLocaleLabel,
|
|
15
16
|
registerLocale,
|
|
16
17
|
resolveSupportedLocale,
|
|
18
|
+
setEnabledLocales,
|
|
17
19
|
} from "./locale-store";
|
|
18
|
-
import enUS from "./locales/en-US";
|
|
19
|
-
import zhCN from "./locales/zh-CN";
|
|
20
20
|
|
|
21
21
|
export type LocaleResources = Record<
|
|
22
22
|
string,
|
|
23
23
|
Record<string, string | number | boolean>
|
|
24
24
|
>;
|
|
25
25
|
|
|
26
|
+
export type LocaleSystemSettings = {
|
|
27
|
+
appLang?: string | null;
|
|
28
|
+
enabledLanguages?: string[] | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type LocalePersistence = (locale: string) => void | Promise<void>;
|
|
32
|
+
|
|
33
|
+
let localePersistence: LocalePersistence | undefined;
|
|
34
|
+
|
|
26
35
|
export const i18n = createInstance();
|
|
27
36
|
|
|
28
37
|
void i18n.init({
|
|
@@ -36,10 +45,6 @@ void i18n.init({
|
|
|
36
45
|
interpolation: {
|
|
37
46
|
escapeValue: false,
|
|
38
47
|
},
|
|
39
|
-
resources: {
|
|
40
|
-
"en-US": enUS,
|
|
41
|
-
"zh-CN": zhCN,
|
|
42
|
-
},
|
|
43
48
|
});
|
|
44
49
|
|
|
45
50
|
function addLocaleResources(namespace: string, resources: LocaleResources) {
|
|
@@ -56,7 +61,6 @@ function addLocaleResources(namespace: string, resources: LocaleResources) {
|
|
|
56
61
|
getTranslationResources().forEach(([namespace, resources]) =>
|
|
57
62
|
addLocaleResources(namespace, resources)
|
|
58
63
|
);
|
|
59
|
-
|
|
60
64
|
subscribeTranslationResources(addLocaleResources);
|
|
61
65
|
|
|
62
66
|
export function getCurrentLocale() {
|
|
@@ -94,18 +98,60 @@ export function applyDocumentLocale(locale = getCurrentLocale()) {
|
|
|
94
98
|
document.documentElement.dir = direction;
|
|
95
99
|
}
|
|
96
100
|
|
|
101
|
+
function resolveSystemLocale(settings?: LocaleSystemSettings) {
|
|
102
|
+
const enabledLanguages = Array.isArray(settings?.enabledLanguages)
|
|
103
|
+
? settings.enabledLanguages.filter(Boolean)
|
|
104
|
+
: [];
|
|
105
|
+
const storedLocale = nocobaseClient.getStoredLocale();
|
|
106
|
+
|
|
107
|
+
if (
|
|
108
|
+
storedLocale &&
|
|
109
|
+
(!enabledLanguages.length || enabledLanguages.includes(storedLocale))
|
|
110
|
+
) {
|
|
111
|
+
return resolveSupportedLocale(storedLocale);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const defaultLocale =
|
|
115
|
+
settings?.appLang || enabledLanguages[0] || DEFAULT_LOCALE;
|
|
116
|
+
return resolveSupportedLocale(defaultLocale);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function applySystemLocale(settings?: LocaleSystemSettings) {
|
|
120
|
+
const enabledLanguages = Array.isArray(settings?.enabledLanguages)
|
|
121
|
+
? settings.enabledLanguages.filter(Boolean)
|
|
122
|
+
: [];
|
|
123
|
+
if (enabledLanguages.length) setEnabledLocales(enabledLanguages);
|
|
124
|
+
|
|
125
|
+
const storedLocale = nocobaseClient.getStoredLocale();
|
|
126
|
+
if (
|
|
127
|
+
storedLocale &&
|
|
128
|
+
enabledLanguages.length &&
|
|
129
|
+
!enabledLanguages.includes(storedLocale)
|
|
130
|
+
) {
|
|
131
|
+
nocobaseClient.setLocale(null);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const locale = resolveSystemLocale(settings);
|
|
135
|
+
nocobaseClient.setRuntimeLocale(locale);
|
|
136
|
+
await i18n.changeLanguage(locale);
|
|
137
|
+
applyDocumentLocale(locale);
|
|
138
|
+
return locale;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function setLocalePersistence(persistence?: LocalePersistence) {
|
|
142
|
+
localePersistence = persistence;
|
|
143
|
+
return () => {
|
|
144
|
+
if (localePersistence === persistence) localePersistence = undefined;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
97
148
|
export async function changeLocale(locale: string) {
|
|
98
149
|
const nextLocale = resolveSupportedLocale(locale);
|
|
99
150
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
body: { appLang: nextLocale },
|
|
105
|
-
});
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.warn("Unable to persist the NocoBase language preference", error);
|
|
108
|
-
}
|
|
151
|
+
try {
|
|
152
|
+
await localePersistence?.(nextLocale);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.warn("Unable to persist the language preference", error);
|
|
109
155
|
}
|
|
110
156
|
|
|
111
157
|
nocobaseClient.setLocale(nextLocale);
|
|
@@ -115,4 +161,5 @@ export async function changeLocale(locale: string) {
|
|
|
115
161
|
if (typeof window !== "undefined") window.location.reload();
|
|
116
162
|
}
|
|
117
163
|
|
|
164
|
+
setTranslationResolver(translate);
|
|
118
165
|
applyDocumentLocale();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
export type SystemSettings = Record<string, unknown> & {
|
|
4
|
+
appLang?: string | null;
|
|
5
|
+
enabledLanguages?: string[] | null;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type SystemSettingsContextValue = {
|
|
9
|
+
settings?: SystemSettings;
|
|
10
|
+
error?: Error;
|
|
11
|
+
loading: boolean;
|
|
12
|
+
refresh: () => Promise<SystemSettings | undefined>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const SystemSettingsContext =
|
|
16
|
+
createContext<SystemSettingsContextValue | null>(null);
|
|
17
|
+
|
|
18
|
+
export function useSystemSettings() {
|
|
19
|
+
const value = useContext(SystemSettingsContext);
|
|
20
|
+
if (!value) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"useSystemSettings must be used inside SystemSettingsProvider"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useState,
|
|
6
|
+
type PropsWithChildren,
|
|
7
|
+
} from "react";
|
|
8
|
+
|
|
9
|
+
import { LoadingState } from "@/components/app-shell/loading-state";
|
|
10
|
+
import { nocobaseClient } from "@/lib/nocobase/client";
|
|
11
|
+
import { applySystemLocale } from "../i18n";
|
|
12
|
+
import {
|
|
13
|
+
SystemSettingsContext,
|
|
14
|
+
type SystemSettings,
|
|
15
|
+
type SystemSettingsContextValue,
|
|
16
|
+
} from "./context";
|
|
17
|
+
|
|
18
|
+
let cachedSettings: SystemSettings | undefined;
|
|
19
|
+
let settingsRequest: Promise<SystemSettings> | undefined;
|
|
20
|
+
|
|
21
|
+
function requestSystemSettings(force = false) {
|
|
22
|
+
if (!force && cachedSettings) return Promise.resolve(cachedSettings);
|
|
23
|
+
if (!force && settingsRequest) return settingsRequest;
|
|
24
|
+
|
|
25
|
+
const request = nocobaseClient
|
|
26
|
+
.action<SystemSettings>("systemSettings", "get", {
|
|
27
|
+
method: "GET",
|
|
28
|
+
includeRole: false,
|
|
29
|
+
withAclMeta: false,
|
|
30
|
+
})
|
|
31
|
+
.then((settings) => {
|
|
32
|
+
cachedSettings = settings;
|
|
33
|
+
return settings;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
settingsRequest = request;
|
|
37
|
+
const clearRequest = () => {
|
|
38
|
+
if (settingsRequest === request) settingsRequest = undefined;
|
|
39
|
+
};
|
|
40
|
+
void request.then(clearRequest, clearRequest);
|
|
41
|
+
return request;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function SystemSettingsProvider({ children }: PropsWithChildren) {
|
|
45
|
+
const [settings, setSettings] = useState(cachedSettings);
|
|
46
|
+
const [error, setError] = useState<Error>();
|
|
47
|
+
const [ready, setReady] = useState(false);
|
|
48
|
+
const [loading, setLoading] = useState(true);
|
|
49
|
+
|
|
50
|
+
const load = useCallback(async (force = false) => {
|
|
51
|
+
setLoading(true);
|
|
52
|
+
try {
|
|
53
|
+
const nextSettings = await requestSystemSettings(force);
|
|
54
|
+
await applySystemLocale(nextSettings);
|
|
55
|
+
setSettings(nextSettings);
|
|
56
|
+
setError(undefined);
|
|
57
|
+
return nextSettings;
|
|
58
|
+
} catch (reason) {
|
|
59
|
+
const nextError =
|
|
60
|
+
reason instanceof Error
|
|
61
|
+
? reason
|
|
62
|
+
: new Error("Unable to load system settings");
|
|
63
|
+
console.warn("Unable to load NocoBase system settings", reason);
|
|
64
|
+
setError(nextError);
|
|
65
|
+
await applySystemLocale();
|
|
66
|
+
return undefined;
|
|
67
|
+
} finally {
|
|
68
|
+
setLoading(false);
|
|
69
|
+
setReady(true);
|
|
70
|
+
}
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
void load();
|
|
75
|
+
}, [load]);
|
|
76
|
+
|
|
77
|
+
const value = useMemo<SystemSettingsContextValue>(
|
|
78
|
+
() => ({
|
|
79
|
+
settings,
|
|
80
|
+
error,
|
|
81
|
+
loading,
|
|
82
|
+
refresh: () => load(true),
|
|
83
|
+
}),
|
|
84
|
+
[error, load, loading, settings]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (!ready) return <LoadingState className="min-h-svh" />;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<SystemSettingsContext.Provider value={value}>
|
|
91
|
+
{children}
|
|
92
|
+
</SystemSettingsContext.Provider>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# NocoBase ACL UI
|
|
2
|
-
|
|
3
|
-
Reusable ACL composition components for the NocoBase Refine starter.
|
|
4
|
-
|
|
5
|
-
After installation, import the components from `@/extensions/nocobase-acl`.
|
|
6
|
-
|
|
7
|
-
- `AclPage` combines page-level permissions.
|
|
8
|
-
- `AclRegion` hides or replaces individual data regions.
|
|
9
|
-
- `AclField` applies NocoBase field whitelists.
|
|
10
|
-
- `RoleSwitcher` can be placed in any application surface. It supports
|
|
11
|
-
normal roles, Anonymous when enabled, and Full permissions through the
|
|
12
|
-
`__union__` role when the system role mode allows it.
|
|
13
|
-
|
|
14
|
-
Mark non-collection resources with `meta.acl.type = "authenticated"`, or use
|
|
15
|
-
the `collection`, `snippet`, and `route` ACL metadata variants when the resource
|
|
16
|
-
should participate in NocoBase permission checks.
|
|
17
|
-
|
|
18
|
-
The underlying `accessControlProvider`, `roles:check` cache, record permissions,
|
|
19
|
-
and Refine integration are provided by the compatible Starter.
|