@dashadmin/dash-admin 1.3.42 → 1.3.45

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.
@@ -0,0 +1,110 @@
1
+ var __async = (__this, __arguments, generator) => {
2
+ return new Promise((resolve, reject) => {
3
+ var fulfilled = (value) => {
4
+ try {
5
+ step(generator.next(value));
6
+ } catch (e) {
7
+ reject(e);
8
+ }
9
+ };
10
+ var rejected = (value) => {
11
+ try {
12
+ step(generator.throw(value));
13
+ } catch (e) {
14
+ reject(e);
15
+ }
16
+ };
17
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
18
+ step((generator = generator.apply(__this, __arguments)).next());
19
+ });
20
+ };
21
+ import { jsx, jsxs } from "react/jsx-runtime";
22
+ import React, { useState } from "react";
23
+ import Alert from "@mui/material/Alert";
24
+ import Box from "@mui/material/Box";
25
+ import IconButton from "@mui/material/IconButton";
26
+ import Tooltip from "@mui/material/Tooltip";
27
+ import Typography from "@mui/material/Typography";
28
+ import ContentCopyIcon from "@mui/icons-material/ContentCopy";
29
+ import CheckIcon from "@mui/icons-material/Check";
30
+ import { useI18nBridge } from "../../contexts/I18nBridgeContext";
31
+ const ApiKeyRevealContent = ({ token, name }) => {
32
+ const { i18nProvider, locale } = useI18nBridge();
33
+ const [copied, setCopied] = useState(false);
34
+ const translate = React.useCallback(
35
+ (key, options) => {
36
+ var _a, _b;
37
+ if (i18nProvider == null ? void 0 : i18nProvider.translate) {
38
+ try {
39
+ return i18nProvider.translate(key, options);
40
+ } catch (e) {
41
+ return (_a = options == null ? void 0 : options._) != null ? _a : key;
42
+ }
43
+ }
44
+ return (_b = options == null ? void 0 : options._) != null ? _b : key;
45
+ },
46
+ // locale is a dependency so the text re-renders on a language switch.
47
+ [i18nProvider, locale]
48
+ );
49
+ const handleCopy = () => __async(null, null, function* () {
50
+ try {
51
+ yield navigator.clipboard.writeText(token);
52
+ setCopied(true);
53
+ setTimeout(() => setCopied(false), 2e3);
54
+ } catch (e) {
55
+ setCopied(false);
56
+ }
57
+ });
58
+ return /* @__PURE__ */ jsxs(Box, { children: [
59
+ /* @__PURE__ */ jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: translate("serviceAccount.reveal.warning", {
60
+ _: "This is the only time this key will be shown. It cannot be recovered later \u2014 if you lose it, you will have to issue a new one."
61
+ }) }),
62
+ name ? /* @__PURE__ */ jsxs(Typography, { variant: "body2", sx: { mb: 1 }, children: [
63
+ translate("serviceAccount.reveal.forKey", { _: "Key" }),
64
+ ": ",
65
+ /* @__PURE__ */ jsx("strong", { children: name })
66
+ ] }) : null,
67
+ /* @__PURE__ */ jsxs(
68
+ Box,
69
+ {
70
+ sx: {
71
+ display: "flex",
72
+ alignItems: "center",
73
+ gap: 1,
74
+ p: 1.5,
75
+ borderRadius: 1,
76
+ border: (theme) => `1px solid ${theme.palette.divider}`,
77
+ bgcolor: (theme) => theme.palette.mode === "dark" ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.04)"
78
+ },
79
+ children: [
80
+ /* @__PURE__ */ jsx(
81
+ Box,
82
+ {
83
+ component: "code",
84
+ sx: {
85
+ flex: 1,
86
+ fontFamily: "monospace",
87
+ fontSize: "0.875rem",
88
+ wordBreak: "break-all",
89
+ userSelect: "all"
90
+ },
91
+ children: token
92
+ }
93
+ ),
94
+ /* @__PURE__ */ jsx(
95
+ Tooltip,
96
+ {
97
+ title: copied ? translate("serviceAccount.reveal.copied", { _: "Copied" }) : translate("serviceAccount.reveal.copy", { _: "Copy" }),
98
+ children: /* @__PURE__ */ jsx(IconButton, { onClick: handleCopy, size: "small", "aria-label": "copy api key", children: copied ? /* @__PURE__ */ jsx(CheckIcon, { color: "success", fontSize: "small" }) : /* @__PURE__ */ jsx(ContentCopyIcon, { fontSize: "small" }) })
99
+ }
100
+ )
101
+ ]
102
+ }
103
+ )
104
+ ] });
105
+ };
106
+ var ApiKeyRevealDialog_default = ApiKeyRevealContent;
107
+ export {
108
+ ApiKeyRevealContent,
109
+ ApiKeyRevealDialog_default as default
110
+ };
@@ -0,0 +1,94 @@
1
+ var __async = (__this, __arguments, generator) => {
2
+ return new Promise((resolve, reject) => {
3
+ var fulfilled = (value) => {
4
+ try {
5
+ step(generator.next(value));
6
+ } catch (e) {
7
+ reject(e);
8
+ }
9
+ };
10
+ var rejected = (value) => {
11
+ try {
12
+ step(generator.throw(value));
13
+ } catch (e) {
14
+ reject(e);
15
+ }
16
+ };
17
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
18
+ step((generator = generator.apply(__this, __arguments)).next());
19
+ });
20
+ };
21
+ import { jsx, jsxs } from "react/jsx-runtime";
22
+ import React, { useState } from "react";
23
+ import { useRecordContext, useNotify, useRefresh } from "react-admin";
24
+ import { useAxios } from "dash-axios-hook";
25
+ import Box from "@mui/material/Box";
26
+ import Switch from "@mui/material/Switch";
27
+ import FormControlLabel from "@mui/material/FormControlLabel";
28
+ import CircularProgress from "@mui/material/CircularProgress";
29
+ import { useI18nBridge } from "../../contexts/I18nBridgeContext";
30
+ const ServiceAccountStatusSwitch = ({
31
+ resourceConfig
32
+ }) => {
33
+ const record = useRecordContext();
34
+ const notify = useNotify();
35
+ const refresh = useRefresh();
36
+ const axios = useAxios();
37
+ const { i18nProvider } = useI18nBridge();
38
+ const [loading, setLoading] = useState(false);
39
+ const translate = React.useCallback(
40
+ (key, fallback) => {
41
+ var _a;
42
+ try {
43
+ return (_a = i18nProvider == null ? void 0 : i18nProvider.translate(key, { _: fallback })) != null ? _a : fallback;
44
+ } catch (e) {
45
+ return fallback;
46
+ }
47
+ },
48
+ [i18nProvider]
49
+ );
50
+ if (!record) return null;
51
+ const isActive = record.status === "active";
52
+ const handleToggle = () => __async(null, null, function* () {
53
+ setLoading(true);
54
+ try {
55
+ const action = isActive ? "disable" : "enable";
56
+ yield axios.post(`${resourceConfig.model}/${record.id}/${action}`);
57
+ notify(
58
+ isActive ? translate("serviceAccount.notify.disabled", "API key disabled") : translate("serviceAccount.notify.enabled", "API key enabled"),
59
+ { type: "success" }
60
+ );
61
+ refresh();
62
+ } catch (error) {
63
+ console.error("ServiceAccountStatusSwitch: toggle failed", error);
64
+ notify(
65
+ translate("serviceAccount.notify.toggle_error", "Could not change the key status"),
66
+ { type: "error" }
67
+ );
68
+ } finally {
69
+ setLoading(false);
70
+ }
71
+ });
72
+ return /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1, my: 1 }, children: [
73
+ /* @__PURE__ */ jsx(
74
+ FormControlLabel,
75
+ {
76
+ control: /* @__PURE__ */ jsx(
77
+ Switch,
78
+ {
79
+ checked: isActive,
80
+ onChange: handleToggle,
81
+ disabled: loading,
82
+ color: "success"
83
+ }
84
+ ),
85
+ label: isActive ? translate("serviceAccount.status.active", "Active") : translate("serviceAccount.status.disabled", "Disabled")
86
+ }
87
+ ),
88
+ loading ? /* @__PURE__ */ jsx(CircularProgress, { size: 16 }) : null
89
+ ] });
90
+ };
91
+ var ServiceAccountStatusSwitch_default = ServiceAccountStatusSwitch;
92
+ export {
93
+ ServiceAccountStatusSwitch_default as default
94
+ };
@@ -178,7 +178,11 @@ const CollapsableSidebarMenu = ({
178
178
  primary: /* @__PURE__ */ jsx(Typography, { children: item.label })
179
179
  }
180
180
  ),
