@localazy/strapi-plugin 0.1.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 (150) hide show
  1. package/.babelrc +7 -0
  2. package/.eslintrc +7 -0
  3. package/.github/workflows/.build.yml +39 -0
  4. package/.nvmrc +1 -0
  5. package/LICENSE +21 -0
  6. package/README.md +40 -0
  7. package/admin/src/.eslintrc +3 -0
  8. package/admin/src/components/Initializer/index.js +26 -0
  9. package/admin/src/components/PluginIcon/index.js +14 -0
  10. package/admin/src/i18n.js +37 -0
  11. package/admin/src/index.js +106 -0
  12. package/admin/src/modules/@common/components/Icons/Localazy/index.js +12 -0
  13. package/admin/src/modules/@common/components/SideNav/index.js +46 -0
  14. package/admin/src/modules/@common/components/TransferReport/index.js +54 -0
  15. package/admin/src/modules/@common/locale/en.js +13 -0
  16. package/admin/src/modules/@common/services/i18n-service.js +15 -0
  17. package/admin/src/modules/@common/services/project-service.js +15 -0
  18. package/admin/src/modules/@common/services/user-service.js +38 -0
  19. package/admin/src/modules/@common/utils/are-locales-compatible.js +42 -0
  20. package/admin/src/modules/@common/utils/functions/flatten-deep.js +23 -0
  21. package/admin/src/modules/@common/utils/get-default-locale.js +32 -0
  22. package/admin/src/modules/@common/utils/get-header-subtitle.js +21 -0
  23. package/admin/src/modules/@common/utils/get-header-title.js +24 -0
  24. package/admin/src/modules/@common/utils/get-nav.js +35 -0
  25. package/admin/src/modules/@common/utils/history.js +6 -0
  26. package/admin/src/modules/@common/utils/is-plugin-route.js +15 -0
  27. package/admin/src/modules/@common/utils/iso-locales-utils.js +15 -0
  28. package/admin/src/modules/@common/utils/redirect-to-plugin-route.js +22 -0
  29. package/admin/src/modules/@common/utils/sort-by-model-name.js +10 -0
  30. package/admin/src/modules/localazy-download/locale/en.js +20 -0
  31. package/admin/src/modules/localazy-download/services/localazy-download-service.js +16 -0
  32. package/admin/src/modules/localazy-upload/components/ReadDocsButton.js +21 -0
  33. package/admin/src/modules/localazy-upload/locale/en.js +18 -0
  34. package/admin/src/modules/localazy-upload/services/localazy-upload-service.js +16 -0
  35. package/admin/src/modules/login/components/LoginButton/index.js +56 -0
  36. package/admin/src/modules/login/components/LogoutButton/index.js +50 -0
  37. package/admin/src/modules/login/locale/en.js +7 -0
  38. package/admin/src/modules/login/services/localazy-login-service.js +29 -0
  39. package/admin/src/modules/plugin-settings/components/ContentTransferSetup/index.js +292 -0
  40. package/admin/src/modules/plugin-settings/components/Tree/index.js +120 -0
  41. package/admin/src/modules/plugin-settings/components/TreeItem/index.js +48 -0
  42. package/admin/src/modules/plugin-settings/functions/build-content-transfer-setup-schema.js +71 -0
  43. package/admin/src/modules/plugin-settings/functions/has-model-changed.js +85 -0
  44. package/admin/src/modules/plugin-settings/locale/en.js +19 -0
  45. package/admin/src/modules/plugin-settings/services/plugin-settings-service.js +32 -0
  46. package/admin/src/modules/plugin-settings/services/strapi-model-service.js +26 -0
  47. package/admin/src/modules/plugin-settings/utils/__tests__/get-models-tree.test.js +135 -0
  48. package/admin/src/modules/plugin-settings/utils/array-of-models-to-object.js +6 -0
  49. package/admin/src/modules/plugin-settings/utils/find-model-value-by-key.js +8 -0
  50. package/admin/src/modules/plugin-settings/utils/get-filtered-models-schemas.js +34 -0
  51. package/admin/src/modules/plugin-settings/utils/get-models-tree.js +73 -0
  52. package/admin/src/modules/settings/components/OverviewItem/index.js +44 -0
  53. package/admin/src/modules/settings/components/OverviewItemLink/index.js +34 -0
  54. package/admin/src/modules/settings/components/PrerequisitiesInfo/index.js +30 -0
  55. package/admin/src/modules/settings/locale/en.js +13 -0
  56. package/admin/src/modules/user/model/localazy-identity.js +14 -0
  57. package/admin/src/modules/user/services/localazy-user-service.js +38 -0
  58. package/admin/src/pages/App/index.js +152 -0
  59. package/admin/src/pages/Download/index.js +179 -0
  60. package/admin/src/pages/Index/index.js +8 -0
  61. package/admin/src/pages/Login/index.js +94 -0
  62. package/admin/src/pages/PluginSettings/index.js +42 -0
  63. package/admin/src/pages/Settings/index.js +120 -0
  64. package/admin/src/pages/Upload/index.js +187 -0
  65. package/admin/src/pluginId.js +5 -0
  66. package/admin/src/plugins/apolloClient.js +33 -0
  67. package/admin/src/state/localazy-identity.js +29 -0
  68. package/admin/src/tests/fixtures/all-models.js +877 -0
  69. package/admin/src/tests/fixtures/restaurant-model.js +230 -0
  70. package/admin/src/translations/en.json +4 -0
  71. package/admin/src/translations/fr.json +1 -0
  72. package/admin/src/utils/createAxiosInstance.js +52 -0
  73. package/admin/src/utils/getTrad.js +5 -0
  74. package/jest.config.js +0 -0
  75. package/package.json +60 -0
  76. package/server/bootstrap.js +5 -0
  77. package/server/config/development/index.js +15 -0
  78. package/server/config/example/index.js +15 -0
  79. package/server/config/index.js +15 -0
  80. package/server/config/production/index.js +16 -0
  81. package/server/config/schema.graphql.js +21 -0
  82. package/server/content-types/index.js +3 -0
  83. package/server/controllers/index.js +19 -0
  84. package/server/controllers/localazy-auth-controller.js +46 -0
  85. package/server/controllers/localazy-project-controller.js +21 -0
  86. package/server/controllers/localazy-transfer-controller.js +469 -0
  87. package/server/controllers/localazy-user-controller.js +24 -0
  88. package/server/controllers/my-controller.js +10 -0
  89. package/server/controllers/plugin-settings-controller.js +17 -0
  90. package/server/controllers/strapi-controller.js +16 -0
  91. package/server/db/model/content-transfer-setup.js +13 -0
  92. package/server/db/model/localazy-user.js +23 -0
  93. package/server/db/model/utils/get-strapi-store.js +9 -0
  94. package/server/destroy.js +5 -0
  95. package/server/index.js +25 -0
  96. package/server/middlewares/index.js +3 -0
  97. package/server/policies/index.js +3 -0
  98. package/server/register.js +5 -0
  99. package/server/routes/index.js +26 -0
  100. package/server/routes/localazy-auth.js +22 -0
  101. package/server/routes/localazy-project.js +14 -0
  102. package/server/routes/localazy-transfer.js +22 -0
  103. package/server/routes/localazy-user.js +30 -0
  104. package/server/routes/plugin-settings.js +22 -0
  105. package/server/routes/strapi.js +22 -0
  106. package/server/services/index.js +21 -0
  107. package/server/services/localazy-auth-service.js +38 -0
  108. package/server/services/localazy-download-service.js +54 -0
  109. package/server/services/localazy-pubapi-service.js +47 -0
  110. package/server/services/localazy-upload-service.js +95 -0
  111. package/server/services/localazy-user-service.js +29 -0
  112. package/server/services/plugin-settings-service.js +30 -0
  113. package/server/services/strapi-i18n-service.js +132 -0
  114. package/server/services/strapi-service.js +23 -0
  115. package/server/tests/fixtures/all-models-with-multimedia.js +1064 -0
  116. package/server/tests/fixtures/all-models.js +869 -0
  117. package/server/tests/fixtures/collection-flattened.js +76 -0
  118. package/server/tests/fixtures/restaurant-flattened.js +46 -0
  119. package/server/tests/fixtures/restaurant-model.js +230 -0
  120. package/server/tests/fixtures/restaurants-entries.js +152 -0
  121. package/server/tests/fixtures/restaurants-pick-paths.js +15 -0
  122. package/server/tests/fixtures/strapi-book-with-multimedia-components-cs.js +31 -0
  123. package/server/tests/fixtures/stringified-book-cs.js +3 -0
  124. package/server/tests/fixtures/stringified-book-with-multimedia-components-cs.js +3 -0
  125. package/server/tests/fixtures/stringified-restaurant-cs.js +3 -0
  126. package/server/tests/fixtures/stringified-restaurant-de.js +5 -0
  127. package/server/utils/__tests__/build-populate.test.js +85 -0
  128. package/server/utils/__tests__/flatten-object.test.js +171 -0
  129. package/server/utils/__tests__/get-models-tree.test.js +170 -0
  130. package/server/utils/__tests__/parsed-localazy-entry-to-create-entry.test.js +153 -0
  131. package/server/utils/__tests__/parsed-localazy-entry-to-update-entry.test.js +172 -0
  132. package/server/utils/__tests__/pick-entries.test.js +83 -0
  133. package/server/utils/build-populate.js +43 -0
  134. package/server/utils/delay.js +6 -0
  135. package/server/utils/flatten-object.js +52 -0
  136. package/server/utils/get-localazy-api.js +22 -0
  137. package/server/utils/get-main-keys-of-nested-array-objects.js +5 -0
  138. package/server/utils/get-models-tree.js +43 -0
  139. package/server/utils/intl-display-name.js +7 -0
  140. package/server/utils/is-generally-applicable-content-type.js +33 -0
  141. package/server/utils/is-localazy-applicable-content-type.js +36 -0
  142. package/server/utils/iso-locales-utils.js +22 -0
  143. package/server/utils/objectivise-entry.js +41 -0
  144. package/server/utils/omit-deep.js +17 -0
  145. package/server/utils/parsed-localazy-entry-to-create-entry.js +115 -0
  146. package/server/utils/parsed-localazy-entry-to-update-entry.js +31 -0
  147. package/server/utils/pick-entries.js +14 -0
  148. package/server/utils/transfer-setup-utils.js +39 -0
  149. package/strapi-admin.js +14 -0
  150. package/strapi-server.js +5 -0
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import House from "@strapi/icons/House";
3
+ import Download from "@strapi/icons/Download";
4
+ import Upload from "@strapi/icons/Upload";
5
+ import pluginId from "../../../pluginId";
6
+ import i18n from "../../../i18n";
7
+
8
+ const BASE_PATH = `/admin/plugins/${pluginId}`;
9
+ const t = i18n.t;
10
+ const gevNav = () => {
11
+ return [
12
+ {
13
+ id: "settings",
14
+ label: t("common.settings"),
15
+ description: t("settings.description"),
16
+ icon: <House />,
17
+ to: `${BASE_PATH}/settings`,
18
+ },
19
+ {
20
+ id: "upload",
21
+ label: t("common.upload_to_localazy"),
22
+ description: t("upload.description"),
23
+ icon: < Upload />,
24
+ to: `${BASE_PATH}/upload`,
25
+ },
26
+ {
27
+ id: "download",
28
+ label: t("common.download_to_strapi"),
29
+ description: t("download.description"),
30
+ icon: <Download />,
31
+ to: `${BASE_PATH}/download`,
32
+ },
33
+ ];
34
+ };
35
+ export default gevNav;
@@ -0,0 +1,6 @@
1
+ // history.js
2
+ import { createBrowserHistory } from 'history';
3
+
4
+ export default createBrowserHistory({
5
+ /* pass a configuration object here if needed */
6
+ })
@@ -0,0 +1,15 @@
1
+ import pluginId from "../../../pluginId";
2
+
3
+ export default (location, path = "") => {
4
+ if (path) {
5
+ return (
6
+ location.pathname.endsWith(`${pluginId}/${path}`) ||
7
+ location.pathname.endsWith(`${pluginId}/${path}/`)
8
+ );
9
+ }
10
+
11
+ return (
12
+ location.pathname.endsWith(pluginId) ||
13
+ location.pathname.endsWith(`${pluginId}/`)
14
+ );
15
+ };
@@ -0,0 +1,15 @@
1
+ export const isoStrapiToLocalazy = (isoStrapi) => {
2
+ if (!isoStrapi) {
3
+ return null;
4
+ }
5
+
6
+ return isoStrapi.replace("_", "-");
7
+ };
8
+
9
+ export const isoLocalazyToStrapi = (isoLocalazy) => {
10
+ if (!isoLocalazy) {
11
+ return null;
12
+ }
13
+
14
+ return isoLocalazy.replace("-", "_");
15
+ };
@@ -0,0 +1,22 @@
1
+ import pluginId from "../../../pluginId";
2
+ import history from "./history";
3
+
4
+ export const PLUGIN_ROUTES = {
5
+ ROOT: "",
6
+ LOGIN: "login",
7
+ UPLOAD: "upload",
8
+ DOWNLOAD: "download",
9
+ SETTINGS: "settings",
10
+ // settings routes
11
+ CONTENT_TRANSFER_SETUP: "content-transfer-setup",
12
+ };
13
+
14
+ export default (route) => {
15
+ if (route === PLUGIN_ROUTES.CONTENT_TRANSFER_SETUP) {
16
+ history.push(`/admin/settings/${pluginId}/${route}`);
17
+
18
+ return;
19
+ }
20
+
21
+ history.push(`/admin/plugins/${pluginId}/${route}`);
22
+ };
@@ -0,0 +1,10 @@
1
+ export default (a, b) => {
2
+ const uidA = Object.keys(a)[0].toLowerCase();
3
+ const uidB = Object.keys(b)[0].toLowerCase();
4
+
5
+ if (uidA < uidB) return -1;
6
+
7
+ if (uidA > uidB) return 1;
8
+
9
+ return 0;
10
+ };
@@ -0,0 +1,20 @@
1
+ export default {
2
+ description: "Download content in multiple languages to your Strapi project",
3
+ download_to_strapi: "Start download",
4
+ content_types_model_changed: "Content Types Model Changed",
5
+ close: "Close",
6
+ please_update_your_content:
7
+ "Please update your content transfer setup before proceeding to upload/download",
8
+ languages_incompatible: "Languages incompatible",
9
+ languages_incompatible_message:
10
+ "Strapi default language and Localazy source language are incompatible, to proceed, please update one of them.",
11
+ download_result: "Download completed:",
12
+ download_success: "Content successfully imported to Strapi.",
13
+ download_failed: "Download partially failed, please check the report for details...",
14
+ translate_in_localazy: "Translate in Localazy",
15
+ download_note_a: "Download ",
16
+ download_note_b: "will rewrite all previous translations.",
17
+ make_sure_note_a: "Make sure the translations are approved in Localazy, otherwise they ",
18
+ make_sure_note_b: "will not ",
19
+ make_sure_note_c: "be downloaded to Strapi.",
20
+ };
@@ -0,0 +1,16 @@
1
+ import createAxiosInstance from "../../../utils/createAxiosInstance";
2
+
3
+ const BASE_PATH = "/transfer";
4
+ const axiosInstance = createAxiosInstance();
5
+
6
+ export default class LocalazyDownloadService {
7
+ static async download(data = {}) {
8
+ try {
9
+ const result = await axiosInstance.post(`${BASE_PATH}/download`, data);
10
+
11
+ return result.data;
12
+ } catch (e) {
13
+ throw e.data;
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { Button } from "@strapi/design-system/Button";
3
+ import { useTranslation } from "react-i18next";
4
+ import "../../../i18n";
5
+
6
+ function ReadDocsButton() {
7
+ const { t } = useTranslation();
8
+
9
+ const link = "https://localazy.com/docs/strapi";
10
+ const onReadDocumentationClick = () => {
11
+ window.open(link, "_blank");
12
+ };
13
+
14
+ return (
15
+ <Button variant="tertiary" onClick={onReadDocumentationClick}>
16
+ {t("upload.read_documentation")}
17
+ </Button>
18
+ );
19
+ }
20
+
21
+ export default ReadDocsButton;
@@ -0,0 +1,18 @@
1
+ export default {
2
+ description: "Upload your content from Strapi to Localazy",
3
+ upload_to_localazy: "Start upload",
4
+ default_strapi_language: "Default Strapi language",
5
+ localazy_source_language: "Localazy source language",
6
+ change_settings: "Change settings",
7
+ read_documentation: "Read documentation",
8
+ content_types_model_changed: "Content Types Model Changed",
9
+ close: "Close",
10
+ please_update_your_content:
11
+ "Please update your content transfer configuration before proceeding to upload/download.",
12
+ languages_incompatible: "Languages mismatch!",
13
+ languages_incompatible_message: "The source language at Localazy doesn't match the Strapi default language setting. To continue, please set both languages to be the same.",
14
+ upload_result: "Upload completed:",
15
+ upload_success: "Content successfully imported to the Localazy project.",
16
+ upload_failed:
17
+ "Upload partially failed, please check the report for details...",
18
+ };
@@ -0,0 +1,16 @@
1
+ import createAxiosInstance from "../../../utils/createAxiosInstance";
2
+
3
+ const BASE_PATH = "/transfer";
4
+ const axiosInstance = createAxiosInstance();
5
+
6
+ export default class LocalazyUploadService {
7
+ static async upload(data = {}) {
8
+ try {
9
+ const result = await axiosInstance.post(`${BASE_PATH}/upload`, data);
10
+
11
+ return result.data;
12
+ } catch (e) {
13
+ throw e.data;
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,56 @@
1
+ /*
2
+ *
3
+ * LoginButton
4
+ *
5
+ */
6
+
7
+ import React, { useState } from "react";
8
+ import PropTypes from "prop-types";
9
+ import { Button } from "@strapi/design-system/Button";
10
+ import { useTranslation } from "react-i18next";
11
+ import LocalazyLoginService from "../../services/localazy-login-service";
12
+ import { getStrapiDefaultLocale } from "../../../@common/utils/get-default-locale";
13
+ import { isoStrapiToLocalazy } from "../../../@common/utils/iso-locales-utils";
14
+ import { config } from "../../../../../../server";
15
+ import "../../../../i18n";
16
+
17
+ function LoginButton(props) {
18
+ const { t } = useTranslation();
19
+
20
+ const [isLoading, setIsLoading] = useState(false);
21
+ const login = async () => {
22
+ setIsLoading(true);
23
+
24
+ const strapiDefaultLocale = await getStrapiDefaultLocale();
25
+ const localazyFormatLocaleCode = isoStrapiToLocalazy(strapiDefaultLocale.code);
26
+ const keys = await LocalazyLoginService.generateKeys();
27
+ const params = new URLSearchParams({
28
+ client_id: config.default.LOCALAZY_OAUTH_APP_CLIENT_ID,
29
+ custom_id: keys.writeKey,
30
+ allow_create: "true",
31
+ create_locale: localazyFormatLocaleCode,
32
+ });
33
+ const url = `${config.default.LOCALAZY_OAUTH_URL}?${params.toString()}`;
34
+ window.open(url);
35
+
36
+ const pollResult = await LocalazyLoginService.continuousPoll(keys.readKey);
37
+
38
+ setIsLoading(false);
39
+
40
+ props.onResultFetched(pollResult);
41
+ };
42
+
43
+ return (
44
+ <div>
45
+ <Button variant="default" size="L" loading={isLoading} onClick={login}>
46
+ {t("login.login_with_localazy")}
47
+ </Button>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ LoginButton.propTypes = {
53
+ onResultFetched: PropTypes.func.isRequired,
54
+ };
55
+
56
+ export default LoginButton;
@@ -0,0 +1,50 @@
1
+ /*
2
+ *
3
+ * LogoutButton
4
+ *
5
+ */
6
+
7
+ import React, { useState } from "react";
8
+ import PropTypes from "prop-types";
9
+ import { Button } from "@strapi/design-system/Button";
10
+ import { useTranslation } from "react-i18next";
11
+ import Exit from "@strapi/icons/Exit";
12
+ import LocalazyUserService from "../../../user/services/localazy-user-service";
13
+ import { setLocalazyIdentity } from "../../../../state/localazy-identity";
14
+ import "../../../../i18n";
15
+
16
+ function LogoutButton(props) {
17
+ const { t } = useTranslation();
18
+ const [isLoading, setIsLoading] = useState(false);
19
+ const logout = async () => {
20
+ setIsLoading(true);
21
+
22
+ // delete identity from db
23
+ // delete identity from app state
24
+ LocalazyUserService.deleteIdentity();
25
+ setLocalazyIdentity();
26
+
27
+ setIsLoading(false);
28
+
29
+ props.onResultFetched();
30
+ };
31
+
32
+ return (
33
+ <div>
34
+ <Button
35
+ startIcon={<Exit />}
36
+ variant="secondary"
37
+ loading={isLoading}
38
+ onClick={logout}
39
+ >
40
+ {t("login.logout_from_localazy")}
41
+ </Button>
42
+ </div>
43
+ );
44
+ }
45
+
46
+ LogoutButton.propTypes = {
47
+ onResultFetched: PropTypes.func.isRequired,
48
+ };
49
+
50
+ export default LogoutButton;
@@ -0,0 +1,7 @@
1
+ export default {
2
+ login_with_localazy: "Login with Localazy",
3
+ login_and_start_translating_your_project: "Login and start translating your project with powerful Localazy tools.",
4
+ you_have_to_own_account_for_the_plugin_to_work_properly: "You have to own a Localazy account for the plugin to work properly.",
5
+ read_the_documentation: "Read the documentation",
6
+ logout_from_localazy: "Disconnect from Localazy",
7
+ };
@@ -0,0 +1,29 @@
1
+ /* eslint-disable no-useless-catch */
2
+ import createAxiosInstance from "../../../utils/createAxiosInstance";
3
+
4
+ const BASE_PATH = "/auth";
5
+ const axiosInstance = createAxiosInstance();
6
+
7
+ export default class LocalazyLoginService {
8
+ static async generateKeys() {
9
+ try {
10
+ const result = await axiosInstance.get(`${BASE_PATH}/generate-keys`);
11
+
12
+ return result.data;
13
+ } catch (e) {
14
+ throw e;
15
+ }
16
+ }
17
+
18
+ static async continuousPoll(readKey) {
19
+ try {
20
+ const result = await axiosInstance.get(
21
+ `${BASE_PATH}/continuous-poll?readKey=${readKey}`
22
+ );
23
+
24
+ return result.data;
25
+ } catch (e) {
26
+ throw e;
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,292 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import isEqual from "lodash-es/isEqual";
4
+ import cloneDeep from "lodash-es/cloneDeep";
5
+ import { HeaderLayout } from "@strapi/design-system/Layout";
6
+ import Check from "@strapi/icons/Check";
7
+ import { Box } from "@strapi/design-system/Box";
8
+ import { Button } from "@strapi/design-system/Button";
9
+ import { Alert } from "@strapi/design-system/Alert";
10
+ import { Redirect } from "react-router-dom";
11
+ import { Flex } from "@strapi/design-system/Flex";
12
+ import { Divider } from "@strapi/design-system/Divider";
13
+ import { Typography } from "@strapi/design-system/Typography";
14
+ import set from "lodash-es/set";
15
+ import get from "lodash-es/get";
16
+ import PluginSettingsService from "../../services/plugin-settings-service";
17
+ import StrapiModelService from "../../services/strapi-model-service";
18
+ import getFilteredModelsSchemas from "../../utils/get-filtered-models-schemas";
19
+ import getModelsTree from "../../utils/get-models-tree";
20
+ import hasModelChanged from "../../functions/has-model-changed";
21
+ import buildContentTransferSetupSchema from "../../functions/build-content-transfer-setup-schema";
22
+ import { getLocalazyIdentity } from "../../../../state/localazy-identity";
23
+ import Tree from "../Tree";
24
+
25
+ import "../../../../i18n";
26
+
27
+ function ContentTransferSetup() {
28
+ /**
29
+ * Translation function
30
+ */
31
+ // eslint-disable-next-line no-unused-vars
32
+ const { t } = useTranslation();
33
+
34
+ /**
35
+ * Is user logged in
36
+ */
37
+ const [localazyIdentity] = getLocalazyIdentity();
38
+ const hasLocalazyIdentity = () => !!localazyIdentity.accessToken;
39
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
40
+
41
+ /**
42
+ * Component state
43
+ */
44
+ const [isLoading, setIsLoading] = useState(true);
45
+ const [showAlert, setShowAlert] = useState(false);
46
+ const [showModalChangedAlert, setShowModalChangedAlert] = useState(false);
47
+
48
+ /**
49
+ * Accordion expanded state model
50
+ */
51
+ const [, setItemsExpandedModel] = useState([]);
52
+ const buildItemsExpandedModel = (models) => {
53
+ const r = {};
54
+ getFilteredModelsSchemas(models).forEach((model, index) => {
55
+ r[model.modelUid] = index === 0;
56
+ });
57
+
58
+ return r;
59
+ };
60
+
61
+ /**
62
+ * Helper form model object
63
+ */
64
+ const [, setTreeModelsSchemas] = useState([]);
65
+
66
+ /**
67
+ * Content transfer setup form model
68
+ */
69
+ const [originalFormModel, setOriginalFormModel] = useState([]);
70
+ const [formModel, setFormModel] = useState([]);
71
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
72
+
73
+ const onCancelClick = () => {
74
+ setFormModel(cloneDeep(originalFormModel));
75
+ setHasUnsavedChanges(false);
76
+ }
77
+
78
+ /**
79
+ * Save the content transfer setup
80
+ */
81
+ const saveContentTransferSetup = async (data) => {
82
+ try {
83
+ await PluginSettingsService.updateContentTransferSetup(data);
84
+ setOriginalFormModel(cloneDeep(data));
85
+ setHasUnsavedChanges(false);
86
+ setShowAlert(true);
87
+ setShowModalChangedAlert(false);
88
+ } catch (e) {
89
+ throw e.data;
90
+ }
91
+ };
92
+
93
+ const onTreeItemClick = (keys, currentValue) => {
94
+ setFormModel((prevState) => {
95
+ // model of a subtree is the same for each of the keys
96
+ const modelName = keys[0].split(".")[0];
97
+ let arrayIndex;
98
+ const model = prevState.find((m, index) => {
99
+ if (Object.keys(m).includes(modelName)) {
100
+ arrayIndex = index;
101
+
102
+ return true;
103
+ }
104
+
105
+ return false;
106
+ });
107
+
108
+ if (model === undefined) {
109
+ return prevState;
110
+ }
111
+
112
+ const newState = cloneDeep(prevState);
113
+ keys.forEach((key) => {
114
+ const isEditable = get(model, key) !== null;
115
+
116
+ if (isEditable) {
117
+ set(model, key, !currentValue);
118
+ }
119
+ });
120
+ newState.splice(arrayIndex, 1, model);
121
+ setHasUnsavedChanges(!isEqual(originalFormModel, newState) || showModalChangedAlert);
122
+
123
+ return newState;
124
+ });
125
+ };
126
+
127
+ const link = "https://localazy.com/docs/strapi";
128
+ const onReadDocumentationClick = () => {
129
+ window.open(link, "_blank");
130
+ };
131
+
132
+ useEffect(() => {
133
+ async function fetchData() {
134
+ setIsLoading(true);
135
+ setIsLoggedIn(hasLocalazyIdentity());
136
+
137
+ if (!hasLocalazyIdentity()) {
138
+ setIsLoading(false);
139
+
140
+ return;
141
+ }
142
+
143
+ /**
144
+ * Fetch current models schemas
145
+ */
146
+ const [models, localizableModels] = await Promise.all([
147
+ StrapiModelService.getModels(),
148
+ StrapiModelService.getLocalizableModels(),
149
+ ]);
150
+
151
+ /**
152
+ * Fetch stored content transfer setup
153
+ */
154
+ const storedContentTransferSetup =
155
+ await PluginSettingsService.getContentTransferSetup();
156
+
157
+ /**
158
+ * Build and set accordion expanded state model
159
+ */
160
+ const itemsExpandedModel = buildItemsExpandedModel(localizableModels);
161
+ setItemsExpandedModel(itemsExpandedModel);
162
+
163
+ /**
164
+ * Build and set form model
165
+ */
166
+ const allModelsTree = getModelsTree(models, models);
167
+
168
+ const localizableTree = getModelsTree(models, localizableModels);
169
+ setTreeModelsSchemas(localizableTree);
170
+
171
+ const computedFormModel = buildContentTransferSetupSchema(
172
+ localizableTree,
173
+ storedContentTransferSetup.setup,
174
+ allModelsTree
175
+ );
176
+
177
+ setFormModel(computedFormModel);
178
+ setOriginalFormModel(cloneDeep(computedFormModel));
179
+
180
+ /**
181
+ * Handle alerts onload apperance
182
+ */
183
+ const localHasModelChanged = await hasModelChanged(localizableTree, storedContentTransferSetup.setup);
184
+ setShowModalChangedAlert(localHasModelChanged);
185
+ setHasUnsavedChanges(localHasModelChanged)
186
+
187
+ setIsLoading(false);
188
+ }
189
+ fetchData();
190
+ // eslint-disable-next-line react-hooks/exhaustive-deps
191
+ }, []);
192
+
193
+ return (
194
+ <>
195
+ {!isLoading && !isLoggedIn && <Redirect to="/plugins/localazy/login" />}
196
+
197
+ <HeaderLayout
198
+ title={t("plugin_settings.content_transfer_setup")}
199
+ subtitle={t("plugin_settings.content_transfer_setup_description")}
200
+ primaryAction={
201
+ <Flex gap={2}>
202
+ <Button
203
+ variant="secondary"
204
+ disabled={!hasUnsavedChanges}
205
+ onClick={onCancelClick}
206
+ >
207
+ {t("plugin_settings.cancel")}
208
+ </Button>
209
+ <Button
210
+ startIcon={<Check />}
211
+ disabled={!hasUnsavedChanges}
212
+ onClick={() => { saveContentTransferSetup(formModel) }}
213
+ >
214
+ {t("plugin_settings.save")}
215
+ </Button>
216
+ </Flex>
217
+ }
218
+ as="h2"
219
+ />
220
+ <Box marginRight={10} marginLeft={10}>
221
+ {!isLoading && showAlert && (
222
+ <Box marginBottom={8}>
223
+ <Alert
224
+ onClose={() => setShowAlert(false)}
225
+ closeLabel={t("plugin_settings.close")}
226
+ title={t("plugin_settings.content_transfer_setup_saved")}
227
+ variant="success"
228
+ >
229
+ {t("plugin_settings.content_transfer_setup_saved_successfully")}
230
+ </Alert>
231
+ </Box>
232
+ )}
233
+ {!isLoading && showModalChangedAlert && (
234
+ <Box marginBottom={8}>
235
+ <Alert
236
+ onClose={() => setShowModalChangedAlert(false)}
237
+ closeLabel={t("plugin_settings.close")}
238
+ title={t("plugin_settings.content_types_model_changed")}
239
+ variant="default"
240
+ >
241
+ {t("plugin_settings.please_update_your_content")}
242
+ </Alert>
243
+ </Box>
244
+ )}
245
+ {!isLoading && (
246
+ <Box
247
+ background="neutral0"
248
+ padding={7}
249
+ paddingTop={6}
250
+ shadow="tableShadow"
251
+ hasRadius
252
+ >
253
+ {formModel.map((tree, index) => {
254
+ return (
255
+ <Box
256
+ // eslint-disable-next-line react/no-array-index-key
257
+ key={`box_tree_${index}`}
258
+ marginBottom={3}
259
+ >
260
+ <Tree onTreeItemClick={onTreeItemClick} objects={tree} />
261
+ </Box>
262
+ );
263
+ })}
264
+ <Box
265
+ paddingTop={6}
266
+ paddingBottom={6}
267
+ >
268
+ <Divider />
269
+ </Box>
270
+ <Typography variant="omega">{t("plugin_settings.you_can_upload_download")}</Typography>
271
+ <Typography variant="omega" fontWeight="semiBold">{t("plugin_settings.only_text_based_content")}</Typography>
272
+ <br />
273
+ <br />
274
+ <Typography variant="omega">{t("plugin_settings.learn_more_in_docs_message_a")}</Typography>
275
+ <Typography
276
+ onClick={onReadDocumentationClick}
277
+ variant="omega"
278
+ fontWeight="semiBold"
279
+ textColor="primary600"
280
+ style={{ cursor: "pointer" }}
281
+ >
282
+ {t("plugin_settings.learn_more_in_docs_message_b")}
283
+ </Typography>
284
+ </Box>
285
+ )
286
+ }
287
+ </Box>
288
+ </>
289
+ );
290
+ }
291
+
292
+ export default ContentTransferSetup;