Fix strict null check in Apps (#2911)
* Fix apps strict null checks * Set strictNullChecks to false * Improve strict null checks * Replace typ to Extension
This commit is contained in:
parent
39ea58f02b
commit
bf2a2035d3
28 changed files with 190 additions and 139 deletions
|
@ -15,4 +15,4 @@ const props: AppActivateDialogProps = {
|
||||||
storiesOf("Views / Apps / Activate app", module)
|
storiesOf("Views / Apps / Activate app", module)
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
||||||
.add("default", () => <AppActivateDialog {...props} />)
|
.add("default", () => <AppActivateDialog {...props} />)
|
||||||
.add("unnamed app", () => <AppActivateDialog {...props} name={null} />);
|
.add("unnamed app", () => <AppActivateDialog {...props} name="" />);
|
||||||
|
|
|
@ -17,4 +17,4 @@ const props: AppDeactivateDialogProps = {
|
||||||
storiesOf("Views / Apps / Deactivate app", module)
|
storiesOf("Views / Apps / Deactivate app", module)
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
||||||
.add("default", () => <AppDeactivateDialog {...props} />)
|
.add("default", () => <AppDeactivateDialog {...props} />)
|
||||||
.add("unnamed app", () => <AppDeactivateDialog {...props} name={null} />);
|
.add("unnamed app", () => <AppDeactivateDialog {...props} name="" />);
|
||||||
|
|
|
@ -16,4 +16,4 @@ const props: AppDeleteDialogProps = {
|
||||||
storiesOf("Views / Apps / Delete app", module)
|
storiesOf("Views / Apps / Delete app", module)
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
||||||
.add("default", () => <AppDeleteDialog {...props} />)
|
.add("default", () => <AppDeleteDialog {...props} />)
|
||||||
.add("unnamed app", () => <AppDeleteDialog {...props} name={null} />);
|
.add("unnamed app", () => <AppDeleteDialog {...props} name="" />);
|
||||||
|
|
|
@ -63,7 +63,7 @@ export const AppDetailsPage: React.FC<AppDetailsPageProps> = ({
|
||||||
<div className={classes.appHeaderLinks}>
|
<div className={classes.appHeaderLinks}>
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
className={classes.headerLinkContainer}
|
className={classes.headerLinkContainer}
|
||||||
href={data.supportUrl}
|
href={data.supportUrl || ""}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<SVG src={supportIcon} />
|
<SVG src={supportIcon} />
|
||||||
|
@ -109,7 +109,11 @@ export const AppDetailsPage: React.FC<AppDetailsPageProps> = ({
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{!loading ? <ReactMarkdown source={data?.aboutApp} /> : <Skeleton />}
|
{!loading ? (
|
||||||
|
<ReactMarkdown source={data?.aboutApp ?? ""} />
|
||||||
|
) : (
|
||||||
|
<Skeleton />
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
|
@ -146,7 +150,7 @@ export const AppDetailsPage: React.FC<AppDetailsPageProps> = ({
|
||||||
</Card>
|
</Card>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
|
|
||||||
{(loading || data?.dataPrivacyUrl) && (
|
{data?.dataPrivacyUrl && (
|
||||||
<Card>
|
<Card>
|
||||||
<CardTitle
|
<CardTitle
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage({
|
||||||
|
@ -159,7 +163,7 @@ export const AppDetailsPage: React.FC<AppDetailsPageProps> = ({
|
||||||
{!loading ? (
|
{!loading ? (
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
className={classes.linkContainer}
|
className={classes.linkContainer}
|
||||||
href={data?.dataPrivacyUrl}
|
href={data.dataPrivacyUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|
|
@ -38,7 +38,7 @@ export const AppFrame: React.FC<Props> = ({
|
||||||
refetch,
|
refetch,
|
||||||
}) => {
|
}) => {
|
||||||
const shop = useShop();
|
const shop = useShop();
|
||||||
const frameRef = React.useRef<HTMLIFrameElement>();
|
const frameRef = React.useRef<HTMLIFrameElement>(null);
|
||||||
const { themeType } = useTheme();
|
const { themeType } = useTheme();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const appOrigin = getOrigin(src);
|
const appOrigin = getOrigin(src);
|
||||||
|
|
|
@ -34,7 +34,7 @@ const isAppDeepUrlChange = (appId: string, from: string, to: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAppActions = (
|
export const useAppActions = (
|
||||||
frameEl: React.MutableRefObject<HTMLIFrameElement>,
|
frameEl: React.MutableRefObject<HTMLIFrameElement | null>,
|
||||||
appOrigin: string,
|
appOrigin: string,
|
||||||
appId: string,
|
appId: string,
|
||||||
) => {
|
) => {
|
||||||
|
@ -125,7 +125,7 @@ export const useAppActions = (
|
||||||
};
|
};
|
||||||
|
|
||||||
const postToExtension = (event: Events) => {
|
const postToExtension = (event: Events) => {
|
||||||
if (frameEl.current) {
|
if (frameEl?.current?.contentWindow) {
|
||||||
frameEl.current.contentWindow.postMessage(event, appOrigin);
|
frameEl.current.contentWindow.postMessage(event, appOrigin);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,10 @@ interface AppToken {
|
||||||
const TIME_BEFORE_REFRESH = 30 * 1000; // 30 seconds
|
const TIME_BEFORE_REFRESH = 30 * 1000; // 30 seconds
|
||||||
|
|
||||||
const useTokenRefresh = (token?: string, refetch?: () => void) => {
|
const useTokenRefresh = (token?: string, refetch?: () => void) => {
|
||||||
let decoded: AppToken;
|
let decoded: AppToken = {
|
||||||
|
exp: 0,
|
||||||
|
iat: 0,
|
||||||
|
};
|
||||||
|
|
||||||
// For some reason jwt_decode causes seemingly unrelated error in tests
|
// For some reason jwt_decode causes seemingly unrelated error in tests
|
||||||
// It seems like at some point undefined token is passed
|
// It seems like at some point undefined token is passed
|
||||||
|
@ -26,11 +29,13 @@ const useTokenRefresh = (token?: string, refetch?: () => void) => {
|
||||||
|
|
||||||
const refreshTimeout = useRef<null | ReturnType<typeof setTimeout>>(null);
|
const refreshTimeout = useRef<null | ReturnType<typeof setTimeout>>(null);
|
||||||
|
|
||||||
const tokenLife = (decoded?.exp - decoded?.iat) * 1000; // in ms
|
const tokenLife = ((decoded?.exp || 0) - (decoded?.iat || 0)) * 1000; // in ms
|
||||||
const refreshTime = tokenLife - TIME_BEFORE_REFRESH;
|
const refreshTime = tokenLife - TIME_BEFORE_REFRESH;
|
||||||
|
|
||||||
const setUpTimeout = () => {
|
const setUpTimeout = () => {
|
||||||
refetch();
|
if (refetch) {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
createTimeout();
|
createTimeout();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,7 +55,11 @@ const useTokenRefresh = (token?: string, refetch?: () => void) => {
|
||||||
createTimeout();
|
createTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => !!refetch && decodedSuccesfully && deleteTimeout();
|
return () => {
|
||||||
|
if (!!refetch && decodedSuccesfully) {
|
||||||
|
deleteTimeout();
|
||||||
|
}
|
||||||
|
};
|
||||||
}, [token]);
|
}, [token]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,4 @@ const props: AppInProgressDeleteDialogProps = {
|
||||||
storiesOf("Views / Apps / Delete app failed installation", module)
|
storiesOf("Views / Apps / Delete app failed installation", module)
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
||||||
.add("default", () => <AppInProgressDeleteDialog {...props} />)
|
.add("default", () => <AppInProgressDeleteDialog {...props} />)
|
||||||
.add("unnamed app", () => (
|
.add("unnamed app", () => <AppInProgressDeleteDialog {...props} name="" />);
|
||||||
<AppInProgressDeleteDialog {...props} name={null} />
|
|
||||||
));
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ const props: AppInstallPageProps = {
|
||||||
data: installApp,
|
data: installApp,
|
||||||
loading: false,
|
loading: false,
|
||||||
navigateToAppsList: () => undefined,
|
navigateToAppsList: () => undefined,
|
||||||
onSubmit: () => undefined,
|
onSubmit: () => Promise.resolve([]),
|
||||||
};
|
};
|
||||||
|
|
||||||
storiesOf("Views / Apps / Install App", module)
|
storiesOf("Views / Apps / Install App", module)
|
||||||
|
|
|
@ -17,10 +17,12 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useStyles } from "../../styles";
|
import { useStyles } from "../../styles";
|
||||||
|
|
||||||
export interface AppInstallPageProps {
|
export interface AppInstallPageProps {
|
||||||
data: AppFetchMutation["appFetchManifest"]["manifest"];
|
data: NonNullable<AppFetchMutation["appFetchManifest"]>["manifest"];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
navigateToAppsList: () => void;
|
navigateToAppsList: () => void;
|
||||||
onSubmit: () => SubmitPromise<AppInstallMutation["appInstall"]["errors"]>;
|
onSubmit: () => SubmitPromise<
|
||||||
|
NonNullable<AppInstallMutation["appInstall"]>["errors"]
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppInstallPage: React.FC<AppInstallPageProps> = ({
|
export const AppInstallPage: React.FC<AppInstallPageProps> = ({
|
||||||
|
|
|
@ -7,7 +7,7 @@ import AppPage, { AppPageProps } from "./AppPage";
|
||||||
|
|
||||||
const props: AppPageProps = {
|
const props: AppPageProps = {
|
||||||
data: appDetails,
|
data: appDetails,
|
||||||
url: appDetails.appUrl,
|
url: appDetails.appUrl!,
|
||||||
aboutHref: "",
|
aboutHref: "",
|
||||||
onError: () => undefined,
|
onError: () => undefined,
|
||||||
};
|
};
|
||||||
|
@ -16,5 +16,5 @@ storiesOf("Views / Apps / App", module)
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
||||||
.add("default", () => <AppPage {...props} />)
|
.add("default", () => <AppPage {...props} />)
|
||||||
.add("settings", () => (
|
.add("settings", () => (
|
||||||
<AppPage {...props} url={appDetails.configurationUrl} />
|
<AppPage {...props} url={appDetails.configurationUrl!} />
|
||||||
));
|
));
|
||||||
|
|
|
@ -80,9 +80,9 @@ export const AppPage: React.FC<AppPageProps> = ({
|
||||||
{url && (
|
{url && (
|
||||||
<AppFrame
|
<AppFrame
|
||||||
src={url}
|
src={url}
|
||||||
appToken={data.accessToken}
|
appToken={data?.accessToken ?? ""}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
appId={data.id}
|
appId={data?.id ?? ""}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -51,65 +51,79 @@ const AppsInProgress: React.FC<AppsInProgressProps> = ({
|
||||||
/>
|
/>
|
||||||
<ResponsiveTable>
|
<ResponsiveTable>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{renderCollection(appsList, ({ status, appName, id, message }) => (
|
{renderCollection(
|
||||||
<TableRowLink key={id} className={classes.tableRow}>
|
appsList,
|
||||||
<TableCell className={classes.colName}>
|
({
|
||||||
<span data-tc="name">{appName}</span>
|
status,
|
||||||
</TableCell>
|
appName,
|
||||||
{status === JobStatusEnum.PENDING && (
|
id,
|
||||||
<TableCell
|
message,
|
||||||
className={clsx(classes.colAction, classes.colInstallAction)}
|
}: AppsInstallationsQuery["appsInstallations"][number]) => (
|
||||||
>
|
<TableRowLink key={id} className={classes.tableRow}>
|
||||||
<Typography variant="body2" className={classes.text}>
|
<TableCell className={classes.colName}>
|
||||||
<FormattedMessage
|
<span data-tc="name">{appName}</span>
|
||||||
id="1qRwgQ"
|
|
||||||
defaultMessage="Installing app..."
|
|
||||||
description="app installation"
|
|
||||||
/>
|
|
||||||
</Typography>
|
|
||||||
<div className={classes.colSpinner}>
|
|
||||||
<Progress size={20} />
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
{status === JobStatusEnum.PENDING && (
|
||||||
{status === JobStatusEnum.FAILED && (
|
<TableCell
|
||||||
<TableCell
|
className={clsx(
|
||||||
className={clsx(classes.colAction, classes.colInstallAction)}
|
classes.colAction,
|
||||||
>
|
classes.colInstallAction,
|
||||||
<Typography variant="body2" className={classes.error}>
|
)}
|
||||||
<FormattedMessage
|
>
|
||||||
id="Xl0o2y"
|
<Typography variant="body2" className={classes.text}>
|
||||||
defaultMessage="Problem occured during installation"
|
|
||||||
description="app installation error"
|
|
||||||
/>
|
|
||||||
<Tooltip title={message} variant="error">
|
|
||||||
<TooltipMountWrapper>
|
|
||||||
<Indicator icon="error" />
|
|
||||||
</TooltipMountWrapper>
|
|
||||||
</Tooltip>
|
|
||||||
</Typography>
|
|
||||||
<TableButtonWrapper>
|
|
||||||
<Button onClick={() => onAppInstallRetry(id)}>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="+c/f61"
|
id="1qRwgQ"
|
||||||
defaultMessage="Retry"
|
defaultMessage="Installing app..."
|
||||||
description="retry installation"
|
description="app installation"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Typography>
|
||||||
</TableButtonWrapper>
|
<div className={classes.colSpinner}>
|
||||||
<TableButtonWrapper>
|
<Progress size={20} />
|
||||||
<IconButton
|
</div>
|
||||||
variant="secondary"
|
</TableCell>
|
||||||
color="primary"
|
)}
|
||||||
onClick={() => onRemove(id)}
|
{status === JobStatusEnum.FAILED && (
|
||||||
>
|
<TableCell
|
||||||
<DeleteIcon />
|
className={clsx(
|
||||||
</IconButton>
|
classes.colAction,
|
||||||
</TableButtonWrapper>
|
classes.colInstallAction,
|
||||||
</TableCell>
|
)}
|
||||||
)}
|
>
|
||||||
</TableRowLink>
|
<Typography variant="body2" className={classes.error}>
|
||||||
))}
|
<FormattedMessage
|
||||||
|
id="Xl0o2y"
|
||||||
|
defaultMessage="Problem occured during installation"
|
||||||
|
description="app installation error"
|
||||||
|
/>
|
||||||
|
<Tooltip title={message} variant="error">
|
||||||
|
<TooltipMountWrapper>
|
||||||
|
<Indicator icon="error" />
|
||||||
|
</TooltipMountWrapper>
|
||||||
|
</Tooltip>
|
||||||
|
</Typography>
|
||||||
|
<TableButtonWrapper>
|
||||||
|
<Button onClick={() => onAppInstallRetry(id)}>
|
||||||
|
<FormattedMessage
|
||||||
|
id="+c/f61"
|
||||||
|
defaultMessage="Retry"
|
||||||
|
description="retry installation"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</TableButtonWrapper>
|
||||||
|
<TableButtonWrapper>
|
||||||
|
<IconButton
|
||||||
|
variant="secondary"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => onRemove(id)}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</TableButtonWrapper>
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</TableRowLink>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</ResponsiveTable>
|
</ResponsiveTable>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -47,7 +47,7 @@ storiesOf("Views / Apps / Apps list", module)
|
||||||
{...props}
|
{...props}
|
||||||
appsInProgressList={undefined}
|
appsInProgressList={undefined}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
installedAppsList={undefined}
|
installedAppsList={[]}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
.add("no data", () => (
|
.add("no data", () => (
|
||||||
|
|
|
@ -78,15 +78,19 @@ const AppsListPage: React.FC<AppsListPageProps> = ({
|
||||||
[installedAppsList, fetchedSaleorApps],
|
[installedAppsList, fetchedSaleorApps],
|
||||||
);
|
);
|
||||||
|
|
||||||
const saleorApps = useMemo(
|
const saleorApps = useMemo<AppListItemFragment[]>(
|
||||||
() =>
|
() =>
|
||||||
fetchedSaleorApps
|
(fetchedSaleorApps || []).reduce<AppListItemFragment[]>((acc, app) => {
|
||||||
?.map(app =>
|
const foundedApp = installedAppsList?.find(installedApp =>
|
||||||
installedAppsList?.find(installedApp =>
|
installedApp.manifestUrl?.includes(app.hostname),
|
||||||
installedApp.manifestUrl?.includes(app.hostname),
|
);
|
||||||
),
|
|
||||||
)
|
if (foundedApp) {
|
||||||
.filter(Boolean),
|
acc.push(foundedApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []),
|
||||||
[installedAppsList, fetchedSaleorApps],
|
[installedAppsList, fetchedSaleorApps],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -16,4 +16,9 @@ export const ExternalAppContext = React.createContext<{
|
||||||
appData: AppData | undefined;
|
appData: AppData | undefined;
|
||||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setAppData: React.Dispatch<React.SetStateAction<AppData | undefined>>;
|
setAppData: React.Dispatch<React.SetStateAction<AppData | undefined>>;
|
||||||
}>(undefined);
|
}>({
|
||||||
|
open: false,
|
||||||
|
appData: undefined,
|
||||||
|
setOpen: () => null,
|
||||||
|
setAppData: () => null,
|
||||||
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@ export interface HorizontalSpacerProps {
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
container: ({ spacing }: HorizontalSpacerProps) => ({
|
container: ({ spacing }: Required<HorizontalSpacerProps>) => ({
|
||||||
width: theme.spacing(spacing),
|
width: theme.spacing(spacing),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -104,11 +104,11 @@ const InstalledApps: React.FC<InstalledAppsProps> = ({
|
||||||
)}
|
)}
|
||||||
<TableButtonWrapper>
|
<TableButtonWrapper>
|
||||||
<Switch
|
<Switch
|
||||||
checked={app.isActive}
|
checked={!!app.isActive}
|
||||||
onChange={getHandleToggle(app)}
|
onChange={getHandleToggle(app)}
|
||||||
/>
|
/>
|
||||||
</TableButtonWrapper>
|
</TableButtonWrapper>
|
||||||
<AppPermissions permissions={app.permissions} />
|
<AppPermissions permissions={app.permissions || []} />
|
||||||
<TableButtonWrapper>
|
<TableButtonWrapper>
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|
|
@ -7,7 +7,7 @@ export interface VerticalSpacerProps {
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
container: ({ spacing }: VerticalSpacerProps) => ({
|
container: ({ spacing }: Required<VerticalSpacerProps>) => ({
|
||||||
height: theme.spacing(spacing),
|
height: theme.spacing(spacing),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -99,7 +99,7 @@ export const appsInProgress: AppsInstallationsQuery["appsInstallations"] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const appDetails: AppQuery["app"] = {
|
export const appDetails: NonNullable<AppQuery["app"]> = {
|
||||||
__typename: "App",
|
__typename: "App",
|
||||||
aboutApp: "Lorem ipsum",
|
aboutApp: "Lorem ipsum",
|
||||||
accessToken: "token",
|
accessToken: "token",
|
||||||
|
@ -134,7 +134,9 @@ export const appDetails: AppQuery["app"] = {
|
||||||
webhooks: [],
|
webhooks: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const installApp: AppFetchMutation["appFetchManifest"]["manifest"] = {
|
export const installApp: NonNullable<
|
||||||
|
AppFetchMutation["appFetchManifest"]
|
||||||
|
>["manifest"] = {
|
||||||
__typename: "Manifest",
|
__typename: "Manifest",
|
||||||
about: "Lorem ipsum",
|
about: "Lorem ipsum",
|
||||||
appUrl: null,
|
appUrl: null,
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { AppDetailsUrlMountQueryParams } from "./urls";
|
||||||
|
|
||||||
export interface Extension {
|
export interface Extension {
|
||||||
id: string;
|
id: string;
|
||||||
app: RelayToFlat<ExtensionListQuery["appExtensions"]>[0]["app"];
|
app: RelayToFlat<NonNullable<ExtensionListQuery["appExtensions"]>>[0]["app"];
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
permissions: PermissionEnum[];
|
permissions: PermissionEnum[];
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -54,14 +54,14 @@ export const extensionMountPoints = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterAndMapToTarget = (
|
const filterAndMapToTarget = (
|
||||||
extensions: RelayToFlat<ExtensionListQuery["appExtensions"]>,
|
extensions: RelayToFlat<NonNullable<ExtensionListQuery["appExtensions"]>>,
|
||||||
openApp: (appData: AppData) => void,
|
openApp: (appData: AppData) => void,
|
||||||
): ExtensionWithParams[] =>
|
): ExtensionWithParams[] =>
|
||||||
extensions.map(
|
extensions.map(
|
||||||
({ id, accessToken, permissions, url, label, mount, target, app }) => ({
|
({ id, accessToken, permissions, url, label, mount, target, app }) => ({
|
||||||
id,
|
id,
|
||||||
app,
|
app,
|
||||||
accessToken,
|
accessToken: accessToken || "",
|
||||||
permissions: permissions.map(({ code }) => code),
|
permissions: permissions.map(({ code }) => code),
|
||||||
url,
|
url,
|
||||||
label,
|
label,
|
||||||
|
@ -69,7 +69,7 @@ const filterAndMapToTarget = (
|
||||||
open: (params: AppDetailsUrlMountQueryParams) =>
|
open: (params: AppDetailsUrlMountQueryParams) =>
|
||||||
openApp({
|
openApp({
|
||||||
id: app.id,
|
id: app.id,
|
||||||
appToken: accessToken,
|
appToken: accessToken || "",
|
||||||
src: url,
|
src: url,
|
||||||
label,
|
label,
|
||||||
target,
|
target,
|
||||||
|
@ -153,7 +153,7 @@ export const useExtensions = <T extends AppExtensionMountEnum>(
|
||||||
});
|
});
|
||||||
|
|
||||||
const extensions = filterAndMapToTarget(
|
const extensions = filterAndMapToTarget(
|
||||||
mapEdgesToItems(data?.appExtensions) || [],
|
mapEdgesToItems(data?.appExtensions ?? undefined) || [],
|
||||||
openApp,
|
openApp,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -37,14 +37,14 @@ export const App: React.FC<AppProps> = ({ id }) => {
|
||||||
|
|
||||||
const appCompleteUrl = getAppCompleteUrlFromDashboardUrl(
|
const appCompleteUrl = getAppCompleteUrlFromDashboardUrl(
|
||||||
location.pathname,
|
location.pathname,
|
||||||
data?.app.appUrl,
|
data?.app?.appUrl || "",
|
||||||
id,
|
id,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppPage
|
<AppPage
|
||||||
data={data?.app}
|
data={data?.app || null}
|
||||||
url={appCompleteUrl}
|
url={appCompleteUrl || ""}
|
||||||
aboutHref={appDetailsUrl(id)}
|
aboutHref={appDetailsUrl(id)}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
onError={() =>
|
onError={() =>
|
||||||
|
|
|
@ -51,7 +51,7 @@ export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => {
|
||||||
refetch();
|
refetch();
|
||||||
closeModal();
|
closeModal();
|
||||||
} else {
|
} else {
|
||||||
if (appExists) {
|
if (appExists && errors) {
|
||||||
errors.forEach(error =>
|
errors.forEach(error =>
|
||||||
notify({
|
notify({
|
||||||
status: "error",
|
status: "error",
|
||||||
|
@ -65,7 +65,7 @@ export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => {
|
||||||
const [deactivateApp, deactivateAppResult] = useAppDeactivateMutation({
|
const [deactivateApp, deactivateAppResult] = useAppDeactivateMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
const errors = data?.appDeactivate?.errors;
|
const errors = data?.appDeactivate?.errors;
|
||||||
if (errors.length === 0) {
|
if (errors?.length === 0) {
|
||||||
notify({
|
notify({
|
||||||
status: "success",
|
status: "success",
|
||||||
text: intl.formatMessage(appMessages.appDeactivated),
|
text: intl.formatMessage(appMessages.appDeactivated),
|
||||||
|
@ -73,7 +73,7 @@ export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => {
|
||||||
refetch();
|
refetch();
|
||||||
closeModal();
|
closeModal();
|
||||||
} else {
|
} else {
|
||||||
if (appExists) {
|
if (appExists && errors) {
|
||||||
errors.forEach(error =>
|
errors.forEach(error =>
|
||||||
notify({
|
notify({
|
||||||
status: "error",
|
status: "error",
|
||||||
|
@ -105,20 +105,20 @@ export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => {
|
||||||
<>
|
<>
|
||||||
<AppActivateDialog
|
<AppActivateDialog
|
||||||
confirmButtonState={activateAppResult.status}
|
confirmButtonState={activateAppResult.status}
|
||||||
name={data?.app.name}
|
name={data?.app?.name || ""}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onConfirm={handleActivateConfirm}
|
onConfirm={handleActivateConfirm}
|
||||||
open={params.action === "app-activate"}
|
open={params.action === "app-activate"}
|
||||||
/>
|
/>
|
||||||
<AppDeactivateDialog
|
<AppDeactivateDialog
|
||||||
confirmButtonState={deactivateAppResult.status}
|
confirmButtonState={deactivateAppResult.status}
|
||||||
name={data?.app.name}
|
name={data?.app?.name || ""}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onConfirm={handleDeactivateConfirm}
|
onConfirm={handleDeactivateConfirm}
|
||||||
open={params.action === "app-deactivate"}
|
open={params.action === "app-deactivate"}
|
||||||
/>
|
/>
|
||||||
<AppDetailsPage
|
<AppDetailsPage
|
||||||
data={data?.app}
|
data={data?.app || null}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
navigateToApp={() => navigate(appUrl(id))}
|
navigateToApp={() => navigate(appUrl(id))}
|
||||||
onAppActivateOpen={() => openModal("app-activate")}
|
onAppActivateOpen={() => openModal("app-activate")}
|
||||||
|
|
|
@ -24,7 +24,9 @@ interface InstallAppCreateProps extends RouteComponentProps {
|
||||||
export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
|
export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
|
||||||
params,
|
params,
|
||||||
}) => {
|
}) => {
|
||||||
const [, setActiveInstallations] = useLocalStorage("activeInstallations", []);
|
const [, setActiveInstallations] = useLocalStorage<
|
||||||
|
Array<Record<"id" | "name", string>>
|
||||||
|
>("activeInstallations", []);
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
@ -32,7 +34,7 @@ export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
|
||||||
|
|
||||||
const [fetchManifest, fetchManifestOpts] = useAppFetchMutation({
|
const [fetchManifest, fetchManifestOpts] = useAppFetchMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
if (data.appFetchManifest.errors.length) {
|
if (data?.appFetchManifest?.errors.length) {
|
||||||
data.appFetchManifest.errors.forEach(error => {
|
data.appFetchManifest.errors.forEach(error => {
|
||||||
notify({
|
notify({
|
||||||
status: "error",
|
status: "error",
|
||||||
|
@ -44,15 +46,20 @@ export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
|
||||||
});
|
});
|
||||||
const [installApp] = useAppInstallMutation({
|
const [installApp] = useAppInstallMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
const installationData = data.appInstall.appInstallation;
|
const installationData = data?.appInstall?.appInstallation;
|
||||||
if (data.appInstall.errors.length === 0) {
|
if (data.appInstall?.errors.length === 0) {
|
||||||
setActiveInstallations(activeInstallations => [
|
if (installationData) {
|
||||||
...activeInstallations,
|
setActiveInstallations(activeInstallations => [
|
||||||
{ id: installationData.id, name: installationData.appName },
|
...activeInstallations,
|
||||||
]);
|
{
|
||||||
|
id: installationData.id,
|
||||||
|
name: installationData.appName,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
navigateToAppsList();
|
navigateToAppsList();
|
||||||
} else {
|
} else {
|
||||||
data.appInstall.errors.forEach(error => {
|
(data?.appInstall?.errors ?? []).forEach(error => {
|
||||||
notify({
|
notify({
|
||||||
status: "error",
|
status: "error",
|
||||||
text: getAppErrorMessage(error, intl),
|
text: getAppErrorMessage(error, intl),
|
||||||
|
@ -72,7 +79,7 @@ export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
|
||||||
input: {
|
input: {
|
||||||
appName: manifest?.name,
|
appName: manifest?.name,
|
||||||
manifestUrl,
|
manifestUrl,
|
||||||
permissions: manifest?.permissions.map(
|
permissions: manifest?.permissions?.map(
|
||||||
permission => permission.code,
|
permission => permission.code,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -97,7 +104,7 @@ export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
|
||||||
<AppInstallErrorPage onBack={() => navigate("/")} />
|
<AppInstallErrorPage onBack={() => navigate("/")} />
|
||||||
) : (
|
) : (
|
||||||
<AppInstallPage
|
<AppInstallPage
|
||||||
data={fetchManifestOpts?.data?.appFetchManifest?.manifest}
|
data={fetchManifestOpts?.data?.appFetchManifest?.manifest ?? null}
|
||||||
navigateToAppsList={navigateToAppsList}
|
navigateToAppsList={navigateToAppsList}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
loading={fetchManifestOpts?.loading}
|
loading={fetchManifestOpts?.loading}
|
||||||
|
|
|
@ -29,8 +29,8 @@ export const AppSettings: React.FC<AppSettingsProps> = ({ id }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppPage
|
<AppPage
|
||||||
data={data?.app}
|
data={data?.app ?? null}
|
||||||
url={data?.app.configurationUrl}
|
url={data?.app?.configurationUrl ?? ""}
|
||||||
aboutHref={appDetailsUrl(id)}
|
aboutHref={appDetailsUrl(id)}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
onError={() =>
|
onError={() =>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import usePaginator, {
|
import usePaginator, {
|
||||||
createPaginationState,
|
createPaginationState,
|
||||||
|
PageInfo,
|
||||||
PaginatorContext,
|
PaginatorContext,
|
||||||
} from "@saleor/hooks/usePaginator";
|
} from "@saleor/hooks/usePaginator";
|
||||||
import { findById } from "@saleor/misc";
|
import { findById } from "@saleor/misc";
|
||||||
|
@ -45,7 +46,7 @@ import { messages } from "./messages";
|
||||||
const getAppInProgressName = (
|
const getAppInProgressName = (
|
||||||
id: string,
|
id: string,
|
||||||
collection?: AppsInstallationsQuery["appsInstallations"],
|
collection?: AppsInstallationsQuery["appsInstallations"],
|
||||||
) => collection?.find(app => app.id === id)?.appName;
|
) => collection?.find(app => app.id === id)?.appName || id;
|
||||||
interface AppsListProps {
|
interface AppsListProps {
|
||||||
params: AppListUrlQueryParams;
|
params: AppListUrlQueryParams;
|
||||||
}
|
}
|
||||||
|
@ -92,7 +93,7 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const paginationValues = usePaginator({
|
const paginationValues = usePaginator({
|
||||||
pageInfo: data?.apps?.pageInfo,
|
pageInfo: data?.apps?.pageInfo as PageInfo,
|
||||||
paginationState,
|
paginationState,
|
||||||
queryString: params,
|
queryString: params,
|
||||||
});
|
});
|
||||||
|
@ -113,11 +114,16 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
||||||
const [retryInstallApp] = useAppRetryInstallMutation({
|
const [retryInstallApp] = useAppRetryInstallMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
if (!data?.appRetryInstall?.errors?.length) {
|
if (!data?.appRetryInstall?.errors?.length) {
|
||||||
const appInstallation = data.appRetryInstall.appInstallation;
|
const appInstallation = data.appRetryInstall?.appInstallation;
|
||||||
setActiveInstallations(installations => [
|
if (appInstallation) {
|
||||||
...installations,
|
setActiveInstallations(installations => [
|
||||||
{ id: appInstallation.id, name: appInstallation.appName },
|
...installations,
|
||||||
]);
|
{
|
||||||
|
id: appInstallation.id,
|
||||||
|
name: appInstallation.appName,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -226,14 +232,14 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
||||||
const handleRemoveInProgressConfirm = () =>
|
const handleRemoveInProgressConfirm = () =>
|
||||||
deleteInProgressApp({
|
deleteInProgressApp({
|
||||||
variables: {
|
variables: {
|
||||||
id: params.id,
|
id: params?.id || "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRemoveConfirm = () =>
|
const handleRemoveConfirm = () =>
|
||||||
deleteApp({
|
deleteApp({
|
||||||
variables: {
|
variables: {
|
||||||
id: params.id,
|
id: params?.id || "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -245,10 +251,10 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleActivateAppConfirm = () =>
|
const handleActivateAppConfirm = () =>
|
||||||
activateApp({ variables: { id: params.id } });
|
activateApp({ variables: { id: params?.id || "" } });
|
||||||
|
|
||||||
const handleDeactivateAppConfirm = () =>
|
const handleDeactivateAppConfirm = () =>
|
||||||
deactivateApp({ variables: { id: params.id } });
|
deactivateApp({ variables: { id: params?.id || "" } });
|
||||||
|
|
||||||
const onAppInstallRetry = (id: string) =>
|
const onAppInstallRetry = (id: string) =>
|
||||||
retryInstallApp({ variables: { id } });
|
retryInstallApp({ variables: { id } });
|
||||||
|
@ -261,8 +267,8 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
||||||
[activateApp, deactivateApp],
|
[activateApp, deactivateApp],
|
||||||
);
|
);
|
||||||
|
|
||||||
const installedApps = mapEdgesToItems(data?.apps);
|
const installedApps = mapEdgesToItems(data?.apps || { edges: [] }) || [];
|
||||||
const currentAppName = findById(params.id, installedApps)?.name;
|
const currentAppName = findById(params?.id || "", installedApps)?.name || "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppListContext.Provider value={context}>
|
<AppListContext.Provider value={context}>
|
||||||
|
@ -292,7 +298,7 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
||||||
<AppInProgressDeleteDialog
|
<AppInProgressDeleteDialog
|
||||||
confirmButtonState={deleteInProgressAppOpts.status}
|
confirmButtonState={deleteInProgressAppOpts.status}
|
||||||
name={getAppInProgressName(
|
name={getAppInProgressName(
|
||||||
params.id,
|
params.id || "",
|
||||||
appsInProgressData?.appsInstallations,
|
appsInProgressData?.appsInstallations,
|
||||||
)}
|
)}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
|
|
|
@ -47,7 +47,7 @@ function usePaginator({
|
||||||
paginationState,
|
paginationState,
|
||||||
pageInfo,
|
pageInfo,
|
||||||
}: UsePaginatorArgs) {
|
}: UsePaginatorArgs) {
|
||||||
const newPageInfo = useMemo<PageInfo>(
|
const newPageInfo = useMemo<PageInfo | undefined>(
|
||||||
() =>
|
() =>
|
||||||
pageInfo
|
pageInfo
|
||||||
? {
|
? {
|
||||||
|
|
|
@ -25,4 +25,4 @@
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "cypress"]
|
"exclude": ["node_modules", "cypress"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue