From a07c857149de6ef41d4812f4b80c9681773c1fc7 Mon Sep 17 00:00:00 2001 From: Edouard Lefebvre <1556109+edlefebvre@users.noreply.github.com> Date: Mon, 27 Mar 2023 15:59:28 +0200 Subject: [PATCH 1/6] translation(core admin): add some missing fr translations --- .../core/admin/admin/src/translations/fr.json | 90 ++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/packages/core/admin/admin/src/translations/fr.json b/packages/core/admin/admin/src/translations/fr.json index 9224b22609..2940a51009 100644 --- a/packages/core/admin/admin/src/translations/fr.json +++ b/packages/core/admin/admin/src/translations/fr.json @@ -3,6 +3,7 @@ "Auth.components.Oops.text": "Votre compte a été suspendu.", "Auth.components.Oops.text.admin": "Si c'est une erreur, veuillez contacter votre administrateur.", "Auth.components.Oops.title": "Oups !", + "Auth.form.active.label": "Actif", "Auth.form.button.forgot-password": "Envoyer à nouveau", "Auth.form.button.go-home": "Retour à l'accueil", "Auth.form.button.login": "Se connecter", @@ -54,6 +55,7 @@ "Auth.login.sso.subtitle": "Vous connecter via SSO", "Auth.privacy-policy-agreement.policy": "la politique de confidentialité", "Auth.privacy-policy-agreement.terms": "termes", + "Auth.reset-password.title": "Réinitialiser le mot de passe", "Content Manager": "Content Manager", "Content Type Builder": "Content Types Builder", "Documentation": "Documentation", @@ -74,22 +76,95 @@ "Roles.ListPage.notification.delete-all-not-allowed": "Certains rôles n'ont pas pu être supprimés car ils sont associés à des utilisateurs.", "Roles.ListPage.notification.delete-not-allowed": "Un rôle ne peu pas être supprimé s'il est associé à des utilisateurs.", "Roles.RoleRow.select-all": "Sélectionner {name} pour action groupée", + "Roles.RoleRow.user-count": "{number, plural, =0 {# utilisateur} one {# utilisateur} other {# utilisateurs}}", "Roles.components.List.empty.withSearch": "Il n'y a pas de rôles correspondant à la recherche ({search})...", "Settings.PageTitle": "Réglages - {name}", + "Settings.apiTokens.ListView.headers.createdAt": "Créé le", + "Settings.apiTokens.ListView.headers.description": "Description", + "Settings.apiTokens.ListView.headers.lastUsedAt": "Dernière utilisation le", + "Settings.apiTokens.ListView.headers.name": "Nom", + "Settings.apiTokens.ListView.headers.type": "Type de jeton", + "Settings.apiTokens.regenerate": "Régénérer", + "Settings.apiTokens.createPage.title": "Créer un jeton d'API", + "Settings.transferTokens.createPage.title": "Créer un jeton de transfert", + "Settings.tokens.RegenerateDialog.title": "Régénérer le jeton", "Settings.apiTokens.addFirstToken": "Ajouter votre premier jeton d'API", "Settings.apiTokens.addNewToken": "Ajouter un nouveau jeton d'API", "Settings.tokens.copy.editMessage": "Pour des raisons de sécurité, vous ne pouvoir voir votre jeton qu'une seule fois", "Settings.tokens.copy.editTitle": "Ce jeton n'est désormais plus accessible", "Settings.tokens.copy.lastWarning": "Assurez-vous de copier ce jeton, vous ne pourrez plus le revoir par la suite !", "Settings.apiTokens.create": "Ajouter une entrée", + "Settings.apiTokens.createPage.permissions.description": "Seules les actions rattachées à une route sont listées ci-dessous.", + "Settings.apiTokens.createPage.permissions.title": "Permissions", "Settings.apiTokens.description": "Liste des jetons générés pour consommer l'API", + "Settings.apiTokens.createPage.BoundRoute.title": "Route rattachée à", + "Settings.apiTokens.createPage.permissions.header.title": "Paramètres avancés", + "Settings.apiTokens.createPage.permissions.header.hint": "Sélectionner les actions de l'application ou du plugin et sur l'icône de la roue crantée pour afficher la route rattachée", + "Settings.tokens.duration.30-days": "30 jours", + "Settings.tokens.duration.7-days": "7 jours", + "Settings.tokens.duration.90-days": "90 jours", + "Settings.tokens.duration.expiration-date": "Date d'expiration", + "Settings.tokens.duration.unlimited": "Illimité", "Settings.apiTokens.emptyStateLayout": "Vous n'avez pas encore de contenu...", + "Settings.tokens.form.duration": "Durée de vie du jeton", + "Settings.tokens.form.type": "Type de jeton", + "Settings.tokens.form.name": "Nom", + "Settings.tokens.form.description": "Description", "Settings.tokens.notification.copied": "Jeton copié dans le press-papiers.", - "Settings.apiTokens.title": "Jetons d'API", + "Settings.tokens.popUpWarning.message": "Êtes-vous sûr(e) de vouloir régénérer ce jeton ?", + "Settings.tokens.Button.cancel": "Annuler", + "Settings.tokens.Button.regenerate": "Régénérer", "Settings.tokens.types.full-access": "Accès total", "Settings.tokens.types.read-only": "Lecture seule", + "Settings.tokens.types.custom": "Custom", + "Settings.tokens.regenerate": "Régénérer", + "Settings.transferTokens.title": "Jetons de transfert", + "Settings.transferTokens.description": "Liste des jetons de transfert générés", + "Settings.transferTokens.create": "Créer un nouveau jeton de transfert", + "Settings.transferTokens.addFirstToken": "Ajouter votre premier jeton de transfert", + "Settings.transferTokens.addNewToken": "Ajouter un nouveau jeton de transfert", + "Settings.transferTokens.emptyStateLayout": "Vous n'avez aucun contenu pour le moment...", + "Settings.tokens.ListView.headers.name": "Nom", + "Settings.tokens.ListView.headers.description": "Description", + "Settings.transferTokens.ListView.headers.type": "Type de jeton", + "Settings.tokens.ListView.headers.createdAt": "Créé le", + "Settings.tokens.ListView.headers.lastUsedAt": "Dernière utilisation", + "Settings.application.ee.admin-seats.count": "{enforcementUserCount}/{permittedSeats}", + "Settings.application.ee.admin-seats.at-limit-tooltip": "Limite atteinte : ajouter des places pour inviter d'autres utilisateurs", + "Settings.application.ee.admin-seats.add-seats": "{isHostedOnStrapiCloud, select, true {AJouter des places} other {Contacter le service clients}}", + "Settings.application.customization": "Customisation", + "Settings.application.customization.auth-logo.carousel-hint": "Remplacer le logo dans la page de connexion", + "Settings.application.customization.carousel-hint": "Changer le logo dans l'interface d'administration (dimensions maximales: {dimension}x{dimension}, poids maximal du fichier : {size}KB)", + "Settings.application.customization.carousel-slide.label": "Logo slide", + "Settings.application.customization.carousel.auth-logo.title": "Logo de connexion", + "Settings.application.customization.carousel.change-action": "Changer le logo", + "Settings.application.customization.carousel.menu-logo.title": "Logo du menu", + "Settings.application.customization.carousel.reset-action": "Réinitialiser le logo", + "Settings.application.customization.carousel.title": "Logo", + "Settings.application.customization.menu-logo.carousel-hint": "Remplacer le logo dans la navigation principale", + "Settings.application.customization.modal.cancel": "Annuler", + "Settings.application.customization.modal.pending": "Téléchargement du logo", + "Settings.application.customization.modal.pending.card-badge": "image", + "Settings.application.customization.modal.pending.choose-another": "Choisir un autre logo", + "Settings.application.customization.modal.pending.subtitle": "Gérer le logo choisi avant de le télécharger", + "Settings.application.customization.modal.pending.title": "Logo prêt pour le téléchargement", + "Settings.application.customization.modal.pending.upload": "Téléchargement du logo", + "Settings.application.customization.modal.tab.label": "Comment voulez-vous télécharger vos medias ?", + "Settings.application.customization.modal.upload": "Télécharger le logo", + "Settings.application.customization.modal.upload.cta.browse": "Explorer les fichiers", + "Settings.application.customization.modal.upload.drag-drop": "Glisser-déposer ici ou", + "Settings.application.customization.modal.upload.error-format": "Mauvais format chargé (formats acceptés : jpeg, jpg, png, svg).", + "Settings.application.customization.modal.upload.error-network": "Erreur réseau", + "Settings.application.customization.modal.upload.error-size": "Le fichier téléchargé est trop grand (dimensions max : {dimension}x{dimension}, poids max: {size}KB)", + "Settings.application.customization.modal.upload.file-validation": "Dimensions maximales : {dimension}x{dimension}, poids maximal : {size}KB", + "Settings.application.customization.modal.upload.from-computer": "Depuis l'ordinateur", + "Settings.application.customization.modal.upload.from-url": "Depuis une URL", + "Settings.application.customization.modal.upload.from-url.input-label": "URL", + "Settings.application.customization.modal.upload.next": "Suivant", + "Settings.application.customization.size-details": "Dimensions maximales : {dimension}×{dimension}, poids maximal : {size}KB", "Settings.application.description": "Informations globales du panneau d'administration", "Settings.application.edition-title": "plan actuel", + "Settings.application.ee-or-ce": "{communityEdition, select, true {Édition Communauté} other {Édition Entreprise}}", "Settings.application.get-help": "Obtenir de l'aide", "Settings.application.link-pricing": "Voir tous les tarifs", "Settings.application.link-upgrade": "Mettez à niveau votre panneau d'administration", @@ -566,6 +641,14 @@ "content-manager.plugin.description.long": "Visualisez, modifiez et supprimez les données de votre base de données.", "content-manager.plugin.description.short": "Visualisez, modifiez et supprimez les données de votre base de données.", "content-manager.popover.display-relations.label": "Afficher les relations", + "content-manager.relation.add": "Ajouter une relation", + "content-manager.relation.disconnect": "Supprimer", + "content-manager.relation.isLoading": "Chargement des relations en cours", + "content-manager.relation.loadMore": "Charger davantage", + "content-manager.relation.notAvailable": "Aucune relation disponible", + "content-manager.relation.publicationState.draft": "Brouillon", + "content-manager.relation.publicationState.published": "Publiée", + "content-manager.select.currently.selected": "{count} actuellement sélectionnées", "content-manager.success.record.delete": "Supprimé", "content-manager.success.record.publish": "Publié", "content-manager.success.record.save": "Sauvegardé", @@ -575,9 +658,11 @@ "content-manager.popUpWarning.warning.publish-question": "Êtes-vous sûr de vouloir le publier ?", "content-manager.popUpwarning.warning.has-draft-relations.button-confirm": "Oui, publier", "content-manager.popUpwarning.warning.has-draft-relations.message": "{count, plural, =0 { des relations de votre contenu n'est} one { des relations de votre contenu n'est} other { des relations de votre contenu ne sont}} pas publié actuellement.