181
- navExpanded && /* @__PURE__ */ jsx("div", { onClick: handleExpandClick, children: sidebarPosition === "bottom" ? localOpen ? /* @__PURE__ */ jsx(ExpandMoreOutlinedIcon, { className: "expand-icon more" }) : /* @__PURE__ */ jsx(ExpandLessOutlinedIcon, { className: "expand-icon less" }) : localOpen ? /* @__PURE__ */ jsx(ExpandLessOutlinedIcon, { className: "expand-icon less" }) : /* @__PURE__ */ jsx(ExpandMoreOutlinedIcon, { className: "expand-icon more" }) })
181
+ navExpanded && // "expand-icon" is ALSO on the wrapper (not just the inner svg): the row's
182
+ // onClick above does closest('.expand-icon') to tell "clicked the arrow"
183
+ // from "clicked the row" — if only the svg had it, clicking the circle's
184
+ // padding (outside the svg's own box) would miss the check and navigate.
185
+ /* @__PURE__ */ jsx("div", { className: "expand-icon expand-icon-wrapper", onClick: handleExpandClick, children: sidebarPosition === "bottom" ? localOpen ? /* @__PURE__ */ jsx(ExpandMoreOutlinedIcon, { className: "expand-icon more" }) : /* @__PURE__ */ jsx(ExpandLessOutlinedIcon, { className: "expand-icon less" }) : localOpen ? /* @__PURE__ */ jsx(ExpandLessOutlinedIcon, { className: "expand-icon less" }) : /* @__PURE__ */ jsx(ExpandMoreOutlinedIcon, { className: "expand-icon more" }) })
182
186
  ]
