@dashadmin/dash-admin 1.3.44 → 1.3.46

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 (53) hide show
  1. package/dist/components/i18n/BridgedLocalesMenuButton.js +1 -2
  2. package/dist/components/menu/DarkToggleMode.js +1 -1
  3. package/dist/components/serviceAccount/ApiKeyRevealDialog.js +110 -0
  4. package/dist/components/serviceAccount/ServiceAccountStatusSwitch.js +94 -0
  5. package/dist/components/subscription/PlanPricesSettings.js +4 -2
  6. package/dist/components/tenancy/GlobalTenantWrapper.js +64 -0
  7. package/dist/components/tenancy/TenantSwitcher.js +389 -0
  8. package/dist/contexts/com/components/EchoNotificationsCenter.js +40 -0
  9. package/dist/default-theme/menu/AppMenuComponents/expanded/SidebarItem.js +1 -0
  10. package/dist/index.js +60 -40
  11. package/dist/pages/Profile.js +32 -38
  12. package/dist/providers/i18n/en.js +108 -0
  13. package/dist/providers/i18n/es.js +108 -0
  14. package/dist/providers/i18n/languages.js +3 -1
  15. package/dist/providers/i18n/mergeTranslations.js +18 -0
  16. package/dist/resources/User/components/AvatarUploadField.js +61 -0
  17. package/dist/resources/User/components/PasswordFields.js +67 -0
  18. package/dist/resources/User/getProfileMenu.js +36 -0
  19. package/dist/resources/User/profileResource.js +33 -0
  20. package/dist/resources/User/userLayout.js +58 -0
  21. package/dist/resources/User/userResource.js +90 -0
  22. package/dist/resources/User/userSchema.js +206 -0
  23. package/dist/resources/User/usersFilters.js +22 -0
  24. package/dist/systemResources.js +30 -19
  25. package/dist/templates/ResourceTemplateCreate.js +5 -2
  26. package/package.json +4 -1
  27. package/src/components/i18n/BridgedLocalesMenuButton.tsx +1 -2
  28. package/src/components/menu/DarkToggleMode.tsx +1 -1
  29. package/src/components/serviceAccount/ApiKeyRevealDialog.tsx +129 -0
  30. package/src/components/serviceAccount/ServiceAccountStatusSwitch.tsx +99 -0
  31. package/src/components/subscription/PlanPricesSettings.tsx +2 -2
  32. package/src/components/tenancy/GlobalTenantWrapper.tsx +84 -0
  33. package/src/components/tenancy/TenantSwitcher.tsx +485 -0
  34. package/src/contexts/com/components/EchoNotificationsCenter.tsx +65 -0
  35. package/src/default-theme/menu/AppMaterialMenu.tsx +1 -1
  36. package/src/default-theme/menu/AppMenuComponents/expanded/SidebarItem.tsx +1 -0
  37. package/src/default-theme/menu/SidebarActions.tsx +1 -1
  38. package/src/index.tsx +15 -0
  39. package/src/pages/Profile.tsx +69 -107
  40. package/src/providers/i18n/en.ts +113 -0
  41. package/src/providers/i18n/es.ts +113 -0
  42. package/src/providers/i18n/languages.tsx +3 -0
  43. package/src/providers/i18n/mergeTranslations.ts +53 -0
  44. package/src/resources/User/components/AvatarUploadField.tsx +57 -0
  45. package/src/resources/User/components/PasswordFields.tsx +73 -0
  46. package/src/resources/User/getProfileMenu.ts +57 -0
  47. package/src/resources/User/profileResource.tsx +51 -0
  48. package/src/resources/User/userLayout.tsx +62 -0
  49. package/src/resources/User/userResource.tsx +111 -0
  50. package/src/resources/User/userSchema.tsx +213 -0
  51. package/src/resources/User/usersFilters.tsx +21 -0
  52. package/src/systemResources.tsx +31 -20
  53. package/src/templates/ResourceTemplateCreate.tsx +17 -2
@@ -0,0 +1,58 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Box, Card, Grid } from "@mui/material";
3
+ const userLayout = (render) => {
4
+ return /* @__PURE__ */ jsxs(Grid, { container: true, spacing: 1, children: [
5
+ /* @__PURE__ */ jsx(Grid, { size: { xs: 12, md: 4 }, children: /* @__PURE__ */ jsxs(Grid, { container: true, children: [
6
+ /* @__PURE__ */ jsx(Grid, { size: 12, children: /* @__PURE__ */ jsxs(Card, { sx: { mb: 1 }, className: "dash-widget", children: [
7
+ /* @__PURE__ */ jsx(
8
+ Box,
9
+ {
10
+ sx: { p: 1 },
11
+ style: { display: "flex", alignItems: "center" },
12
+ children: /* @__PURE__ */ jsx("span", { className: "card-title", children: "Avatar" })
13
+ }
14
+ ),
15
+ /* @__PURE__ */ jsx(Box, { sx: { p: 1 }, children: render("Avatar") })
16
+ ] }) }),
17
+ /* @__PURE__ */ jsx(Grid, { size: 12, children: /* @__PURE__ */ jsxs(Card, { sx: { mb: 1 }, className: "dash-widget", children: [
18
+ /* @__PURE__ */ jsx(
19
+ Box,
20
+ {
21
+ sx: { p: 1 },
22
+ style: { display: "flex", alignItems: "center" },
23
+ children: /* @__PURE__ */ jsx("span", { className: "card-title", children: "Credenciales" })
24
+ }
25
+ ),
26
+ /* @__PURE__ */ jsx(Box, { sx: { p: 1 }, children: render("Credenciales") })
27
+ ] }) })
28
+ ] }) }),
29
+ /* @__PURE__ */ jsx(Grid, { size: { xs: 12, md: 8 }, children: /* @__PURE__ */ jsxs(Grid, { container: true, children: [
30
+ /* @__PURE__ */ jsx(Grid, { size: 12, children: /* @__PURE__ */ jsxs(Card, { sx: { mb: 1 }, className: "dash-widget", children: [
31
+ /* @__PURE__ */ jsx(
32
+ Box,
33
+ {
34
+ sx: { p: 1 },
35
+ style: { display: "flex", alignItems: "center" },
36
+ children: /* @__PURE__ */ jsx("span", { className: "card-title", children: "Datos del usuario" })
37
+ }
38
+ ),
39
+ /* @__PURE__ */ jsx(Box, { sx: { p: 1 }, children: render("Datos del usuario") })
40
+ ] }) }),
41
+ /* @__PURE__ */ jsx(Grid, { size: 12, children: /* @__PURE__ */ jsxs(Card, { sx: { mb: 1 }, className: "dash-widget", children: [
42
+ /* @__PURE__ */ jsx(
43
+ Box,
44
+ {
45
+ sx: { p: 1 },
46
+ style: { display: "flex", alignItems: "center" },
47
+ children: /* @__PURE__ */ jsx("span", { className: "card-title", children: "Configuraci\xF3n" })
48
+ }
49
+ ),
50
+ /* @__PURE__ */ jsx(Box, { sx: { p: 1 }, children: render("Configuraci\xF3n") })
51
+ ] }) })
52
+ ] }) })
53
+ ] });
54
+ };
55
+ var userLayout_default = userLayout;
56
+ export {
57
+ userLayout_default as default
58
+ };
@@ -0,0 +1,90 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import ResourceTemplate from "../../templates/ResourceTemplate";
3
+ import Settings from "@mui/icons-material/Settings";
4
+ import { PaginationComponent } from "dash-components";
5
+ import { DASHAppConstants } from "dash-constants";
6
+ import { DASHAdminSystemConstants } from "dash-constants";
7
+ import userLayout from "./userLayout";
8
+ import userSchema from "./userSchema";
9
+ import { NoResults } from "../../components/misc/NoResults";
10
+ import usersFilters from "./usersFilters";
11
+ const userResource = {
12
+ filterWithSubmit: true,
13
+ roles: [
14
+ DASHAdminSystemConstants.system.DASH_SYSTEM_ROLE,
15
+ DASHAppConstants.system.TENANT_ROLE
16
+ ],
17
+ component: ResourceTemplate,
18
+ trash: true,
19
+ model: "tenant/user",
20
+ schema: userSchema,
21
+ icon: /* @__PURE__ */ jsx(Settings, {}),
22
+ group: "resource.groups.configuration",
23
+ label: "resource.ecommerce.users.label",
24
+ menu: [
25
+ {
26
+ title: "Usuarios",
27
+ redirect: "/tenant/user"
28
+ },
29
+ {
30
+ title: "\u{1F5D1}",
31
+ redirect: "/tenant/user/trash"
32
+ }
33
+ ],
34
+ resourceMenuDisabled: false,
35
+ refreshAfter: true,
36
+ /*customListActions: (props) => {
37
+ const { children } = props;
38
+ return <Card sx={{ mb: 2 }} className='dash-card-content dash-module top-toolbar'>{children}</Card>;
39
+ },*/
40
+ isFormData: true,
41
+ /** @deprecated TODO: side effects? seems is not implemented anywhere */
42
+ listEditButton: { enabled: true },
43
+ postFormatter: (params, _method) => {
44
+ if (!params.avatar) {
45
+ delete params.avatar;
46
+ }
47
+ return params;
48
+ },
49
+ dataGridProps: { stickyHeader: true, empty: /* @__PURE__ */ jsx(NoResults, {}), bulkActionButtons: false },
50
+ listProps: { perPage: DASHAdminSystemConstants.system.DEFAULT_PER_PAGE || 50, empty: false },
51
+ dataGridWrapper: (props) => {
52
+ const { children } = props;
53
+ return /* @__PURE__ */ jsx("div", { className: "sticky-header-wrapper", children });
54
+ },
55
+ redirectAfterUpdate: false,
56
+ createLayout: userLayout,
57
+ editLayout: userLayout,
58
+ formGroupMode: "layout",
59
+ /*exporter: (clients) => {
60
+ const clientsForExport = clients.map((client) => {
61
+ return client;
62
+ });
63
+ jsonExport(
64
+ clientsForExport,
65
+ {
66
+ //headers: ['id', 'title', 'author_name', 'body'] // order fields in the export
67
+ },
68
+ (err, csv) => {
69
+ downloadCSV(csv, 'clientes');
70
+ },
71
+ );
72
+ },*/
73
+ create: true,
74
+ edit: true,
75
+ view: false,
76
+ listViewButton: { enabled: false },
77
+ toolbarCreateButton: { enabled: true },
78
+ toolbarEditButton: { enabled: false },
79
+ toolbarExportButton: { enabled: false },
80
+ toolbarListButton: { enabled: true },
81
+ listDeleteButton: { enabled: true, props: { confirm: true } },
82
+ Pagination: PaginationComponent,
83
+ drawer: false,
84
+ mutationMode: "pessimistic",
85
+ referenceFilters: usersFilters
86
+ };
87
+ var userResource_default = userResource;
88
+ export {
89
+ userResource_default as default
90
+ };
@@ -0,0 +1,206 @@
1
+ import { SelectArrayInput } from "react-admin";
2
+ import { RutValidator } from "../../utils/validators";
3
+ import AvatarUploadField from "./components/AvatarUploadField";
4
+ import PasswordFields from "./components/PasswordFields";
5
+ const userSchema = [
6
+ {
7
+ tab: "Datos del usuario",
8
+ attribute: "id",
9
+ label: "Id",
10
+ type: String,
11
+ sortable: true,
12
+ inEdit: false,
13
+ inCreate: false
14
+ },
15
+ {
16
+ tab: "Datos del usuario",
17
+ attribute: "public_id",
18
+ label: "Rut",
19
+ type: String,
20
+ fieldOptions: { required: true, showDiv: true, fullWidth: true },
21
+ sortable: true,
22
+ inEdit: true,
23
+ inCreate: true,
24
+ inShow: true,
25
+ inList: false,
26
+ validate: (value) => {
27
+ if (!value) throw Error("El campo es requerido");
28
+ if (!RutValidator(value)) {
29
+ throw Error("El Rut es inv\xE1lido");
30
+ }
31
+ }
32
+ },
33
+ {
34
+ tab: "Datos del usuario",
35
+ attribute: "name",
36
+ label: "Nombre",
37
+ type: String,
38
+ sortable: true,
39
+ inEdit: true,
40
+ inCreate: true,
41
+ inShow: false,
42
+ inList: false,
43
+ fieldOptions: { required: true }
44
+ },
45
+ {
46
+ tab: "Datos del usuario",
47
+ attribute: "lastname",
48
+ label: "Apellido",
49
+ type: String,
50
+ sortable: true,
51
+ inEdit: true,
52
+ inCreate: true,
53
+ inShow: true,
54
+ inList: false,
55
+ fieldOptions: { required: true }
56
+ },
57
+ {
58
+ tab: "Datos del usuario",
59
+ attribute: "full_name",
60
+ label: "Nombre",
61
+ type: String,
62
+ sortable: true,
63
+ inEdit: false,
64
+ inShow: true,
65
+ inCreate: false,
66
+ fieldOptions: { required: true }
67
+ },
68
+ {
69
+ tab: "Datos del usuario",
70
+ attribute: "email",
71
+ label: "Email",
72
+ type: String,
73
+ sortable: true,
74
+ inEdit: true,
75
+ inShow: true,
76
+ inCreate: true,
77
+ fieldOptions: { required: true }
78
+ },
79
+ {
80
+ tab: "Datos del usuario",
81
+ attribute: "phone",
82
+ label: "Telefono",
83
+ type: String,
84
+ sortable: true,
85
+ inEdit: true,
86
+ inCreate: true,
87
+ inShow: true,
88
+ inList: false
89
+ },
90
+ /*{
91
+ tab: 'Configuración',
92
+ attribute: 'active',
93
+ type: Boolean,
94
+ inList: true,
95
+ inCreate: false,
96
+ label: 'Activo',
97
+ showLabel: false,
98
+ processor: 'Boolean',
99
+ },*/
100
+ {
101
+ tab: "Credenciales",
102
+ attribute: "password",
103
+ label: "Contrase\xF1a",
104
+ type: String,
105
+ sortable: true,
106
+ inEdit: false,
107
+ inCreate: false,
108
+ inShow: false,
109
+ inList: false
110
+ },
111
+ {
112
+ tab: "Credenciales",
113
+ attribute: "password_confirmation",
114
+ label: "Confirma Contrase\xF1a",
115
+ type: String,
116
+ sortable: true,
117
+ inEdit: false,
118
+ inCreate: false,
119
+ inShow: false,
120
+ inList: false
121
+ },
122
+ /*{
123
+ tab: 'Credenciales',
124
+ attribute: 'role_id',
125
+ type: 'system/role/forSelect.name',
126
+ label: 'Rol de usuario',
127
+ componentProps: {
128
+ filter: { substitutable: true } // Shows only substituable roles for current user, backend logic.
129
+ },
130
+ sortable: true,
131
+ inEdit: true,
132
+ inCreate: true,
133
+ inShow: true,
134
+ inList: false,
135
+ multiple: false,
136
+ },*/
137
+ {
138
+ tab: "Credenciales",
139
+ label: "Roles",
140
+ attribute: "role_ids",
141
+ //listAttribute: 'name',
142
+ // system/role is core-owned and works for every domain (kitchntabs-*, vanexa-*),
143
+ // unlike tenant/role which only exists in kitchntabs-backend-domain
144
+ // (Domain\App\Http\Controllers\API\ECommerce\RoleController — an empty
145
+ // Domain\App\Models\ECommerce\Role subclass of the same core Role/roles table,
146
+ // with equivalent privilege-level-based visibility filtering).
147
+ type: "system/role/forSelect.name",
148
+ //type: Object,
149
+ // TODO Agregar un filtro, solo para traer los roles de cliente.
150
+ pagination: false,
151
+ multiple: true,
152
+ //custom: true,
153
+ //componentProps:{ options: {fullwidth: true} },
154
+ component: SelectArrayInput
155
+ //searchField: "subdomain"
156
+ },
157
+ {
158
+ // @TODO Solo mostrar cuando se está creando:
159
+ label: "Genear Contrase\xF1a Aleatorea Autom\xE1ticamente",
160
+ attribute: "generate_password",
161
+ type: Boolean,
162
+ inList: false,
163
+ inEdit: false,
164
+ tab: "Configuraci\xF3n"
165
+ },
166
+ {
167
+ attribute: "active",
168
+ type: Boolean,
169
+ inList: true,
170
+ label: "Activo",
171
+ tab: "Configuraci\xF3n",
172
+ processor: "Boolean"
173
+ },
174
+ {
175
+ tab: "Avatar",
176
+ attribute: "avatar",
177
+ listAttribute: "image_url",
178
+ label: "Foto Usuario",
179
+ type: String,
180
+ sortable: true,
181
+ inEdit: true,
182
+ inCreate: true,
183
+ inShow: true,
184
+ inList: false,
185
+ custom: true,
186
+ component: AvatarUploadField,
187
+ processor: "File"
188
+ },
189
+ {
190
+ tab: "Credenciales",
191
+ attribute: "password",
192
+ label: "Contrase\xF1a",
193
+ type: String,
194
+ sortable: true,
195
+ inList: false,
196
+ inEdit: true,
197
+ inCreate: true,
198
+ custom: true,
199
+ inShow: false,
200
+ component: PasswordFields
201
+ }
202
+ ];
203
+ var userSchema_default = userSchema;
204
+ export {
205
+ userSchema_default as default
206
+ };
@@ -0,0 +1,22 @@
1
+ const usersReferenceFilters = [
2
+ {
3
+ id: "q",
4
+ label: "Nombres/Email/Busqueda General",
5
+ source: "q",
6
+ alwaysOn: true,
7
+ reference: null,
8
+ optionText: null
9
+ },
10
+ {
11
+ id: "email",
12
+ label: "Email",
13
+ source: "email",
14
+ alwaysOn: true,
15
+ reference: null,
16
+ optionText: null
17
+ }
18
+ ];
19
+ var usersFilters_default = usersReferenceFilters;
20
+ export {
21
+ usersFilters_default as default
22
+ };
@@ -29,6 +29,8 @@ import Avatar from "./components/avatar/Avatar";
29
29
  import SystemUpdateAlt from "@mui/icons-material/SystemUpdateAlt";