Cela peut engendrer des liens cassés ou des erreurs dans votre projet.", + "dark": "Sombre", "form.button.continue": "Continuer", "global.search": "Rechercher", "global.actions": "Actions", + "global.auditLogs": "Journaux d'audit", "global.back": "Retour", "global.cancel": "Annuler", "global.change-password": "Modifier le mot de passe", @@ -606,6 +691,7 @@ "global.settings": "Paramètres", "global.type": "Type", "global.users": "Utilisateurs", + "light": "Clair", "form.button.done": "Terminer", "global.prompt.unsaved": "Êtes-vous sûr de vouloir quitter cette page? Toutes vos modifications seront perdues", "notification.contentType.relations.conflict": "Le Type de Contenu à des relations qui rentrent en conflit", @@ -621,8 +707,10 @@ "notification.success.title": "Succès :", "notification.version.update.message": "Une nouvelle version de Strapi est disponible !", "notification.warning.title": "Attention :", + "notification.warning.404": "404 - Introuvable", "or": "OU", "request.error.model.unknown": "Le model n'existe pas", + "selectButtonTitle": "Sélectionner", "skipToContent": "Aller au contenu", "submit": "Soumettre" } From 52f5b7a16e02d8cd543e252b665a97d63f142f48 Mon Sep 17 00:00:00 2001 From: derrickmehaffy Date: Mon, 17 Apr 2023 07:30:28 -0700 Subject: [PATCH 2/6] update blacklist with all system fields --- .../plugins/users-permissions/server/controllers/auth.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/plugins/users-permissions/server/controllers/auth.js b/packages/plugins/users-permissions/server/controllers/auth.js index cb73abd6e8..2d455602c2 100644 --- a/packages/plugins/users-permissions/server/controllers/auth.js +++ b/packages/plugins/users-permissions/server/controllers/auth.js @@ -280,6 +280,11 @@ module.exports = { 'confirmationToken', 'resetPasswordToken', 'provider', + 'id', + 'createdAt', + 'updatedAt', + 'createdBy', + 'updatedBy', ]), provider: 'local', }; From 773db0dcaf2476a50a20fa59b9e6796b6cdca0be Mon Sep 17 00:00:00 2001 From: derrickmehaffy Date: Mon, 17 Apr 2023 08:08:19 -0700 Subject: [PATCH 3/6] add role as a failsafe --- packages/plugins/users-permissions/server/controllers/auth.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/plugins/users-permissions/server/controllers/auth.js b/packages/plugins/users-permissions/server/controllers/auth.js index 2d455602c2..02821f7ba4 100644 --- a/packages/plugins/users-permissions/server/controllers/auth.js +++ b/packages/plugins/users-permissions/server/controllers/auth.js @@ -285,6 +285,7 @@ module.exports = { 'updatedAt', 'createdBy', 'updatedBy', + 'role', ]), provider: 'local', }; From a572f92b1e8943fcc99d14a561ef4514d7124a46 Mon Sep 17 00:00:00 2001 From: Josh <37798644+joshuaellis@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:12:32 +0100 Subject: [PATCH 4/6] fix: localstorage in fe tests --- .../lib/mocks/LocalStorageMock.js | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/admin-test-utils/lib/mocks/LocalStorageMock.js b/packages/admin-test-utils/lib/mocks/LocalStorageMock.js index d516355392..701520ac77 100644 --- a/packages/admin-test-utils/lib/mocks/LocalStorageMock.js +++ b/packages/admin-test-utils/lib/mocks/LocalStorageMock.js @@ -2,24 +2,36 @@ class LocalStorageMock { constructor() { - this.store = {}; + this.store = new Map(); } clear() { - this.store = {}; + this.store.clear(); } getItem(key) { - return this.store[key] || null; + /** + * We return null to avoid returning `undefined` + * because `undefined` is not a valid JSON value. + */ + return this.store.get(key) ?? null; } setItem(key, value) { - this.store[key] = String(value); + this.store.set(key, String(value)); } removeItem(key) { - delete this.store[key]; + this.store.delete(key); + } + + get length() { + return this.store.size; } } -global.localStorage = new LocalStorageMock(); +// eslint-disable-next-line no-undef +Object.defineProperty(window, 'localStorage', { + writable: true, + value: new LocalStorageMock(), +}); From 2d55462e466cc11f21ff6db74e6ccf9a33983bdb Mon Sep 17 00:00:00 2001 From: Josh <37798644+joshuaellis@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:27:47 +0100 Subject: [PATCH 5/6] fix: remove instances of request and ban with eslint chore: remove straggler chore: fix hook chore: pr amends chore: pr amends chore: use axios for analytics fix missing once function import chore(test): use defineProperty fix: webhooks creation fix: sso Revert "chore(test): use defineProperty" This reverts commit dfdcb82ccef720c4e4993fe4021acc190f38a204. chore: make sure theres a url before trying to render the image fix: webhook --- .../hooks/useFetchPermissionsLayout/index.js | 35 +++++--- .../admin/src/hooks/useFetchRole/index.js | 67 +++++++------- .../admin/admin/src/hooks/useModels/index.js | 34 ++++--- .../admin/src/hooks/useSettingsForm/index.js | 20 +++-- .../core/admin/admin/src/pages/App/index.js | 43 ++++----- .../LogoModalStepper/PendingLogoDialog.js | 4 +- .../pages/Roles/CreatePage/index.js | 18 ++-- .../pages/Roles/EditPage/index.js | 16 ++-- .../pages/Webhooks/EditView/index.js | 51 ++++------- .../pages/Webhooks/ListView/index.js | 88 +++++++++---------- .../ee/admin/hooks/useAuthProviders/index.js | 57 ++++++------ .../ee/admin/pages/AuthResponse/index.js | 10 ++- .../src/utils/hasPermissions/index.js | 13 +-- packages/core/helper-plugin/src/utils/once.js | 20 +++++ .../helper-plugin/src/utils/request/index.js | 10 ++- .../admin/src/pages/utils/api.js | 31 ------- .../admin/src/pages/utils/useReactQuery.js | 48 +++++++--- .../admin/src/hooks/useAddLocale/index.js | 31 +++---- .../src/hooks/useDefaultLocales/index.js | 37 ++++---- .../admin/src/hooks/useDeleteLocale/index.js | 47 +++++----- .../admin/src/hooks/useEditLocale/index.js | 47 ++++------ .../i18n/admin/src/hooks/useLocales/index.js | 43 +++++---- .../addCommonFieldsToInitialDataMiddleware.js | 10 +-- .../SettingsPage/tests/SettingsPage.test.js | 59 ++++++------- .../admin/src/hooks/useForm/index.js | 11 +-- .../admin/src/hooks/useRolesList/index.js | 37 ++++---- packages/utils/eslint-config-custom/front.js | 6 ++ 27 files changed, 442 insertions(+), 451 deletions(-) create mode 100644 packages/core/helper-plugin/src/utils/once.js delete mode 100644 packages/plugins/documentation/admin/src/pages/utils/api.js diff --git a/packages/core/admin/admin/src/hooks/useFetchPermissionsLayout/index.js b/packages/core/admin/admin/src/hooks/useFetchPermissionsLayout/index.js index 36c61c63f6..e38f7fc9a0 100644 --- a/packages/core/admin/admin/src/hooks/useFetchPermissionsLayout/index.js +++ b/packages/core/admin/admin/src/hooks/useFetchPermissionsLayout/index.js @@ -1,30 +1,39 @@ import { useEffect, useReducer } from 'react'; -import { request } from '@strapi/helper-plugin'; +import { useFetchClient } from '@strapi/helper-plugin'; import reducer, { initialState } from './reducer'; +/** + * TODO: refactor this to use react-query and move it to the `Roles` SettingsPage + */ const useFetchPermissionsLayout = (id) => { const [{ data, error, isLoading }, dispatch] = useReducer(reducer, initialState); + const { get } = useFetchClient(); useEffect(() => { const getData = async () => { - dispatch({ - type: 'GET_DATA', - }); + try { + dispatch({ + type: 'GET_DATA', + }); - const { data } = await request('/admin/permissions', { - method: 'GET', - params: { role: id }, - }); + const { + data: { data }, + } = await get('/admin/permissions', { + params: { role: id }, + }); - dispatch({ - type: 'GET_DATA_SUCCEEDED', - data, - }); + dispatch({ + type: 'GET_DATA_SUCCEEDED', + data, + }); + } catch (err) { + // silence is golden + } }; getData(); - }, [id]); + }, [id, get]); return { data, error, isLoading }; }; diff --git a/packages/core/admin/admin/src/hooks/useFetchRole/index.js b/packages/core/admin/admin/src/hooks/useFetchRole/index.js index 6d756c0c0e..633126ee66 100644 --- a/packages/core/admin/admin/src/hooks/useFetchRole/index.js +++ b/packages/core/admin/admin/src/hooks/useFetchRole/index.js @@ -1,13 +1,48 @@ import { useCallback, useReducer, useEffect } from 'react'; -import { request, useNotification } from '@strapi/helper-plugin'; +import { useFetchClient, useNotification } from '@strapi/helper-plugin'; import reducer, { initialState } from './reducer'; const useFetchRole = (id) => { const toggleNotification = useNotification(); const [state, dispatch] = useReducer(reducer, initialState); + const { get } = useFetchClient(); + useEffect(() => { if (id) { + const fetchRole = async (roleId) => { + try { + const [ + { + data: { data: role }, + }, + { + data: { data: permissions }, + }, + ] = await Promise.all( + [`roles/${roleId}`, `roles/${roleId}/permissions`].map((endPoint) => + get(`/admin/${endPoint}`) + ) + ); + + dispatch({ + type: 'GET_DATA_SUCCEEDED', + role, + permissions, + }); + } catch (err) { + console.error(err); + + dispatch({ + type: 'GET_DATA_ERROR', + }); + toggleNotification({ + type: 'warning', + message: { id: 'notification.error' }, + }); + } + }; + fetchRole(id); } else { dispatch({ @@ -16,35 +51,7 @@ const useFetchRole = (id) => { permissions: [], }); } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [id]); - - const fetchRole = async (roleId) => { - try { - const [{ data: role }, { data: permissions }] = await Promise.all( - [`roles/${roleId}`, `roles/${roleId}/permissions`].map((endPoint) => - request(`/admin/${endPoint}`, { method: 'GET' }) - ) - ); - - dispatch({ - type: 'GET_DATA_SUCCEEDED', - role, - permissions, - }); - } catch (err) { - console.error(err); - - dispatch({ - type: 'GET_DATA_ERROR', - }); - toggleNotification({ - type: 'warning', - message: { id: 'notification.error' }, - }); - } - }; + }, [get, id, toggleNotification]); const handleSubmitSucceeded = useCallback((data) => { dispatch({ diff --git a/packages/core/admin/admin/src/hooks/useModels/index.js b/packages/core/admin/admin/src/hooks/useModels/index.js index d3458c4c56..7d631ac661 100644 --- a/packages/core/admin/admin/src/hooks/useModels/index.js +++ b/packages/core/admin/admin/src/hooks/useModels/index.js @@ -1,26 +1,32 @@ -import { useReducer, useEffect } from 'react'; -import { request, useNotification } from '@strapi/helper-plugin'; +import { useReducer, useEffect, useCallback } from 'react'; +import { useFetchClient, useNotification } from '@strapi/helper-plugin'; import reducer, { initialState } from './reducer'; +/** + * TODO: refactor this to not use the `useReducer` hook, + * it's not really necessary. Also use `useQuery`? + */ const useModels = () => { const toggleNotification = useNotification(); const [state, dispatch] = useReducer(reducer, initialState); - useEffect(() => { - fetchModels(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const { get } = useFetchClient(); - const fetchModels = async () => { + const fetchModels = useCallback(async () => { dispatch({ type: 'GET_MODELS', }); try { - const [{ data: components }, { data: contentTypes }] = await Promise.all( - ['components', 'content-types'].map((endPoint) => - request(`/content-manager/${endPoint}`, { method: 'GET' }) - ) + const [ + { + data: { data: components }, + }, + { + data: { data: contentTypes }, + }, + ] = await Promise.all( + ['components', 'content-types'].map((endPoint) => get(`/content-manager/${endPoint}`)) ); dispatch({ @@ -37,7 +43,11 @@ const useModels = () => { message: { id: 'notification.error' }, }); } - }; + }, [toggleNotification, get]); + + useEffect(() => { + fetchModels(); + }, [fetchModels]); return { ...state, diff --git a/packages/core/admin/admin/src/hooks/useSettingsForm/index.js b/packages/core/admin/admin/src/hooks/useSettingsForm/index.js index beb0fc4e77..95345b3891 100644 --- a/packages/core/admin/admin/src/hooks/useSettingsForm/index.js +++ b/packages/core/admin/admin/src/hooks/useSettingsForm/index.js @@ -1,10 +1,15 @@ import { useEffect, useReducer } from 'react'; -import { request, useNotification, useOverlayBlocker } from '@strapi/helper-plugin'; +import { useFetchClient, useNotification, useOverlayBlocker } from '@strapi/helper-plugin'; import omit from 'lodash/omit'; import { checkFormValidity, formatAPIErrors } from '../../utils'; import { initialState, reducer } from './reducer'; import init from './init'; +/** + * TODO: refactor this, it's confusing and hard to read. + * It's also only used in `Settings/pages/SingleSignOn` so it can + * probably be deleted and everything written there... + */ const useSettingsForm = (endPoint, schema, cbSuccess, fieldsToPick) => { const [ { formErrors, initialData, isLoading, modifiedData, showHeaderButtonLoader, showHeaderLoader }, @@ -13,10 +18,14 @@ const useSettingsForm = (endPoint, schema, cbSuccess, fieldsToPick) => { const toggleNotification = useNotification(); const { lockApp, unlockApp } = useOverlayBlocker(); + const { get, put } = useFetchClient(); + useEffect(() => { const getData = async () => { try { - const { data } = await request(endPoint, { method: 'GET' }); + const { + data: { data }, + } = await get(endPoint); dispatch({ type: 'GET_DATA_SUCCEEDED', @@ -85,10 +94,9 @@ const useSettingsForm = (endPoint, schema, cbSuccess, fieldsToPick) => { cleanedData.roles = cleanedData.roles.map((role) => role.id); } - const { data } = await request(endPoint, { - method: 'PUT', - body: cleanedData, - }); + const { + data: { data }, + } = await put(endPoint, cleanedData); cbSuccess(data); diff --git a/packages/core/admin/admin/src/pages/App/index.js b/packages/core/admin/admin/src/pages/App/index.js index ddd9e993e2..08cba52e3d 100644 --- a/packages/core/admin/admin/src/pages/App/index.js +++ b/packages/core/admin/admin/src/pages/App/index.js @@ -6,17 +6,16 @@ import React, { useEffect, useState, useMemo, lazy, Suspense } from 'react'; import { Switch, Route } from 'react-router-dom'; +import axios from 'axios'; import { LoadingIndicatorPage, auth, - request, useNotification, TrackingProvider, prefixFileUrlWithBackendUrl, useAppInfos, useFetchClient, } from '@strapi/helper-plugin'; -import axios from 'axios'; import { SkipToContent } from '@strapi/design-system'; import { useIntl } from 'react-intl'; import PrivateRoute from '../../components/PrivateRoute'; @@ -41,7 +40,7 @@ function App() { hasAdmin: false, }); const appInfo = useAppInfos(); - const { get } = useFetchClient(); + const { get, post } = useFetchClient(); const authRoutes = useMemo(() => { return makeUniqueRoutes( @@ -57,11 +56,10 @@ function App() { const renewToken = async () => { try { const { - data: { token }, - } = await request('/admin/renew-token', { - method: 'POST', - body: { token: currentToken }, - }); + data: { + data: { token }, + }, + } = await post('/admin/renew-token', { token: currentToken }); auth.updateToken(token); } catch (err) { // Refresh app @@ -73,7 +71,7 @@ function App() { if (currentToken) { renewToken(); } - }, []); + }, [post]); useEffect(() => { const getData = async () => { @@ -82,7 +80,7 @@ function App() { data: { data: { hasAdmin, uuid, menuLogo, authLogo }, }, - } = await axios.get(`${strapi.backendURL}/admin/init`); + } = await get(`/admin/init`); updateProjectSettings({ menuLogo: prefixFileUrlWithBackendUrl(menuLogo), @@ -102,20 +100,17 @@ function App() { setTelemetryProperties(properties); try { - await fetch('https://analytics.strapi.io/api/v2/track', { - method: 'POST', - body: JSON.stringify({ - // This event is anonymous - event: 'didInitializeAdministration', - userId: '', - deviceId, - eventPropeties: {}, - userProperties: { environment: appInfo.currentEnvironment }, - groupProperties: { ...properties, projectId: uuid }, - }), - headers: { - 'Content-Type': 'application/json', - }, + /** + * TODO: remove this call to `axios` + */ + await axios.post('https://analytics.strapi.io/api/v2/track', { + // This event is anonymous + event: 'didInitializeAdministration', + userId: '', + deviceId, + eventPropeties: {}, + userProperties: { environment: appInfo.currentEnvironment }, + groupProperties: { ...properties, projectId: uuid }, }); } catch (e) { // Silent. diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/PendingLogoDialog.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/PendingLogoDialog.js index f9ef1de9d0..0b11708d5b 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/PendingLogoDialog.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/PendingLogoDialog.js @@ -43,9 +43,7 @@ const PendingLogoDialog = ({ onClose, asset, prev, next, goTo, setLocalImage, on })} - - - + {asset.url ? : null} { const { isLoading: isLayoutLoading, data: permissionsLayout } = useFetchPermissionsLayout(); const { permissions: rolePermissions, isLoading: isRoleLoading } = useFetchRole(id); + const { post, put } = useFetchClient(); + const handleCreateRoleSubmit = (data) => { lockApp(); setIsSubmiting(true); @@ -69,13 +71,8 @@ const CreatePage = () => { trackUsage('willCreateNewRole'); } - Promise.resolve( - request('/admin/roles', { - method: 'POST', - body: data, - }) - ) - .then(async (res) => { + Promise.resolve(post('/admin/roles', data)) + .then(async ({ data: res }) => { const { permissionsToSend } = permissionsRef.current.getPermissions(); if (id) { @@ -85,10 +82,7 @@ const CreatePage = () => { } if (res.data.id && !isEmpty(permissionsToSend)) { - await request(`/admin/roles/${res.data.id}/permissions`, { - method: 'PUT', - body: { permissions: permissionsToSend }, - }); + await put(`/admin/roles/${res.data.id}/permissions`, { permissions: permissionsToSend }); } return res; diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/Roles/EditPage/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/Roles/EditPage/index.js index 89f74ef9bf..8ba6a28e6f 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/Roles/EditPage/index.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/Roles/EditPage/index.js @@ -1,6 +1,6 @@ import React, { useRef, useState } from 'react'; import { - request, + useFetchClient, useNotification, useOverlayBlocker, useTracking, @@ -37,6 +37,8 @@ const EditPage = () => { onSubmitSucceeded, } = useFetchRole(id); + const { put } = useFetchClient(); + const handleEditRoleSubmit = async (data) => { try { lockApp(); @@ -44,17 +46,11 @@ const EditPage = () => { const { permissionsToSend, didUpdateConditions } = permissionsRef.current.getPermissions(); - await request(`/admin/roles/${id}`, { - method: 'PUT', - body: data, - }); + await put(`/admin/roles/${id}`, data); if (role.code !== 'strapi-super-admin') { - await request(`/admin/roles/${id}/permissions`, { - method: 'PUT', - body: { - permissions: permissionsToSend, - }, + await put(`/admin/roles/${id}/permissions`, { + permissions: permissionsToSend, }); if (didUpdateConditions) { diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/Webhooks/EditView/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/Webhooks/EditView/index.js index 07ec03510e..0153c6a883 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/Webhooks/EditView/index.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/Webhooks/EditView/index.js @@ -3,12 +3,10 @@ * EditView * */ -import React, { useCallback, useMemo } from 'react'; +import * as React from 'react'; import { LoadingIndicatorPage, - request, SettingsPageTitle, - to, useNotification, useOverlayBlocker, useFetchClient, @@ -30,19 +28,20 @@ const EditView = () => { const toggleNotification = useNotification(); const queryClient = useQueryClient(); const { isLoading: isLoadingForModels, collectionTypes } = useModels(); - const { post } = useFetchClient(); + const { put, get, post } = useFetchClient(); const isCreating = id === 'create'; - const fetchWebhook = useCallback( - async (id) => { - const [err, { data }] = await to( - request(`/admin/webhooks/${id}`, { - method: 'GET', - }) - ); + const { isLoading, data } = useQuery( + ['get-webhook', id], + async () => { + try { + const { + data: { data }, + } = await get(`/admin/webhooks/${id}`); - if (err) { + return data; + } catch (err) { toggleNotification({ type: 'warning', message: { id: 'notification.error' }, @@ -50,16 +49,12 @@ const EditView = () => { return null; } - - return data; }, - [toggleNotification] + { + enabled: !isCreating, + } ); - const { isLoading, data } = useQuery(['get-webhook', id], () => fetchWebhook(id), { - enabled: !isCreating, - }); - const { isLoading: isTriggering, data: triggerResponse, @@ -77,25 +72,15 @@ const EditView = () => { }, }); - const createWebhookMutation = useMutation((body) => - request('/admin/webhooks', { - method: 'POST', - body, - }) - ); + const createWebhookMutation = useMutation((body) => post('/admin/webhooks', body)); - const updateWebhookMutation = useMutation(({ id, body }) => - request(`/admin/webhooks/${id}`, { - method: 'PUT', - body, - }) - ); + const updateWebhookMutation = useMutation(({ id, body }) => put(`/admin/webhooks/${id}`, body)); const handleSubmit = async (data) => { if (isCreating) { lockApp(); createWebhookMutation.mutate(cleanData(data), { - onSuccess(result) { + onSuccess({ data: result }) { toggleNotification({ type: 'success', message: { id: 'Settings.webhooks.created' }, @@ -138,7 +123,7 @@ const EditView = () => { } }; - const isDraftAndPublishEvents = useMemo( + const isDraftAndPublishEvents = React.useMemo( () => collectionTypes.some((ct) => ct.options.draftAndPublish === true), [collectionTypes] ); diff --git a/packages/core/admin/admin/src/pages/SettingsPage/pages/Webhooks/ListView/index.js b/packages/core/admin/admin/src/pages/SettingsPage/pages/Webhooks/ListView/index.js index 4d1d391eb1..f4eedd593e 100644 --- a/packages/core/admin/admin/src/pages/SettingsPage/pages/Webhooks/ListView/index.js +++ b/packages/core/admin/admin/src/pages/SettingsPage/pages/Webhooks/ListView/index.js @@ -9,7 +9,7 @@ import React, { useEffect, useReducer, useRef, useState } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import { useIntl } from 'react-intl'; import { - request, + useFetchClient, useRBAC, LoadingIndicatorPage, useNotification, @@ -63,6 +63,8 @@ const ListView = () => { ); const { notifyStatus } = useNotifyAT(); + const { get, del, post, put } = useFetchClient(); + useFocusWhenNavigate(); const { push } = useHistory(); const { pathname } = useLocation(); @@ -78,42 +80,45 @@ const ListView = () => { }; }, []); + /** + * TODO: refactor this, but actually refactor + * the whole component. Needs some love. + */ useEffect(() => { + const fetchWebHooks = async () => { + try { + const { + data: { data }, + } = await get('/admin/webhooks'); + + if (isMounted.current) { + dispatch({ + type: 'GET_DATA_SUCCEEDED', + data, + }); + notifyStatus('webhooks have been loaded'); + } + } catch (err) { + console.log(err); + + if (isMounted.current) { + if (err.code !== 20) { + toggleNotification({ + type: 'warning', + message: { id: 'notification.error' }, + }); + } + dispatch({ + type: 'TOGGLE_LOADING', + }); + } + } + }; + if (canRead) { fetchWebHooks(); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [canRead]); - - const fetchWebHooks = async () => { - try { - const { data } = await request('/admin/webhooks', { - method: 'GET', - }); - - if (isMounted.current) { - dispatch({ - type: 'GET_DATA_SUCCEEDED', - data, - }); - notifyStatus('webhooks have been loaded'); - } - } catch (err) { - console.log(err); - - if (isMounted.current) { - if (err.code !== 20) { - toggleNotification({ - type: 'warning', - message: { id: 'notification.error' }, - }); - } - dispatch({ - type: 'TOGGLE_LOADING', - }); - } - } - }; + }, [canRead, get, notifyStatus, toggleNotification]); const handleToggleModal = () => { setShowModal((prev) => !prev); @@ -129,15 +134,16 @@ const ListView = () => { const handleConfirmDeleteOne = async () => { try { - await request(`/admin/webhooks/${webhookToDelete}`, { - method: 'DELETE', - }); + await del(`/admin/webhooks/${webhookToDelete}`); dispatch({ type: 'WEBHOOK_DELETED', index: getWebhookIndex(webhookToDelete), }); } catch (err) { + /** + * TODO: especially this. + */ if (err.code !== 20) { toggleNotification({ type: 'warning', @@ -154,10 +160,7 @@ const ListView = () => { }; try { - await request('/admin/webhooks/batch-delete', { - method: 'POST', - body, - }); + await post('/admin/webhooks/batch-delete', body); if (isMounted.current) { dispatch({ @@ -207,10 +210,7 @@ const ListView = () => { value, }); - await request(`/admin/webhooks/${id}`, { - method: 'PUT', - body, - }); + await put(`/admin/webhooks/${id}`, body); } catch (err) { if (isMounted.current) { dispatch({ diff --git a/packages/core/admin/ee/admin/hooks/useAuthProviders/index.js b/packages/core/admin/ee/admin/hooks/useAuthProviders/index.js index f31b0ad9a6..b1396773a2 100644 --- a/packages/core/admin/ee/admin/hooks/useAuthProviders/index.js +++ b/packages/core/admin/ee/admin/hooks/useAuthProviders/index.js @@ -1,5 +1,5 @@ import { useReducer, useEffect } from 'react'; -import { request, useNotification } from '@strapi/helper-plugin'; +import { useFetchClient, useNotification } from '@strapi/helper-plugin'; import { getRequestUrl } from '../../../../admin/src/utils'; import reducer, { initialState } from './reducer'; @@ -7,43 +7,42 @@ import reducer, { initialState } from './reducer'; const useAuthProviders = ({ ssoEnabled }) => { const [state, dispatch] = useReducer(reducer, initialState); const toggleNotification = useNotification(); + const { get } = useFetchClient(); useEffect(() => { - fetchAuthProviders(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const fetchAuthProviders = async () => { + try { + if (!ssoEnabled) { + dispatch({ + type: 'GET_DATA_SUCCEEDED', + data: [], + }); + + return; + } + + const { data } = await get(getRequestUrl('providers')); - const fetchAuthProviders = async () => { - try { - if (!ssoEnabled) { dispatch({ type: 'GET_DATA_SUCCEEDED', - data: [], + data, + }); + } catch (err) { + console.error(err); + + dispatch({ + type: 'GET_DATA_ERROR', }); - return; + toggleNotification({ + type: 'warning', + message: { id: 'notification.error' }, + }); } + }; - const requestUrl = getRequestUrl('providers'); - const data = await request(requestUrl, { method: 'GET' }); - - dispatch({ - type: 'GET_DATA_SUCCEEDED', - data, - }); - } catch (err) { - console.error(err); - - dispatch({ - type: 'GET_DATA_ERROR', - }); - - toggleNotification({ - type: 'warning', - message: { id: 'notification.error' }, - }); - } - }; + fetchAuthProviders(); + }, [get, ssoEnabled, toggleNotification]); return state; }; diff --git a/packages/core/admin/ee/admin/pages/AuthResponse/index.js b/packages/core/admin/ee/admin/pages/AuthResponse/index.js index 024abd3934..dc1b8a7108 100644 --- a/packages/core/admin/ee/admin/pages/AuthResponse/index.js +++ b/packages/core/admin/ee/admin/pages/AuthResponse/index.js @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useCallback } from 'react'; import { useHistory, useRouteMatch } from 'react-router-dom'; import { useIntl } from 'react-intl'; import Cookies from 'js-cookie'; -import { auth, LoadingIndicatorPage, request } from '@strapi/helper-plugin'; +import { auth, LoadingIndicatorPage, useFetchClient } from '@strapi/helper-plugin'; import { getRequestUrl } from '../../../../admin/src/utils'; const AuthResponse = () => { @@ -24,6 +24,8 @@ const AuthResponse = () => { ); }, [push]); + const { get } = useFetchClient(); + const fetchUserInfo = useCallback(async () => { try { const jwtToken = Cookies.get('jwtToken'); @@ -33,7 +35,9 @@ const AuthResponse = () => { if (jwtToken) { auth.setToken(jwtToken, true); const requestUrl = getRequestUrl('users/me'); - const { data } = await request(requestUrl, { method: 'GET' }); + const { + data: { data }, + } = await get(requestUrl); auth.setUserInfo(data, true); @@ -44,7 +48,7 @@ const AuthResponse = () => { } catch (e) { redirectToOops(); } - }, [push, redirectToOops]); + }, [get, push, redirectToOops]); useEffect(() => { if (authResponse === 'error') { diff --git a/packages/core/helper-plugin/src/utils/hasPermissions/index.js b/packages/core/helper-plugin/src/utils/hasPermissions/index.js index 080bd208a2..64dd60dfc7 100644 --- a/packages/core/helper-plugin/src/utils/hasPermissions/index.js +++ b/packages/core/helper-plugin/src/utils/hasPermissions/index.js @@ -3,6 +3,7 @@ import pickBy from 'lodash/pickBy'; import transform from 'lodash/transform'; import request from '../request'; +import getFetchClient from '../getFetchClient'; const findMatchingPermissions = (userPermissions, permissions) => { return transform( @@ -47,13 +48,15 @@ const hasPermissions = async (userPermissions, permissions, signal) => { let hasPermission = false; try { - const { data } = await request('/admin/permissions/check', { - method: 'POST', - body: { + const { + data: { data }, + } = await getFetchClient().post( + '/admin/permissions/check', + { permissions: formatPermissionsForRequest(matchingPermissions), }, - signal, - }); + { signal } + ); hasPermission = data.every((v) => v === true); } catch (err) { diff --git a/packages/core/helper-plugin/src/utils/once.js b/packages/core/helper-plugin/src/utils/once.js new file mode 100644 index 0000000000..87d2a99010 --- /dev/null +++ b/packages/core/helper-plugin/src/utils/once.js @@ -0,0 +1,20 @@ +export const PREFIX = '[@strapi/helper-plugin]:'; + +/** + * @type { any>(fn: TFunc) => (...args: any[]) => void} + */ +export const once = (fn) => { + const func = fn; + let called = false; + + if (typeof func !== 'function') { + throw new TypeError(`${PREFIX} once requires a function parameter`); + } + + return (...args) => { + if (!called) { + func(...args); + called = true; + } + }; +}; diff --git a/packages/core/helper-plugin/src/utils/request/index.js b/packages/core/helper-plugin/src/utils/request/index.js index 40da1525e7..395101a711 100644 --- a/packages/core/helper-plugin/src/utils/request/index.js +++ b/packages/core/helper-plugin/src/utils/request/index.js @@ -1,6 +1,6 @@ import startsWith from 'lodash/startsWith'; import auth from '../auth'; - +import { once } from '../once'; /** * Parses the JSON returned by a network request * @@ -89,9 +89,13 @@ function serverRestartWatcher(response) { }); } +const warnOnce = once(console.warn); + /** * Requests a URL, returning a promise * + * @deprecated use `useFetchClient` instead. + * * @param {string} url The URL we want to request * @param {object} [options] The options we want to pass to "fetch" * @@ -101,6 +105,10 @@ export default function request(...args) { let [url, options = {}, shouldWatchServerRestart, stringify = true, ...rest] = args; let noAuth; + warnOnce( + 'The `request` function is deprecated and will be removed in the next major version. Please use `useFetchClient` instead.' + ); + try { [{ noAuth }] = rest; } catch (err) { diff --git a/packages/plugins/documentation/admin/src/pages/utils/api.js b/packages/plugins/documentation/admin/src/pages/utils/api.js deleted file mode 100644 index ab4d3b9e7d..0000000000 --- a/packages/plugins/documentation/admin/src/pages/utils/api.js +++ /dev/null @@ -1,31 +0,0 @@ -import { request } from '@strapi/helper-plugin'; -import pluginId from '../../pluginId'; - -const deleteDoc = ({ prefix, version }) => { - return request(`${prefix}/deleteDoc/${version}`, { method: 'DELETE' }); -}; - -const fetchDocumentationVersions = async (toggleNotification) => { - try { - const data = await request(`/${pluginId}/getInfos`, { method: 'GET' }); - - return data; - } catch (err) { - toggleNotification({ - type: 'warning', - message: { id: 'notification.error' }, - }); - - // FIXME - return null; - } -}; - -const regenerateDoc = ({ prefix, version }) => { - return request(`${prefix}/regenerateDoc`, { method: 'POST', body: { version } }); -}; - -const updateSettings = ({ prefix, body }) => - request(`${prefix}/updateSettings`, { method: 'PUT', body }); - -export { deleteDoc, fetchDocumentationVersions, regenerateDoc, updateSettings }; diff --git a/packages/plugins/documentation/admin/src/pages/utils/useReactQuery.js b/packages/plugins/documentation/admin/src/pages/utils/useReactQuery.js index 79c5f525b6..430eb618a1 100644 --- a/packages/plugins/documentation/admin/src/pages/utils/useReactQuery.js +++ b/packages/plugins/documentation/admin/src/pages/utils/useReactQuery.js @@ -1,14 +1,28 @@ import { useQuery, useMutation, useQueryClient } from 'react-query'; -import { useNotification } from '@strapi/helper-plugin'; -import { fetchDocumentationVersions, deleteDoc, regenerateDoc, updateSettings } from './api'; +import { useNotification, useFetchClient } from '@strapi/helper-plugin'; +import pluginId from '../../pluginId'; import getTrad from '../../utils/getTrad'; const useReactQuery = () => { const queryClient = useQueryClient(); const toggleNotification = useNotification(); - const { isLoading, data } = useQuery('get-documentation', () => - fetchDocumentationVersions(toggleNotification) - ); + const { isLoading, data } = useQuery(['get-documentation', pluginId], async () => { + try { + const { data } = await get(`/${pluginId}/getInfos`); + + return data; + } catch (err) { + toggleNotification({ + type: 'warning', + message: { id: 'notification.error' }, + }); + + // FIXME + return null; + } + }); + + const { del, post, put, get } = useFetchClient(); const handleError = (err) => { toggleNotification({ @@ -25,20 +39,26 @@ const useReactQuery = () => { }); }; - const deleteMutation = useMutation(deleteDoc, { - onSuccess: () => handleSuccess('info', 'notification.delete.success'), - onError: (error) => handleError(error), - }); + const deleteMutation = useMutation( + ({ prefix, version }) => del(`${prefix}/deleteDoc/${version}`), + { + onSuccess: () => handleSuccess('info', 'notification.delete.success'), + onError: (error) => handleError(error), + } + ); - const submitMutation = useMutation(updateSettings, { + const submitMutation = useMutation(({ prefix, body }) => put(`${prefix}/updateSettings`, body), { onSuccess: () => handleSuccess('success', 'notification.update.success'), onError: handleError, }); - const regenerateDocMutation = useMutation(regenerateDoc, { - onSuccess: () => handleSuccess('info', 'notification.generate.success'), - onError: (error) => handleError(error), - }); + const regenerateDocMutation = useMutation( + ({ prefix, version }) => post(`${prefix}/regenerateDoc`, { version }), + { + onSuccess: () => handleSuccess('info', 'notification.generate.success'), + onError: (error) => handleError(error), + } + ); return { data, isLoading, deleteMutation, submitMutation, regenerateDocMutation }; }; diff --git a/packages/plugins/i18n/admin/src/hooks/useAddLocale/index.js b/packages/plugins/i18n/admin/src/hooks/useAddLocale/index.js index eb34bcd477..cf93518b88 100644 --- a/packages/plugins/i18n/admin/src/hooks/useAddLocale/index.js +++ b/packages/plugins/i18n/admin/src/hooks/useAddLocale/index.js @@ -1,39 +1,28 @@ import { useState } from 'react'; -import { request, useNotification } from '@strapi/helper-plugin'; +import { useFetchClient, useNotification } from '@strapi/helper-plugin'; import { useDispatch } from 'react-redux'; import get from 'lodash/get'; import { getTrad } from '../../utils'; import { ADD_LOCALE } from '../constants'; -const addLocale = async ({ code, name, isDefault }, toggleNotification) => { - const data = await request('/i18n/locales', { - method: 'POST', - body: { - name, - code, - isDefault, - }, - }); - - toggleNotification({ - type: 'success', - message: { id: getTrad('Settings.locales.modal.create.success') }, - }); - - return data; -}; - const useAddLocale = () => { const [isLoading, setLoading] = useState(false); const dispatch = useDispatch(); const toggleNotification = useNotification(); + const { post } = useFetchClient(); const persistLocale = async (locale) => { setLoading(true); try { - const newLocale = await addLocale(locale, toggleNotification); - dispatch({ type: ADD_LOCALE, newLocale }); + const { data } = await post('/i18n/locales', locale); + + toggleNotification({ + type: 'success', + message: { id: getTrad('Settings.locales.modal.create.success') }, + }); + + dispatch({ type: ADD_LOCALE, newLocale: data }); } catch (e) { const message = get(e, 'response.payload.message', null); diff --git a/packages/plugins/i18n/admin/src/hooks/useDefaultLocales/index.js b/packages/plugins/i18n/admin/src/hooks/useDefaultLocales/index.js index 5c931f0c39..d308934759 100644 --- a/packages/plugins/i18n/admin/src/hooks/useDefaultLocales/index.js +++ b/packages/plugins/i18n/admin/src/hooks/useDefaultLocales/index.js @@ -1,32 +1,18 @@ import { useQuery } from 'react-query'; -import { request, useNotification } from '@strapi/helper-plugin'; +import { useFetchClient, useNotification } from '@strapi/helper-plugin'; import { useNotifyAT } from '@strapi/design-system'; import { useIntl } from 'react-intl'; import { getTrad } from '../../utils'; -const fetchDefaultLocalesList = async (toggleNotification) => { - try { - const data = await request('/i18n/iso-locales', { - method: 'GET', - }); - - return data; - } catch (e) { - toggleNotification({ - type: 'warning', - message: { id: 'notification.error' }, - }); - - return []; - } -}; - const useDefaultLocales = () => { const { formatMessage } = useIntl(); const { notifyStatus } = useNotifyAT(); const toggleNotification = useNotification(); - const { isLoading, data } = useQuery('default-locales', () => - fetchDefaultLocalesList(toggleNotification).then((data) => { + const { get } = useFetchClient(); + const { isLoading, data } = useQuery(['plugin-i18n', 'locales'], async () => { + try { + const { data } = await get('/i18n/iso-locales'); + notifyStatus( formatMessage({ id: getTrad('Settings.locales.modal.locales.loaded'), @@ -35,8 +21,15 @@ const useDefaultLocales = () => { ); return data; - }) - ); + } catch (e) { + toggleNotification({ + type: 'warning', + message: { id: 'notification.error' }, + }); + + return []; + } + }); return { defaultLocales: data, isLoading }; }; diff --git a/packages/plugins/i18n/admin/src/hooks/useDeleteLocale/index.js b/packages/plugins/i18n/admin/src/hooks/useDeleteLocale/index.js index f6fb1df4c1..2d9fc364ee 100644 --- a/packages/plugins/i18n/admin/src/hooks/useDeleteLocale/index.js +++ b/packages/plugins/i18n/admin/src/hooks/useDeleteLocale/index.js @@ -1,43 +1,36 @@ import { useState } from 'react'; -import { request, useNotification } from '@strapi/helper-plugin'; +import { useFetchClient, useNotification } from '@strapi/helper-plugin'; import { useDispatch } from 'react-redux'; import { getTrad } from '../../utils'; import { DELETE_LOCALE } from '../constants'; -const deleteLocale = async (id, toggleNotification) => { - try { - const data = await request(`/i18n/locales/${id}`, { - method: 'DELETE', - }); - - toggleNotification({ - type: 'success', - message: { id: getTrad('Settings.locales.modal.delete.success') }, - }); - - return data; - } catch (e) { - toggleNotification({ - type: 'warning', - message: { id: 'notification.error' }, - }); - - return e; - } -}; - const useDeleteLocale = () => { const [isLoading, setLoading] = useState(false); const dispatch = useDispatch(); const toggleNotification = useNotification(); + const { del } = useFetchClient(); + const removeLocale = async (id) => { - setLoading(true); + try { + setLoading(true); - await deleteLocale(id, toggleNotification); + await del(`/i18n/locales/${id}`); - dispatch({ type: DELETE_LOCALE, id }); - setLoading(false); + toggleNotification({ + type: 'success', + message: { id: getTrad('Settings.locales.modal.delete.success') }, + }); + + dispatch({ type: DELETE_LOCALE, id }); + } catch { + toggleNotification({ + type: 'warning', + message: { id: 'notification.error' }, + }); + } finally { + setLoading(false); + } }; return { isDeleting: isLoading, deleteLocale: removeLocale }; diff --git a/packages/plugins/i18n/admin/src/hooks/useEditLocale/index.js b/packages/plugins/i18n/admin/src/hooks/useEditLocale/index.js index 112dd0e666..564e1fe72c 100644 --- a/packages/plugins/i18n/admin/src/hooks/useEditLocale/index.js +++ b/packages/plugins/i18n/admin/src/hooks/useEditLocale/index.js @@ -1,44 +1,35 @@ import { useState } from 'react'; -import { request, useNotification } from '@strapi/helper-plugin'; +import { useFetchClient, useNotification } from '@strapi/helper-plugin'; import { useDispatch } from 'react-redux'; import { getTrad } from '../../utils'; import { UPDATE_LOCALE } from '../constants'; -const editLocale = async (id, payload, toggleNotification) => { - try { - const data = await request(`/i18n/locales/${id}`, { - method: 'PUT', - body: payload, - }); - - toggleNotification({ - type: 'success', - message: { id: getTrad('Settings.locales.modal.edit.success') }, - }); - - return data; - } catch { - toggleNotification({ - type: 'warning', - message: { id: 'notification.error' }, - }); - - return null; - } -}; - const useEditLocale = () => { const [isLoading, setLoading] = useState(false); const dispatch = useDispatch(); const toggleNotification = useNotification(); + const { put } = useFetchClient(); const modifyLocale = async (id, payload) => { - setLoading(true); + try { + setLoading(true); - const editedLocale = await editLocale(id, payload, toggleNotification); + const { data } = await put(`/i18n/locales/${id}`, payload); - dispatch({ type: UPDATE_LOCALE, editedLocale }); - setLoading(false); + toggleNotification({ + type: 'success', + message: { id: getTrad('Settings.locales.modal.edit.success') }, + }); + + dispatch({ type: UPDATE_LOCALE, editedLocale: data }); + } catch { + toggleNotification({ + type: 'warning', + message: { id: 'notification.error' }, + }); + } finally { + setLoading(false); + } }; return { isEditing: isLoading, editLocale: modifyLocale }; diff --git a/packages/plugins/i18n/admin/src/hooks/useLocales/index.js b/packages/plugins/i18n/admin/src/hooks/useLocales/index.js index fbfff22bea..1b165b8323 100644 --- a/packages/plugins/i18n/admin/src/hooks/useLocales/index.js +++ b/packages/plugins/i18n/admin/src/hooks/useLocales/index.js @@ -1,36 +1,35 @@ import { useEffect } from 'react'; -import { request, useNotification } from '@strapi/helper-plugin'; +import { useFetchClient, useNotification } from '@strapi/helper-plugin'; import { useSelector, useDispatch } from 'react-redux'; import { RESOLVE_LOCALES } from '../constants'; -const fetchLocalesList = async (toggleNotification) => { - try { - const data = await request('/i18n/locales', { - method: 'GET', - }); - - return data; - } catch (e) { - toggleNotification({ - type: 'warning', - message: { id: 'notification.error' }, - }); - - return e; - } -}; - const useLocales = () => { const dispatch = useDispatch(); const toggleNotification = useNotification(); const locales = useSelector((state) => state.i18n_locales.locales); const isLoading = useSelector((state) => state.i18n_locales.isLoading); + const { get } = useFetchClient(); + useEffect(() => { - fetchLocalesList(toggleNotification).then((locales) => - dispatch({ type: RESOLVE_LOCALES, locales }) - ); - }, [dispatch, toggleNotification]); + get('/i18n/locales') + .then(({ data }) => dispatch({ type: RESOLVE_LOCALES, locales: data })) + .catch((err) => { + /** + * TODO: this should be refactored. + * + * In fact it should be refactored to use react-query? + */ + if ('code' in err && err?.code === 'ERR_CANCELED') { + return; + } + + toggleNotification({ + type: 'warning', + message: { id: 'notification.error' }, + }); + }); + }, [dispatch, get, toggleNotification]); return { locales, isLoading }; }; diff --git a/packages/plugins/i18n/admin/src/middlewares/addCommonFieldsToInitialDataMiddleware.js b/packages/plugins/i18n/admin/src/middlewares/addCommonFieldsToInitialDataMiddleware.js index f2f60a3356..544771670f 100644 --- a/packages/plugins/i18n/admin/src/middlewares/addCommonFieldsToInitialDataMiddleware.js +++ b/packages/plugins/i18n/admin/src/middlewares/addCommonFieldsToInitialDataMiddleware.js @@ -3,7 +3,7 @@ import merge from 'lodash/merge'; import cloneDeep from 'lodash/cloneDeep'; import { parse } from 'qs'; import { - request, + getFetchClient, formatContentTypeData, contentManagementUtilRemoveFieldsFromData, } from '@strapi/helper-plugin'; @@ -44,10 +44,10 @@ const addCommonFieldsToInitialDataMiddleware = const defaultDataStructure = cloneDeep(contentTypeDataStructure); try { - const requestURL = `/${pluginId}/content-manager/actions/get-non-localized-fields`; - const body = { model: currentLayout.contentType.uid, id: relatedEntityId, locale }; - - const data = await request(requestURL, { method: 'POST', body }); + const { data } = await getFetchClient().post( + `/${pluginId}/content-manager/actions/get-non-localized-fields`, + { model: currentLayout.contentType.uid, id: relatedEntityId, locale } + ); const { nonLocalizedFields, localizations } = data; diff --git a/packages/plugins/i18n/admin/src/pages/SettingsPage/tests/SettingsPage.test.js b/packages/plugins/i18n/admin/src/pages/SettingsPage/tests/SettingsPage.test.js index 60b56a0233..8dde473c1c 100644 --- a/packages/plugins/i18n/admin/src/pages/SettingsPage/tests/SettingsPage.test.js +++ b/packages/plugins/i18n/admin/src/pages/SettingsPage/tests/SettingsPage.test.js @@ -3,7 +3,7 @@ import React from 'react'; // import { createStore, combineReducers } from 'redux'; // import { Provider } from 'react-redux'; -import { request, useRBAC } from '@strapi/helper-plugin'; +// import { request, useRBAC } from '@strapi/helper-plugin'; // import { fireEvent, render, screen, within, waitFor } from '@testing-library/react'; // import { ThemeProvider } from 'styled-components'; // import { QueryClient, QueryClientProvider } from 'react-query'; @@ -81,27 +81,26 @@ jest.mock('react-intl', () => ({ describe('i18n settings page', () => { beforeEach(() => { - request.mockImplementation(() => - Promise.resolve([ - { - id: 1, - name: 'French', - code: 'fr-FR', - isDefault: false, - }, - { - id: 2, - name: 'English', - code: 'en-US', - isDefault: true, - }, - ]) - ); - - useRBAC.mockImplementation(() => ({ - isLoading: false, - allowedActions: { canRead: true, canUpdate: true, canCreate: true, canDelete: true }, - })); + // request.mockImplementation(() => + // Promise.resolve([ + // { + // id: 1, + // name: 'French', + // code: 'fr-FR', + // isDefault: false, + // }, + // { + // id: 2, + // name: 'English', + // code: 'en-US', + // isDefault: true, + // }, + // ]) + // ); + // useRBAC.mockImplementation(() => ({ + // isLoading: false, + // allowedActions: { canRead: true, canUpdate: true, canCreate: true, canDelete: true }, + // })); }); afterEach(() => { @@ -583,14 +582,14 @@ describe('i18n settings page', () => { describe('create', () => { beforeEach(() => { - request.mockImplementation((url) => - url.includes('/i18n/locales') - ? Promise.resolve([]) - : Promise.resolve([ - { code: 'fr-FR', name: 'Francais' }, - { code: 'en-EN', name: 'English' }, - ]) - ); + // request.mockImplementation((url) => + // url.includes('/i18n/locales') + // ? Promise.resolve([]) + // : Promise.resolve([ + // { code: 'fr-FR', name: 'Francais' }, + // { code: 'en-EN', name: 'English' }, + // ]) + // ); }); test.todo('shows the default create modal layout'); // it('shows the default create modal layout', async () => { diff --git a/packages/plugins/users-permissions/admin/src/hooks/useForm/index.js b/packages/plugins/users-permissions/admin/src/hooks/useForm/index.js index 3b3ea7b5b1..35000194a4 100644 --- a/packages/plugins/users-permissions/admin/src/hooks/useForm/index.js +++ b/packages/plugins/users-permissions/admin/src/hooks/useForm/index.js @@ -1,5 +1,5 @@ import { useCallback, useEffect, useReducer, useRef } from 'react'; -import { useRBAC, request, useNotification } from '@strapi/helper-plugin'; +import { useRBAC, useFetchClient, useNotification } from '@strapi/helper-plugin'; import { getRequestURL } from '../../utils'; import reducer, { initialState } from './reducer'; @@ -9,8 +9,7 @@ const useUserForm = (endPoint, permissions) => { const toggleNotification = useNotification(); const isMounted = useRef(true); - const abortController = new AbortController(); - const { signal } = abortController; + const { get } = useFetchClient(); useEffect(() => { const getData = async () => { @@ -19,7 +18,7 @@ const useUserForm = (endPoint, permissions) => { type: 'GET_DATA', }); - const data = await request(getRequestURL(endPoint), { method: 'GET', signal }); + const { data } = await get(getRequestURL(endPoint)); dispatch({ type: 'GET_DATA_SUCCEEDED', @@ -45,11 +44,9 @@ const useUserForm = (endPoint, permissions) => { } return () => { - abortController.abort(); isMounted.current = false; }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isLoadingForPermissions, endPoint]); + }, [isLoadingForPermissions, endPoint, get, toggleNotification]); const dispatchSubmitSucceeded = useCallback((data) => { dispatch({ diff --git a/packages/plugins/users-permissions/admin/src/hooks/useRolesList/index.js b/packages/plugins/users-permissions/admin/src/hooks/useRolesList/index.js index d760e60588..10e4d37ad9 100644 --- a/packages/plugins/users-permissions/admin/src/hooks/useRolesList/index.js +++ b/packages/plugins/users-permissions/admin/src/hooks/useRolesList/index.js @@ -1,5 +1,5 @@ -import { useEffect, useReducer, useRef } from 'react'; -import { request, useNotification } from '@strapi/helper-plugin'; +import { useCallback, useEffect, useReducer, useRef } from 'react'; +import { useFetchClient, useNotification } from '@strapi/helper-plugin'; import get from 'lodash/get'; import init from './init'; import pluginId from '../../pluginId'; @@ -12,28 +12,17 @@ const useRolesList = (shouldFetchData = true) => { const toggleNotification = useNotification(); const isMounted = useRef(true); - const abortController = new AbortController(); - const { signal } = abortController; + const fetchClient = useFetchClient(); - useEffect(() => { - if (shouldFetchData) { - fetchRolesList(); - } - - return () => { - abortController.abort(); - isMounted.current = false; - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [shouldFetchData]); - - const fetchRolesList = async () => { + const fetchRolesList = useCallback(async () => { try { dispatch({ type: 'GET_DATA', }); - const { roles } = await request(`/${pluginId}/roles`, { method: 'GET', signal }); + const { + data: { roles }, + } = await fetchClient.get(`/${pluginId}/roles`); dispatch({ type: 'GET_DATA_SUCCEEDED', @@ -55,7 +44,17 @@ const useRolesList = (shouldFetchData = true) => { } } } - }; + }, [fetchClient, toggleNotification]); + + useEffect(() => { + if (shouldFetchData) { + fetchRolesList(); + } + + return () => { + isMounted.current = false; + }; + }, [shouldFetchData, fetchRolesList]); return { roles, isLoading, getData: fetchRolesList }; }; diff --git a/packages/utils/eslint-config-custom/front.js b/packages/utils/eslint-config-custom/front.js index 46840b4a28..7756c675de 100644 --- a/packages/utils/eslint-config-custom/front.js +++ b/packages/utils/eslint-config-custom/front.js @@ -49,6 +49,12 @@ module.exports = { message: "'Stack' has been deprecated. Please import 'Flex' from '@strapi/design-system' instead.", }, + { + name: '@strapi/helper-plugin', + importNames: ['request'], + message: + "'request' has been deprecated. Please import 'useFetchClient' from '@strapi/helper-plugin' instead.", + }, { name: 'lodash', message: 'Please use import [method] from lodash/[method]', From 18ddcf22742d7b564c3fc9e13b3855879bf2f594 Mon Sep 17 00:00:00 2001 From: Simone Taeggi Date: Thu, 20 Apr 2023 18:59:55 +0200 Subject: [PATCH 6/6] remove unused request --- packages/core/helper-plugin/src/utils/hasPermissions/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/helper-plugin/src/utils/hasPermissions/index.js b/packages/core/helper-plugin/src/utils/hasPermissions/index.js index 64dd60dfc7..17340a9811 100644 --- a/packages/core/helper-plugin/src/utils/hasPermissions/index.js +++ b/packages/core/helper-plugin/src/utils/hasPermissions/index.js @@ -2,7 +2,6 @@ import isEmpty from 'lodash/isEmpty'; import pickBy from 'lodash/pickBy'; import transform from 'lodash/transform'; -import request from '../request'; import getFetchClient from '../getFetchClient'; const findMatchingPermissions = (userPermissions, permissions) => {