* Add tabs to apps page

* Detect app in tunnel

* Disable manifest install if Saleor Apps context

* Show only app origin instead full manifest

* Add copy manifest feature

* Update tests

* Add ENV with marketplace endpoint for fetching saleor apps

* Fetch Saleor apps for Marketplace and display them in the tab, otherwise hide

* Code review fixes

* Extract translation messages

* Update tests
This commit is contained in:
Lukasz Ostrowski 2022-10-20 13:35:31 +02:00 committed by GitHub
parent 5c48f5387e
commit d67e4799b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 522 additions and 261 deletions

View file

@ -25,7 +25,8 @@ greatly reduce the amount of work needed to review your work. -->
Modify API_URI if you want test instance to use custom backend. CYPRESS_API_URI is optional, use when necessary. -->
API_URI=https://automation-dashboard.staging.saleor.cloud/graphql/
MARKETPLACE_URL=https://marketplace-gray.vercel.app/
MARKETPLACE_URL=https://apps.saleor.io
SALEOR_APPS_ENDPOINT=https://apps.saleor.io/api/saleor-apps
### Do you want to run more stable tests?
To run all tests, just select the stable checkbox. To speed up tests, increase the number of containers. Tests will be re-run only when the "run e2e" label is added.

View file

@ -18,6 +18,7 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
MARKETPLACE_URL: "https://apps.saleor.io/"
SALEOR_APPS_ENDPOINT: "https://apps.saleor.io/api/saleor-apps"
IS_CLOUD_INSTANCE: true
steps:
- uses: actions/checkout@v2

View file

@ -21,6 +21,7 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
MARKETPLACE_URL: "https://apps.saleor.io/"
SALEOR_APPS_ENDPOINT: "https://apps.saleor.io/api/saleor-apps"
ENVIRONMENT: demo-staging
DEMO_MODE: true
steps:

View file

@ -16,6 +16,7 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
MARKETPLACE_URL: "https://apps.saleor.io/"
SALEOR_APPS_ENDPOINT: "https://apps.saleor.io/api/saleor-apps"
ENVIRONMENT: demo
DEMO_MODE: true
steps:

View file

@ -18,6 +18,7 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
MARKETPLACE_URL: "https://apps.saleor.io/"
SALEOR_APPS_ENDPOINT: "https://apps.saleor.io/api/saleor-apps"
ENVIRONMENT: saleor-master-staging
IS_CLOUD_INSTANCE: true
steps:

View file

@ -23,6 +23,7 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
MARKETPLACE_URL: "https://apps.saleor.io/"
SALEOR_APPS_ENDPOINT: "https://apps.saleor.io/api/saleor-apps"
VERSION: ${{ github.event.inputs.git_ref || github.ref_name }}
IS_CLOUD_INSTANCE: true
steps:

View file

