@localazy/strapi-plugin 1.4.0 → 1.6.0

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 (61) hide show
  1. package/README.md +30 -0
  2. package/dist/_chunks/{FetchIdentity-Cor4ooZv.mjs → FetchIdentity-Bm7bc5bS.mjs} +36 -44
  3. package/dist/_chunks/{FetchIdentity-BpFOT163.js → FetchIdentity-CcEjakiz.js} +24 -32
  4. package/dist/_chunks/{LanguagesSelector-BbrMKwQY.mjs → LanguagesSelector-B1rwN7-Y.mjs} +4 -3
  5. package/dist/_chunks/{LanguagesSelector-CC1ecWnc.js → LanguagesSelector-DejYCwBl.js} +3 -2
  6. package/dist/_chunks/{LocalazyApp-DpFR8p10.mjs → LocalazyApp-C4kbsnQh.mjs} +70 -34
  7. package/dist/_chunks/{LocalazyApp-D0JGRQeF.js → LocalazyApp-jPO7Yr1S.js} +75 -39
  8. package/dist/_chunks/{LocalazyContentTransferSetup-Dael1Q4w.mjs → LocalazyContentTransferSetup-C1wDD9hA.mjs} +58 -22
  9. package/dist/_chunks/{LocalazyContentTransferSetup-Cr7D-BUi.js → LocalazyContentTransferSetup-DZh4ZdSi.js} +62 -26
  10. package/dist/_chunks/{LocalazyGlobalSettings-D3nG4zl7.js → LocalazyGlobalSettings-D76it6rn.js} +56 -25
  11. package/dist/_chunks/{LocalazyGlobalSettings-DzBI5M-K.mjs → LocalazyGlobalSettings-DOW5rjLn.mjs} +56 -25
  12. package/dist/_chunks/{LocalazyPanel-W6j8N1WC.mjs → LocalazyPanel-C_Z4W49T.mjs} +12 -6
  13. package/dist/_chunks/{LocalazyPanel-KQPLxTDz.js → LocalazyPanel-D2LQpqii.js} +14 -8
  14. package/dist/_chunks/{LocalazyStatusColumn-CrhsCjgp.mjs → LocalazyStatusColumn-C_ZCd_s7.mjs} +10 -4
  15. package/dist/_chunks/{LocalazyStatusColumn-B7Q_U2Lu.js → LocalazyStatusColumn-Dy6N4_X6.js} +10 -4
  16. package/dist/_chunks/{RequireLocalazyAuth-D3GeZPDh.js → RequireLocalazyAuth-BVwF0bRC.js} +30 -3
  17. package/dist/_chunks/{RequireLocalazyAuth-BwoGRfJq.mjs → RequireLocalazyAuth-DNhTZZ_6.mjs} +32 -5
  18. package/dist/_chunks/{entry-exclusion-service-D7gaRHPE.mjs → entry-exclusion-service-CF_yNCly.mjs} +1 -1
  19. package/dist/_chunks/{entry-exclusion-service-Bwruvyce.js → entry-exclusion-service-CH3ZoJQN.js} +1 -1
  20. package/dist/_chunks/{has-model-changed-Dp2Vkwjw.mjs → has-model-changed-CVJEuGxM.mjs} +2 -2
  21. package/dist/_chunks/{has-model-changed-5IV9WI_-.js → has-model-changed-DdYbyv0v.js} +2 -2
  22. package/dist/_chunks/{i18n-o24yXf-j.mjs → i18n-76ox2llT.mjs} +4 -1
  23. package/dist/_chunks/{i18n-C_5tJvr0.js → i18n-C5ucROBh.js} +4 -1
  24. package/dist/_chunks/{index-jAg6jjCu.js → index-CEGaWWh3.js} +33 -11
  25. package/dist/_chunks/{index-C9hN3KbD.mjs → index-DP2ugzc_.mjs} +34 -12
  26. package/dist/_chunks/{index-BjL9XpRh.mjs → index-shryj0jT.mjs} +16 -16
  27. package/dist/_chunks/{strapi-api-base-IG3Pe5Sg.js → strapi-api-base-CCUuu7EY.js} +1 -1
  28. package/dist/_chunks/{strapi-api-base-CAzGcwF6.mjs → strapi-api-base-IiMEtDoH.mjs} +1 -1
  29. package/dist/admin/index.js +1 -1
  30. package/dist/admin/index.mjs +1 -1
  31. package/dist/admin/src/components/LocalazyStatusColumn.d.ts +1 -1
  32. package/dist/admin/src/constants/permissions.d.ts +29 -0
  33. package/dist/admin/src/modules/@common/components/LanguagesSelector.d.ts +1 -0
  34. package/dist/admin/src/modules/login/locale/en.d.ts +1 -0
  35. package/dist/admin/src/modules/plugin-settings/components/Tree.d.ts +1 -0
  36. package/dist/admin/src/modules/plugin-settings/components/TreeItem.d.ts +1 -0
  37. package/dist/admin/src/modules/plugin-settings/components/WebhookSetup.d.ts +4 -1
  38. package/dist/admin/src/modules/plugin-settings/locale/en.d.ts +2 -0
  39. package/dist/admin/src/modules/plugin-settings/services/plugin-settings-service.d.ts +4 -0
  40. package/dist/server/index.js +157 -36
  41. package/dist/server/index.mjs +157 -36
  42. package/dist/server/src/bootstrap.d.ts +1 -1
  43. package/dist/server/src/constants/permissions.d.ts +38 -0
  44. package/dist/server/src/controllers/index.d.ts +1 -0
  45. package/dist/server/src/controllers/plugin-settings-controller.d.ts +1 -0
  46. package/dist/server/src/core/index.d.ts +3 -1
  47. package/dist/server/src/index.d.ts +57 -8
  48. package/dist/server/src/routes/activity-logs-routes.d.ts +20 -3
  49. package/dist/server/src/routes/debug-bundle-routes.d.ts +6 -1
  50. package/dist/server/src/routes/entry-exclusion-routes.d.ts +20 -3
  51. package/dist/server/src/routes/index.d.ts +52 -6
  52. package/dist/server/src/routes/localazy-auth-routes.d.ts +6 -1
  53. package/dist/server/src/routes/localazy-project-routes.d.ts +20 -3
  54. package/dist/server/src/routes/localazy-transfer-routes.d.ts +6 -1
  55. package/dist/server/src/routes/localazy-user-routes.d.ts +20 -3
  56. package/dist/server/src/routes/plugin-settings-routes.d.ts +20 -3
  57. package/dist/server/src/routes/strapi-routes.d.ts +20 -3
  58. package/dist/server/src/services/index.d.ts +3 -1
  59. package/dist/server/src/services/localazy-transfer-download-service.d.ts +2 -1
  60. package/dist/server/src/services/plugin-settings-service.d.ts +4 -0
  61. package/package.json +1 -1
