From dfb5f167d44bd93a4bbda4b869112f22035e69e9 Mon Sep 17 00:00:00 2001 From: Dawid Date: Wed, 1 Mar 2023 15:04:53 +0100 Subject: [PATCH] Migrate app details view (settings view) to new-apps (#3167) * Migrate app details view to new-apps * Fix data privacy card --- locale/defaultMessages.json | 52 ++--- .../AppActivateDialog/AppActivateDialog.tsx | 65 ------ .../components/AppActivateDialog/index.ts | 2 - .../AppDeactivateDialog.tsx | 67 ------- .../components/AppDeactivateDialog/index.ts | 2 - .../AppDeactivateDialog/messages.ts | 28 --- .../AppDeleteDialog/AppDeleteDialog.tsx | 71 ------- src/apps/components/AppDeleteDialog/index.ts | 2 - .../AppDetailsPage/AppDetailsPage.tsx | 186 ------------------ src/apps/index.tsx | 15 -- .../CustomAppListPage/CustomAppListPage.tsx | 2 +- .../CustomAppDetails/CustomAppDetails.tsx | 4 +- src/custom-apps/views/CustomAppList.tsx | 2 +- src/intl.ts | 5 + .../components/AppDetailsPage/AboutCard.tsx | 27 +++ .../AppDetailsPage/AppDetailsPage.stories.tsx | 0 .../AppDetailsPage/AppDetailsPage.test.tsx | 79 ++++++++ .../AppDetailsPage/AppDetailsPage.tsx | 45 +++++ .../AppDetailsPage/DataPrivacyCard.tsx | 46 +++++ .../components/AppDetailsPage/Header.test.tsx | 112 +++++++++++ .../components/AppDetailsPage/Header.tsx | 48 +++++ .../AppDetailsPage/HeaderOptions.tsx | 77 ++++++++ .../AppDetailsPage/PermissionsCard.tsx | 47 +++++ .../components/AppDetailsPage/index.ts | 0 .../components/AppDetailsPage/messages.ts | 39 ++++ .../components/AppDetailsPage/styles.ts | 65 ++++++ .../DeactivatedText/DeactivatedText.tsx | 7 +- .../components/DeactivatedText/index.ts | 0 .../components/DeactivatedText/styles.ts | 0 src/new-apps/index.tsx | 2 +- src/new-apps/messages.ts | 10 + .../views/AppDetails/AppDetails.tsx | 17 +- .../views/AppDetails/index.ts | 0 .../views/AppDetails/messages.ts | 0 34 files changed, 627 insertions(+), 497 deletions(-) delete mode 100644 src/apps/components/AppActivateDialog/AppActivateDialog.tsx delete mode 100644 src/apps/components/AppActivateDialog/index.ts delete mode 100644 src/apps/components/AppDeactivateDialog/AppDeactivateDialog.tsx delete mode 100644 src/apps/components/AppDeactivateDialog/index.ts delete mode 100644 src/apps/components/AppDeactivateDialog/messages.ts delete mode 100644 src/apps/components/AppDeleteDialog/AppDeleteDialog.tsx delete mode 100644 src/apps/components/AppDeleteDialog/index.ts delete mode 100644 src/apps/components/AppDetailsPage/AppDetailsPage.tsx create mode 100644 src/new-apps/components/AppDetailsPage/AboutCard.tsx rename src/{apps => new-apps}/components/AppDetailsPage/AppDetailsPage.stories.tsx (100%) create mode 100644 src/new-apps/components/AppDetailsPage/AppDetailsPage.test.tsx create mode 100644 src/new-apps/components/AppDetailsPage/AppDetailsPage.tsx create mode 100644 src/new-apps/components/AppDetailsPage/DataPrivacyCard.tsx create mode 100644 src/new-apps/components/AppDetailsPage/Header.test.tsx create mode 100644 src/new-apps/components/AppDetailsPage/Header.tsx create mode 100644 src/new-apps/components/AppDetailsPage/HeaderOptions.tsx create mode 100644 src/new-apps/components/AppDetailsPage/PermissionsCard.tsx rename src/{apps => new-apps}/components/AppDetailsPage/index.ts (100%) create mode 100644 src/new-apps/components/AppDetailsPage/messages.ts create mode 100644 src/new-apps/components/AppDetailsPage/styles.ts rename src/{apps => new-apps}/components/DeactivatedText/DeactivatedText.tsx (73%) rename src/{apps => new-apps}/components/DeactivatedText/index.ts (100%) rename src/{apps => new-apps}/components/DeactivatedText/styles.ts (100%) rename src/{apps => new-apps}/views/AppDetails/AppDetails.tsx (91%) rename src/{apps => new-apps}/views/AppDetails/index.ts (100%) rename src/{apps => new-apps}/views/AppDetails/messages.ts (100%) diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 906e856f9..516085bc3 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -31,6 +31,10 @@ "context": "order shipping method name", "string": "Shipping" }, + "+FWlRD": { + "context": "button", + "string": "Open app" + }, "+HuipK": { "context": "variant sku", "string": "SKU {sku}" @@ -908,10 +912,6 @@ "context": "content", "string": "Saleor couldn’t fetch crucial information regarding installation. Without those System can’t install the app in your Saleor. Please use the button below to get back to system’s dashboard." }, - "5+Xcrz": { - "context": "app deactivated", - "string": "Deactivated" - }, "51HE+Q": { "string": "No sales found" }, @@ -1192,10 +1192,6 @@ "context": "tax classes name input placeholder", "string": "Tax rate name" }, - "73RU3R": { - "context": "deactivate app", - "string": "Are you sure you want to disable this app? Your data will be kept until you reactivate the app. You will be still billed for the app." - }, "74Cxe8": { "context": "table header product label", "string": "Product" @@ -1234,10 +1230,6 @@ "context": "custom search delete, dialog header", "string": "Delete Search" }, - "7O2EsY": { - "context": "deactivate local app", - "string": "Are you sure you want to disable this app? Your data will be kept until you reactivate the app." - }, "7Oorx5": { "context": "navigator section header", "string": "Search in Catalog" @@ -1922,10 +1914,6 @@ "D2qihU": { "string": "Permission is invalid" }, - "D3E2b5": { - "context": "button label", - "string": "Activate" - }, "D3WUc/": { "context": "order history message", "string": "Order was refunded by {refundedBy}" @@ -2465,10 +2453,6 @@ "context": "header", "string": "Create New App" }, - "Gjb6eq": { - "context": "link", - "string": "Get Support" - }, "Gkip05": { "context": "button", "string": "Unassign" @@ -2632,10 +2616,6 @@ "context": "create gift card product alert message", "string": "Create a gift card product" }, - "HtfL5/": { - "context": "button", - "string": "Open App" - }, "HvJPcU": { "string": "Category deleted" }, @@ -3143,10 +3123,6 @@ "context": "button", "string": "Create order" }, - "LtqrM8": { - "context": "delete custom app", - "string": "Deleting {name}, you will delete all the data and webhooks regarding this app. Are you sure you want to do that?" - }, "Lx1ima": { "context": "button", "string": "Upload image" @@ -3395,14 +3371,14 @@ "context": "header field name, header", "string": "Name" }, - "Np7J92": { - "context": "deactivate local named app", - "string": "Are you sure you want to disable {name}? Your data will be kept until you reactivate the app." - }, "NqxvFh": { "context": "navigator placeholder", "string": "Type Command" }, + "Nsk5WL": { + "context": "link", + "string": "Get support" + }, "NtFVFS": { "context": "weight", "string": "{value} {unit}" @@ -4500,10 +4476,6 @@ "VyzsWZ": { "string": "Default Billing Address" }, - "W+AFZY": { - "context": "button label", - "string": "Deactivate" - }, "W/Es0H": { "string": "Order successfully cancelled" }, @@ -6891,6 +6863,10 @@ "context": "search label", "string": "Search Countries" }, + "pGwvpX": { + "context": "status", + "string": "Deactivated" + }, "pNrF72": { "context": "tab name", "string": "All Vouchers" @@ -7810,10 +7786,6 @@ "context": "section header", "string": "Plugin Information and Status" }, - "w6Gau0": { - "context": "deactivate named app", - "string": "Are you sure you want to disable {name}? Your data will be kept until you reactivate the app. You will be still billed for the app." - }, "w9xgN9": { "context": "see error log label in notification", "string": "See error log" diff --git a/src/apps/components/AppActivateDialog/AppActivateDialog.tsx b/src/apps/components/AppActivateDialog/AppActivateDialog.tsx deleted file mode 100644 index a79e7b706..000000000 --- a/src/apps/components/AppActivateDialog/AppActivateDialog.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import ActionDialog from "@dashboard/components/ActionDialog"; -import { getStringOrPlaceholder } from "@dashboard/misc"; -import { DialogContentText } from "@material-ui/core"; -import { ConfirmButtonTransitionState } from "@saleor/macaw-ui"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; - -export interface AppActivateDialogProps { - confirmButtonState: ConfirmButtonTransitionState; - open: boolean; - name: string; - onClose: () => void; - onConfirm: () => void; -} - -const AppActivateDialog: React.FC = ({ - confirmButtonState, - open, - name, - onClose, - onConfirm, -}) => { - const intl = useIntl(); - - return ( - - - {["", null].includes(name) ? ( - - ) : ( - {getStringOrPlaceholder(name)}, - }} - /> - )} - - - ); -}; -AppActivateDialog.displayName = "AppActivateDialog"; -export default AppActivateDialog; diff --git a/src/apps/components/AppActivateDialog/index.ts b/src/apps/components/AppActivateDialog/index.ts deleted file mode 100644 index 7b7d24553..000000000 --- a/src/apps/components/AppActivateDialog/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./AppActivateDialog"; -export { default } from "./AppActivateDialog"; diff --git a/src/apps/components/AppDeactivateDialog/AppDeactivateDialog.tsx b/src/apps/components/AppDeactivateDialog/AppDeactivateDialog.tsx deleted file mode 100644 index 2a8bdde94..000000000 --- a/src/apps/components/AppDeactivateDialog/AppDeactivateDialog.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import ActionDialog from "@dashboard/components/ActionDialog"; -import { getStringOrPlaceholder } from "@dashboard/misc"; -import { DialogContentText } from "@material-ui/core"; -import { ConfirmButtonTransitionState } from "@saleor/macaw-ui"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; - -import msgs from "./messages"; - -export interface AppDeactivateDialogProps { - confirmButtonState: ConfirmButtonTransitionState; - open: boolean; - name: string; - thirdParty?: boolean; - onClose: () => void; - onConfirm: () => void; -} - -const AppDeactivateDialog: React.FC = ({ - confirmButtonState, - open, - name, - thirdParty = true, - onClose, - onConfirm, -}) => { - const intl = useIntl(); - - return ( - - - {["", null].includes(name) ? ( - - ) : ( - {getStringOrPlaceholder(name)}, - }} - /> - )} - - - ); -}; -AppDeactivateDialog.displayName = "AppDeactivateDialog"; -export default AppDeactivateDialog; diff --git a/src/apps/components/AppDeactivateDialog/index.ts b/src/apps/components/AppDeactivateDialog/index.ts deleted file mode 100644 index d58865cf1..000000000 --- a/src/apps/components/AppDeactivateDialog/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./AppDeactivateDialog"; -export { default } from "./AppDeactivateDialog"; diff --git a/src/apps/components/AppDeactivateDialog/messages.ts b/src/apps/components/AppDeactivateDialog/messages.ts deleted file mode 100644 index bb0157435..000000000 --- a/src/apps/components/AppDeactivateDialog/messages.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { defineMessages } from "react-intl"; - -export default defineMessages({ - deactivateApp: { - id: "73RU3R", - defaultMessage: - "Are you sure you want to disable this app? Your data will be kept until you reactivate the app. You will be still billed for the app.", - description: "deactivate app", - }, - deactivateNamedApp: { - id: "w6Gau0", - defaultMessage: - "Are you sure you want to disable {name}? Your data will be kept until you reactivate the app. You will be still billed for the app.", - description: "deactivate named app", - }, - deactivateLocalApp: { - id: "7O2EsY", - defaultMessage: - "Are you sure you want to disable this app? Your data will be kept until you reactivate the app.", - description: "deactivate local app", - }, - deactivateLocalNamedApp: { - id: "Np7J92", - defaultMessage: - "Are you sure you want to disable {name}? Your data will be kept until you reactivate the app.", - description: "deactivate local named app", - }, -}); diff --git a/src/apps/components/AppDeleteDialog/AppDeleteDialog.tsx b/src/apps/components/AppDeleteDialog/AppDeleteDialog.tsx deleted file mode 100644 index 7d8f1ddf5..000000000 --- a/src/apps/components/AppDeleteDialog/AppDeleteDialog.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import ActionDialog from "@dashboard/components/ActionDialog"; -import { getStringOrPlaceholder } from "@dashboard/misc"; -import { DialogContentText } from "@material-ui/core"; -import { ConfirmButtonTransitionState } from "@saleor/macaw-ui"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; - -export interface AppDeleteDialogProps { - confirmButtonState: ConfirmButtonTransitionState; - open: boolean; - name: string; - onClose: () => void; - onConfirm: () => void; - type: "CUSTOM" | "EXTERNAL"; -} - -const AppDeleteDialog: React.FC = ({ - confirmButtonState, - open, - name, - onClose, - onConfirm, - type, -}) => { - const intl = useIntl(); - - return ( - - - {["", null].includes(name) ? ( - - ) : type === "EXTERNAL" ? ( - {getStringOrPlaceholder(name)}, - }} - /> - ) : ( - {getStringOrPlaceholder(name)}, - }} - /> - )} - - - ); -}; -AppDeleteDialog.displayName = "AppDeleteDialog"; -export default AppDeleteDialog; diff --git a/src/apps/components/AppDeleteDialog/index.ts b/src/apps/components/AppDeleteDialog/index.ts deleted file mode 100644 index 06d0e0693..000000000 --- a/src/apps/components/AppDeleteDialog/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./AppDeleteDialog"; -export { default } from "./AppDeleteDialog"; diff --git a/src/apps/components/AppDetailsPage/AppDetailsPage.tsx b/src/apps/components/AppDetailsPage/AppDetailsPage.tsx deleted file mode 100644 index 2861b0b0e..000000000 --- a/src/apps/components/AppDetailsPage/AppDetailsPage.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { appsListPath } from "@dashboard/apps/urls"; -import { TopNav } from "@dashboard/components/AppLayout/TopNav"; -import CardSpacer from "@dashboard/components/CardSpacer"; -import CardTitle from "@dashboard/components/CardTitle"; -import ExternalLink from "@dashboard/components/ExternalLink"; -import Skeleton from "@dashboard/components/Skeleton"; -import { AppQuery } from "@dashboard/graphql"; -import { buttonMessages } from "@dashboard/intl"; -import { ButtonBase, Card, CardContent, Typography } from "@material-ui/core"; -import { Box, Button } from "@saleor/macaw-ui/next"; -import React from "react"; -import SVG from "react-inlinesvg"; -import { FormattedMessage, useIntl } from "react-intl"; -import ReactMarkdown from "react-markdown"; - -import activateIcon from "../../../../assets/images/activate-icon.svg"; -import deleteIcon from "../../../../assets/images/delete.svg"; -import supportIcon from "../../../../assets/images/support-icon.svg"; -import { useStyles } from "../../styles"; -import DeactivatedText from "../DeactivatedText"; - -export interface AppDetailsPageProps { - loading: boolean; - data: AppQuery["app"]; - navigateToApp: () => void; - onAppActivateOpen: () => void; - onAppDeactivateOpen: () => void; - onAppDeleteOpen: () => void; -} - -export const AppDetailsPage: React.FC = ({ - data, - loading, - navigateToApp, - onAppActivateOpen, - onAppDeactivateOpen, - onAppDeleteOpen, -}) => { - const intl = useIntl(); - const classes = useStyles({}); - - return ( - <> - - {data?.name} {!data?.isActive && } - - } - > - - - - {data ? ( -
- - - - - - - {data?.isActive ? ( - - ) : ( - - )} - - - - - -
- ) : ( - - )} -
- - - - - - {!loading ? ( - - ) : ( - - )} - - - - - - - {!loading ? ( - <> - - - - {!!data?.permissions?.length && ( -
    - {data?.permissions?.map(perm => ( -
  • {perm.name}
  • - ))} -
- )} - - ) : ( - - )} -
-
- - - {data?.dataPrivacyUrl && ( - - - - {!loading ? ( - - - - ) : ( - - )} - - - )} - - - ); -}; - -AppDetailsPage.displayName = "AppDetailsPage"; -export default AppDetailsPage; diff --git a/src/apps/index.tsx b/src/apps/index.tsx index b79496e37..5e8f8e778 100644 --- a/src/apps/index.tsx +++ b/src/apps/index.tsx @@ -7,28 +7,14 @@ import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { WindowTitle } from "../components/WindowTitle"; import { - appDetailsPath, - AppDetailsUrlQueryParams, appInstallPath, AppInstallUrlQueryParams, AppListUrlQueryParams, appsListPath, } from "./urls"; -import AppDetailsView from "./views/AppDetails"; import AppInstallView from "./views/AppInstall"; import AppsListView from "./views/AppsList"; -const AppDetails: React.FC> = ({ - match, -}) => { - const qs = parseQs(location.search.substr(1)); - const params: AppDetailsUrlQueryParams = qs; - - return ( - - ); -}; - const AppInstall: React.FC = props => { const qs = parseQs(location.search.substr(1)); const params: AppInstallUrlQueryParams = qs; @@ -51,7 +37,6 @@ const Component = () => { - diff --git a/src/custom-apps/components/CustomAppListPage/CustomAppListPage.tsx b/src/custom-apps/components/CustomAppListPage/CustomAppListPage.tsx index 7c63d0f54..e5263f6dd 100644 --- a/src/custom-apps/components/CustomAppListPage/CustomAppListPage.tsx +++ b/src/custom-apps/components/CustomAppListPage/CustomAppListPage.tsx @@ -14,8 +14,8 @@ import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; import AppsSkeleton from "../../../apps/components/AppsSkeleton"; -import DeactivatedText from "../../../apps/components/DeactivatedText"; import { useStyles } from "../../../apps/styles"; +import DeactivatedText from "../../../new-apps/components/DeactivatedText"; export interface CustomAppListPageProps { appsList: AppListItemFragment[]; diff --git a/src/custom-apps/views/CustomAppDetails/CustomAppDetails.tsx b/src/custom-apps/views/CustomAppDetails/CustomAppDetails.tsx index 6cb10534a..8bc78326e 100644 --- a/src/custom-apps/views/CustomAppDetails/CustomAppDetails.tsx +++ b/src/custom-apps/views/CustomAppDetails/CustomAppDetails.tsx @@ -1,5 +1,3 @@ -import AppActivateDialog from "@dashboard/apps/components/AppActivateDialog"; -import AppDeactivateDialog from "@dashboard/apps/components/AppDeactivateDialog"; import { appMessages } from "@dashboard/apps/messages"; import NotFoundPage from "@dashboard/components/NotFoundPage"; import { WindowTitle } from "@dashboard/components/WindowTitle"; @@ -26,6 +24,8 @@ import useNotifier from "@dashboard/hooks/useNotifier"; import useShop from "@dashboard/hooks/useShop"; import { commonMessages } from "@dashboard/intl"; import { extractMutationErrors, getStringOrPlaceholder } from "@dashboard/misc"; +import AppActivateDialog from "@dashboard/new-apps/components/AppActivateDialog"; +import AppDeactivateDialog from "@dashboard/new-apps/components/AppDeactivateDialog"; import getAppErrorMessage from "@dashboard/utils/errors/app"; import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers"; import React from "react"; diff --git a/src/custom-apps/views/CustomAppList.tsx b/src/custom-apps/views/CustomAppList.tsx index b418053b8..956d728e7 100644 --- a/src/custom-apps/views/CustomAppList.tsx +++ b/src/custom-apps/views/CustomAppList.tsx @@ -1,5 +1,4 @@ import { useApolloClient } from "@apollo/client"; -import AppDeleteDialog from "@dashboard/apps/components/AppDeleteDialog"; import { EXTENSION_LIST_QUERY } from "@dashboard/apps/queries"; import { WindowTitle } from "@dashboard/components/WindowTitle"; import { @@ -13,6 +12,7 @@ import useNavigator from "@dashboard/hooks/useNavigator"; import useNotifier from "@dashboard/hooks/useNotifier"; import { sectionNames } from "@dashboard/intl"; import { findById } from "@dashboard/misc"; +import AppDeleteDialog from "@dashboard/new-apps/components/AppDeleteDialog"; import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers"; import { mapEdgesToItems } from "@dashboard/utils/maps"; import React from "react"; diff --git a/src/intl.ts b/src/intl.ts index c76bac6d8..93db79232 100644 --- a/src/intl.ts +++ b/src/intl.ts @@ -562,6 +562,11 @@ export const commonStatusMessages = defineMessages({ id: "tthToS", defaultMessage: "Disabled", }, + deactivated: { + id: "pGwvpX", + defaultMessage: "Deactivated", + description: "status", + }, }); export const orderStatusMessages = defineMessages({ diff --git a/src/new-apps/components/AppDetailsPage/AboutCard.tsx b/src/new-apps/components/AppDetailsPage/AboutCard.tsx new file mode 100644 index 000000000..e61d91a84 --- /dev/null +++ b/src/new-apps/components/AppDetailsPage/AboutCard.tsx @@ -0,0 +1,27 @@ +import CardTitle from "@dashboard/components/CardTitle"; +import Skeleton from "@dashboard/components/Skeleton"; +import { Card, CardContent } from "@material-ui/core"; +import React from "react"; +import { useIntl } from "react-intl"; +import ReactMarkdown from "react-markdown"; + +import messages from "./messages"; + +interface AboutCardProps { + aboutApp?: string | null; + loading: boolean; +} + +const AboutCard: React.FC = ({ aboutApp, loading }) => { + const intl = useIntl(); + + return ( + + + + {!loading ? : } + + + ); +}; +export default AboutCard; diff --git a/src/apps/components/AppDetailsPage/AppDetailsPage.stories.tsx b/src/new-apps/components/AppDetailsPage/AppDetailsPage.stories.tsx similarity index 100% rename from src/apps/components/AppDetailsPage/AppDetailsPage.stories.tsx rename to src/new-apps/components/AppDetailsPage/AppDetailsPage.stories.tsx diff --git a/src/new-apps/components/AppDetailsPage/AppDetailsPage.test.tsx b/src/new-apps/components/AppDetailsPage/AppDetailsPage.test.tsx new file mode 100644 index 000000000..ebd1e855a --- /dev/null +++ b/src/new-apps/components/AppDetailsPage/AppDetailsPage.test.tsx @@ -0,0 +1,79 @@ +import { render } from "@testing-library/react"; +import React from "react"; + +import { appDetails } from "../../fixtures"; +import AppDetailsPage from "./AppDetailsPage"; + +const mockHeader = jest.fn(); +jest.mock("./Header", () => props => { + mockHeader(props); + return <>; +}); + +const mockAboutCard = jest.fn(); +jest.mock("./AboutCard", () => props => { + mockAboutCard(props); + return <>; +}); + +const mockPermissionsCard = jest.fn(); +jest.mock("./PermissionsCard", () => props => { + mockPermissionsCard(props); + return <>; +}); + +const mockDataPrivacyCard = jest.fn(); +jest.mock("./DataPrivacyCard", () => props => { + mockDataPrivacyCard(props); + return <>; +}); + +beforeEach(() => { + mockHeader.mockClear(); + mockAboutCard.mockClear(); + mockPermissionsCard.mockClear(); + mockDataPrivacyCard.mockClear(); +}); + +describe("Apps AppDetailsPage", () => { + it("displays app details when app data passed", () => { + // Arrange + const navigateToApp = jest.fn(); + const onAppActivateOpen = jest.fn(); + const onAppDeactivateOpen = jest.fn(); + const onAppDeleteOpen = jest.fn(); + + // Act + render( + , + ); + + // Assert + expect(mockHeader).toHaveBeenCalledWith({ + data: appDetails, + navigateToApp, + onAppActivateOpen, + onAppDeactivateOpen, + onAppDeleteOpen, + }); + expect(mockAboutCard).toHaveBeenCalledWith({ + aboutApp: appDetails.aboutApp, + loading: false, + }); + expect(mockPermissionsCard).toHaveBeenCalledWith({ + permissions: appDetails.permissions, + loading: false, + }); + expect(mockDataPrivacyCard).toHaveBeenCalledWith({ + dataPrivacyUrl: appDetails.dataPrivacyUrl, + loading: false, + }); + }); +}); diff --git a/src/new-apps/components/AppDetailsPage/AppDetailsPage.tsx b/src/new-apps/components/AppDetailsPage/AppDetailsPage.tsx new file mode 100644 index 000000000..7b7ad8fca --- /dev/null +++ b/src/new-apps/components/AppDetailsPage/AppDetailsPage.tsx @@ -0,0 +1,45 @@ +import CardSpacer from "@dashboard/components/CardSpacer"; +import { AppQuery } from "@dashboard/graphql"; +import React from "react"; + +import AboutCard from "./AboutCard"; +import DataPrivacyCard from "./DataPrivacyCard"; +import Header from "./Header"; +import PermissionsCard from "./PermissionsCard"; + +export interface AppDetailsPageProps { + loading: boolean; + data: AppQuery["app"]; + navigateToApp: () => void; + onAppActivateOpen: () => void; + onAppDeactivateOpen: () => void; + onAppDeleteOpen: () => void; +} + +export const AppDetailsPage: React.FC = ({ + data, + loading, + navigateToApp, + onAppActivateOpen, + onAppDeactivateOpen, + onAppDeleteOpen, +}) => ( + <> +
+ + + + + + + +); + +AppDetailsPage.displayName = "AppDetailsPage"; +export default AppDetailsPage; diff --git a/src/new-apps/components/AppDetailsPage/DataPrivacyCard.tsx b/src/new-apps/components/AppDetailsPage/DataPrivacyCard.tsx new file mode 100644 index 000000000..4156d0a41 --- /dev/null +++ b/src/new-apps/components/AppDetailsPage/DataPrivacyCard.tsx @@ -0,0 +1,46 @@ +import CardTitle from "@dashboard/components/CardTitle"; +import ExternalLink from "@dashboard/components/ExternalLink"; +import Skeleton from "@dashboard/components/Skeleton"; +import { Card, CardContent } from "@material-ui/core"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import messages from "./messages"; +import { useStyles } from "./styles"; + +interface DataPrivacyCardProps { + dataPrivacyUrl?: string | null; + loading: boolean; +} + +const DataPrivacyCard: React.FC = ({ + dataPrivacyUrl, + loading, +}) => { + const classes = useStyles(); + const intl = useIntl(); + + if (!dataPrivacyUrl && !loading) { + return null; + } + + return ( + + + + {!loading ? ( + + + + ) : ( + + )} + + + ); +}; +export default DataPrivacyCard; diff --git a/src/new-apps/components/AppDetailsPage/Header.test.tsx b/src/new-apps/components/AppDetailsPage/Header.test.tsx new file mode 100644 index 000000000..338d7d797 --- /dev/null +++ b/src/new-apps/components/AppDetailsPage/Header.test.tsx @@ -0,0 +1,112 @@ +import { appDetails } from "@dashboard/new-apps/fixtures"; +import { render } from "@testing-library/react"; +import React from "react"; + +import Header from "./Header"; + +const mockHeaderOptions = jest.fn(); +const mockTopNav = jest.fn(); +const mockButton = jest.fn(); + +jest.mock("@dashboard/components/AppLayout/TopNav", () => ({ + TopNav: props => { + mockTopNav(props); + return <>{props.children}; + }, +})); +jest.mock("@saleor/macaw-ui/next", () => ({ + Button: props => { + mockButton(props); + return <>{props.children}; + }, +})); +jest.mock("../DeactivatedText", () => () => "deactivated"); +jest.mock("react-intl", () => ({ + useIntl: jest.fn(() => ({ + formatMessage: jest.fn(x => x.defaultMessage), + })), + defineMessages: jest.fn(x => x), + FormattedMessage: ({ defaultMessage }) => <>{defaultMessage}, +})); +jest.mock("./HeaderOptions", () => props => { + mockHeaderOptions(props); + return <>; +}); + +beforeEach(() => { + mockHeaderOptions.mockClear(); + mockTopNav.mockClear(); + mockButton.mockClear(); +}); + +describe("Apps AppDetailsPage Header", () => { + it("displays app details options when active app data passed", () => { + // Arrange + const navigateToApp = jest.fn(); + const onAppActivateOpen = jest.fn(); + const onAppDeactivateOpen = jest.fn(); + const onAppDeleteOpen = jest.fn(); + + // Act + render( +
, + ); + const title = render(mockTopNav.mock.calls[0][0].title); + + // Assert + expect(mockHeaderOptions).toHaveBeenCalledWith({ + data: appDetails, + onAppActivateOpen, + onAppDeactivateOpen, + onAppDeleteOpen, + }); + expect(mockButton).toHaveBeenCalledWith( + expect.objectContaining({ + onClick: navigateToApp, + }), + ); + expect(mockTopNav).toHaveBeenCalled(); + expect(title.container).toHaveTextContent(appDetails.name as string); + }); + + it("displays app details options when inactive app data passed", () => { + // Arrange + const navigateToApp = jest.fn(); + const onAppActivateOpen = jest.fn(); + const onAppDeactivateOpen = jest.fn(); + const onAppDeleteOpen = jest.fn(); + + // Act + render( +
, + ); + const title = render(mockTopNav.mock.calls[0][0].title); + + // Assert + expect(mockHeaderOptions).toHaveBeenCalledWith({ + data: { ...appDetails, isActive: false }, + onAppActivateOpen, + onAppDeactivateOpen, + onAppDeleteOpen, + }); + expect(mockButton).toHaveBeenCalledWith( + expect.objectContaining({ + onClick: navigateToApp, + }), + ); + expect(mockTopNav).toHaveBeenCalled(); + expect(title.container).toHaveTextContent(`${appDetails.name} deactivated`); + }); +}); diff --git a/src/new-apps/components/AppDetailsPage/Header.tsx b/src/new-apps/components/AppDetailsPage/Header.tsx new file mode 100644 index 000000000..d4ee25a6c --- /dev/null +++ b/src/new-apps/components/AppDetailsPage/Header.tsx @@ -0,0 +1,48 @@ +import { TopNav } from "@dashboard/components/AppLayout/TopNav"; +import { AppQuery } from "@dashboard/graphql"; +import { AppPaths } from "@dashboard/new-apps/urls"; +import { Button } from "@saleor/macaw-ui/next"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import DeactivatedText from "../DeactivatedText"; +import HeaderOptions from "./HeaderOptions"; +import messages from "./messages"; + +interface HeaderProps { + data: AppQuery["app"]; + navigateToApp: () => void; + onAppActivateOpen: () => void; + onAppDeactivateOpen: () => void; + onAppDeleteOpen: () => void; +} + +const Header: React.FC = ({ + data, + navigateToApp, + onAppActivateOpen, + onAppDeactivateOpen, + onAppDeleteOpen, +}) => ( + <> + + {data?.name} {!data?.isActive && } + + } + > + + + + +); +export default Header; diff --git a/src/new-apps/components/AppDetailsPage/HeaderOptions.tsx b/src/new-apps/components/AppDetailsPage/HeaderOptions.tsx new file mode 100644 index 000000000..d14e83f5d --- /dev/null +++ b/src/new-apps/components/AppDetailsPage/HeaderOptions.tsx @@ -0,0 +1,77 @@ +import ExternalLink from "@dashboard/components/ExternalLink"; +import Skeleton from "@dashboard/components/Skeleton"; +import { AppQuery } from "@dashboard/graphql"; +import { buttonMessages } from "@dashboard/intl"; +import { ButtonBase } from "@material-ui/core"; +import { Box } from "@saleor/macaw-ui/next"; +import React from "react"; +import SVG from "react-inlinesvg"; +import { FormattedMessage } from "react-intl"; + +import activateIcon from "../../../../assets/images/activate-icon.svg"; +import deleteIcon from "../../../../assets/images/delete.svg"; +import supportIcon from "../../../../assets/images/support-icon.svg"; +import messages from "./messages"; +import { useStyles } from "./styles"; + +interface HeaderOptionsProps { + data: AppQuery["app"]; + onAppActivateOpen: () => void; + onAppDeactivateOpen: () => void; + onAppDeleteOpen: () => void; +} + +const HeaderOptions: React.FC = ({ + data, + onAppActivateOpen, + onAppDeactivateOpen, + onAppDeleteOpen, +}) => { + const classes = useStyles(); + + if (!data) { + return ( + + +
+ + ); + } + + return ( + +
+ + + + + + + {data?.isActive ? ( + + ) : ( + + )} + + + + + +
+
+ + ); +}; +export default HeaderOptions; diff --git a/src/new-apps/components/AppDetailsPage/PermissionsCard.tsx b/src/new-apps/components/AppDetailsPage/PermissionsCard.tsx new file mode 100644 index 000000000..b294ba4f9 --- /dev/null +++ b/src/new-apps/components/AppDetailsPage/PermissionsCard.tsx @@ -0,0 +1,47 @@ +import CardTitle from "@dashboard/components/CardTitle"; +import Skeleton from "@dashboard/components/Skeleton"; +import { AppQuery } from "@dashboard/graphql"; +import { Card, CardContent, Typography } from "@material-ui/core"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import messages from "./messages"; +import { useStyles } from "./styles"; + +interface PermissionsCardProps { + permissions?: AppQuery["app"]["permissions"]; + loading: boolean; +} + +const PermissionsCard: React.FC = ({ + permissions, + loading, +}) => { + const classes = useStyles(); + const intl = useIntl(); + + return ( + + + + {!loading ? ( + <> + + + + {!!permissions?.length && ( +
    + {permissions?.map(perm => ( +
  • {perm.name}
  • + ))} +
+ )} + + ) : ( + + )} +
+
+ ); +}; +export default PermissionsCard; diff --git a/src/apps/components/AppDetailsPage/index.ts b/src/new-apps/components/AppDetailsPage/index.ts similarity index 100% rename from src/apps/components/AppDetailsPage/index.ts rename to src/new-apps/components/AppDetailsPage/index.ts diff --git a/src/new-apps/components/AppDetailsPage/messages.ts b/src/new-apps/components/AppDetailsPage/messages.ts new file mode 100644 index 000000000..b5e6fc448 --- /dev/null +++ b/src/new-apps/components/AppDetailsPage/messages.ts @@ -0,0 +1,39 @@ +import { defineMessages } from "react-intl"; + +export default defineMessages({ + openApp: { + id: "+FWlRD", + defaultMessage: "Open app", + description: "button", + }, + supportLink: { + id: "Nsk5WL", + defaultMessage: "Get support", + description: "link", + }, + aboutAppTitle: { + id: "jDIRQV", + defaultMessage: "About this app", + description: "section header", + }, + appPermissionsTitle: { + id: "VsGcdP", + defaultMessage: "App permissions", + description: "section header", + }, + appPermissionsDescription: { + id: "7oQUMG", + defaultMessage: "This app has permissions to:", + description: "apps about permissions", + }, + dataPrivacyTitle: { + id: "a55zOn", + defaultMessage: "Data privacy", + description: "section header", + }, + dataPrivacyDescription: { + id: "Go50v2", + defaultMessage: "View this app’s privacy policy", + description: "app privacy policy link", + }, +}); diff --git a/src/new-apps/components/AppDetailsPage/styles.ts b/src/new-apps/components/AppDetailsPage/styles.ts new file mode 100644 index 000000000..c850307fa --- /dev/null +++ b/src/new-apps/components/AppDetailsPage/styles.ts @@ -0,0 +1,65 @@ +import { makeStyles } from "@saleor/macaw-ui"; + +export const useStyles = makeStyles( + theme => ({ + appHeader: { + marginBottom: theme.spacing(3), + }, + appHeaderLinks: { + "& img": { + marginRight: theme.spacing(1), + }, + alignItems: "center", + display: "flex", + padding: theme.spacing(2, 0), + }, + headerLinkContainer: { + "& svg": { + marginRight: theme.spacing(), + }, + "& span": { + fontWeight: 500, + }, + alignItems: "center", + color: theme.palette.text.primary, + display: "flex", + fontSize: theme.spacing(2), + fontWeight: 500, + lineHeight: 1.2, + marginRight: theme.spacing(3), + padding: 0, + textTransform: "none", + }, + hr: { + border: "none", + borderTop: `1px solid ${theme.palette.divider}`, + height: 0, + marginBottom: 0, + marginTop: 0, + width: "100%", + }, + linkContainer: { + fontWeight: 500, + marginTop: theme.spacing(1.5), + }, + marketplaceContent: { + "& button": { + marginTop: theme.spacing(1), + }, + "&:last-child": { + padding: theme.spacing(2, 3, 2, 3), + }, + padding: theme.spacing(1), + }, + permissionsContainer: { + "& li": { + "&:last-child": { + marginBottom: 0, + }, + marginBottom: theme.spacing(1), + }, + paddingLeft: theme.spacing(2), + }, + }), + { name: "AppDetailsPage" }, +); diff --git a/src/apps/components/DeactivatedText/DeactivatedText.tsx b/src/new-apps/components/DeactivatedText/DeactivatedText.tsx similarity index 73% rename from src/apps/components/DeactivatedText/DeactivatedText.tsx rename to src/new-apps/components/DeactivatedText/DeactivatedText.tsx index d00597b1f..8dfe60762 100644 --- a/src/apps/components/DeactivatedText/DeactivatedText.tsx +++ b/src/new-apps/components/DeactivatedText/DeactivatedText.tsx @@ -1,3 +1,4 @@ +import { commonStatusMessages } from "@dashboard/intl"; import { Typography } from "@material-ui/core"; import React from "react"; import { FormattedMessage } from "react-intl"; @@ -8,11 +9,7 @@ export const DeactivatedText: React.FC<{}> = () => { const classes = useStyles({}); return ( - + ); }; diff --git a/src/apps/components/DeactivatedText/index.ts b/src/new-apps/components/DeactivatedText/index.ts similarity index 100% rename from src/apps/components/DeactivatedText/index.ts rename to src/new-apps/components/DeactivatedText/index.ts diff --git a/src/apps/components/DeactivatedText/styles.ts b/src/new-apps/components/DeactivatedText/styles.ts similarity index 100% rename from src/apps/components/DeactivatedText/styles.ts rename to src/new-apps/components/DeactivatedText/styles.ts diff --git a/src/new-apps/index.tsx b/src/new-apps/index.tsx index 7fb37bae3..47aee34e2 100644 --- a/src/new-apps/index.tsx +++ b/src/new-apps/index.tsx @@ -2,7 +2,6 @@ import { AppDetailsUrlQueryParams, AppInstallUrlQueryParams, } from "@dashboard/apps/urls"; -import AppDetailsView from "@dashboard/apps/views/AppDetails"; import AppInstallView from "@dashboard/apps/views/AppInstall"; import { sectionNames } from "@dashboard/intl"; import { parse as parseQs } from "qs"; @@ -12,6 +11,7 @@ import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { WindowTitle } from "../components/WindowTitle"; import { AppListUrlQueryParams, AppPaths } from "./urls"; +import AppDetailsView from "./views/AppDetails"; import AppListView from "./views/AppList"; import AppView from "./views/AppView"; diff --git a/src/new-apps/messages.ts b/src/new-apps/messages.ts index 5f0af8faa..4f1bd8a3a 100644 --- a/src/new-apps/messages.ts +++ b/src/new-apps/messages.ts @@ -34,6 +34,16 @@ export const appMessages = defineMessages({ defaultMessage: "Failed to fetch app settings", description: "app settings error", }, + appActivated: { + id: "D/+84n", + defaultMessage: "App activated", + description: "snackbar text", + }, + appDeactivated: { + id: "USO8PB", + defaultMessage: "App deactivated", + description: "snackbar text", + }, }); export const appInstallationStatusMessages = defineMessages({ diff --git a/src/apps/views/AppDetails/AppDetails.tsx b/src/new-apps/views/AppDetails/AppDetails.tsx similarity index 91% rename from src/apps/views/AppDetails/AppDetails.tsx rename to src/new-apps/views/AppDetails/AppDetails.tsx index c7e3abdbd..009f127f6 100644 --- a/src/apps/views/AppDetails/AppDetails.tsx +++ b/src/new-apps/views/AppDetails/AppDetails.tsx @@ -1,6 +1,4 @@ import { useApolloClient } from "@apollo/client"; -import AppDeleteDialog from "@dashboard/apps/components/AppDeleteDialog"; -import { appMessages } from "@dashboard/apps/messages"; import { EXTENSION_LIST_QUERY } from "@dashboard/apps/queries"; import NotFoundPage from "@dashboard/components/NotFoundPage"; import { @@ -11,6 +9,8 @@ import { } from "@dashboard/graphql"; import useNavigator from "@dashboard/hooks/useNavigator"; import useNotifier from "@dashboard/hooks/useNotifier"; +import AppDeleteDialog from "@dashboard/new-apps/components/AppDeleteDialog"; +import { appMessages } from "@dashboard/new-apps/messages"; import getAppErrorMessage from "@dashboard/utils/errors/app"; import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers"; import React from "react"; @@ -20,11 +20,10 @@ import AppActivateDialog from "../../components/AppActivateDialog"; import AppDeactivateDialog from "../../components/AppDeactivateDialog"; import AppDetailsPage from "../../components/AppDetailsPage"; import { - appDetailsUrl, AppDetailsUrlDialog, AppDetailsUrlQueryParams, - appsListPath, - appUrl, + AppPaths, + AppUrls, } from "../../urls"; import { messages } from "./messages"; @@ -110,7 +109,7 @@ export const AppDetails: React.FC = ({ id, params }) => { refetch(); refetchExtensionList(); removeAppNotify(); - navigate(appsListPath); + navigate(AppPaths.appListPath); } }, }); @@ -118,14 +117,14 @@ export const AppDetails: React.FC = ({ id, params }) => { const [openModal, closeModal] = createDialogActionHandlers< AppDetailsUrlDialog, AppDetailsUrlQueryParams - >(navigate, params => appDetailsUrl(id, params), params); + >(navigate, params => AppUrls.resolveAppDetailsUrl(id, params), params); const handleActivateConfirm = () => activateApp(mutationOpts); const handleDeactivateConfirm = () => deactivateApp(mutationOpts); const handleRemoveConfirm = () => deleteApp(mutationOpts); if (!appExists) { - return ; + return ; } return ( @@ -155,7 +154,7 @@ export const AppDetails: React.FC = ({ id, params }) => { navigate(appUrl(id))} + navigateToApp={() => navigate(AppUrls.resolveAppUrl(id))} onAppActivateOpen={() => openModal("app-activate")} onAppDeactivateOpen={() => openModal("app-deactivate")} onAppDeleteOpen={() => openModal("app-delete")} diff --git a/src/apps/views/AppDetails/index.ts b/src/new-apps/views/AppDetails/index.ts similarity index 100% rename from src/apps/views/AppDetails/index.ts rename to src/new-apps/views/AppDetails/index.ts diff --git a/src/apps/views/AppDetails/messages.ts b/src/new-apps/views/AppDetails/messages.ts similarity index 100% rename from src/apps/views/AppDetails/messages.ts rename to src/new-apps/views/AppDetails/messages.ts