@ -6,11 +6,12 @@ COPY . .
ARG APP_MOUNT_URI
ARG API_URI
ARG MARKETPLACE_URL
ARG SALEOR_APPS_ENDPOINT
ARG STATIC_URL
ENV API_URI ${API_URI:-http://localhost:8000/graphql/}
ENV APP_MOUNT_URI ${APP_MOUNT_URI:-/dashboard/}
ENV STATIC_URL ${STATIC_URL:-/dashboard/}
RUN STATIC_URL=${STATIC_URL} API_URI=${API_URI} MARKETPLACE_URL=${MARKETPLACE_URL} APP_MOUNT_URI=${APP_MOUNT_URI} npm run build
RUN STATIC_URL=${STATIC_URL} API_URI=${API_URI} MARKETPLACE_URL=${MARKETPLACE_URL} SALEOR_APPS_ENDPOINT=${SALEOR_APPS_ENDPOINT} APP_MOUNT_URI=${APP_MOUNT_URI} npm run build
FROM nginx:stable
WORKDIR /app

View file

@ -6,6 +6,7 @@ COPY . .
ARG APP_MOUNT_URI
ARG API_URI
ARG MARKETPLACE_URL
ARG SALEOR_APPS_ENDPOINT
ARG STATIC_URL
ENV API_URI ${API_URI:-http://localhost:8000/graphql/}
ENV APP_MOUNT_URI ${APP_MOUNT_URI:-/}

View file

@ -97,6 +97,9 @@
"context": "dialog header",
"string": "Change Password"
},
"+niGip": {
"string": "Saleor Apps"
},
"+pLi+M": {
"context": "issued by app label",
"string": "Issued by app"
@ -2013,6 +2016,9 @@
"context": "change warehouse dialog warehouse list label",
"string": "Warehouses A to Z"
},
"EqDdoh": {
"string": "Local apps are custom webhooks & token pairs that can be used to connect apps and access Saleor API. Read more."
},
"ErNH3D": {
"string": "Define where this attribute should be used in Saleor system"
},
@ -2077,6 +2083,9 @@
"context": "dialog content",
"string": "Select method you want to use to change address"
},
"FLtdaw": {
"string": "Saleor apps are hosted and maintained by Saleor Team. They are preinstalled for you and ready to use"
},
"FNAZoh": {
"string": "Last login"
},
@ -2646,6 +2655,9 @@
"context": "currency code select",
"string": "{code} - {countries}"
},
"J8frvS": {
"string": "3rd party apps"
},
"JDz5h8": {
"context": "number of subcategories in category",
"string": "Subcategories"
@ -3438,6 +3450,10 @@
"context": "attribute list",
"string": "Attribute {number}"
},
"PbQJY5": {
"context": "section header",
"string": "Saleor Apps"
},
"PbqNhi": {
"context": "order status",
"string": "Partially fulfilled"
@ -3628,6 +3644,9 @@
"context": "number of collections",
"string": "Collections ({quantity})"
},
"QdQ9z7": {
"string": "(TUNNEL - DEVELOPMENT)"
},
"Qe4XHv": {
"context": "years after label",
"string": "years after issue"
@ -4153,6 +4172,9 @@
"context": "attribute values",
"string": "Value {number}"
},
"UxTSw7": {
"string": "Webhooks & Events"
},
"UxdBmI": {
"context": "collection availability",
"string": "Availability"
@ -7398,6 +7420,9 @@
"context": "gift card history message",
"string": "Gift card tags were updated"
},
"vkY3W9": {
"string": "Third party apps are installed with App Manifests. They contain UI accessible from dashboard and can extend it. Read more here."
},
"vlLyvk": {
"string": "{inputType} attributes cannot be used as variant selection attributes."
},

View file

@ -0,0 +1,72 @@
import { Typography } from "@material-ui/core";
import { CopyIcon, makeStyles, Tooltip } from "@saleor/macaw-ui";
import clsx from "clsx";
import React, { useState } from "react";
const useStyles = makeStyles(
theme => ({
"@keyframes pulse": {
from: { transform: "scale(1)" },
to: { transform: "scale(1.2)" },
},
manifestText: {
color: theme.palette.text.secondary,
"&:hover svg": {
visibility: "visible",
},
},
copyIcon: {
marginRight: theme.spacing(1),
visibility: "hidden",
verticalAlign: "middle",
transition: "0.2s",
},
copyIconColorful: {
color: theme.palette.primary.main,
animation: "$pulse 0.2s",
},
}),
{ name: "AppManifestTableDisplay" },
);
interface AppManifestTableDisplayProps {
manifestUrl: string;
}
const getAppDomainFromManifest = (manifest: string) => new URL(manifest).host;
export const AppManifestTableDisplay = ({
manifestUrl,
}: AppManifestTableDisplayProps) => {
const styles = useStyles();
const [copied, setCopied] = useState(false);
return (
<Tooltip placement="top" title={manifestUrl} header="App Manifest URL">
<Typography
onMouseOut={() => setCopied(false)}
className={styles.manifestText}
onClick={e => {
try {
e.stopPropagation();
e.preventDefault();
navigator.clipboard.writeText(manifestUrl);
setCopied(true);
} catch (e) {
// Copy not supported, ignore
}
}}
>
{!!navigator.clipboard && (
<CopyIcon
className={clsx(styles.copyIcon, {
[styles.copyIconColorful]: copied,
})}
/>
)}
{getAppDomainFromManifest(manifestUrl)}
</Typography>
</Tooltip>
);
};