30
30
  import Person from "@mui/icons-material/Person";
31
31
  import CardMembershipIcon from "@mui/icons-material/CardMembership";
32
+ import PaidIcon from "@mui/icons-material/Paid";
33
+ import TranslateIcon from "@mui/icons-material/Translate";
32
34
  import { lazy } from "react";
33
35
  import BusinessIcon from "@mui/icons-material/Business";
34
36
  import Chip from "@mui/material/Chip";
@@ -878,12 +880,19 @@ const systemResources = [
878
880
  redirectAfterUpdate: false,
879
881
  redirectAfterCreate: true
880
882
  }, drawerSettings),
883
+ // ============ Currency Management (core shared master data) ============
884
+ // Backed by dash-backend core's system/currency route (App\Http\Controllers\
885
+ // API\System\CurrencyController). Previously pointed at the domain's
886
+ // ecommerce/currency route — currencies moved to core as "shared master
887
+ // data" (see CLAUDE.md's core/domain split), so every app using this
888
+ // shared systemResources manifest (kitchntabs-*, vanexa-*) now gets a
889
+ // working Currencies page with no domain layer required.
881
890
  {
882
891
  roles: [DASHAppConstants.system.SYSTEM_ROLE],
883
892
  component: ResourceTemplate,
884
893
  trash: true,
885
- model: "ecommerce/currency",
886
- label: "resource.ecommerce.currencies.label",
894
+ model: "system/currency",
895
+ label: "resource.system.currencies.label",
887
896
  schema: [
888
897
  {
889
898
  label: "C\xF3digo",
@@ -920,23 +929,20 @@ const systemResources = [
920
929
  component: FormatCurrency
921
930
  }
922
931
  ],
923
- //references: [{ reference: 'roles', target: 'role', schema: rolesSchema }]
924
- //references: [{ reference: 'roles', target: 'id', schema: roleSchema }],
925
- icon: /* @__PURE__ */ jsx(Person, {}),
926
- // group: "resource.groups.system_resources",
932
+ icon: /* @__PURE__ */ jsx(PaidIcon, {}),
927
933
  group: "System",
928
934
  menu: [
929
935
  {
930
- title: "resource.ecommerce.currencies.menu_list",
931
- redirect: "/ecommerce/currency"
936
+ title: "resource.system.currencies.menu_list",
937
+ redirect: "/system/currency"
932
938
  },
933
939
  {
934
940
  title: "\u{1F5D1}",
935
- redirect: "/ecommerce/currency/trash"
941
+ redirect: "/system/currency/trash"
936
942
  }
937
943
  ],
938
944
  mainAction: {
939
- title: "resource.ecommerce.currencies.main_action",
945
+ title: "resource.system.currencies.main_action",
940
946
  mode: "create",
941
947
  fn: "virtualhash",
942
948
  redirect: "inline/create"
@@ -950,16 +956,21 @@ const systemResources = [
950
956
  mutationMode: "pessimistic",
951
957
  saveButtonAlwaysEnabled: true,
952
958
  listProps: { storeKey: false, filterDefaultValues: { show_disabled: true } },
953
- // deshabilita persistencia deel estado, cache de los valores seleccionados sort, page, etc.
954
959
  resetSelectedIdsOnLoad: true
955
960
  },
956
- // ============ Language Management ============
961
+ // ============ Language Management (core shared master data) ============
962
+ // Backed by dash-backend core's system/language route (App\Http\Controllers\
963
+ // API\System\LanguageController). Previously pointed at the domain's
964
+ // common/language route — languages moved to core as "shared master data"
965
+ // (see CLAUDE.md's core/domain split), so every app using this shared
966
+ // systemResources manifest (kitchntabs-*, vanexa-*) now gets a working
967
+ // Languages page with no domain layer required.
957
968
  {
958
969
  roles: [DASHAppConstants.system.SYSTEM_ROLE],
959
970
  component: ResourceTemplate,
960
971
  trash: true,
961
- model: "common/language",
962
- label: "resource.common.languages.label",
972
+ model: "system/language",
973
+ label: "resource.system.languages.label",
963
974
  schema: [
964
975
  {
965
976
  label: "Code",
@@ -1003,20 +1014,20 @@ const systemResources = [
1003
1014
  }
1004
1015
  }
1005
1016
  ],
1006
- icon: /* @__PURE__ */ jsx(Person, {}),
1017
+ icon: /* @__PURE__ */ jsx(TranslateIcon, {}),
1007
1018
  group: "System",
1008
1019
  menu: [
1009
1020
  {
1010
- title: "resource.common.languages.menu_list",
1011
- redirect: "/common/language"
1021
+ title: "resource.system.languages.menu_list",
1022
+ redirect: "/system/language"
1012
1023
  },
1013
1024
  {
1014
1025
  title: "\u{1F5D1}",
1015
- redirect: "/common/language/trash"
1026
+ redirect: "/system/language/trash"
1016
1027
  }
1017
1028
  ],
1018
1029
  mainAction: {
1019
- title: "resource.common.languages.main_action",
1030
+ title: "resource.system.languages.main_action",
1020
1031
  mode: "create",
1021
1032
  fn: "virtualhash",
1022
1033
  redirect: "inline/create"
@@ -40,15 +40,18 @@ const ResourceTemplateCreate = (props) => {
40
40
  return `/${path}`.replace(/\/+/g, "/");
41
41
  };
42
42
  const onCreate = (data) => {
43
+ var _a, _b;
43
44
  const translatedLabel = translate(resourceConfig.label, { _: resourceConfig.label });
44
45
  if (_showNotifyAfterSubmit) {
45
46
  notify("dash.resource.created", { type: "success" });
46
47
  }
47
48
  if (_showDialogAfterSubmit) {
49
+ const successOverride = (_a = resourceConfig == null ? void 0 : resourceConfig.createSuccessDialog) == null ? void 0 : _a.call(resourceConfig, data);
50
+ const overrideTitle = typeof (successOverride == null ? void 0 : successOverride.title) === "string" ? translate(successOverride.title, { _: successOverride.title }) : successOverride == null ? void 0 : successOverride.title;
48
51
  dialog({
49
52
  variant: "info",
50
- title: translate("dash.resource.created"),
51
- content: translate("dash.resource.created_message", { label: translatedLabel }),
53
+ title: overrideTitle != null ? overrideTitle : translate("dash.resource.created"),
54
+ content: (_b = successOverride == null ? void 0 : successOverride.content) != null ? _b : translate("dash.resource.created_message", { label: translatedLabel }),
52
55
  onConfirm: () => {
53
56
  switch (resourceConfig == null ? void 0 : resourceConfig.redirectAfterCreate) {
54
57
  case false:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dashadmin/dash-admin",
3
- "version": "1.3.44",
3
+ "version": "1.3.46",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -27,6 +27,9 @@
27
27
  "dependencies": {
28
28
  "@capacitor/core": "^7.4.2",
29
29
  "@capacitor/push-notifications": "^7.0.1",
30
+ "antd": "latest",
31
+ "@tanstack/react-query": "latest",
32
+ "@tanstack/react-query-persist-client": "latest",
30
33
  "@emotion/react": "latest",
31
34
  "@emotion/styled": "latest",
32
35
  "@mui/material": "^7.3.10",
@@ -111,8 +111,7 @@ const BridgedLocalesMenuButton: React.FC<BridgedLocalesMenuButtonProps> = ({
111
111
  <Tooltip title={`Language: ${currentLocaleName}`}>
112
112
  <IconButton
113
113
  onClick={handleClick}
114
- color="inherit"
115
- size="small"
114
+ className="dash-icon-button-color dash-icon-button-bg"
116
115
  aria-label="change language"
117
116
  aria-controls={open ? 'locale-menu' : undefined}
118
117
  aria-haspopup="true"
@@ -38,7 +38,7 @@ const DarkToggleMode = () => {
38
38
 
39
39
  return (
40
40
  <>
41
- <div onClick={onClick}>
41
+ <div className='dash-theme-mode-switcher' onClick={onClick}>
42
42
 
43
43
  <DashThemeHelperProvider>
44
44
  <Avatar
@@ -0,0 +1,129 @@
1
+ import React, { useState } from "react";
2
+ import Alert from "@mui/material/Alert";
3
+ import Box from "@mui/material/Box";
4
+ import IconButton from "@mui/material/IconButton";
5
+ import Tooltip from "@mui/material/Tooltip";
6
+ import Typography from "@mui/material/Typography";
7
+ import ContentCopyIcon from "@mui/icons-material/ContentCopy";
8
+ import CheckIcon from "@mui/icons-material/Check";
9
+ import { useI18nBridge } from "../../contexts/I18nBridgeContext";
10
+
11
+ export interface IApiKeyRevealContentProps {
12
+ /** The plaintext token, shown exactly once. */
13
+ token: string;
14
+ /** Name of the key, for context. */
15
+ name?: string | null;
16
+ }
17
+
18
+ /**
19
+ * One-time reveal of a newly issued API key.
20
+ *
21
+ * The plaintext credential is never persisted in recoverable form - only a
22
+ * short display prefix is stored - so this is the single moment the operator
23
+ * can copy it.
24
+ *
25
+ * Designed to be passed as the `content` of the framework dialog via a
26
+ * resource's `createSuccessDialog`, so it inherits the app's dialog chrome
27
+ * rather than introducing a second dialog system:
28
+ *
29
+ * createSuccessDialog: (data) => data?.plain_text_token
30
+ * ? { title: '...', content: <ApiKeyRevealContent token={data.plain_text_token} name={data.name} /> }
31
+ * : null,
32
+ */
33
+ export const ApiKeyRevealContent: React.FC<IApiKeyRevealContentProps> = ({ token, name }) => {
34
+ // DialogServiceProvider is mounted ABOVE <Admin> in DASHAppProviders, so
35
+ // dialog content renders outside React Admin's i18n context and
36
+ // useTranslate() would silently fall through to the English defaults.
37
+ // The bridge is the supported way to translate from out there - same
38
+ // pattern as AppMaterialMenu.
39
+ const { i18nProvider, locale } = useI18nBridge();
40
+ const [copied, setCopied] = useState(false);
41
+
42
+ const translate = React.useCallback(
43
+ (key: string, options?: any) => {
44
+ if (i18nProvider?.translate) {
45
+ try {
46
+ return i18nProvider.translate(key, options);
47
+ } catch {
48
+ return options?._ ?? key;
49
+ }
50
+ }
51
+ return options?._ ?? key;
52
+ },
53
+ // locale is a dependency so the text re-renders on a language switch.
54
+ [i18nProvider, locale]
55
+ );
56
+
57
+ const handleCopy = async () => {
58
+ try {
59
+ await navigator.clipboard.writeText(token);
60
+ setCopied(true);
61
+ setTimeout(() => setCopied(false), 2000);
62
+ } catch {
63
+ // Clipboard API unavailable (insecure context or denied permission).
64
+ // The value stays selectable so the operator can still copy it by
65
+ // hand - never leave them without a path to the secret.
66
+ setCopied(false);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <Box>
72
+ <Alert severity="warning" sx={{ mb: 2 }}>
73
+ {translate("serviceAccount.reveal.warning", {
74
+ _: "This is the only time this key will be shown. It cannot be recovered later — if you lose it, you will have to issue a new one.",
75
+ })}
76
+ </Alert>
77
+
78
+ {name ? (
79
+ <Typography variant="body2" sx={{ mb: 1 }}>
80
+ {translate("serviceAccount.reveal.forKey", { _: "Key" })}: <strong>{name}</strong>
81
+ </Typography>
82
+ ) : null}
83
+
84
+ <Box
85
+ sx={{
86
+ display: "flex",
87
+ alignItems: "center",
88
+ gap: 1,
89
+ p: 1.5,
90
+ borderRadius: 1,
91
+ border: (theme) => `1px solid ${theme.palette.divider}`,
92
+ bgcolor: (theme) =>
93
+ theme.palette.mode === "dark" ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.04)",
94
+ }}
95
+ >
96
+ <Box
97
+ component="code"
98
+ sx={{
99
+ flex: 1,
100
+ fontFamily: "monospace",
101
+ fontSize: "0.875rem",
102
+ wordBreak: "break-all",
103
+ userSelect: "all",
104
+ }}
105
+ >
106
+ {token}
107
+ </Box>
108
+
109
+ <Tooltip
110
+ title={
111
+ copied
112
+ ? translate("serviceAccount.reveal.copied", { _: "Copied" })
113
+ : translate("serviceAccount.reveal.copy", { _: "Copy" })
114
+ }
115
+ >
116
+ <IconButton onClick={handleCopy} size="small" aria-label="copy api key">
117
+ {copied ? (
118
+ <CheckIcon color="success" fontSize="small" />
119
+ ) : (
120
+ <ContentCopyIcon fontSize="small" />
121
+ )}
122
+ </IconButton>
123
+ </Tooltip>
124
+ </Box>
125
+ </Box>
126
+ );
127
+ };
128
+
129
+ export default ApiKeyRevealContent;