183
187
  }
184
188
  )
@@ -26,9 +26,14 @@ const getSubmenuBaseStyle = (sidebarPosition, renderAtTop = false) => ({
26
26
  overflowY: "auto",
27
27
  width: sidebarPosition === "top" || sidebarPosition === "bottom" ? "auto" : "260px",
28
28
  minWidth: "200px",
29
- background: "var(--module-bg, #252526)",
30
- boxShadow: "0 4px 12px rgba(0,0,0,0.25)",
31
- borderRadius: 8
29
+ // The wrapper is the SINGLE owner of the panel visuals (surface, border,
30
+ // shadow, radius, padding). The inner ul.dropdown must stay transparent —
31
+ // if both layers draw a border/background, their edges show as a seam line.
32
+ background: "var(--primary-contrast, #252526)",
33
+ border: "1px solid rgba(255,255,255,0.08)",
34
+ boxShadow: "0 10px 15px -3px rgba(0,0,0,0.25), 0 4px 6px -4px rgba(0,0,0,0.4)",
35
+ borderRadius: 12,
36
+ padding: 6
32
37
  });
33
38
  const calculateSubmenuPosition = (rect, sidebarPosition, renderAtTop = false) => {
34
39
  switch (sidebarPosition) {
@@ -1,6 +1,39 @@
1
+ var __async = (__this, __arguments, generator) => {
2
+ return new Promise((resolve, reject) => {
3
+ var fulfilled = (value) => {
4
+ try {
5
+ step(generator.next(value));
6
+ } catch (e) {
7
+ reject(e);
8
+ }
9
+ };
10
+ var rejected = (value) => {
11
+ try {
12
+ step(generator.throw(value));
13
+ } catch (e) {
14
+ reject(e);
15
+ }
16
+ };
17
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
18
+ step((generator = generator.apply(__this, __arguments)).next());
19
+ });
20
+ };
1
21
  import axios from "axios";
2
22
  import constants from "dash-constants/src/DASHAdminSystemConstants";
3
23
  import { dashStorage } from "dash-utils";
24
+ let isRefreshing = false;
25
+ let failedQueue = [];
26
+ const processQueue = (error, token) => {
27
+ failedQueue.forEach((prom) => {
28
+ if (error) {
29
+ prom.reject(error);
30
+ } else {
31
+ prom.resolve(token);
32
+ }
33
+ });
34
+ isRefreshing = false;
35
+ failedQueue = [];
36
+ };
4
37
  const initAxios = () => {
5
38
  let CSRFAuth = false;
6
39
  let options = {
@@ -28,17 +61,76 @@ const initAxios = () => {
28
61
  (response) => {
29
62
  return response;
30
63
  },
31
- (error) => {
32
- var _a;
33
- window.dispatchEvent(new MessageEvent("GlobalError", { data: { error } }));
64
+ (error) => __async(null, null, function* () {
65
+ var _a, _b, _c;
66
+ const originalRequest = error.config;
67
+ const authEndpoints = ["/login", "/auth/refresh", "/logout", "/register"];
68
+ const isAuthEndpoint = authEndpoints.some(
69
+ (endpoint) => {
70
+ var _a2;
71
+ return (_a2 = originalRequest.url) == null ? void 0 : _a2.includes(endpoint);
72
+ }
73
+ );
74
+ if (((_a = error.response) == null ? void 0 : _a.status) === 401 && !isAuthEndpoint && !originalRequest._retry) {
75
+ if (isRefreshing) {
76
+ return new Promise((resolve, reject) => {
77
+ failedQueue.push({ resolve, reject });
78
+ }).then((token) => {
79
+ originalRequest.headers.Authorization = "Bearer " + token;
80
+ return instance(originalRequest);
81
+ }).catch((err) => {
82
+ return Promise.reject(err);
83
+ });
84
+ }
85
+ originalRequest._retry = true;
86
+ isRefreshing = true;
87
+ try {
88
+ const refreshToken = dashStorage.getItem("refreshToken");
89
+ if (!refreshToken) {
90
+ yield handleAuthFailure();
91
+ return Promise.reject(error);
92
+ }
93
+ const refreshResponse = yield axios.post(
94
+ `${constants.system.ADMIN_API_URL}/auth/refresh`,
95
+ { refresh_token: refreshToken }
96
+ );
97
+ const { token, refresh_token } = refreshResponse.data;
98
+ dashStorage.setItem("token", token);
99
+ dashStorage.setItem("refreshToken", refresh_token);
100
+ originalRequest.headers.Authorization = "Bearer " + token;
101
+ processQueue(null, token);
102
+ return instance(originalRequest);
103
+ } catch (refreshError) {
104
+ yield handleAuthFailure();
105
+ processQueue(refreshError, null);
106
+ return Promise.reject(refreshError);
107
+ }
108
+ }
109
+ if (((_b = error.response) == null ? void 0 : _b.status) !== 401) {
110
+ window.dispatchEvent(new MessageEvent("GlobalError", { data: { error } }));
111
+ }
34
112
  if (error.response && error.response.status === 422) {
35
- return Promise.reject(((_a = error.response.data) == null ? void 0 : _a.errors) || error);
113
+ return Promise.reject(((_c = error.response.data) == null ? void 0 : _c.errors) || error);
36
114
  }
37
115
  return Promise.reject(error);
38
- }
116
+ })
39
117
  );
40
118
  return instance;
41
119
  };
120
+ function handleAuthFailure() {
121
+ return __async(this, null, function* () {
122
+ dashStorage.removeItem("token");
123
+ dashStorage.removeItem("refreshToken");
124
+ dashStorage.removeItem("user");
125
+ dashStorage.removeItem("auth");
126
+ dashStorage.removeItem("authenticated");
127
+ dashStorage.removeItem("roles");
128
+ window.dispatchEvent(new MessageEvent("auth:logout"));
129
+ if (!window.location.pathname.includes("/login")) {
130
+ window.location.href = "/login";
131
+ }
132
+ });
133
+ }
42
134
  const useAxios = () => {
43
135
  const axios2 = initAxios();
44
136
  return {
@@ -28,6 +28,8 @@ const logoutFromStorage = () => __async(null, null, function* () {
28
28
  removeCookie("token");
29
29
  dashStorage.removeItem("user");
30
30
  dashStorage.removeItem("auth");
31
+ dashStorage.removeItem("token");
32
+ dashStorage.removeItem("refreshToken");
31
33
  dashStorage.setItem("authenticated", "false");
32
34
  dashStorage.setItem("roles", null);
33
35
  yield clearDeviceStoreAuth();
@@ -46,6 +48,9 @@ var authProvider_default = {
46
48
  if (loginResponse.status >= 200 && loginResponse.status <= 299) {
47
49
  setCookie("token", loginResponse.data.token);
48
50
  dashStorage.setItem("token", loginResponse.data.token);
51
+ if (loginResponse.data.refresh_token) {
52
+ dashStorage.setItem("refreshToken", loginResponse.data.refresh_token);
53
+ }
49
54
  try {
50
55
  let { data: auth } = yield axios.get("/auth/getauth");
51
56
  dashStorage.setItem("roles", JSON.stringify(auth.user.roles));
@@ -302,8 +302,55 @@ const dashEnglish = {
302
302
  auto_reset_notice: "This screen will automatically reset in 30 seconds",
303
303
  loading: "Loading..."
304
304
  },
305
+ // Service account API keys
306
+ serviceAccount: {
307
+ reveal: {
308
+ title: "Copy your API key now",
309
+ warning: "This is the only time this key will be shown. It cannot be recovered later \u2014 if you lose it, you will have to issue a new one.",
310
+ forKey: "Key",
311
+ copy: "Copy",
312
+ copied: "Copied",
313
+ dismiss: "I have saved it"
314
+ },
315
+ status: {
316
+ active: "Active",
317
+ disabled: "Disabled"
318
+ },
319
+ expiration: {
320
+ "1": "1 day",
321
+ "7": "7 days",
322
+ "30": "30 days",
323
+ never: "Never"
324
+ },
325
+ notify: {
326
+ enabled: "API key enabled",
327
+ disabled: "API key disabled",
328
+ toggle_error: "Could not change the key status"
329
+ },
330
+ action: {
331
+ enable: "Enable",
332
+ disable: "Disable"
333
+ },
334
+ // Shown when a tenancy-level role is selected: such a key reaches every
335
+ // tenant in the account, so the tenant field stops being a boundary.
336
+ tenancy_role_warning: "This role grants access to every tenant in the account. The selected tenant is informational, not a security boundary."
337
+ },
305
338
  // Resource labels for admin panel
306
339
  resource: {
340
+ tenancy: {
341
+ serviceAccounts: {
342
+ label: "Service Accounts",
343
+ name: "Name",
344
+ tenant: "Tenant",
345
+ roles: "Roles",
346
+ expiration: "Expiration",
347
+ status: "Status",
348
+ token: "Key",
349
+ expires_at: "Expires",
350
+ last_used_at: "Last used",
351
+ created_at: "Created"
352
+ }
353
+ },
307
354
  // Groups
308
355
  groups: {
309
356
  system_resources: "System Resources",
@@ -506,6 +553,34 @@ const dashEnglish = {
506
553
  all_products: "All Products",
507
554
  has_modifiers: "Customizable",
508
555
  no_more_products: "No more products"
556
+ },
557
+ // JSON Color Selector - image-based theme extraction
558
+ colorSelector: {
559
+ imageExtractor: {
560
+ title_ai: "AI Theme Generator",
561
+ title_local: "Theme from Image",
562
+ upload_prompt: "Upload an Image to Extract Colors",
563
+ upload_hint: "Select an image file and we'll automatically extract a color palette",
564
+ upload_button: "Choose Image",
565
+ drop_hint: "or drag and drop it here",
566
+ invalid_file_type: "Please select a valid image file",
567
+ file_too_large: "File size must be less than %{limit}MB",
568
+ extracting: "Extracting colors from image...",
569
+ extracted_colors: "Extracted Colors (%{count} colors found)",
570
+ dominant_label: "Dominant",
571
+ uploaded_image_alt: "Uploaded image",
572
+ no_palette_error: "Please extract colors from an image first",
573
+ theme_generation_failed: "Failed to generate theme",
574
+ theme_description_label: "Theme Description (Optional)",
575
+ theme_description_placeholder: "Optional: Describe the theme style you want (e.g., 'modern and minimalist', 'warm and cozy', 'professional corporate')",
576
+ apply_colors_button: "Apply Colors",
577
+ generate_button: "Generate AI Theme",
578
+ generating_button: "Generating...",
579
+ apply_colors_help_label: "Apply Colors:",
580
+ apply_colors_help_desc: "Maps the extracted palette to your base theme colors locally \u2014 no AI involved",
581
+ generate_help_label: "Generate AI Theme:",
582
+ generate_help_desc: "Uses AI to create a complete color scheme based on extracted colors and your description"
583
+ }
509
584
  }
510
585
  };
511
586
  var en_default = __spreadValues(__spreadValues({}, raEnglish), dashEnglish);
@@ -309,8 +309,55 @@ const dashSpanish = {
309
309
  auto_reset_notice: "Esta pantalla se reiniciar\xE1 autom\xE1ticamente en 30 segundos",
310
310
  loading: "Cargando..."
311
311
  },
312
+ // Service account API keys
313
+ serviceAccount: {
314
+ reveal: {
315
+ title: "Copia tu API Key ahora",
316
+ warning: "Esta es la \xFAnica vez que se mostrar\xE1 esta clave. No se puede recuperar m\xE1s adelante \u2014 si la pierdes, tendr\xE1s que emitir una nueva.",
317
+ forKey: "Clave",
318
+ copy: "Copiar",
319
+ copied: "Copiado",
320
+ dismiss: "La he guardado"
321
+ },
322
+ status: {
323
+ active: "Activa",
324
+ disabled: "Deshabilitada"
325
+ },
326
+ expiration: {
327
+ "1": "1 d\xEDa",
328
+ "7": "7 d\xEDas",
329
+ "30": "30 d\xEDas",
330
+ never: "Nunca"
331
+ },
332
+ notify: {
333
+ enabled: "API Key habilitada",
334
+ disabled: "API Key deshabilitada",
335
+ toggle_error: "No se pudo cambiar el estado de la API Key"
336
+ },
337
+ action: {
338
+ enable: "Habilitar",
339
+ disable: "Deshabilitar"
340
+ },
341
+ // Shown when a tenancy-level role is selected: such a key reaches every
342
+ // tenant in the account, so the tenant field stops being a boundary.
343
+ tenancy_role_warning: "Este rol otorga acceso a todos los locales de la cuenta. El local seleccionado es informativo, no un l\xEDmite de seguridad."
344
+ },
312
345
  // Resource labels for admin panel
313
346
  resource: {
347
+ tenancy: {
348
+ serviceAccounts: {
349
+ label: "Cuentas de Servicio",
350
+ name: "Nombre",
351
+ tenant: "Local",
352
+ roles: "Roles",
353
+ expiration: "Expiraci\xF3n",
354
+ status: "Estado",
355
+ token: "Clave",
356
+ expires_at: "Expira",
357
+ last_used_at: "\xDAltimo uso",
358
+ created_at: "Creado"
359
+ }
360
+ },
314
361
  // Groups
315
362
  groups: {
316
363
  system_resources: "Recursos de Sistema",
@@ -512,6 +559,34 @@ const dashSpanish = {
512
559
  all_products: "Todos los Productos",
513
560
  has_modifiers: "Personalizable",
514
561
  no_more_products: "No hay m\xE1s productos"
562
+ },
563
+ // JSON Color Selector - extracción de tema desde imagen
564
+ colorSelector: {
565
+ imageExtractor: {
566
+ title_ai: "Generador de Tema con IA",
567
+ title_local: "Tema desde Imagen",
568
+ upload_prompt: "Sube una Imagen para Extraer Colores",
569
+ upload_hint: "Selecciona un archivo de imagen y extraeremos autom\xE1ticamente una paleta de colores",
570
+ upload_button: "Elegir Imagen",
571
+ drop_hint: "o arr\xE1strala aqu\xED",
572
+ invalid_file_type: "Por favor selecciona un archivo de imagen v\xE1lido",
573
+ file_too_large: "El archivo debe pesar menos de %{limit}MB",
574
+ extracting: "Extrayendo colores de la imagen...",
575
+ extracted_colors: "Colores Extra\xEDdos (%{count} colores encontrados)",
576
+ dominant_label: "Dominante",
577
+ uploaded_image_alt: "Imagen subida",
578
+ no_palette_error: "Primero extrae los colores de una imagen",
579
+ theme_generation_failed: "No se pudo generar el tema",
580
+ theme_description_label: "Descripci\xF3n del Tema (Opcional)",
581
+ theme_description_placeholder: "Opcional: Describe el estilo de tema que deseas (ej: 'moderno y minimalista', 'c\xE1lido y acogedor', 'corporativo profesional')",
582
+ apply_colors_button: "Aplicar Colores",
583
+ generate_button: "Generar Tema con IA",
584
+ generating_button: "Generando...",
585
+ apply_colors_help_label: "Aplicar Colores:",
586
+ apply_colors_help_desc: "Aplica la paleta extra\xEDda a los colores base de tu tema localmente \u2014 sin usar IA",
587
+ generate_help_label: "Generar Tema con IA:",
588
+ generate_help_desc: "Usa IA para crear un esquema de color completo basado en los colores extra\xEDdos y tu descripci\xF3n"
589
+ }
515
590
  }
516
591
  };
517
592
  var es_default = __spreadValues(__spreadValues({}, raSpanish), dashSpanish);
@@ -2,7 +2,9 @@ import enMessages from "./en";
2
2
  import esMessages from "./es";
3
3
  const en = enMessages;
4
4
  const es = esMessages;
5
+ import { mergeTranslations } from "./mergeTranslations";
5
6
  export {
6
7
  en,
7
- es
8
+ es,
9
+ mergeTranslations
8
10
  };
@@ -0,0 +1,18 @@
1
+ const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2
+ const mergeTranslations = (...sources) => {
3
+ const result = {};
4
+ for (const source of sources) {
5
+ if (!isPlainObject(source)) continue;
6
+ for (const key of Object.keys(source)) {
7
+ const incoming = source[key];
8
+ const existing = result[key];
9
+ result[key] = isPlainObject(existing) && isPlainObject(incoming) ? mergeTranslations(existing, incoming) : incoming;
10
+ }
11
+ }
12
+ return result;
13
+ };
14
+ var mergeTranslations_default = mergeTranslations;
15
+ export {
16
+ mergeTranslations_default as default,
17
+ mergeTranslations
18
+ };
@@ -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.42",
3
+ "version": "1.3.45",
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",