Migrate app details view (settings view) to new-apps (#3167)
* Migrate app details view to new-apps * Fix data privacy card
This commit is contained in:
parent
3c082b3386
commit
dfb5f167d4
34 changed files with 627 additions and 497 deletions
|
@ -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"
|
||||
|
|
|
@ -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<AppActivateDialogProps> = ({
|
||||
confirmButtonState,
|
||||
open,
|
||||
name,
|
||||
onClose,
|
||||
onConfirm,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
confirmButtonLabel={intl.formatMessage({
|
||||
id: "D3E2b5",
|
||||
defaultMessage: "Activate",
|
||||
description: "button label",
|
||||
})}
|
||||
confirmButtonState={confirmButtonState}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={intl.formatMessage({
|
||||
id: "YHNozE",
|
||||
defaultMessage: "Activate App",
|
||||
description: "dialog header",
|
||||
})}
|
||||
variant="default"
|
||||
>
|
||||
<DialogContentText>
|
||||
{["", null].includes(name) ? (
|
||||
<FormattedMessage
|
||||
id="Q47ovw"
|
||||
defaultMessage="Are you sure you want to activate this app? Activating will start gathering events."
|
||||
description="activate app"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="JbUg2b"
|
||||
defaultMessage="Are you sure you want to activate {name}? Activating will start gathering events."
|
||||
description="activate app"
|
||||
values={{
|
||||
name: <strong>{getStringOrPlaceholder(name)}</strong>,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
AppActivateDialog.displayName = "AppActivateDialog";
|
||||
export default AppActivateDialog;
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./AppActivateDialog";
|
||||
export { default } from "./AppActivateDialog";
|
|
@ -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<AppDeactivateDialogProps> = ({
|
||||
confirmButtonState,
|
||||
open,
|
||||
name,
|
||||
thirdParty = true,
|
||||
onClose,
|
||||
onConfirm,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
confirmButtonLabel={intl.formatMessage({
|
||||
id: "W+AFZY",
|
||||
defaultMessage: "Deactivate",
|
||||
description: "button label",
|
||||
})}
|
||||
confirmButtonState={confirmButtonState}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={intl.formatMessage({
|
||||
id: "yMi8I8",
|
||||
defaultMessage: "Dectivate App",
|
||||
description: "dialog header",
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
{["", null].includes(name) ? (
|
||||
<FormattedMessage
|
||||
{...(thirdParty ? msgs.deactivateApp : msgs.deactivateLocalApp)}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
{...(thirdParty
|
||||
? msgs.deactivateNamedApp
|
||||
: msgs.deactivateLocalNamedApp)}
|
||||
values={{
|
||||
name: <strong>{getStringOrPlaceholder(name)}</strong>,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
AppDeactivateDialog.displayName = "AppDeactivateDialog";
|
||||
export default AppDeactivateDialog;
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./AppDeactivateDialog";
|
||||
export { default } from "./AppDeactivateDialog";
|
|
@ -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",
|
||||
},
|
||||
});
|
|
@ -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<AppDeleteDialogProps> = ({
|
||||
confirmButtonState,
|
||||
open,
|
||||
name,
|
||||
onClose,
|
||||
onConfirm,
|
||||
type,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
confirmButtonState={confirmButtonState}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={intl.formatMessage({
|
||||
id: "zQX6xO",
|
||||
defaultMessage: "Delete App",
|
||||
description: "dialog header",
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
{["", null].includes(name) ? (
|
||||
<FormattedMessage
|
||||
id="6hLZNA"
|
||||
defaultMessage="Are you sure you want to delete this app?"
|
||||
description="delete app"
|
||||
/>
|
||||
) : type === "EXTERNAL" ? (
|
||||
<FormattedMessage
|
||||
id="EWD/wU"
|
||||
defaultMessage="Deleting {name}, you will remove installation of the app. If you are paying for app subscription, remember to unsubscribe from the app in Saleor Marketplace. Are you sure you want to delete the app?"
|
||||
description="delete app"
|
||||
values={{
|
||||
name: <strong>{getStringOrPlaceholder(name)}</strong>,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="LtqrM8"
|
||||
defaultMessage="Deleting {name}, you will delete all the data and webhooks regarding this app. Are you sure you want to do that?"
|
||||
description="delete custom app"
|
||||
values={{
|
||||
name: <strong>{getStringOrPlaceholder(name)}</strong>,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
AppDeleteDialog.displayName = "AppDeleteDialog";
|
||||
export default AppDeleteDialog;
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./AppDeleteDialog";
|
||||
export { default } from "./AppDeleteDialog";
|
|
@ -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<AppDetailsPageProps> = ({
|
||||
data,
|
||||
loading,
|
||||
navigateToApp,
|
||||
onAppActivateOpen,
|
||||
onAppDeactivateOpen,
|
||||
onAppDeleteOpen,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopNav
|
||||
href={appsListPath}
|
||||
title={
|
||||
<>
|
||||
{data?.name} {!data?.isActive && <DeactivatedText />}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Button onClick={navigateToApp} variant="primary" data-tc="open-app">
|
||||
<FormattedMessage
|
||||
id="HtfL5/"
|
||||
defaultMessage="Open App"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
</TopNav>
|
||||
<Box marginX={10}>
|
||||
{data ? (
|
||||
<div className={classes.appHeaderLinks}>
|
||||
<ExternalLink
|
||||
className={classes.headerLinkContainer}
|
||||
href={data.supportUrl || ""}
|
||||
target="_blank"
|
||||
>
|
||||
<SVG src={supportIcon} />
|
||||
<FormattedMessage
|
||||
id="Gjb6eq"
|
||||
defaultMessage="Get Support"
|
||||
description="link"
|
||||
/>
|
||||
</ExternalLink>
|
||||
<ButtonBase
|
||||
className={classes.headerLinkContainer}
|
||||
disableRipple
|
||||
onClick={data.isActive ? onAppDeactivateOpen : onAppActivateOpen}
|
||||
>
|
||||
<SVG src={activateIcon} />
|
||||
{data?.isActive ? (
|
||||
<FormattedMessage {...buttonMessages.deactivate} />
|
||||
) : (
|
||||
<FormattedMessage {...buttonMessages.activate} />
|
||||
)}
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
className={classes.headerLinkContainer}
|
||||
disableRipple
|
||||
onClick={onAppDeleteOpen}
|
||||
>
|
||||
<SVG src={deleteIcon} />
|
||||
<FormattedMessage {...buttonMessages.delete} />
|
||||
</ButtonBase>
|
||||
</div>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
<div className={classes.hr} />
|
||||
</Box>
|
||||
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
id: "jDIRQV",
|
||||
defaultMessage: "About this app",
|
||||
description: "section header",
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
{!loading ? (
|
||||
<ReactMarkdown source={data?.aboutApp ?? ""} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<CardSpacer />
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
id: "VsGcdP",
|
||||
defaultMessage: "App permissions",
|
||||
description: "section header",
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
id="7oQUMG"
|
||||
defaultMessage="This app has permissions to:"
|
||||
description="apps about permissions"
|
||||
/>
|
||||
</Typography>
|
||||
{!!data?.permissions?.length && (
|
||||
<ul className={classes.permissionsContainer}>
|
||||
{data?.permissions?.map(perm => (
|
||||
<li key={perm.code}>{perm.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<CardSpacer />
|
||||
|
||||
{data?.dataPrivacyUrl && (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
id: "a55zOn",
|
||||
defaultMessage: "Data privacy",
|
||||
description: "section header",
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
{!loading ? (
|
||||
<ExternalLink
|
||||
className={classes.linkContainer}
|
||||
href={data.dataPrivacyUrl}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="Go50v2"
|
||||
defaultMessage="View this app’s privacy policy"
|
||||
description="app privacy policy link"
|
||||
/>
|
||||
</ExternalLink>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
<CardSpacer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
AppDetailsPage.displayName = "AppDetailsPage";
|
||||
export default AppDetailsPage;
|
|
@ -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<RouteComponentProps<{ id: string }>> = ({
|
||||
match,
|
||||
}) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: AppDetailsUrlQueryParams = qs;
|
||||
|
||||
return (
|
||||
<AppDetailsView id={decodeURIComponent(match.params.id)} params={params} />
|
||||
);
|
||||
};
|
||||
|
||||
const AppInstall: React.FC<RouteComponentProps> = props => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: AppInstallUrlQueryParams = qs;
|
||||
|
@ -51,7 +37,6 @@ const Component = () => {
|
|||
<Switch>
|
||||
<Route exact path={appsListPath} component={AppsList} />
|
||||
<Route exact path={appInstallPath} component={AppInstall} />
|
||||
<Route exact path={appDetailsPath(":id")} component={AppDetails} />
|
||||
<WebhooksRoutes />
|
||||
</Switch>
|
||||
</>
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -562,6 +562,11 @@ export const commonStatusMessages = defineMessages({
|
|||
id: "tthToS",
|
||||
defaultMessage: "Disabled",
|
||||
},
|
||||
deactivated: {
|
||||
id: "pGwvpX",
|
||||
defaultMessage: "Deactivated",
|
||||
description: "status",
|
||||
},
|
||||
});
|
||||
|
||||
export const orderStatusMessages = defineMessages({
|
||||
|
|
27
src/new-apps/components/AppDetailsPage/AboutCard.tsx
Normal file
27
src/new-apps/components/AppDetailsPage/AboutCard.tsx
Normal file
|
@ -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<AboutCardProps> = ({ aboutApp, loading }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle title={intl.formatMessage(messages.aboutAppTitle)} />
|
||||
<CardContent>
|
||||
{!loading ? <ReactMarkdown source={aboutApp ?? ""} /> : <Skeleton />}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default AboutCard;
|
|
@ -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(
|
||||
<AppDetailsPage
|
||||
data={appDetails}
|
||||
loading={false}
|
||||
navigateToApp={navigateToApp}
|
||||
onAppActivateOpen={onAppActivateOpen}
|
||||
onAppDeactivateOpen={onAppDeactivateOpen}
|
||||
onAppDeleteOpen={onAppDeleteOpen}
|
||||
/>,
|
||||
);
|
||||
|
||||
// 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,
|
||||
});
|
||||
});
|
||||
});
|
45
src/new-apps/components/AppDetailsPage/AppDetailsPage.tsx
Normal file
45
src/new-apps/components/AppDetailsPage/AppDetailsPage.tsx
Normal file
|
@ -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<AppDetailsPageProps> = ({
|
||||
data,
|
||||
loading,
|
||||
navigateToApp,
|
||||
onAppActivateOpen,
|
||||
onAppDeactivateOpen,
|
||||
onAppDeleteOpen,
|
||||
}) => (
|
||||
<>
|
||||
<Header
|
||||
data={data}
|
||||
navigateToApp={navigateToApp}
|
||||
onAppActivateOpen={onAppActivateOpen}
|
||||
onAppDeactivateOpen={onAppDeactivateOpen}
|
||||
onAppDeleteOpen={onAppDeleteOpen}
|
||||
/>
|
||||
<AboutCard aboutApp={data?.aboutApp} loading={loading} />
|
||||
<CardSpacer />
|
||||
<PermissionsCard permissions={data?.permissions} loading={loading} />
|
||||
<CardSpacer />
|
||||
<DataPrivacyCard dataPrivacyUrl={data?.dataPrivacyUrl} loading={loading} />
|
||||
<CardSpacer />
|
||||
</>
|
||||
);
|
||||
|
||||
AppDetailsPage.displayName = "AppDetailsPage";
|
||||
export default AppDetailsPage;
|
46
src/new-apps/components/AppDetailsPage/DataPrivacyCard.tsx
Normal file
46
src/new-apps/components/AppDetailsPage/DataPrivacyCard.tsx
Normal file
|
@ -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<DataPrivacyCardProps> = ({
|
||||
dataPrivacyUrl,
|
||||
loading,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const intl = useIntl();
|
||||
|
||||
if (!dataPrivacyUrl && !loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle title={intl.formatMessage(messages.dataPrivacyTitle)} />
|
||||
<CardContent>
|
||||
{!loading ? (
|
||||
<ExternalLink
|
||||
className={classes.linkContainer}
|
||||
href={dataPrivacyUrl}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage {...messages.dataPrivacyDescription} />
|
||||
</ExternalLink>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default DataPrivacyCard;
|
112
src/new-apps/components/AppDetailsPage/Header.test.tsx
Normal file
112
src/new-apps/components/AppDetailsPage/Header.test.tsx
Normal file
|
@ -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(
|
||||
<Header
|
||||
data={appDetails}
|
||||
navigateToApp={navigateToApp}
|
||||
onAppActivateOpen={onAppActivateOpen}
|
||||
onAppDeactivateOpen={onAppDeactivateOpen}
|
||||
onAppDeleteOpen={onAppDeleteOpen}
|
||||
/>,
|
||||
);
|
||||
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(
|
||||
<Header
|
||||
data={{ ...appDetails, isActive: false }}
|
||||
navigateToApp={navigateToApp}
|
||||
onAppActivateOpen={onAppActivateOpen}
|
||||
onAppDeactivateOpen={onAppDeactivateOpen}
|
||||
onAppDeleteOpen={onAppDeleteOpen}
|
||||
/>,
|
||||
);
|
||||
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`);
|
||||
});
|
||||
});
|
48
src/new-apps/components/AppDetailsPage/Header.tsx
Normal file
48
src/new-apps/components/AppDetailsPage/Header.tsx
Normal file
|
@ -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<HeaderProps> = ({
|
||||
data,
|
||||
navigateToApp,
|
||||
onAppActivateOpen,
|
||||
onAppDeactivateOpen,
|
||||
onAppDeleteOpen,
|
||||
}) => (
|
||||
<>
|
||||
<TopNav
|
||||
href={AppPaths.appListPath}
|
||||
title={
|
||||
<>
|
||||
{data?.name} {!data?.isActive && <DeactivatedText />}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Button onClick={navigateToApp} variant="primary" data-tc="open-app">
|
||||
<FormattedMessage {...messages.openApp} />
|
||||
</Button>
|
||||
</TopNav>
|
||||
<HeaderOptions
|
||||
data={data}
|
||||
onAppActivateOpen={onAppActivateOpen}
|
||||
onAppDeactivateOpen={onAppDeactivateOpen}
|
||||
onAppDeleteOpen={onAppDeleteOpen}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
export default Header;
|
77
src/new-apps/components/AppDetailsPage/HeaderOptions.tsx
Normal file
77
src/new-apps/components/AppDetailsPage/HeaderOptions.tsx
Normal file
|
@ -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<HeaderOptionsProps> = ({
|
||||
data,
|
||||
onAppActivateOpen,
|
||||
onAppDeactivateOpen,
|
||||
onAppDeleteOpen,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<Box marginX={10}>
|
||||
<Skeleton />
|
||||
<div className={classes.hr} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box marginX={10}>
|
||||
<div className={classes.appHeaderLinks}>
|
||||
<ExternalLink
|
||||
className={classes.headerLinkContainer}
|
||||
href={data.supportUrl || ""}
|
||||
target="_blank"
|
||||
>
|
||||
<SVG src={supportIcon} />
|
||||
<FormattedMessage {...messages.supportLink} />
|
||||
</ExternalLink>
|
||||
<ButtonBase
|
||||
className={classes.headerLinkContainer}
|
||||
disableRipple
|
||||
onClick={data.isActive ? onAppDeactivateOpen : onAppActivateOpen}
|
||||
>
|
||||
<SVG src={activateIcon} />
|
||||
{data?.isActive ? (
|
||||
<FormattedMessage {...buttonMessages.deactivate} />
|
||||
) : (
|
||||
<FormattedMessage {...buttonMessages.activate} />
|
||||
)}
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
className={classes.headerLinkContainer}
|
||||
disableRipple
|
||||
onClick={onAppDeleteOpen}
|
||||
>
|
||||
<SVG src={deleteIcon} />
|
||||
<FormattedMessage {...buttonMessages.delete} />
|
||||
</ButtonBase>
|
||||
</div>
|
||||
<div className={classes.hr} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default HeaderOptions;
|
47
src/new-apps/components/AppDetailsPage/PermissionsCard.tsx
Normal file
47
src/new-apps/components/AppDetailsPage/PermissionsCard.tsx
Normal file
|
@ -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<PermissionsCardProps> = ({
|
||||
permissions,
|
||||
loading,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle title={intl.formatMessage(messages.appPermissionsTitle)} />
|
||||
<CardContent>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Typography>
|
||||
<FormattedMessage {...messages.appPermissionsDescription} />
|
||||
</Typography>
|
||||
{!!permissions?.length && (
|
||||
<ul className={classes.permissionsContainer}>
|
||||
{permissions?.map(perm => (
|
||||
<li key={perm.code}>{perm.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default PermissionsCard;
|
39
src/new-apps/components/AppDetailsPage/messages.ts
Normal file
39
src/new-apps/components/AppDetailsPage/messages.ts
Normal file
|
@ -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",
|
||||
},
|
||||
});
|
65
src/new-apps/components/AppDetailsPage/styles.ts
Normal file
65
src/new-apps/components/AppDetailsPage/styles.ts
Normal file
|
@ -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" },
|
||||
);
|
|
@ -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 (
|
||||
<Typography className={classes.root}>
|
||||
<FormattedMessage
|
||||
id="5+Xcrz"
|
||||
defaultMessage="Deactivated"
|
||||
description="app deactivated"
|
||||
/>
|
||||
<FormattedMessage {...commonStatusMessages.deactivated} />
|
||||
</Typography>
|
||||
);
|
||||
};
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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<AppDetailsProps> = ({ id, params }) => {
|
|||
refetch();
|
||||
refetchExtensionList();
|
||||
removeAppNotify();
|
||||
navigate(appsListPath);
|
||||
navigate(AppPaths.appListPath);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -118,14 +117,14 @@ export const AppDetails: React.FC<AppDetailsProps> = ({ 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 <NotFoundPage backHref={appsListPath} />;
|
||||
return <NotFoundPage backHref={AppPaths.appListPath} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -155,7 +154,7 @@ export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => {
|
|||
<AppDetailsPage
|
||||
data={data?.app || null}
|
||||
loading={loading}
|
||||
navigateToApp={() => navigate(appUrl(id))}
|
||||
navigateToApp={() => navigate(AppUrls.resolveAppUrl(id))}
|
||||
onAppActivateOpen={() => openModal("app-activate")}
|
||||
onAppDeactivateOpen={() => openModal("app-deactivate")}
|
||||
onAppDeleteOpen={() => openModal("app-delete")}
|
Loading…
Reference in a new issue