Apps tabs (#2380)
* 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:
parent
5c48f5387e
commit
d67e4799b2
19 changed files with 522 additions and 261 deletions
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -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.
|
||||
|
|
1
.github/workflows/deploy-cloud.yaml
vendored
1
.github/workflows/deploy-cloud.yaml
vendored
|
@ -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
|
||||
|
|
1
.github/workflows/deploy-demo-staging.yaml
vendored
1
.github/workflows/deploy-demo-staging.yaml
vendored
|
@ -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:
|
||||
|
|
1
.github/workflows/deploy-demo.yaml
vendored
1
.github/workflows/deploy-demo.yaml
vendored
|
@ -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:
|
||||
|
|
1
.github/workflows/deploy-master-staging.yaml
vendored
1
.github/workflows/deploy-master-staging.yaml
vendored
|
@ -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:
|
||||
|
|
1
.github/workflows/deploy-staging.yaml
vendored
1
.github/workflows/deploy-staging.yaml
vendored
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:-/}
|
||||
|
|
|
@ -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."
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
46
src/apps/components/AppPageTabs/AppPageTabs.tsx
Normal file
46
src/apps/components/AppPageTabs/AppPageTabs.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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,24 +50,73 @@ 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 (
|
||||
<Container>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.apps)} />
|
||||
<>
|
||||
<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
|
||||
appsList={installedAppsList}
|
||||
title={intl.formatMessage({
|
||||
id: "BvmnJq",
|
||||
defaultMessage: "Third Party Apps",
|
||||
description: "section header",
|
||||
})}
|
||||
appsList={thirdPartyApps}
|
||||
onRemove={onInstalledAppRemove}
|
||||
displayQuickManifestButton
|
||||
{...listProps}
|
||||
/>
|
||||
|
||||
<CardSpacer />
|
||||
<CustomApps
|
||||
appsList={customAppsList}
|
||||
getCustomAppHref={getCustomAppHref}
|
||||
onRemove={onCustomAppRemove}
|
||||
/>
|
||||
|
||||
{!!appsInProgress?.length && (
|
||||
<>
|
||||
<CardSpacer />
|
||||
|
@ -61,6 +127,63 @@ const AppsListPage: React.FC<AppsListPageProps> = ({
|
|||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
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)} />
|
||||
<AppPageTabs
|
||||
showSaleorApps={saleorAppsEnabled}
|
||||
className={styles.topTabs}
|
||||
onChange={setActiveTab}
|
||||
value={activeTab}
|
||||
/>
|
||||
{renderContent()}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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={
|
||||
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
|
||||
|
|
44
src/apps/hooks/useSaleorApps.ts
Normal file
44
src/apps/hooks/useSaleorApps.ts
Normal 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,
|
||||
};
|
||||
};
|
|
@ -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
4
src/apps/utils.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
const tunnelKeywords = [".ngrok.io", ".saleor.live"];
|
||||
|
||||
export const isAppInTunnel = (manifestUrl: string) =>
|
||||
Boolean(tunnelKeywords.find(keyword => manifestUrl.includes(keyword)));
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
`;
|
||||
|
|
|
@ -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: "",
|
||||
|
|
Loading…
Reference in a new issue