Compare commits
3 commits
main
...
slack-maca
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7946576714 | ||
![]() |
2b27c0c463 | ||
![]() |
0fb3108321 |
12 changed files with 128 additions and 420 deletions
5
.changeset/plenty-mayflies-enjoy.md
Normal file
5
.changeset/plenty-mayflies-enjoy.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-app-slack": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Rewritten UI to use modern macaw UI, instead legacy one with Material UI. There are slight changes in typography and spacings, but no new changes were introduced.
|
|
@ -16,7 +16,9 @@
|
||||||
"@material-ui/lab": "4.0.0-alpha.61",
|
"@material-ui/lab": "4.0.0-alpha.61",
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.40.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/macaw-ui": "^0.7.2",
|
"@saleor/apps-ui": "workspace:*",
|
||||||
|
"@saleor/macaw-ui": "0.8.0-pre.95",
|
||||||
|
"@saleor/react-hook-form-macaw": "workspace:*",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@sentry/nextjs": "7.55.2",
|
||||||
"@urql/exchange-auth": "^2.1.4",
|
"@urql/exchange-auth": "^2.1.4",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Typography } from "@material-ui/core";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { Text } from "@saleor/macaw-ui/next";
|
||||||
|
|
||||||
type WarningCause =
|
type WarningCause =
|
||||||
| "not_in_iframe"
|
| "not_in_iframe"
|
||||||
|
@ -20,13 +20,13 @@ const warnings: Record<WarningCause, string> = {
|
||||||
|
|
||||||
export function AccessWarning({ cause = "unknown_cause" }: AccessWarningProps) {
|
export function AccessWarning({ cause = "unknown_cause" }: AccessWarningProps) {
|
||||||
return (
|
return (
|
||||||
<div suppressHydrationWarning>
|
<div>
|
||||||
<Typography variant="subtitle1">
|
<Text as={"h2"} variant="heading">
|
||||||
App can't be accessed outside of the Saleor Dashboard
|
App can't be accessed outside of the Saleor Dashboard
|
||||||
</Typography>
|
</Text>
|
||||||
<Typography variant="subtitle2" style={{ marginTop: "2rem" }}>
|
<Text variant="body" style={{ marginTop: "2rem" }}>
|
||||||
❌ {warnings[cause]}
|
❌ {warnings[cause]}
|
||||||
</Typography>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { Box } from "@saleor/macaw-ui/next";
|
||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
|
||||||
root: {
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "280px auto 280px",
|
|
||||||
alignItems: "start",
|
|
||||||
gap: 32,
|
|
||||||
maxWidth: 1180,
|
|
||||||
margin: "0 auto",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
type Props = PropsWithChildren<{}>;
|
type Props = PropsWithChildren<{}>;
|
||||||
|
|
||||||
export const AppColumnsLayout = ({ children }: Props) => {
|
export const AppColumnsLayout = ({ children }: Props) => {
|
||||||
const styles = useStyles();
|
return (
|
||||||
|
<Box
|
||||||
return <div className={styles.root}>{children}</div>;
|
paddingX={8}
|
||||||
|
display={"grid"}
|
||||||
|
marginTop={8}
|
||||||
|
__maxWidth={"1180px"}
|
||||||
|
__gridTemplateColumns={"380px auto"}
|
||||||
|
gap={8}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { CircularProgress, Typography } from "@material-ui/core";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { useStyles } from "./styles";
|
|
||||||
|
|
||||||
function LoadingPage() {
|
|
||||||
const classes = useStyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.loaderContainer}>
|
|
||||||
<CircularProgress size={100} />
|
|
||||||
|
|
||||||
<Typography variant="subtitle1" className={classes.message}>
|
|
||||||
Attempting connection to Saleor Dashboard
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
loaderContainer: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
},
|
|
||||||
message: {
|
|
||||||
marginTop: theme.spacing(4),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export { useStyles };
|
|
|
@ -1,40 +1,25 @@
|
||||||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
import { useTheme } from "@saleor/macaw-ui";
|
import { useTheme } from "@saleor/macaw-ui/next";
|
||||||
import { memo, useEffect } from "react";
|
import { memo, useEffect } from "react";
|
||||||
|
|
||||||
/**
|
// todo move to shared
|
||||||
* Macaw-ui stores its theme mode in memory and local storage. To synchronize App with Dashboard,
|
export function ThemeSynchronizer() {
|
||||||
* Macaw must be informed about this change from AppBridge.
|
const { appBridgeState } = useAppBridge();
|
||||||
*
|
const { setTheme } = useTheme();
|
||||||
* If you are not using Macaw, you can remove this.
|
|
||||||
*/
|
|
||||||
function _ThemeSynchronizer() {
|
|
||||||
const { appBridgeState, appBridge } = useAppBridge();
|
|
||||||
const { setTheme, themeType } = useTheme();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* todo - replace this hook to appBridge.subscribe and react only only on initial theme event
|
|
||||||
* useEffect(() =>{
|
|
||||||
* appBridge?.subscribe('theme',console.log)
|
|
||||||
* },[appBridge])
|
|
||||||
*/
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!setTheme || !appBridgeState?.theme) {
|
if (!setTheme || !appBridgeState?.theme) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (themeType !== appBridgeState?.theme) {
|
if (appBridgeState.theme === "light") {
|
||||||
setTheme(appBridgeState.theme);
|
setTheme("defaultLight");
|
||||||
/**
|
|
||||||
* Hack to fix macaw, which is going into infinite loop on light mode (probably de-sync local storage with react state)
|
|
||||||
* TODO Fix me when Macaw 2.0 is shipped
|
|
||||||
*/
|
|
||||||
window.localStorage.setItem("macaw-ui-theme", appBridgeState.theme);
|
|
||||||
}
|
}
|
||||||
}, [appBridgeState?.theme, setTheme, themeType]);
|
|
||||||
|
if (appBridgeState.theme === "dark") {
|
||||||
|
setTheme("defaultDark");
|
||||||
|
}
|
||||||
|
}, [appBridgeState?.theme, setTheme]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ThemeSynchronizer = memo(_ThemeSynchronizer);
|
|
||||||
|
|
|
@ -1,90 +1,26 @@
|
||||||
import "../styles/globals.css";
|
import "@saleor/macaw-ui/next/style";
|
||||||
|
|
||||||
import { Theme } from "@material-ui/core/styles";
|
|
||||||
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
||||||
|
import React from "react";
|
||||||
|
import { AppProps } from "next/app";
|
||||||
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
||||||
import {
|
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
|
||||||
dark,
|
import { NoSSRWrapper } from "@saleor/apps-shared";
|
||||||
light,
|
|
||||||
SaleorThemeColors,
|
|
||||||
ThemeProvider as MacawUIThemeProvider,
|
|
||||||
} from "@saleor/macaw-ui";
|
|
||||||
import React, { PropsWithChildren, useEffect } from "react";
|
|
||||||
|
|
||||||
import { AppLayoutProps } from "../../types";
|
|
||||||
import { ThemeSynchronizer } from "../hooks/theme-synchronizer";
|
import { ThemeSynchronizer } from "../hooks/theme-synchronizer";
|
||||||
import { NoSSRWrapper } from "../lib/no-ssr-wrapper";
|
|
||||||
|
|
||||||
const themeOverrides: Partial<Theme> = {
|
|
||||||
overrides: {
|
|
||||||
MuiTableCell: {
|
|
||||||
body: {
|
|
||||||
paddingBottom: 8,
|
|
||||||
paddingTop: 8,
|
|
||||||
},
|
|
||||||
root: {
|
|
||||||
height: 56,
|
|
||||||
paddingBottom: 4,
|
|
||||||
paddingTop: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure instance is a singleton.
|
* Ensure instance is a singleton.
|
||||||
* TODO: This is React 18 issue, consider hiding this workaround inside app-sdk
|
|
||||||
*/
|
*/
|
||||||
const appBridgeInstance =
|
export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge() : undefined;
|
||||||
typeof window !== "undefined" ? new AppBridge({ autoNotifyReady: false }) : undefined;
|
|
||||||
|
|
||||||
type PalettesOverride = Record<"light" | "dark", SaleorThemeColors>;
|
function SaleorApp({ Component, pageProps }: AppProps) {
|
||||||
|
// @ts-ignore todo refactor
|
||||||
/**
|
|
||||||
* Temporary override of colors, to match new dashboard palette.
|
|
||||||
* Long term this will be replaced with Macaw UI 2.x with up to date design tokens
|
|
||||||
*/
|
|
||||||
const palettes: PalettesOverride = {
|
|
||||||
light: {
|
|
||||||
...light,
|
|
||||||
background: {
|
|
||||||
default: "#fff",
|
|
||||||
paper: "#fff",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
...dark,
|
|
||||||
background: {
|
|
||||||
default: "hsla(211, 42%, 14%, 1)",
|
|
||||||
paper: "hsla(211, 42%, 14%, 1)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* That's a hack required by Macaw-UI incompatibility with React@18
|
|
||||||
*/
|
|
||||||
const ThemeProvider = MacawUIThemeProvider as React.FC<
|
|
||||||
PropsWithChildren<{ overrides?: Partial<Theme>; ssr: boolean; palettes: PalettesOverride }>
|
|
||||||
>;
|
|
||||||
|
|
||||||
function SaleorApp({ Component, pageProps }: AppLayoutProps) {
|
|
||||||
const getLayout = Component.getLayout ?? ((page) => page);
|
const getLayout = Component.getLayout ?? ((page) => page);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const jssStyles = document.querySelector("#jss-server-side");
|
|
||||||
|
|
||||||
if (jssStyles) {
|
|
||||||
jssStyles?.parentElement?.removeChild(jssStyles);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NoSSRWrapper>
|
<NoSSRWrapper>
|
||||||
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
||||||
<ThemeProvider palettes={palettes} overrides={themeOverrides} ssr>
|
<ThemeProvider>
|
||||||
<ThemeSynchronizer />
|
<ThemeSynchronizer />
|
||||||
<RoutePropagator />
|
|
||||||
{getLayout(<Component {...pageProps} />)}
|
{getLayout(<Component {...pageProps} />)}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</AppBridgeProvider>
|
</AppBridgeProvider>
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
/**
|
|
||||||
* NOTE: This requires `@sentry/nextjs` version 7.3.0 or higher.
|
|
||||||
*
|
|
||||||
* NOTE: If using this with `next` version 12.2.0 or lower, uncomment the
|
|
||||||
* penultimate line in `CustomErrorComponent`.
|
|
||||||
*
|
|
||||||
* This page is loaded by Nextjs:
|
|
||||||
* - on the server, when data-fetching methods throw or reject
|
|
||||||
* - on the client, when `getInitialProps` throws or rejects
|
|
||||||
* - on the client, when a React lifecycle method throws or rejects, and it's
|
|
||||||
* caught by the built-in Nextjs error boundary
|
|
||||||
*
|
|
||||||
* See:
|
|
||||||
* - https://nextjs.org/docs/basic-features/data-fetching/overview
|
|
||||||
* - https://nextjs.org/docs/api-reference/data-fetching/get-initial-props
|
|
||||||
* - https://reactjs.org/docs/error-boundaries.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
import NextErrorComponent from "next/error";
|
|
||||||
|
|
||||||
const CustomErrorComponent = (props) => {
|
|
||||||
/*
|
|
||||||
* If you're using a Nextjs version prior to 12.2.1, uncomment this to
|
|
||||||
* compensate for https://github.com/vercel/next.js/issues/8592
|
|
||||||
* Sentry.captureUnderscoreErrorException(props);
|
|
||||||
*/
|
|
||||||
|
|
||||||
return <NextErrorComponent statusCode={props.statusCode} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
CustomErrorComponent.getInitialProps = async (contextData) => {
|
|
||||||
/*
|
|
||||||
* In case this is running in a serverless function, await this in order to give Sentry
|
|
||||||
* time to send the error before the lambda exits
|
|
||||||
*/
|
|
||||||
await Sentry.captureUnderscoreErrorException(contextData);
|
|
||||||
|
|
||||||
// This will contain the status code of the response
|
|
||||||
return NextErrorComponent.getInitialProps(contextData);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CustomErrorComponent;
|
|
|
@ -1,48 +1,26 @@
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardHeader,
|
|
||||||
Link,
|
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
TextField,
|
|
||||||
Typography,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import Skeleton from "@material-ui/lab/Skeleton";
|
|
||||||
import { useAppBridge, withAuthorization } from "@saleor/app-sdk/app-bridge";
|
import { useAppBridge, withAuthorization } from "@saleor/app-sdk/app-bridge";
|
||||||
import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@saleor/app-sdk/const";
|
import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@saleor/app-sdk/const";
|
||||||
import { ConfirmButton, ConfirmButtonTransitionState, makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import { ChangeEvent, ReactElement, SyntheticEvent, useEffect, useState } from "react";
|
import { ChangeEvent, ReactElement, SyntheticEvent, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { AccessWarning } from "../components/AccessWarning/AccessWarning";
|
|
||||||
import { ConfigurationError } from "../components/ConfigurationError/ConfigurationError";
|
import { ConfigurationError } from "../components/ConfigurationError/ConfigurationError";
|
||||||
import { useAppApi } from "../hooks/useAppApi";
|
import { useAppApi } from "../hooks/useAppApi";
|
||||||
import { AppColumnsLayout } from "../components/AppColumnsLayout/AppColumnsLayout";
|
import { AppColumnsLayout } from "../components/AppColumnsLayout/AppColumnsLayout";
|
||||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
|
|
||||||
|
import { Input, Text, Box, Button } from "@saleor/macaw-ui/next";
|
||||||
|
|
||||||
|
import { TextLink } from "@saleor/apps-ui";
|
||||||
|
import { AccessWarning } from "../components/AccessWarning/AccessWarning";
|
||||||
|
|
||||||
interface ConfigurationField {
|
interface ConfigurationField {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
confirmButton: {
|
|
||||||
marginLeft: "auto",
|
|
||||||
},
|
|
||||||
fieldContainer: {
|
|
||||||
marginBottom: theme.spacing(2),
|
|
||||||
},
|
|
||||||
additionalInfo: {
|
|
||||||
marginBottom: theme.spacing(3),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
function Configuration() {
|
function Configuration() {
|
||||||
const classes = useStyles();
|
|
||||||
const { appBridgeState } = useAppBridge();
|
const { appBridgeState } = useAppBridge();
|
||||||
const { notifyError, notifySuccess } = useDashboardNotification();
|
const { notifyError, notifySuccess } = useDashboardNotification();
|
||||||
const [configuration, setConfiguration] = useState<ConfigurationField[]>();
|
const [configuration, setConfiguration] = useState<ConfigurationField[]>();
|
||||||
const [transitionState, setTransitionState] = useState<ConfirmButtonTransitionState>("default");
|
|
||||||
|
|
||||||
const { data: configurationData, error } = useAppApi<{ data: ConfigurationField[] }>({
|
const { data: configurationData, error } = useAppApi<{ data: ConfigurationField[] }>({
|
||||||
url: "/api/configuration",
|
url: "/api/configuration",
|
||||||
|
@ -56,7 +34,6 @@ function Configuration() {
|
||||||
|
|
||||||
const handleSubmit = (event: SyntheticEvent) => {
|
const handleSubmit = (event: SyntheticEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setTransitionState("loading");
|
|
||||||
|
|
||||||
fetch("/api/configuration", {
|
fetch("/api/configuration", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -68,11 +45,9 @@ function Configuration() {
|
||||||
body: JSON.stringify({ data: configuration }),
|
body: JSON.stringify({ data: configuration }),
|
||||||
})
|
})
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
setTransitionState(response.status === 200 ? "success" : "error");
|
|
||||||
notifySuccess("Success", "Configuration updated successfully");
|
notifySuccess("Success", "Configuration updated successfully");
|
||||||
})
|
})
|
||||||
.catch(async () => {
|
.catch(async () => {
|
||||||
setTransitionState("error");
|
|
||||||
await notifyError("Configuration update failed");
|
await notifyError("Configuration update failed");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -91,33 +66,29 @@ function Configuration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configuration === undefined) {
|
if (configuration === undefined) {
|
||||||
return <Skeleton />;
|
return <Text>Loading</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
{configuration!.map(({ key, value }) => (
|
{configuration!.map(({ key, value }) => (
|
||||||
<div key={key} className={classes.fieldContainer}>
|
<div key={key}>
|
||||||
<TextField label={key} name={key} fullWidth onChange={onChange} value={value} />
|
<Input
|
||||||
</div>
|
label={key}
|
||||||
))}
|
name={key}
|
||||||
<p className={classes.additionalInfo}>
|
onChange={onChange}
|
||||||
This webhook will be called when new order is created and `order_created` event is
|
value={value}
|
||||||
triggered.
|
helperText={
|
||||||
</p>
|
"This webhook will be called when new order is created and `order_created` event is triggered."
|
||||||
<div>
|
}
|
||||||
<ConfirmButton
|
|
||||||
type="submit"
|
|
||||||
variant="primary"
|
|
||||||
fullWidth
|
|
||||||
transitionState={transitionState}
|
|
||||||
labels={{
|
|
||||||
confirm: "Save",
|
|
||||||
error: "Error",
|
|
||||||
}}
|
|
||||||
className={classes.confirmButton}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
<Box marginTop={4}>
|
||||||
|
<Button type="submit" variant="primary">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -147,50 +118,29 @@ function Instructions() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography>How to configure</Typography>
|
<Text variant={"heading"}>How to configure</Text>
|
||||||
<List>
|
<Box display={"flex"} gap={2} as={"ul"} flexDirection={"column"}>
|
||||||
<ListItem>
|
<li>
|
||||||
<Link
|
<TextLink href={slackUrl.href}>1. Install Slack application</TextLink>
|
||||||
onClick={(e) => {
|
</li>
|
||||||
e.preventDefault();
|
<li>
|
||||||
openExternalUrl(slackUrl.href);
|
<Text>
|
||||||
}}
|
2. Copy incoming Webhook URL from Slack app configuration and paste it below into{" "}
|
||||||
href={slackUrl.href}
|
<Text variant={"bodyStrong"}>WEBHOOK_URL</Text> field
|
||||||
>
|
</Text>
|
||||||
Install Slack application
|
</li>
|
||||||
</Link>
|
<li>
|
||||||
</ListItem>
|
<Text>3. Save configuration</Text>
|
||||||
<ListItem>
|
</li>
|
||||||
Copy incoming Webhook URL from Slack app configuration and paste it below into
|
</Box>
|
||||||
`WEBHOOK_URL` field
|
<Text variant={"heading"}>Useful links</Text>
|
||||||
</ListItem>
|
<ul>
|
||||||
<ListItem>Save configuration</ListItem>
|
<li>
|
||||||
</List>
|
<TextLink newTab href={"https://api.slack.com/messaging/webhooks"}>
|
||||||
<Typography>Useful links</Typography>
|
|
||||||
<List>
|
|
||||||
<ListItem>
|
|
||||||
<Link
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
openExternalUrl("https://github.com/saleor/saleor-app-slack");
|
|
||||||
}}
|
|
||||||
href="https://github.com/saleor/saleor-app-slack"
|
|
||||||
>
|
|
||||||
Visit repository & readme
|
|
||||||
</Link>
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
<Link
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
openExternalUrl("https://api.slack.com/messaging/webhooks");
|
|
||||||
}}
|
|
||||||
href="https://api.slack.com/messaging/webhooks"
|
|
||||||
>
|
|
||||||
Read about Slack apps that use incoming webhooks
|
Read about Slack apps that use incoming webhooks
|
||||||
</Link>
|
</TextLink>
|
||||||
</ListItem>
|
</li>
|
||||||
</List>
|
</ul>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -204,17 +154,21 @@ const ConfigurationWithAuth = withAuthorization({
|
||||||
|
|
||||||
ConfigurationWithAuth.getLayout = (page: ReactElement) => (
|
ConfigurationWithAuth.getLayout = (page: ReactElement) => (
|
||||||
<AppColumnsLayout>
|
<AppColumnsLayout>
|
||||||
<div />
|
<Box marginBottom={4}>
|
||||||
<Card>
|
|
||||||
<CardHeader title="Configuration" />
|
|
||||||
<CardContent>{page}</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card style={{ marginBottom: 40 }}>
|
|
||||||
<CardHeader title="Instructions" />
|
|
||||||
<CardContent>
|
|
||||||
<Instructions />
|
<Instructions />
|
||||||
</CardContent>
|
</Box>
|
||||||
</Card>
|
<Box
|
||||||
|
borderColor={"neutralHighlight"}
|
||||||
|
borderStyle={"solid"}
|
||||||
|
borderWidth={1}
|
||||||
|
padding={4}
|
||||||
|
borderRadius={4}
|
||||||
|
>
|
||||||
|
<Text as={"h2"} marginBottom={4} variant={"heading"}>
|
||||||
|
Configuration
|
||||||
|
</Text>
|
||||||
|
<Box>{page}</Box>
|
||||||
|
</Box>
|
||||||
</AppColumnsLayout>
|
</AppColumnsLayout>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,85 +1,15 @@
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
import { useRouter } from "next/router";
|
import { useEffect } from "react";
|
||||||
import React, { FormEventHandler, useEffect } from "react";
|
|
||||||
import { useIsMounted } from "usehooks-ts";
|
import { useIsMounted } from "usehooks-ts";
|
||||||
import Image from "next/image";
|
import { useRouter } from "next/router";
|
||||||
import SaleorLogoImage from "../assets/saleor-logo.svg";
|
|
||||||
import SaleorLogoImageDark from "../assets/saleor-logo-dark.svg";
|
|
||||||
import { InputAdornment, LinearProgress, TextField, Typography } from "@material-ui/core";
|
|
||||||
import { Button, makeStyles, useTheme } from "@saleor/macaw-ui";
|
|
||||||
import { isInIframe } from "@saleor/apps-shared";
|
import { isInIframe } from "@saleor/apps-shared";
|
||||||
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
|
||||||
root: {
|
|
||||||
maxWidth: 960,
|
|
||||||
margin: "50px auto",
|
|
||||||
},
|
|
||||||
headline: {
|
|
||||||
marginTop: 70,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "50% 50%",
|
|
||||||
gap: 116,
|
|
||||||
marginTop: 48,
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
marginTop: 24,
|
|
||||||
},
|
|
||||||
submitButton: {
|
|
||||||
marginTop: 12,
|
|
||||||
},
|
|
||||||
buttons: {
|
|
||||||
marginTop: 24,
|
|
||||||
"& > *": {
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const Input = () => {
|
|
||||||
return (
|
|
||||||
<TextField
|
|
||||||
label="Your Saleor URL"
|
|
||||||
fullWidth
|
|
||||||
name="saleor-url"
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: <InputAdornment position="end">.saleor.cloud</InputAdornment>,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common landing page in case of app being open outside the Dashboard.
|
|
||||||
* Allows quick installation with Saleor env input
|
|
||||||
*
|
|
||||||
* Can be safely removed
|
|
||||||
*/
|
|
||||||
const IndexPage: NextPage = () => {
|
const IndexPage: NextPage = () => {
|
||||||
const styles = useStyles();
|
|
||||||
const { appBridgeState } = useAppBridge();
|
const { appBridgeState } = useAppBridge();
|
||||||
const isMounted = useIsMounted();
|
const isMounted = useIsMounted();
|
||||||
const { replace } = useRouter();
|
const { replace } = useRouter();
|
||||||
const { themeType } = useTheme();
|
|
||||||
|
|
||||||
const onFormSubmit: FormEventHandler<HTMLFormElement> = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const appOrigin = window.location.origin;
|
|
||||||
const appManifestUrl = new URL("api/manifest", appOrigin);
|
|
||||||
const saleorUrlSlug = new FormData(e.currentTarget).get("saleor-url") as string;
|
|
||||||
const saleorUrl = new URL("https://" + saleorUrlSlug.replace("https://", "") + ".saleor.cloud");
|
|
||||||
|
|
||||||
const installationUrl = new URL(
|
|
||||||
`dashboard/apps/install?manifestUrl=${appManifestUrl}`,
|
|
||||||
saleorUrl
|
|
||||||
).href;
|
|
||||||
|
|
||||||
window.location.href = installationUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isMounted() && appBridgeState?.ready) {
|
if (isMounted() && appBridgeState?.ready) {
|
||||||
|
@ -87,54 +17,21 @@ const IndexPage: NextPage = () => {
|
||||||
}
|
}
|
||||||
}, [isMounted, appBridgeState?.ready]);
|
}, [isMounted, appBridgeState?.ready]);
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO Check also some timeout and show error if appBridge never handshakes
|
|
||||||
*/
|
|
||||||
if (isInIframe()) {
|
if (isInIframe()) {
|
||||||
return <LinearProgress />;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<Box>
|
||||||
<Image
|
<Text as={"h1"} variant={"hero"}>
|
||||||
alt="Saleor logo"
|
Saleor Slack App
|
||||||
width={200}
|
</Text>
|
||||||
src={themeType === "light" ? SaleorLogoImage : SaleorLogoImageDark}
|
<Text as={"p"}>This is Saleor App that allows invoices generation</Text>
|
||||||
/>
|
<Text as={"p"}>
|
||||||
<Typography className={styles.headline} variant="h1">
|
Install app in your Saleor instance and open in with Dashboard{" "}
|
||||||
The Slack App has to be <br />
|
<a href={"https://github.com/saleor/apps"}>or check it on GitHub</a>
|
||||||
launched in the Saleor Dashboard
|
</Text>
|
||||||
</Typography>
|
</Box>
|
||||||
<div className={styles.grid}>
|
|
||||||
<div>
|
|
||||||
<Typography variant="h3">
|
|
||||||
Provide you Saleor URL
|
|
||||||
<br /> to quickly install the app
|
|
||||||
</Typography>
|
|
||||||
<form onSubmit={onFormSubmit} className={styles.form}>
|
|
||||||
<Input />
|
|
||||||
<Button type="submit" className={styles.submitButton} fullWidth variant="primary">
|
|
||||||
{" "}
|
|
||||||
Submit and start installation
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Typography variant="h3">
|
|
||||||
Or check the instructions
|
|
||||||
<br /> and see how to install the app
|
|
||||||
</Typography>
|
|
||||||
<div className={styles.buttons}>
|
|
||||||
<Button variant="secondary" fullWidth>
|
|
||||||
Open repository
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" fullWidth>
|
|
||||||
See Saleor Docs
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1273,9 +1273,15 @@ importers:
|
||||||
'@saleor/apps-shared':
|
'@saleor/apps-shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/shared
|
version: link:../../packages/shared
|
||||||
|
'@saleor/apps-ui':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/ui
|
||||||
'@saleor/macaw-ui':
|
'@saleor/macaw-ui':
|
||||||
specifier: ^0.7.2
|
specifier: 0.8.0-pre.95
|
||||||
version: 0.7.3(@material-ui/core@4.12.4)(@material-ui/icons@4.11.3)(@material-ui/lab@4.0.0-alpha.61)(@types/react@18.2.5)(react-dom@18.2.0)(react-helmet@6.1.0)(react@18.2.0)
|
version: 0.8.0-pre.95(@types/react-dom@18.2.5)(@types/react@18.2.5)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@saleor/react-hook-form-macaw':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/react-hook-form-macaw
|
||||||
'@sentry/nextjs':
|
'@sentry/nextjs':
|
||||||
specifier: 7.55.2
|
specifier: 7.55.2
|
||||||
version: 7.55.2(next@13.3.0)(react@18.2.0)
|
version: 7.55.2(next@13.3.0)(react@18.2.0)
|
||||||
|
|
Loading…
Reference in a new issue