package/README.md CHANGED
@@ -71,6 +71,36 @@ localazy: {
71
71
  },
72
72
  ```
73
73
 
74
+ ## 🔐 Access control (RBAC)
75
+
76
+ The plugin registers four Strapi permission actions, all visible under
77
+ **Settings → Administration Panel → Roles → Plugins → Localazy** (grouped
78
+ by `General` and `Settings` sub-categories):
79
+
80
+ | Action | Unlocks |
81
+ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
82
+ | `Localazy → Read` (`plugin::localazy.read`) | Localazy menu link, Overview, Activity Logs (list + detail), Content-Manager side panel & Localazy status column, read endpoints (identity, project, models, plugin settings, **content-transfer-setup**, sync cursor, activity logs, troubleshooting bundle, entry-exclusion state). |
83
+ | `Localazy → Transfer` (`plugin::localazy.transfer`) | Upload, Download, Entry Exclusion mutations (incl. Content-Manager bulk actions), Activity Log session clearing. Reading the content-transfer-setup is bundled under `Read` above so transfer-only roles can still open the Upload/Download pages. |
84
+ | `Localazy → Settings → Read` (`plugin::localazy.settings.read`) | Adds the Localazy Settings menu links (Global Settings, Content Transfer Setup) to the admin sidebar. Form controls render read-only — Save, Cancel, the Content Transfer Setup tree, and the webhook setup buttons are disabled without `Settings → Update`. |
85
+ | `Localazy → Settings → Update` (`plugin::localazy.settings.update`) | **Connecting / disconnecting the Localazy account**, webhook setup, updating Content Transfer Setup, and updating Global Settings. |
86
+
87
+ > **Note:** the **Webhook author** picker in _Localazy Settings → Global
88
+ > Settings_ populates from Strapi's core `/admin/users` endpoint, which is
89
+ > gated by Strapi's own `admin::users.read` permission — independent of the
90
+ > plugin's actions. A role with only the plugin's `Settings → Read` will
91
+ > still see the rest of the page; the author dropdown silently falls back
92
+ > to an empty list.
93
+
94
+ Server is the enforcement perimeter — all admin-typed plugin routes are
95
+ gated by `admin::hasPermissions`, so UI gates are convenience only.
96
+
97
+ ### Upgrade note
98
+
99
+ Strapi grants new actions to the **Super Admin** role automatically. **Other
100
+ roles (Editor, Author, custom roles) keep no Localazy access until an admin
101
+ re-grants the actions** under _Settings → Administration Panel → Roles →
102
+ Plugins → Localazy_. Plan this step when upgrading from `<= 1.4.x`.
103
+
74
104
  ## 🛟 Support
75
105
 
76
106
  If you encounter any issues or have questions, feel free to contact us through whichever channel suits you best:
@@ -1,8 +1,9 @@
1
- import { c as createStrapiApiAxiosInstance } from "./strapi-api-base-CAzGcwF6.mjs";
2
- import { V as darkTheme, W as lightTheme } from "./index-BjL9XpRh.mjs";
1
+ import { c as createStrapiApiAxiosInstance } from "./strapi-api-base-IiMEtDoH.mjs";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { c as Flex, L as Loader, V as darkTheme, W as lightTheme } from "./index-shryj0jT.mjs";
4
+ import { u as useTranslation } from "./useTranslation-CZQ4lU1m.mjs";
3
5
  import { createGlobalStyle } from "styled-components";
4
6
  import { useState, useMemo, createContext, useContext, useEffect } from "react";
5
- import { jsx } from "react/jsx-runtime";
6
7
  const emptyIdentity = {
7
8
  accessToken: "",
8
9
  project: {
@@ -72,6 +73,17 @@ class PluginSettingsService {
72
73
  throw e;
73
74
  }
74
75
  }
76
+ // Read-gated counterpart for per-user UI prefs (last visited route, sort
77
+ // prefs). Server filters the body to a fixed allowlist; sending anything else
78
+ // is silently dropped, so callers must only pass UI-pref fields here.
79
+ static async updatePluginSettingsUiPrefs(data) {
80
+ try {
81
+ const result = await axiosInstance.put(`${BASE_PATH$1}/ui-prefs`, data);
82
+ return result.data;
83
+ } catch (e) {
84
+ throw e;
85
+ }
86
+ }
75
87
  static async getSyncCursor() {
76
88
  try {
77
89
  const result = await axiosInstance.get(`${BASE_PATH$1}/sync-cursor`);
@@ -81,6 +93,10 @@ class PluginSettingsService {
81
93
  }
82
94
  }
83
95
  }
96
+ function PluginPageLoader() {
97
+ const { t } = useTranslation();
98
+ return /* @__PURE__ */ jsx(Flex, { marginTop: 10, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx(Loader, { children: t("common.loading_content") }) });
99
+ }
84
100
  const BASE_PATH = "/user";
85
101
  const strapiApiInstance = createStrapiApiAxiosInstance();
86
102
  class LocalazyUserService {
@@ -501,9 +517,9 @@ function cacheHas(cache, key) {
501
517
  return cache.has(key);
502
518
  }
503
519
  var Set = getNative(root, "Set");
504
- function setToArray(set2) {
505
- var index = -1, result = Array(set2.size);
506
- set2.forEach(function(value) {
520
+ function setToArray(set) {
521
+ var index = -1, result = Array(set.size);
522
+ set.forEach(function(value) {
507
523
  result[++index] = value;
508
524
  });
509
525
  return result;
@@ -937,32 +953,6 @@ var CLONE_DEEP_FLAG = 1, CLONE_SYMBOLS_FLAG = 4;
937
953
  function cloneDeep(value) {
938
954
  return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG);
939
955
  }
940
- function baseSet(object, path, value, customizer) {
941
- if (!isObject(object)) {
942
- return object;
943
- }
944
- path = castPath(path, object);
945
- var index = -1, length = path.length, lastIndex = length - 1, nested = object;
946
- while (nested != null && ++index < length) {
947
- var key = toKey(path[index]), newValue = value;
948
- if (key === "__proto__" || key === "constructor" || key === "prototype") {
949
- return object;
950
- }
951
- if (index != lastIndex) {
952
- var objValue = nested[key];
953
- newValue = void 0;
954
- if (newValue === void 0) {
955
- newValue = isObject(objValue) ? objValue : isIndex(path[index + 1]) ? [] : {};
956
- }
957
- }
958
- assignValue(nested, key, newValue);
959
- nested = nested[key];
960
- }
961
- return object;
962
- }
963
- function set(object, path, value) {
964
- return object == null ? object : baseSet(object, path, value);
965
- }
966
956
  const FetchIdentity = () => {
967
957
  const { setIdentity, setIsFetchingIdentity } = useLocalazyIdentity();
968
958
  useEffect(() => {
@@ -991,10 +981,10 @@ export {
991
981
  PluginSettingsService as P,
992
982
  SetCache as S,
993
983
  Uint8Array as U,
994
- cacheHas as a,
995
- Symbol$1 as b,
984
+ PluginPageLoader as a,
985
+ cacheHas as b,
996
986
  cloneDeep as c,
997
- setToArray as d,
987
+ Symbol$1 as d,
998
988
  eq as e,
999
989
  getAllKeys as f,
1000
990
  getDefaultTheme as g,
@@ -1004,15 +994,17 @@ export {
1004
994
  isTypedArray as k,
1005
995
  isArray as l,
1006
996
  isObjectLike as m,
1007
- keys as n,
1008
- isArrayLike as o,
1009
- arrayEach as p,
1010
- isObject as q,
1011
- castPath as r,
1012
- set as s,
997
+ isObject as n,
998
+ castPath as o,
999
+ isIndex as p,
1000
+ assignValue as q,
1001
+ keys as r,
1002
+ setToArray as s,
1013
1003
  toKey as t,
1014
1004
  useLocalazyIdentity as u,
1015
- Set as v,
1016
- LocalazyUserService as w,
1017
- emptyIdentity as x
1005
+ isArrayLike as v,
1006
+ arrayEach as w,
1007
+ Set as x,
1008
+ LocalazyUserService as y,
1009
+ emptyIdentity as z
1018
1010
  };
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
- const strapiApiBase = require("./strapi-api-base-IG3Pe5Sg.js");
2
+ const strapiApiBase = require("./strapi-api-base-CCUuu7EY.js");
3
+ const jsxRuntime = require("react/jsx-runtime");
3
4
  const index = require("./index-CTkxoioB.js");
5
+ const useTranslation = require("./useTranslation-BdIl-aLe.js");
4
6
  const styled = require("styled-components");
5
7
  const React = require("react");
6
- const jsxRuntime = require("react/jsx-runtime");
7
8
  const emptyIdentity = {
8
9
  accessToken: "",
9
10
  project: {
@@ -73,6 +74,17 @@ class PluginSettingsService {
73
74
  throw e;
74
75
  }
75
76
  }
77
+ // Read-gated counterpart for per-user UI prefs (last visited route, sort
78
+ // prefs). Server filters the body to a fixed allowlist; sending anything else
79
+ // is silently dropped, so callers must only pass UI-pref fields here.
80
+ static async updatePluginSettingsUiPrefs(data) {
81
+ try {
82
+ const result = await axiosInstance.put(`${BASE_PATH$1}/ui-prefs`, data);
83
+ return result.data;
84
+ } catch (e) {
85
+ throw e;
86
+ }
87
+ }
76
88
  static async getSyncCursor() {
77
89
  try {
78
90
  const result = await axiosInstance.get(`${BASE_PATH$1}/sync-cursor`);
@@ -82,6 +94,10 @@ class PluginSettingsService {
82
94
  }
83
95
  }
84
96
  }
97
+ function PluginPageLoader() {
98
+ const { t } = useTranslation.useTranslation();
99
+ return /* @__PURE__ */ jsxRuntime.jsx(index.Flex, { marginTop: 10, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxRuntime.jsx(index.Loader, { children: t("common.loading_content") }) });
100
+ }
85
101
  const BASE_PATH = "/user";
86
102
  const strapiApiInstance = strapiApiBase.createStrapiApiAxiosInstance();
87
103
  class LocalazyUserService {
@@ -502,9 +518,9 @@ function cacheHas(cache, key) {
502
518
  return cache.has(key);
503
519
  }
504
520
  var Set = getNative(root, "Set");
505
- function setToArray(set2) {
506
- var index2 = -1, result = Array(set2.size);
507
- set2.forEach(function(value) {
521
+ function setToArray(set) {
522
+ var index2 = -1, result = Array(set.size);
523
+ set.forEach(function(value) {
508
524
  result[++index2] = value;
509
525
  });
510
526
  return result;
@@ -938,32 +954,6 @@ var CLONE_DEEP_FLAG = 1, CLONE_SYMBOLS_FLAG = 4;
938
954
  function cloneDeep(value) {
939
955
  return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG);
940
956
  }
941
- function baseSet(object, path, value, customizer) {
942
- if (!isObject(object)) {
943
- return object;
944
- }
945
- path = castPath(path, object);
946
- var index2 = -1, length = path.length, lastIndex = length - 1, nested = object;
947
- while (nested != null && ++index2 < length) {
948
- var key = toKey(path[index2]), newValue = value;
949
- if (key === "__proto__" || key === "constructor" || key === "prototype") {
950
- return object;
951
- }
952
- if (index2 != lastIndex) {
953
- var objValue = nested[key];
954
- newValue = void 0;
955
- if (newValue === void 0) {
956
- newValue = isObject(objValue) ? objValue : isIndex(path[index2 + 1]) ? [] : {};
957
- }
958
- }
959
- assignValue(nested, key, newValue);
960
- nested = nested[key];
961
- }
962
- return object;
963
- }
964
- function set(object, path, value) {
965
- return object == null ? object : baseSet(object, path, value);
966
- }
967
957
  const FetchIdentity = () => {
968
958
  const { setIdentity, setIsFetchingIdentity } = useLocalazyIdentity();
969
959
  React.useEffect(() => {
@@ -989,6 +979,7 @@ exports.FetchIdentity = FetchIdentity;
989
979
  exports.HeadingFixGlobalStyle = HeadingFixGlobalStyle;
990
980
  exports.LocalazyIdentityProvider = LocalazyIdentityProvider;
991
981
  exports.LocalazyUserService = LocalazyUserService;
982
+ exports.PluginPageLoader = PluginPageLoader;
992
983
  exports.PluginSettingsService = PluginSettingsService;
993
984
  exports.Set = Set;
994
985
  exports.SetCache = SetCache;
@@ -996,6 +987,7 @@ exports.Stack = Stack;
996
987
  exports.Symbol = Symbol$1;
997
988
  exports.Uint8Array = Uint8Array;
998
989
  exports.arrayEach = arrayEach;
990
+ exports.assignValue = assignValue;
999
991
  exports.cacheHas = cacheHas;
1000
992
  exports.castPath = castPath;
1001
993
  exports.cloneDeep = cloneDeep;
@@ -1006,11 +998,11 @@ exports.getDefaultTheme = getDefaultTheme;
1006
998
  exports.isArray = isArray;
1007
999
  exports.isArrayLike = isArrayLike;
1008
1000
  exports.isBuffer = isBuffer;
1001
+ exports.isIndex = isIndex;
1009
1002
  exports.isObject = isObject;
1010
1003
  exports.isObjectLike = isObjectLike;
1011
1004
  exports.isTypedArray = isTypedArray;
1012
1005
  exports.keys = keys;
1013
- exports.set = set;
1014
1006
  exports.setToArray = setToArray;
1015
1007
  exports.toKey = toKey;
1016
1008
  exports.useLocalazyIdentity = useLocalazyIdentity;
@@ -1,8 +1,8 @@
1
- import { c as createStrapiApiAxiosInstance } from "./strapi-api-base-CAzGcwF6.mjs";
1
+ import { c as createStrapiApiAxiosInstance } from "./strapi-api-base-IiMEtDoH.mjs";
2
2
  import { jsxs, jsx } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from "react";
4
- import { c as cloneDeep } from "./FetchIdentity-Cor4ooZv.mjs";
5
- import { F as Field, M as MultiSelect, b as MultiSelectOption } from "./index-BjL9XpRh.mjs";
4
+ import { c as cloneDeep } from "./FetchIdentity-Bm7bc5bS.mjs";
5
+ import { F as Field, M as MultiSelect, b as MultiSelectOption } from "./index-shryj0jT.mjs";
6
6
  import { u as useTranslation } from "./useTranslation-CZQ4lU1m.mjs";
7
7
  const strapiApiInstance = createStrapiApiAxiosInstance();
8
8
  class ProjectService {
@@ -48,6 +48,7 @@ const LanguagesSelector = (props) => {
48
48
  onClear: () => setSelectedLanguages([]),
49
49
  value: selectedLanguages || [],
50
50
  onChange: (values) => onChange(values),
51
+ disabled: props.disabled,
51
52
  multi: true,
52
53
  withTags: true,
53
54
  children: props.projectLanguages.map((lang) => /* @__PURE__ */ jsx(MultiSelectOption, { value: lang.code, children: `${lang.name} (${lang.code})` }, lang.id))
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
- const strapiApiBase = require("./strapi-api-base-IG3Pe5Sg.js");
2
+ const strapiApiBase = require("./strapi-api-base-CCUuu7EY.js");
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const React = require("react");
5
- const FetchIdentity = require("./FetchIdentity-BpFOT163.js");
5
+ const FetchIdentity = require("./FetchIdentity-CcEjakiz.js");
6
6
  const index = require("./index-CTkxoioB.js");
7
7
  const useTranslation = require("./useTranslation-BdIl-aLe.js");
8
8
  const strapiApiInstance = strapiApiBase.createStrapiApiAxiosInstance();
@@ -49,6 +49,7 @@ const LanguagesSelector = (props) => {
49
49
  onClear: () => setSelectedLanguages([]),
50
50
  value: selectedLanguages || [],
51
51
  onChange: (values) => onChange(values),
52
+ disabled: props.disabled,
52
53
  multi: true,
53
54
  withTags: true,
54
55
  children: props.projectLanguages.map((lang) => /* @__PURE__ */ jsxRuntime.jsx(index.MultiSelectOption, { value: lang.code, children: `${lang.name} (${lang.code})` }, lang.id))
@@ -1,16 +1,16 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useNavigate, useLocation, NavLink, useParams, Routes, Route } from "react-router-dom";
3
- import { o as ForwardRef$2H, p as ForwardRef$d, q as ForwardRef$3P, r as ForwardRef$4j, c as Flex, L as Loader, e as Button, B as Box, T as Typography, g as Link, s as SubNav, t as SubNavHeader, u as SubNavSections, v as SubNavSection, w as SubNavLink, x as ForwardRef$L, G as Grid, j as Divider, f as ForwardRef$3, A as Alert, D as Dialog, y as ForwardRef$4L, z as ForwardRef$4N, P as Pagination, E as PreviousLink, H as PageLink, I as Dots, N as NextLink, J as lodashExports, K as ForwardRef$j, i as ForwardRef$2x, F as Field, O as DatePicker$1, Q as Tabs, R as TooltipImpl, U as ForwardRef$57, l as DesignSystemProvider } from "./index-BjL9XpRh.mjs";
3
+ import { o as ForwardRef$2H, p as ForwardRef$d, q as ForwardRef$3P, r as ForwardRef$4j, e as Button, s as TooltipImpl, B as Box, T as Typography, g as Link, t as SubNav, u as SubNavHeader, v as SubNavSections, w as SubNavSection, x as SubNavLink, y as ForwardRef$L, G as Grid, j as Divider, c as Flex, f as ForwardRef$3, A as Alert, D as Dialog, z as ForwardRef$4L, E as ForwardRef$4N, P as Pagination, H as PreviousLink, I as PageLink, J as Dots, N as NextLink, K as lodashExports, O as ForwardRef$j, i as ForwardRef$2x, F as Field, Q as DatePicker$1, R as Tabs, U as ForwardRef$57, l as DesignSystemProvider } from "./index-shryj0jT.mjs";
4
4
  import React__default, { useState, useEffect, useCallback, useMemo, useRef } from "react";
5
- import { u as useLocalazyIdentity, w as LocalazyUserService, x as emptyIdentity, P as PluginSettingsService, c as cloneDeep, s as set, g as getDefaultTheme, H as HeadingFixGlobalStyle, L as LocalazyIdentityProvider, F as FetchIdentity } from "./FetchIdentity-Cor4ooZv.mjs";
6
- import { P as PLUGIN_ID } from "./index-C9hN3KbD.mjs";
5
+ import { u as useLocalazyIdentity, a as PluginPageLoader, y as LocalazyUserService, z as emptyIdentity, P as PluginSettingsService, g as getDefaultTheme, H as HeadingFixGlobalStyle, L as LocalazyIdentityProvider, F as FetchIdentity } from "./FetchIdentity-Bm7bc5bS.mjs";
6
+ import { a as PLUGIN_ID, P as PERMISSIONS } from "./index-DP2ugzc_.mjs";
7
7
  import { u as useTranslation } from "./useTranslation-CZQ4lU1m.mjs";
8
8
  import { g as getDefaultExportFromCjs } from "./_commonjsHelpers-DWwsNxpa.mjs";
9
- import { c as createStrapiApiAxiosInstance } from "./strapi-api-base-CAzGcwF6.mjs";
10
- import { P as ProjectService, L as LanguagesSelector } from "./LanguagesSelector-BbrMKwQY.mjs";
11
- import { Layouts, useAuth, useNotification, Page } from "@strapi/strapi/admin";
12
- import "./i18n-o24yXf-j.mjs";
13
- import { h as hasModelChanged } from "./has-model-changed-Dp2Vkwjw.mjs";
9
+ import { useRBAC, Layouts, useAuth, useNotification, Page } from "@strapi/strapi/admin";
10
+ import { c as createStrapiApiAxiosInstance } from "./strapi-api-base-IiMEtDoH.mjs";
11
+ import { P as ProjectService, L as LanguagesSelector } from "./LanguagesSelector-B1rwN7-Y.mjs";
12
+ import "./i18n-76ox2llT.mjs";
13
+ import { h as hasModelChanged } from "./has-model-changed-CVJEuGxM.mjs";
14
14
  import { useTheme } from "styled-components";
15
15
  var PLUGIN_ROUTES = /* @__PURE__ */ ((PLUGIN_ROUTES2) => {
16
16
  PLUGIN_ROUTES2["ROOT"] = "";
@@ -98,10 +98,6 @@ const useHeaderSubtitle = () => {
98
98
  const currentNavItem = navigation.find((navItem) => pluginSegment.includes(navItem.id));
99
99
  return currentNavItem?.description || "";
100
100
  };
101
- function PluginPageLoader() {
102
- const { t: t2 } = useTranslation();
103
- return /* @__PURE__ */ jsx(Flex, { marginTop: 10, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx(Loader, { children: t2("common.loading_content") }) });
104
- }
105
101
  var propTypes = { exports: {} };
106
102
  var reactIs = { exports: {} };
107
103
  var reactIs_production_min = {};
@@ -1258,6 +1254,8 @@ if (process.env.STRAPI_ADMIN_LOCALAZY_ENV === "development") {
1258
1254
  }
1259
1255
  const LoginButton = (props) => {
1260
1256
  const { t: t2 } = useTranslation();
1257
+ const { allowedActions } = useRBAC(PERMISSIONS.SETTINGS_UPDATE);
1258
+ const canConnect = !!allowedActions.canUpdate;
1261
1259
  const [isLoading, setIsLoading] = useState(false);
1262
1260
  const login = async () => {
1263
1261
  setIsLoading(true);
@@ -1278,7 +1276,8 @@ const LoginButton = (props) => {
1278
1276
  setIsLoading(false);
1279
1277
  props.onResultFetched(pollResult);
1280
1278
  };
1281
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { variant: "default", size: "L", loading: isLoading, onClick: login, children: t2("login.login_with_localazy") }) });
1279
+ const button = /* @__PURE__ */ jsx(Button, { variant: "default", size: "L", loading: isLoading, onClick: login, disabled: !canConnect, children: t2("login.login_with_localazy") });
1280
+ return /* @__PURE__ */ jsx("div", { children: canConnect ? button : /* @__PURE__ */ jsx(TooltipImpl, { label: t2("login.requires_settings_update_permission"), children: /* @__PURE__ */ jsx("span", { children: button }) }) });
1282
1281
  };
1283
1282
  LoginButton.propTypes = {
1284
1283
  onResultFetched: PropTypes.func.isRequired
@@ -1436,6 +1435,8 @@ const LogoutButton = (props) => {
1436
1435
  const { t: t2 } = useTranslation();
1437
1436
  const [isLoading, setIsLoading] = useState(false);
1438
1437
  const { setIdentity } = useLocalazyIdentity();
1438
+ const { allowedActions } = useRBAC(PERMISSIONS.SETTINGS_UPDATE);
1439
+ const canDisconnect = !!allowedActions.canUpdate;
1439
1440
  const logout = async () => {
1440
1441
  setIsLoading(true);
1441
1442
  void LocalazyUserService.deleteIdentity();
@@ -1443,7 +1444,8 @@ const LogoutButton = (props) => {
1443
1444
  setIsLoading(false);
1444
1445
  props.onResultFetched();
1445
1446
  };
1446
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { startIcon: /* @__PURE__ */ jsx(ForwardRef$L, {}), variant: "secondary", loading: isLoading, onClick: logout, children: t2("login.logout_from_localazy") }) });
1447
+ const button = /* @__PURE__ */ jsx(Button, { startIcon: /* @__PURE__ */ jsx(ForwardRef$L, {}), variant: "secondary", loading: isLoading, onClick: logout, disabled: !canDisconnect, children: t2("login.logout_from_localazy") });
1448
+ return /* @__PURE__ */ jsx("div", { children: canDisconnect ? button : /* @__PURE__ */ jsx(TooltipImpl, { label: t2("login.requires_settings_update_permission"), children: /* @__PURE__ */ jsx("span", { children: button }) }) });
1447
1449
  };
1448
1450
  const OverviewItem = (props) => {
1449
1451
  return /* @__PURE__ */ jsxs(Grid.Root, { padding: 1, col: 6, s: 12, children: [
@@ -1474,7 +1476,7 @@ const Overview = ({ title, subtitle, isLoadingProp = false }) => {
1474
1476
  setIsLoading(true);
1475
1477
  const project = await ProjectService.getConnectedProject();
1476
1478
  setConnectedProject(project);
1477
- void PluginSettingsService.updatePluginSettings({ defaultRoute: PLUGIN_ROUTES.OVERVIEW });
1479
+ void PluginSettingsService.updatePluginSettingsUiPrefs({ defaultRoute: PLUGIN_ROUTES.OVERVIEW });
1478
1480
  setIsLoading(false);
1479
1481
  }
1480
1482
  void initComponent();
@@ -5061,7 +5063,7 @@ const Upload = (props) => {
5061
5063
  setLocalazySourceLanguage(sourceLanguage);
5062
5064
  setModelChanged(modelChangedResult);
5063
5065
  setLocalesIncompatible(!localesCompatibleResult);
5064
- void PluginSettingsService.updatePluginSettings({ defaultRoute: PLUGIN_ROUTES.UPLOAD });
5066
+ void PluginSettingsService.updatePluginSettingsUiPrefs({ defaultRoute: PLUGIN_ROUTES.UPLOAD });
5065
5067
  setIsLoading(false);
5066
5068
  }
5067
5069
  void initComponent();
@@ -5194,16 +5196,9 @@ const Download = (props) => {
5194
5196
  window.open(identity.project.url, "_blank");
5195
5197
  };
5196
5198
  const [projectLanguages, setProjectLanguages] = useState([]);
5197
- const [formModel, setFormModel] = useState({});
5199
+ const [uiLanguages, setUiLanguages] = useState([]);
5198
5200
  const onDownloadLanguagesChange = (languages) => {
5199
- const newFormModel = cloneDeep(formModel);
5200
- set(newFormModel, "download.uiLanguages", languages);
5201
- setFormModel(newFormModel);
5202
- try {
5203
- void PluginSettingsService.updatePluginSettings(newFormModel);
5204
- } catch (e2) {
5205
- console.error(e2.message);
5206
- }
5201
+ setUiLanguages(languages);
5207
5202
  };
5208
5203
  const onDownloadClick = async (fullSync = false) => {
5209
5204
  setIsDownloading(true);
@@ -5212,7 +5207,7 @@ const Download = (props) => {
5212
5207
  success: false,
5213
5208
  report: []
5214
5209
  });
5215
- const result = await LocalazyDownloadService.download({ fullSync });
5210
+ const result = await LocalazyDownloadService.download({ fullSync, uiLanguages });
5216
5211
  const { streamIdentifier } = result;
5217
5212
  downloadAlertsService.setStreamIdentifier(streamIdentifier);
5218
5213
  downloadAlertsService.onDownload((data) => {
@@ -5253,8 +5248,8 @@ const Download = (props) => {
5253
5248
  setHasSyncCursor(syncCursor?.processedKeys !== void 0 && Object.keys(syncCursor.processedKeys).length > 0);
5254
5249
  const projectLanguagesWithoutDefaultLanguage = project?.languages?.filter((language) => language.id !== project.sourceLanguage) || [];
5255
5250
  setProjectLanguages(projectLanguagesWithoutDefaultLanguage);
5256
- setFormModel(globalSettings);
5257
- void PluginSettingsService.updatePluginSettings({ defaultRoute: PLUGIN_ROUTES.DOWNLOAD });
5251
+ setUiLanguages(Array.isArray(globalSettings?.download?.uiLanguages) ? globalSettings.download.uiLanguages : []);
5252
+ void PluginSettingsService.updatePluginSettingsUiPrefs({ defaultRoute: PLUGIN_ROUTES.DOWNLOAD });
5258
5253
  setIsLoading(false);
5259
5254
  }
5260
5255
  void initComponent();
@@ -5312,7 +5307,7 @@ const Download = (props) => {
5312
5307
  /* @__PURE__ */ jsx(Box, { paddingTop: 6, children: /* @__PURE__ */ jsx(
5313
5308
  LanguagesSelector,
5314
5309
  {
5315
- preselectedLanguages: formModel?.download?.uiLanguages || [],
5310
+ preselectedLanguages: uiLanguages,
5316
5311
  projectLanguages,
5317
5312
  onChange: (languages) => onDownloadLanguagesChange(languages),
5318
5313
  label: t2("download.ui_languages"),
@@ -5689,6 +5684,9 @@ const useDebouncedSearch = () => {
5689
5684
  const ActivityLogs = (props) => {
5690
5685
  const { t: t2 } = useTranslation();
5691
5686
  const navigate = useNavigate();
5687
+ const {
5688
+ allowedActions: { canTransfer }
5689
+ } = useRBAC(PERMISSIONS.TRANSFER);
5692
5690
  const [isLoading, setIsLoading] = useState(true);
5693
5691
  const [sessions, setSessions] = useState([]);
5694
5692
  const [activeTab, setActiveTab] = useState("upload");
@@ -5702,7 +5700,7 @@ const ActivityLogs = (props) => {
5702
5700
  const debouncedSaveSortPrefs = useCallback((prefs) => {
5703
5701
  if (saveSortTimerRef.current) clearTimeout(saveSortTimerRef.current);
5704
5702
  saveSortTimerRef.current = setTimeout(() => {
5705
- void PluginSettingsService.updatePluginSettings({ activityLogsSort: prefs });
5703
+ void PluginSettingsService.updatePluginSettingsUiPrefs({ activityLogsSort: prefs });
5706
5704
  }, 1e3);
5707
5705
  }, []);
5708
5706
  const onSortChange = (eventType, key, direction) => {
@@ -5758,7 +5756,7 @@ const ActivityLogs = (props) => {
5758
5756
  void fetchSessions("upload");
5759
5757
  };
5760
5758
  void init();
5761
- void PluginSettingsService.updatePluginSettings({ defaultRoute: PLUGIN_ROUTES.ACTIVITY_LOGS });
5759
+ void PluginSettingsService.updatePluginSettingsUiPrefs({ defaultRoute: PLUGIN_ROUTES.ACTIVITY_LOGS });
5762
5760
  }, []);
5763
5761
  return /* @__PURE__ */ jsxs(Fragment, { children: [
5764
5762
  /* @__PURE__ */ jsx(
@@ -5768,7 +5766,16 @@ const ActivityLogs = (props) => {
5768
5766
  subtitle: props.subtitle,
5769
5767
  primaryAction: /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
5770
5768
  /* @__PURE__ */ jsx(Button, { variant: "secondary", startIcon: /* @__PURE__ */ jsx(ForwardRef$3P, {}), onClick: onExportLogs, children: t2("activity_logs.export_logs") }),
5771
- /* @__PURE__ */ jsx(Button, { variant: "danger-light", startIcon: /* @__PURE__ */ jsx(ForwardRef$j, {}), onClick: () => setShowClearConfirm(true), children: t2("activity_logs.clear_logs") })
5769
+ /* @__PURE__ */ jsx(
5770
+ Button,
5771
+ {
5772
+ variant: "danger-light",
5773
+ startIcon: /* @__PURE__ */ jsx(ForwardRef$j, {}),
5774
+ disabled: !canTransfer,
5775
+ onClick: () => setShowClearConfirm(true),
5776
+ children: t2("activity_logs.clear_logs")
5777
+ }
5778
+ )
5772
5779
  ] })
5773
5780
  }
5774
5781
  ),
@@ -6136,6 +6143,10 @@ const App = () => {
6136
6143
  const { isLoggedIn, isFetchingIdentity } = useLocalazyIdentity();
6137
6144
  const headerTitle = useHeaderTitle();
6138
6145
  const headerSubtitle = useHeaderSubtitle();
6146
+ const {
6147
+ allowedActions: { canRead, canTransfer },
6148
+ isLoading: isLoadingPermissions
6149
+ } = useRBAC([...PERMISSIONS.READ, ...PERMISSIONS.TRANSFER]);
6139
6150
  const normalizedPath = location2.pathname.replace(/\/+$/, "");
6140
6151
  const pluginRoot = `/plugins/${PLUGIN_ID}`;
6141
6152
  const isAtPluginRoot = normalizedPath === pluginRoot;
@@ -6157,6 +6168,19 @@ const App = () => {
6157
6168
  };
6158
6169
  void fetchData();
6159
6170
  }, [isLoggedIn, isFetchingIdentity, normalizedPath]);
6171
+ if (isLoadingPermissions) {
6172
+ return /* @__PURE__ */ jsxs(DesignSystemProvider, { theme: getDefaultTheme(), children: [
6173
+ /* @__PURE__ */ jsx(HeadingFixGlobalStyle, {}),
6174
+ /* @__PURE__ */ jsx(Box, { background: "neutral100", children: /* @__PURE__ */ jsx(Layouts.Root, { children: /* @__PURE__ */ jsx(PluginPageLoader, {}) }) })
6175
+ ] });
6176
+ }
6177
+ if (!canRead) {
6178
+ return /* @__PURE__ */ jsxs(DesignSystemProvider, { theme: getDefaultTheme(), children: [
6179
+ /* @__PURE__ */ jsx(HeadingFixGlobalStyle, {}),
6180
+ /* @__PURE__ */ jsx(Box, { background: "neutral100", children: /* @__PURE__ */ jsx(Layouts.Root, { children: /* @__PURE__ */ jsx(Page.NoPermissions, {}) }) })
6181
+ ] });
6182
+ }
6183
+ const transferElement = (page) => canTransfer ? page : /* @__PURE__ */ jsx(Page.NoPermissions, {});
6160
6184
  return /* @__PURE__ */ jsxs(DesignSystemProvider, { theme: getDefaultTheme(), children: [
6161
6185
  /* @__PURE__ */ jsx(HeadingFixGlobalStyle, {}),
6162
6186
  /* @__PURE__ */ jsx(PluginErrorBoundary, { children: /* @__PURE__ */ jsx(Box, { background: "neutral100", children: /* @__PURE__ */ jsxs(Layouts.Root, { sideNav: isLoggedIn && /* @__PURE__ */ jsx(SideNav, {}), children: [
@@ -6176,8 +6200,20 @@ const App = () => {
6176
6200
  element: /* @__PURE__ */ jsx(Overview, { title: headerTitle, subtitle: headerSubtitle, isLoadingProp: false })
6177
6201
  }
6178
6202
  ),
6179
- /* @__PURE__ */ jsx(Route, { path: `upload`, element: /* @__PURE__ */ jsx(Upload, { title: headerTitle, subtitle: headerSubtitle }) }),
6180
- /* @__PURE__ */ jsx(Route, { path: `download`, element: /* @__PURE__ */ jsx(Download, { title: headerTitle, subtitle: headerSubtitle }) }),
6203
+ /* @__PURE__ */ jsx(
6204
+ Route,
6205
+ {
6206
+ path: `upload`,
6207
+ element: transferElement(/* @__PURE__ */ jsx(Upload, { title: headerTitle, subtitle: headerSubtitle }))
6208
+ }
6209
+ ),
6210
+ /* @__PURE__ */ jsx(
6211
+ Route,
6212
+ {
6213
+ path: `download`,
6214
+ element: transferElement(/* @__PURE__ */ jsx(Download, { title: headerTitle, subtitle: headerSubtitle }))
6215
+ }
6216
+ ),
6181
6217
  /* @__PURE__ */ jsx(Route, { path: `activity-logs`, element: /* @__PURE__ */ jsx(ActivityLogs, { title: headerTitle, subtitle: headerSubtitle }) }),
6182
6218
  /* @__PURE__ */ jsx(
6183
6219
  Route,