View file

@ -0,0 +1,46 @@
import { PageTab, PageTabs } from "@saleor/macaw-ui";
import React, { ComponentProps } from "react";
import { useIntl } from "react-intl";
export type AppPageTabValue =
| "THIRD_PARTY"
| "WEBHOOKS_AND_EVENTS"
| "SALEOR_APPS";
type AllProps = ComponentProps<typeof PageTabs>;
type AvailableProps = Omit<AllProps, "children" | "onChange" | "value"> & {
value: AppPageTabValue;
showSaleorApps: boolean;
onChange(newValue: AppPageTabValue): void;
};
export const AppPageTabs = ({ showSaleorApps, ...props }: AvailableProps) => {
const intl = useIntl();
return (
<PageTabs {...props}>
<PageTab
value="THIRD_PARTY"
label={intl.formatMessage({
defaultMessage: "3rd party apps",
id: "J8frvS",
})}
/>
<PageTab
value="WEBHOOKS_AND_EVENTS"
label={intl.formatMessage({
defaultMessage: "Webhooks & Events",
id: "UxTSw7",
})}
/>
{showSaleorApps && (
<PageTab
value="SALEOR_APPS"
label={intl.formatMessage({
defaultMessage: "Saleor Apps",
id: "+niGip",
})}
/>
)}
</PageTabs>
);
};

View file

