From eb01b844127dd761787448bf0d56948b8ceeb9e8 Mon Sep 17 00:00:00 2001 From: Dawid Date: Thu, 12 Jan 2023 09:19:13 +0100 Subject: [PATCH] Improve apps contextual actions (#2943) --- assets/images/delete.svg | 2 +- .../AppDetailsPage/AppDetailsPage.stories.tsx | 1 + .../AppDetailsPage/AppDetailsPage.tsx | 25 +-- .../AppsListPage/AppListPage.stories.tsx | 10 +- .../components/AppsListPage/AppsListPage.tsx | 8 +- .../InstalledApps/InstalledApps.tsx | 34 +--- src/apps/context.ts | 21 --- src/apps/urls.ts | 11 +- src/apps/utils.ts | 7 + src/apps/views/AppDetails/AppDetails.tsx | 48 +++++- src/apps/views/AppDetails/messages.ts | 9 ++ src/apps/views/AppsList/AppsList.tsx | 151 ++++-------------- src/intl.ts | 10 ++ 13 files changed, 128 insertions(+), 209 deletions(-) delete mode 100644 src/apps/context.ts create mode 100644 src/apps/views/AppDetails/messages.ts diff --git a/assets/images/delete.svg b/assets/images/delete.svg index 003d4187f..f98d6ed12 100644 --- a/assets/images/delete.svg +++ b/assets/images/delete.svg @@ -1,5 +1,5 @@ - void; onAppActivateOpen: () => void; onAppDeactivateOpen: () => void; + onAppDeleteOpen: () => void; } export const AppDetailsPage: React.FC = ({ @@ -34,6 +36,7 @@ export const AppDetailsPage: React.FC = ({ navigateToApp, onAppActivateOpen, onAppDeactivateOpen, + onAppDeleteOpen, }) => { const intl = useIntl(); const classes = useStyles({}); @@ -80,19 +83,19 @@ export const AppDetailsPage: React.FC = ({ > {data?.isActive ? ( - + ) : ( - + )} + + + + ) : ( diff --git a/src/apps/components/AppsListPage/AppListPage.stories.tsx b/src/apps/components/AppsListPage/AppListPage.stories.tsx index 4a5bd81a9..0674ba431 100644 --- a/src/apps/components/AppsListPage/AppListPage.stories.tsx +++ b/src/apps/components/AppsListPage/AppListPage.stories.tsx @@ -1,4 +1,3 @@ -import { AppListContext } from "@saleor/apps/context"; import { listActionsProps, pageListProps, @@ -28,18 +27,11 @@ const props: AppsListPageProps = { installedAppsList: appsList, onAppInProgressRemove: () => undefined, onAppInstallRetry: () => undefined, - onInstalledAppRemove: () => undefined, + onSettingsAppOpen: () => undefined, }; storiesOf("Apps / Apps list", module) .addDecorator(Decorator) - .addDecorator(story => ( - undefined, deactivateApp: () => undefined }} - > - {story()} - - )) .addDecorator(PaginatorContextDecorator) .add("default", () => ) .add("loading", () => ( diff --git a/src/apps/components/AppsListPage/AppsListPage.tsx b/src/apps/components/AppsListPage/AppsListPage.tsx index 35808b392..a639caf98 100644 --- a/src/apps/components/AppsListPage/AppsListPage.tsx +++ b/src/apps/components/AppsListPage/AppsListPage.tsx @@ -19,7 +19,7 @@ import InstalledApps from "../InstalledApps/InstalledApps"; export interface AppsListPageProps extends ListProps { installedAppsList: AppListItemFragment[]; appsInProgressList?: AppsInstallationsQuery; - onInstalledAppRemove: (id: string) => void; + onSettingsAppOpen: (id: string) => void; onAppInProgressRemove: (id: string) => void; onAppInstallRetry: (id: string) => void; } @@ -42,7 +42,7 @@ const useStyles = makeStyles( const AppsListPage: React.FC = ({ appsInProgressList, installedAppsList, - onInstalledAppRemove, + onSettingsAppOpen, onAppInProgressRemove, onAppInstallRetry, ...listProps @@ -123,7 +123,7 @@ const AppsListPage: React.FC = ({ description: "section header", })} appsList={thirdPartyApps} - onRemove={onInstalledAppRemove} + onSettingsClick={onSettingsAppOpen} displayQuickManifestButton {...listProps} /> @@ -149,7 +149,7 @@ const AppsListPage: React.FC = ({ description: "section header", })} appsList={saleorApps} - onRemove={onInstalledAppRemove} + onSettingsClick={onSettingsAppOpen} {...listProps} />
diff --git a/src/apps/components/InstalledApps/InstalledApps.tsx b/src/apps/components/InstalledApps/InstalledApps.tsx index e22a3b436..096c9c840 100644 --- a/src/apps/components/InstalledApps/InstalledApps.tsx +++ b/src/apps/components/InstalledApps/InstalledApps.tsx @@ -1,13 +1,6 @@ -import { - Card, - Switch, - TableBody, - TableCell, - Typography, -} from "@material-ui/core"; +import { Card, TableBody, TableCell, Typography } from "@material-ui/core"; import { AppManifestTableDisplay } from "@saleor/apps/components/AppManifestTableDisplay/AppManifestTableDisplay"; import { InstallWithManifestFormButton } from "@saleor/apps/components/InstallWithManifestFormButton"; -import { useAppListContext } from "@saleor/apps/context"; import { appUrl, createAppInstallUrl } from "@saleor/apps/urls"; import { isAppInTunnel } from "@saleor/apps/utils"; import CardTitle from "@saleor/components/CardTitle"; @@ -16,7 +9,7 @@ import { TableButtonWrapper } from "@saleor/components/TableButtonWrapper/TableB import TableRowLink from "@saleor/components/TableRowLink"; import { AppListItemFragment } from "@saleor/graphql"; import useNavigator from "@saleor/hooks/useNavigator"; -import { DeleteIcon, ResponsiveTable } from "@saleor/macaw-ui"; +import { ResponsiveTable, SettingsIcon } from "@saleor/macaw-ui"; import { renderCollection } from "@saleor/misc"; import { ListProps } from "@saleor/types"; import React, { useCallback } from "react"; @@ -28,20 +21,19 @@ import AppsSkeleton from "../AppsSkeleton"; export interface InstalledAppsProps extends ListProps { appsList: AppListItemFragment[]; - onRemove: (id: string) => void; + onSettingsClick: (id: string) => void; displayQuickManifestButton?: boolean; title: string; } const InstalledApps: React.FC = ({ appsList, - onRemove, + onSettingsClick, title, displayQuickManifestButton = false, ...props }) => { const classes = useStyles(props); - const { activateApp, deactivateApp } = useAppListContext(); const navigate = useNavigator(); const navigateToAppInstallPage = useCallback( @@ -51,14 +43,6 @@ const InstalledApps: React.FC = ({ [navigate], ); - const getHandleToggle = (app: AppListItemFragment) => () => { - if (app.isActive) { - deactivateApp(app.id); - } else { - activateApp(app.id); - } - }; - return ( = ({ {app.manifestUrl && ( )} - - - onRemove(app.id)} + onClick={() => onSettingsClick(app.id)} > - + diff --git a/src/apps/context.ts b/src/apps/context.ts deleted file mode 100644 index 6648aa044..000000000 --- a/src/apps/context.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import React from "react"; - -export interface AppListContextValues { - activateApp: (appId: string) => void; - deactivateApp: (appId: string) => void; -} - -export const AppListContext = React.createContext< - AppListContextValues | undefined ->(undefined); - -export const useAppListContext = () => { - const context = React.useContext(AppListContext); - - if (!context) { - throw new Error("useAppListContext must be used within a AppListContext"); - } - - return context; -}; diff --git a/src/apps/urls.ts b/src/apps/urls.ts index 285a9fd94..dcaba4d13 100644 --- a/src/apps/urls.ts +++ b/src/apps/urls.ts @@ -6,13 +6,12 @@ import { ActiveTab, Dialog, Pagination, SingleAction } from "../types"; export const MANIFEST_ATTR = "manifestUrl"; -export type AppListUrlDialog = - | "remove" - | "remove-app" - | "app-activate" - | "app-deactivate"; +export type AppListUrlDialog = "app-installation-remove"; -export type AppDetailsUrlDialog = "app-activate" | "app-deactivate"; +export type AppDetailsUrlDialog = + | "app-activate" + | "app-deactivate" + | "app-delete"; export type AppListUrlQueryParams = ActiveTab & Dialog & diff --git a/src/apps/utils.ts b/src/apps/utils.ts index 9ceb29d2a..32d531f88 100644 --- a/src/apps/utils.ts +++ b/src/apps/utils.ts @@ -1,4 +1,11 @@ +import { AppsInstallationsQuery } from "@saleor/graphql"; + const tunnelKeywords = [".ngrok.io", ".saleor.live"]; export const isAppInTunnel = (manifestUrl: string) => Boolean(tunnelKeywords.find(keyword => manifestUrl.includes(keyword))); + +export const getAppInProgressName = ( + id: string, + collection?: AppsInstallationsQuery["appsInstallations"], +) => collection?.find(app => app.id === id)?.appName || id; diff --git a/src/apps/views/AppDetails/AppDetails.tsx b/src/apps/views/AppDetails/AppDetails.tsx index e51067b78..b00ffaf91 100644 --- a/src/apps/views/AppDetails/AppDetails.tsx +++ b/src/apps/views/AppDetails/AppDetails.tsx @@ -1,8 +1,12 @@ +import { useApolloClient } from "@apollo/client"; +import AppDeleteDialog from "@saleor/apps/components/AppDeleteDialog"; import { appMessages } from "@saleor/apps/messages"; +import { EXTENSION_LIST_QUERY } from "@saleor/apps/queries"; import NotFoundPage from "@saleor/components/NotFoundPage"; import { useAppActivateMutation, useAppDeactivateMutation, + useAppDeleteMutation, useAppQuery, } from "@saleor/graphql"; import useNavigator from "@saleor/hooks/useNavigator"; @@ -22,6 +26,7 @@ import { appsListPath, appUrl, } from "../../urls"; +import { messages } from "./messages"; interface AppDetailsProps { id: string; @@ -29,6 +34,7 @@ interface AppDetailsProps { } export const AppDetails: React.FC = ({ id, params }) => { + const client = useApolloClient(); const { data, loading, refetch } = useAppQuery({ displayLoader: true, variables: { id }, @@ -85,17 +91,38 @@ export const AppDetails: React.FC = ({ id, params }) => { }, }); + const refetchExtensionList = () => { + client.refetchQueries({ + include: [EXTENSION_LIST_QUERY], + }); + }; + + const removeAppNotify = () => { + notify({ + status: "success", + text: intl.formatMessage(messages.appRemoved), + }); + }; + + const [deleteApp, deleteAppOpts] = useAppDeleteMutation({ + onCompleted: data => { + if (!data?.appDelete?.errors?.length) { + refetch(); + closeModal(); + refetchExtensionList(); + removeAppNotify(); + } + }, + }); + const [openModal, closeModal] = createDialogActionHandlers< AppDetailsUrlDialog, AppDetailsUrlQueryParams >(navigate, params => appDetailsUrl(id, params), params); - const handleActivateConfirm = () => { - activateApp(mutationOpts); - }; - const handleDeactivateConfirm = () => { - deactivateApp(mutationOpts); - }; + const handleActivateConfirm = () => activateApp(mutationOpts); + const handleDeactivateConfirm = () => deactivateApp(mutationOpts); + const handleRemoveConfirm = () => deleteApp(mutationOpts); if (!appExists) { return ; @@ -117,12 +144,21 @@ export const AppDetails: React.FC = ({ id, params }) => { onConfirm={handleDeactivateConfirm} open={params.action === "app-deactivate"} /> + navigate(appUrl(id))} onAppActivateOpen={() => openModal("app-activate")} onAppDeactivateOpen={() => openModal("app-deactivate")} + onAppDeleteOpen={() => openModal("app-delete")} /> ); diff --git a/src/apps/views/AppDetails/messages.ts b/src/apps/views/AppDetails/messages.ts new file mode 100644 index 000000000..888b9f878 --- /dev/null +++ b/src/apps/views/AppDetails/messages.ts @@ -0,0 +1,9 @@ +import { defineMessages } from "react-intl"; + +export const messages = defineMessages({ + appRemoved: { + id: "uIPD1i", + defaultMessage: "App successfully removed", + description: "app has been removed", + }, +}); diff --git a/src/apps/views/AppsList/AppsList.tsx b/src/apps/views/AppsList/AppsList.tsx index 0beab77b6..f01fce9c5 100644 --- a/src/apps/views/AppsList/AppsList.tsx +++ b/src/apps/views/AppsList/AppsList.tsx @@ -1,17 +1,11 @@ import { useApolloClient } from "@apollo/client"; -import AppActivateDialog from "@saleor/apps/components/AppActivateDialog"; -import AppDeactivateDialog from "@saleor/apps/components/AppDeactivateDialog"; -import { AppListContext, AppListContextValues } from "@saleor/apps/context"; +import { getAppInProgressName } from "@saleor/apps/utils"; import { - AppsInstallationsQuery, AppSortField, AppTypeEnum, JobStatusEnum, OrderDirection, - useAppActivateMutation, - useAppDeactivateMutation, useAppDeleteFailedInstallationMutation, - useAppDeleteMutation, useAppRetryInstallMutation, useAppsInstallationsQuery, useAppsListQuery, @@ -25,28 +19,23 @@ import usePaginator, { PageInfo, PaginatorContext, } from "@saleor/hooks/usePaginator"; -import { findById } from "@saleor/misc"; import { ListViews } from "@saleor/types"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import { mapEdgesToItems } from "@saleor/utils/maps"; import React, { useEffect, useRef } from "react"; import { useIntl } from "react-intl"; -import AppDeleteDialog from "../../components/AppDeleteDialog"; import AppInProgressDeleteDialog from "../../components/AppInProgressDeleteDialog"; import AppsListPage from "../../components/AppsListPage"; import { EXTENSION_LIST_QUERY } from "../../queries"; import { + appDetailsUrl, AppListUrlDialog, AppListUrlQueryParams, appsListUrl, } from "../../urls"; import { messages } from "./messages"; -const getAppInProgressName = ( - id: string, - collection?: AppsInstallationsQuery["appsInstallations"], -) => collection?.find(app => app.id === id)?.appName || id; interface AppsListProps { params: AppListUrlQueryParams; } @@ -127,45 +116,11 @@ export const AppsList: React.FC = ({ params }) => { } }, }); - const [activateApp, activateAppResult] = useAppActivateMutation({ - onCompleted: data => { - if (!data?.appActivate?.errors?.length) { - notify({ - status: "success", - text: intl.formatMessage(messages.appActivated), - }); - refetch(); - closeModal(); - } - }, - }); - const [deactivateApp, deactivateAppResult] = useAppDeactivateMutation({ - onCompleted: data => { - if (!data?.appDeactivate?.errors?.length) { - notify({ - status: "success", - text: intl.formatMessage(messages.appDeactivated), - }); - refetch(); - closeModal(); - } - }, - }); const [openModal, closeModal] = createDialogActionHandlers< AppListUrlDialog, AppListUrlQueryParams >(navigate, appsListUrl, params); - const [deleteApp, deleteAppOpts] = useAppDeleteMutation({ - onCompleted: data => { - if (!data?.appDelete?.errors?.length) { - refetch(); - closeModal(); - refetchExtensionList(); - removeAppNotify(); - } - }, - }); const [ deleteInProgressApp, deleteInProgressAppOpts, @@ -236,13 +191,6 @@ export const AppsList: React.FC = ({ params }) => { }, }); - const handleRemoveConfirm = () => - deleteApp({ - variables: { - id: params?.id || "", - }, - }); - const removeAppNotify = () => { notify({ status: "success", @@ -250,81 +198,38 @@ export const AppsList: React.FC = ({ params }) => { }); }; - const handleActivateAppConfirm = () => - activateApp({ variables: { id: params?.id || "" } }); - - const handleDeactivateAppConfirm = () => - deactivateApp({ variables: { id: params?.id || "" } }); - const onAppInstallRetry = (id: string) => retryInstallApp({ variables: { id } }); - const context: AppListContextValues = React.useMemo( - () => ({ - activateApp: id => openModal("app-activate", { id }), - deactivateApp: id => openModal("app-deactivate", { id }), - }), - [activateApp, deactivateApp], - ); - const installedApps = mapEdgesToItems(data?.apps || { edges: [] }) || []; - const currentAppName = findById(params?.id || "", installedApps)?.name || ""; return ( - - - - - - - - openModal("remove-app", { - id, - }) - } - onAppInProgressRemove={id => - openModal("remove", { - id, - }) - } - /> - - + + + navigate(appDetailsUrl(id))} + onAppInProgressRemove={id => + openModal("app-installation-remove", { + id, + }) + } + /> + ); }; diff --git a/src/intl.ts b/src/intl.ts index 17e07e6aa..76fd93ba7 100644 --- a/src/intl.ts +++ b/src/intl.ts @@ -237,6 +237,16 @@ export const buttonMessages = defineMessages({ defaultMessage: "Assign", description: "button", }, + activate: { + id: "+b3KCV", + defaultMessage: "Activate", + description: "button", + }, + deactivate: { + id: "gygOA1", + defaultMessage: "Deactivate", + description: "button", + }, back: { id: "0OfZJA", defaultMessage: "Back",