@ -1,11 +1,17 @@
import {
AppPageTabs,
AppPageTabValue,
} from "@saleor/apps/components/AppPageTabs/AppPageTabs";
import { useSaleorApps } from "@saleor/apps/hooks/useSaleorApps";
import CardSpacer from "@saleor/components/CardSpacer";
import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader";
import { AppsInstallationsQuery, AppsListQuery } from "@saleor/graphql";
import { sectionNames } from "@saleor/intl";
import { makeStyles } from "@saleor/macaw-ui";
import { ListProps } from "@saleor/types";
import React from "react";
import { useIntl } from "react-intl";
import React, { useEffect, useMemo, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import AppsInProgress from "../AppsInProgress/AppsInProgress";
import CustomApps from "../CustomApps/CustomApps";
@ -22,6 +28,17 @@ export interface AppsListPageProps extends ListProps {
onAppInstallRetry: (id: string) => void;
}
const useStyles = makeStyles(
theme => ({
topTabs: {
marginBottom: theme.spacing(4),
},
}),
{
name: "AppsListPageStyles",
},
);
const AppsListPage: React.FC<AppsListPageProps> = ({
appsInProgressList,
customAppsList,
@ -33,34 +50,140 @@ const AppsListPage: React.FC<AppsListPageProps> = ({
onAppInstallRetry,
...listProps
}) => {
const {
fetchApps,
apps: fetchedSaleorApps,
saleorAppsEnabled,
} = useSaleorApps();
useEffect(() => {
if (saleorAppsEnabled) {
fetchApps();
}
}, [saleorAppsEnabled, fetchApps]);
const styles = useStyles();
const intl = useIntl();
const [activeTab, setActiveTab] = useState<AppPageTabValue>("THIRD_PARTY");
const appsInProgress = appsInProgressList?.appsInstallations;
const thirdPartyApps = useMemo(
() =>
installedAppsList?.filter(
app =>
!(fetchedSaleorApps ?? []).find(fetchedApp =>
app.node.manifestUrl.includes(fetchedApp.hostname),
),
),
[installedAppsList, fetchedSaleorApps],
);
const saleorApps = useMemo(
() =>
fetchedSaleorApps
?.map(app =>
installedAppsList?.find(installedApp =>
installedApp.node.manifestUrl.includes(app.hostname),
),
)
.filter(Boolean),
[installedAppsList, fetchedSaleorApps],
);
const renderContent = () => {
switch (activeTab) {
case "THIRD_PARTY": {
return (
<>
<p>
<FormattedMessage
defaultMessage="Third party apps are installed with App Manifests. They contain UI
accessible from dashboard and can extend it. Read more here."
id="vkY3W9"
/>
</p>
<InstalledApps
title={intl.formatMessage({
id: "BvmnJq",
defaultMessage: "Third Party Apps",
description: "section header",
})}
appsList={thirdPartyApps}
onRemove={onInstalledAppRemove}
displayQuickManifestButton
{...listProps}
/>
<CardSpacer />
{!!appsInProgress?.length && (
<>
<CardSpacer />
<AppsInProgress
appsList={appsInProgress}
onAppInstallRetry={onAppInstallRetry}
onRemove={onAppInProgressRemove}
/>
</>
)}
</>
);
}
case "WEBHOOKS_AND_EVENTS": {
return (
<>
<p>
<FormattedMessage
defaultMessage="Local apps are custom webhooks & token pairs that can be used to
connect apps and access Saleor API. Read more."
id="EqDdoh"
/>
</p>
<CustomApps
appsList={customAppsList}
getCustomAppHref={getCustomAppHref}
onRemove={onCustomAppRemove}
/>
</>
);
}
case "SALEOR_APPS": {
return (
<>
<p>
<FormattedMessage
defaultMessage="Saleor apps are hosted and maintained by Saleor Team. They are
preinstalled for you and ready to use"
id="FLtdaw"
/>
</p>
<InstalledApps
title={intl.formatMessage({
id: "PbQJY5",
defaultMessage: "Saleor Apps",
description: "section header",
})}
appsList={saleorApps}
onRemove={onInstalledAppRemove}
{...listProps}
/>
</>
);
}
}
};
return (
<Container>
<PageHeader title={intl.formatMessage(sectionNames.apps)} />
<InstalledApps
appsList={installedAppsList}
onRemove={onInstalledAppRemove}
{...listProps}
<AppPageTabs
showSaleorApps={saleorAppsEnabled}
className={styles.topTabs}
onChange={setActiveTab}
value={activeTab}
/>
<CardSpacer />
<CustomApps
appsList={customAppsList}
getCustomAppHref={getCustomAppHref}
onRemove={onCustomAppRemove}
/>
{!!appsInProgress?.length && (
<>
<CardSpacer />
<AppsInProgress
appsList={appsInProgress}
onAppInstallRetry={onAppInstallRetry}
onRemove={onAppInProgressRemove}
/>
</>
)}
{renderContent()}
</Container>
);
};

View file

@ -6,9 +6,11 @@ import {
TableRow,
Typography,
} from "@material-ui/core";
import { AppManifestTableDisplay } from "@saleor/apps/components/AppManifestTableDisplay/AppManifestTableDisplay";
import { InstallWithManifestFormButton } from "@saleor/apps/components/InstallWithManifestFormButton";
import { useAppListContext } from "@saleor/apps/context";
import { appUrl, createAppInstallUrl } from "@saleor/apps/urls";
import { isAppInTunnel } from "@saleor/apps/utils";
import CardTitle from "@saleor/components/CardTitle";
import { IconButton } from "@saleor/components/IconButton";
import { TableButtonWrapper } from "@saleor/components/TableButtonWrapper/TableButtonWrapper";
@ -18,9 +20,8 @@ import useNavigator from "@saleor/hooks/useNavigator";
import { DeleteIcon, ResponsiveTable } from "@saleor/macaw-ui";
import { renderCollection } from "@saleor/misc";
import { ListProps } from "@saleor/types";
import clsx from "clsx";
import React, { useCallback } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { FormattedMessage } from "react-intl";
import { useStyles } from "../../styles";
import { AppPermissions } from "../AppPermissions/AppPermissions";
@ -29,14 +30,17 @@ import AppsSkeleton from "../AppsSkeleton";
export interface InstalledAppsProps extends ListProps {
appsList: AppsListQuery["apps"]["edges"];
onRemove: (id: string) => void;
displayQuickManifestButton?: boolean;
title: string;
}
const InstalledApps: React.FC<InstalledAppsProps> = ({
appsList,
onRemove,
title,
displayQuickManifestButton = false,
...props
}) => {
const intl = useIntl();
const classes = useStyles(props);
const { activateApp, deactivateApp } = useAppListContext();
const navigate = useNavigator();
@ -59,15 +63,15 @@ const InstalledApps: React.FC<InstalledAppsProps> = ({
return (
<Card className={classes.apps}>
<CardTitle
title={intl.formatMessage({
id: "BvmnJq",
defaultMessage: "Third Party Apps",
description: "section header",
})}
title={title}
toolbar={
<InstallWithManifestFormButton
onSubmitted={navigateToAppInstallPage}
/>
displayQuickManifestButton ? (
<InstallWithManifestFormButton
onSubmitted={navigateToAppInstallPage}
/>
) : (
undefined
)
}
/>
<ResponsiveTable>
@ -85,15 +89,22 @@ const InstalledApps: React.FC<InstalledAppsProps> = ({
<span data-tc="name" className={classes.appName}>
{app.node.name}
</span>
{app.node.manifestUrl &&
isAppInTunnel(app.node.manifestUrl) ? (
<Typography variant="caption">
<FormattedMessage
defaultMessage="(TUNNEL - DEVELOPMENT)"
id="QdQ9z7"
/>
</Typography>
) : null}
</TableCell>
<TableCell className={classes.colAction}>
{app.node.manifestUrl && (
<Typography
className={clsx(classes.text, classes.manifestUrl)}
variant="body2"
>
{app.node.manifestUrl}
</Typography>
<AppManifestTableDisplay
manifestUrl={app.node.manifestUrl}
/>
)}
<TableButtonWrapper>
<Switch

View file

@ -0,0 +1,44 @@
import { SALEOR_APPS_ENDPOINT } from "@saleor/config";
import { useCallback, useState } from "react";
export interface SaleorApp {
name: string;
hostname: string;
}
const saleorAppsEnabled = Boolean(SALEOR_APPS_ENDPOINT);
export const useSaleorApps = () => {
const [apps, setApps] = useState<SaleorApp[] | undefined>(undefined);
const fetchApps = useCallback(async () => {
if (!saleorAppsEnabled) {
return;
}
return fetch(SALEOR_APPS_ENDPOINT)
.then(response => response.json())
.then((data: SaleorApp[]) => {
if (
!data.every(
item =>
typeof item.hostname === "string" ||
typeof item.name === "string",
)
) {
console.error(
'Invalid Saleor Apps data received from Marketplace. Expected array of objects with "name" and "hostname" properties',
);
return;
}
setApps(data);
});
}, []);
return {
saleorAppsEnabled,
apps,
fetchApps,
};
};

View file

@ -53,7 +53,7 @@ export const useStyles = makeStyles(
},
colName: {
paddingLeft: 0,
width: theme.spacing(30),
minWidth: theme.spacing(30),
},
colSpinner: {
"& svg": {
@ -213,9 +213,6 @@ export const useStyles = makeStyles(
marginRight: theme.spacing(1),
},
},
manifestUrl: {
marginRight: theme.spacing(1),
},
}),
{ name: "AppList" },
);

4
src/apps/utils.ts Normal file
View file

@ -0,0 +1,4 @@
const tunnelKeywords = [".ngrok.io", ".saleor.live"];
export const isAppInTunnel = (manifestUrl: string) =>
Boolean(tunnelKeywords.find(keyword => manifestUrl.includes(keyword)));

View file

@ -9,6 +9,7 @@ export const API_URI = process.env.API_URI;
export const SW_INTERVAL = parseInt(process.env.SW_INTERVAL, 10);
export const IS_CLOUD_INSTANCE = process.env.IS_CLOUD_INSTANCE === "true";
export const MARKETPLACE_URL = process.env.MARKETPLACE_URL;
export const SALEOR_APPS_ENDPOINT = process.env.SALEOR_APPS_ENDPOINT;
export const DEFAULT_INITIAL_SEARCH_DATA: SearchVariables = {
after: null,

View file

@ -25005,6 +25005,53 @@ exports[`Storyshots Views / Apps / Apps list default 1`] = `
/>
</div>
</div>
<div
class="MuiTabs-root-id PageTab-containerRoot-id AppsListPageStyles-topTabs-id"
>
<div
class="MuiTabs-scroller-id MuiTabs-fixed-id"
style="overflow:hidden"
>
<div
class="MuiTabs-flexContainer-id PageTab-containerFlex-id"
role="tablist"
>
<button
aria-selected="true"
class="MuiButtonBase-root-id MuiTab-root-id PageTab-tabRoot-id MuiTab-textColorInherit-id MuiTab-selected-id"
role="tab"
tabindex="0"
type="button"
>
<span
class="MuiTab-wrapper-id"
>
3rd party apps
</span>
<span
class="PrivateTabIndicator-root-id PrivateTabIndicator-colorSecondary-id MuiTabs-indicator-id"
style="display:none"
/>
</button>
<button
aria-selected="false"
class="MuiButtonBase-root-id MuiTab-root-id PageTab-tabRoot-id MuiTab-textColorInherit-id"
role="tab"
tabindex="-1"
type="button"
>
<span
class="MuiTab-wrapper-id"
>
Webhooks & Events
</span>
</button>
</div>
</div>
</div>
<p>
Third party apps are installed with App Manifests. They contain UI accessible from dashboard and can extend it. Read more here.
</p>
<div
class="MuiPaper-root-id MuiCard-root-id AppList-apps-id MuiPaper-elevation0-id MuiPaper-rounded-id"
>
@ -25071,9 +25118,9 @@ exports[`Storyshots Views / Apps / Apps list default 1`] = `
class="MuiTableCell-root-id MuiTableCell-body-id AppList-colAction-id"
>
<div
class="MuiTypography-root-id AppList-text-id AppList-manifestUrl-id MuiTypography-body2-id"
class="MuiTypography-root-id AppManifestTableDisplay-manifestText-id MuiTypography-body1-id"
>
http://localhost:3000/api/manifest
localhost:3000
</div>
<span
class="MuiSwitch-root-id"
@ -25168,9 +25215,9 @@ exports[`Storyshots Views / Apps / Apps list default 1`] = `
class="MuiTableCell-root-id MuiTableCell-body-id AppList-colAction-id"
>
<div
class="MuiTypography-root-id AppList-text-id AppList-manifestUrl-id MuiTypography-body2-id"
class="MuiTypography-root-id AppManifestTableDisplay-manifestText-id MuiTypography-body1-id"
>
http://localhost:3000/api/manifest
localhost:3000
</div>
<span
class="MuiSwitch-root-id"
@ -25254,96 +25301,6 @@ exports[`Storyshots Views / Apps / Apps list default 1`] = `
<div
class="CardSpacer-spacer-id"
/>
<div
class="MuiPaper-root-id MuiCard-root-id AppList-customApps-id MuiPaper-elevation0-id MuiPaper-rounded-id"
>
<div
class="MuiCardHeader-root-id"
>
<div
class="MuiCardHeader-content-id"
>
<span
class="MuiTypography-root-id MuiCardHeader-title-id MuiTypography-h5-id MuiTypography-displayBlock-id"
>
Internal Apps
</span>
</div>
<div
class="MuiCardHeader-action-id"
>
<a
aria-disabled="false"
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id"
data-test-id="create-app"
href="/apps/custom/add"
role="button"
tabindex="0"
>
<span
class="MuiButton-label-id"
>
Create App
</span>
</a>
</div>
</div>
<div
class="ResponsiveTable-root-id"
>
<table
class="MuiTable-root-id"
>
<tbody
class="MuiTableBody-root-id"
>
<tr
class="MuiTableRow-root-id AppList-tableRow-id MuiTableRow-hover-id"
>
<td
class="MuiTableCell-root-id MuiTableCell-body-id AppList-colName-id"
>
<span
class="AppList-appName-id"
data-tc="name"
>
app custom
</span>
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id AppList-colAction-id"
>
<button
class="MuiButtonBase-root-id IconButton-secondary-id IconButton-hoverOutline-id"
color="primary"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id MacawIcon-root-id"
fill="none"
focusable="false"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 10v8m3-8v8m3-8v8M5 4.5V21a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4.5m-14 0H4m1 0h4m10 0h1m-1 0h-4m-6 0V3a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v1.5m-6 0h6"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
/>
</svg>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div
class="CardSpacer-spacer-id"
/>
@ -25559,6 +25516,53 @@ exports[`Storyshots Views / Apps / Apps list loading 1`] = `
/>
</div>
</div>
<div
class="MuiTabs-root-id PageTab-containerRoot-id AppsListPageStyles-topTabs-id"
>
<div
class="MuiTabs-scroller-id MuiTabs-fixed-id"
style="overflow:hidden"
>
<div
class="MuiTabs-flexContainer-id PageTab-containerFlex-id"
role="tablist"
>
<button
aria-selected="true"
class="MuiButtonBase-root-id MuiTab-root-id PageTab-tabRoot-id MuiTab-textColorInherit-id MuiTab-selected-id"
role="tab"
tabindex="0"
type="button"
>
<span
class="MuiTab-wrapper-id"
>
3rd party apps
</span>
<span
class="PrivateTabIndicator-root-id PrivateTabIndicator-colorSecondary-id MuiTabs-indicator-id"
style="display:none"
/>
</button>
<button
aria-selected="false"
class="MuiButtonBase-root-id MuiTab-root-id PageTab-tabRoot-id MuiTab-textColorInherit-id"
role="tab"
tabindex="-1"
type="button"
>
<span
class="MuiTab-wrapper-id"
>
Webhooks & Events
</span>
</button>
</div>
</div>
</div>
<p>
Third party apps are installed with App Manifests. They contain UI accessible from dashboard and can extend it. Read more here.
</p>
<div
class="MuiPaper-root-id MuiCard-root-id AppList-apps-id MuiPaper-elevation0-id MuiPaper-rounded-id"
>
@ -25622,68 +25626,6 @@ exports[`Storyshots Views / Apps / Apps list loading 1`] = `
<div
class="CardSpacer-spacer-id"
/>
<div
class="MuiPaper-root-id MuiCard-root-id AppList-customApps-id MuiPaper-elevation0-id MuiPaper-rounded-id"
>
<div
class="MuiCardHeader-root-id"
>
<div
class="MuiCardHeader-content-id"
>
<span
class="MuiTypography-root-id MuiCardHeader-title-id MuiTypography-h5-id MuiTypography-displayBlock-id"
>
Internal Apps
</span>
</div>
<div
class="MuiCardHeader-action-id"
>
<a
aria-disabled="false"
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id"
data-test-id="create-app"
href="/apps/custom/add"
role="button"
tabindex="0"
>
<span
class="MuiButton-label-id"
>
Create App
</span>
</a>
</div>
</div>
<div
class="ResponsiveTable-root-id"
>
<table
class="MuiTable-root-id"
>
<tbody
class="MuiTableBody-root-id"
>
<tr
class="MuiTableRow-root-id AppList-tableRow-id MuiTableRow-hover-id"
>
<td
class="MuiTableCell-root-id MuiTableCell-body-id AppList-colName-id"
colspan="2"
>
<span
class="Skeleton-skeleton-id"
data-test-id="skeleton"
>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
`;
@ -25716,6 +25658,53 @@ exports[`Storyshots Views / Apps / Apps list no data 1`] = `
/>
</div>
</div>
<div
class="MuiTabs-root-id PageTab-containerRoot-id AppsListPageStyles-topTabs-id"
>
<div
class="MuiTabs-scroller-id MuiTabs-fixed-id"
style="overflow:hidden"
>
<div
class="MuiTabs-flexContainer-id PageTab-containerFlex-id"
role="tablist"
>
<button
aria-selected="true"
class="MuiButtonBase-root-id MuiTab-root-id PageTab-tabRoot-id MuiTab-textColorInherit-id MuiTab-selected-id"
role="tab"
tabindex="0"
type="button"
>
<span
class="MuiTab-wrapper-id"
>
3rd party apps
</span>
<span
class="PrivateTabIndicator-root-id PrivateTabIndicator-colorSecondary-id MuiTabs-indicator-id"
style="display:none"
/>
</button>
<button
aria-selected="false"
class="MuiButtonBase-root-id MuiTab-root-id PageTab-tabRoot-id MuiTab-textColorInherit-id"
role="tab"
tabindex="-1"
type="button"
>
<span
class="MuiTab-wrapper-id"
>
Webhooks & Events
</span>
</button>
</div>
</div>
</div>
<p>
Third party apps are installed with App Manifests. They contain UI accessible from dashboard and can extend it. Read more here.
</p>
<div
class="MuiPaper-root-id MuiCard-root-id AppList-apps-id MuiPaper-elevation0-id MuiPaper-rounded-id"
>
@ -25777,66 +25766,6 @@ exports[`Storyshots Views / Apps / Apps list no data 1`] = `
<div
class="CardSpacer-spacer-id"
/>
<div
class="MuiPaper-root-id MuiCard-root-id AppList-customApps-id MuiPaper-elevation0-id MuiPaper-rounded-id"
>
<div
class="MuiCardHeader-root-id"
>
<div
class="MuiCardHeader-content-id"
>
<span
class="MuiTypography-root-id MuiCardHeader-title-id MuiTypography-h5-id MuiTypography-displayBlock-id"
>
Internal Apps
</span>
</div>
<div
class="MuiCardHeader-action-id"
>
<a
aria-disabled="false"
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id"
data-test-id="create-app"
href="/apps/custom/add"
role="button"
tabindex="0"
>
<span
class="MuiButton-label-id"
>
Create App
</span>
</a>
</div>
</div>
<div
class="ResponsiveTable-root-id"
>
<table
class="MuiTable-root-id"
>
<tbody
class="MuiTableBody-root-id"
>
<tr
class="MuiTableRow-root-id AppList-tableRow-id MuiTableRow-hover-id"
>
<td
class="MuiTableCell-root-id MuiTableCell-body-id AppList-colName-id"
>
<div
class="MuiTypography-root-id AppList-text-id MuiTypography-body2-id"
>
Your custom-created apps will be shown here.
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
`;

View file

@ -39,6 +39,7 @@ const htmlWebpackPlugin = new HtmlWebpackPlugin({
const environmentPlugin = new webpack.EnvironmentPlugin({
API_URI: "",
MARKETPLACE_URL: "",
SALEOR_APPS_ENDPOINT: "",
APP_MOUNT_URI: "/",
DEMO_MODE: false,
ENVIRONMENT: "",