2023-02-09 10:58:20 +00:00
|
|
|
import {
|
|
|
|
Card,
|
|
|
|
CardContent,
|
|
|
|
CardHeader,
|
2023-02-09 14:56:52 +00:00
|
|
|
Link,
|
2023-02-09 10:58:20 +00:00
|
|
|
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 { 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 AccessWarning from "../components/AccessWarning/AccessWarning";
|
|
|
|
import { ConfigurationError } from "../components/ConfigurationError/ConfigurationError";
|
|
|
|
import useAppApi from "../hooks/useAppApi";
|
|
|
|
import useDashboardNotifier from "../utils/useDashboardNotifier";
|
|
|
|
import { AppColumnsLayout } from "../components/AppColumnsLayout/AppColumnsLayout";
|
|
|
|
|
|
|
|
interface ConfigurationField {
|
|
|
|
key: string;
|
|
|
|
value: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
const useStyles = makeStyles((theme) => ({
|
|
|
|
confirmButton: {
|
|
|
|
marginLeft: "auto",
|
|
|
|
},
|
|
|
|
fieldContainer: {
|
|
|
|
marginBottom: theme.spacing(2),
|
|
|
|
},
|
|
|
|
additionalInfo: {
|
|
|
|
marginBottom: theme.spacing(3),
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
|
2023-02-09 14:56:52 +00:00
|
|
|
function Configuration() {
|
2023-02-09 10:58:20 +00:00
|
|
|
const classes = useStyles();
|
|
|
|
const { appBridgeState } = useAppBridge();
|
|
|
|
const [notify] = useDashboardNotifier();
|
|
|
|
const [configuration, setConfiguration] = useState<ConfigurationField[]>();
|
|
|
|
const [transitionState, setTransitionState] = useState<ConfirmButtonTransitionState>("default");
|
|
|
|
|
|
|
|
const { data: configurationData, error } = useAppApi<{ data: ConfigurationField[] }>({
|
|
|
|
url: "/api/configuration",
|
|
|
|
});
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (configurationData && !configuration) {
|
|
|
|
setConfiguration(configurationData.data);
|
|
|
|
}
|
|
|
|
}, [configurationData, configuration]);
|
|
|
|
|
|
|
|
const handleSubmit = (event: SyntheticEvent) => {
|
|
|
|
event.preventDefault();
|
|
|
|
setTransitionState("loading");
|
|
|
|
|
|
|
|
fetch("/api/configuration", {
|
|
|
|
method: "POST",
|
|
|
|
headers: [
|
|
|
|
["content-type", "application/json"],
|
|
|
|
[SALEOR_API_URL_HEADER, appBridgeState?.saleorApiUrl!],
|
|
|
|
[SALEOR_AUTHORIZATION_BEARER_HEADER, appBridgeState?.token!],
|
|
|
|
],
|
|
|
|
body: JSON.stringify({ data: configuration }),
|
|
|
|
})
|
|
|
|
.then(async (response) => {
|
|
|
|
setTransitionState(response.status === 200 ? "success" : "error");
|
|
|
|
await notify({
|
|
|
|
status: "success",
|
|
|
|
title: "Success",
|
|
|
|
text: "Configuration updated successfully",
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.catch(async () => {
|
|
|
|
setTransitionState("error");
|
|
|
|
await notify({
|
|
|
|
status: "error",
|
|
|
|
title: "Configuration update failed",
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const onChange = (event: ChangeEvent) => {
|
|
|
|
const { name, value } = event.target as HTMLInputElement;
|
|
|
|
setConfiguration((prev) =>
|
|
|
|
prev!.map((prevField) => (prevField.key === name ? { ...prevField, value } : prevField))
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
console.error("Can't establish connection with the App API: ", error);
|
2023-02-09 14:56:52 +00:00
|
|
|
return <ConfigurationError />;
|
2023-02-09 10:58:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (configuration === undefined) {
|
|
|
|
return <Skeleton />;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
|
|
{configuration!.map(({ key, value }) => (
|
|
|
|
<div key={key} className={classes.fieldContainer}>
|
|
|
|
<TextField label={key} name={key} fullWidth onChange={onChange} value={value} />
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
<p className={classes.additionalInfo}>
|
|
|
|
This webhook will be called when new order is created and `order_created` event is
|
|
|
|
triggered.
|
|
|
|
</p>
|
|
|
|
<div>
|
|
|
|
<ConfirmButton
|
|
|
|
type="submit"
|
|
|
|
variant="primary"
|
|
|
|
fullWidth
|
|
|
|
transitionState={transitionState}
|
|
|
|
labels={{
|
|
|
|
confirm: "Save",
|
|
|
|
error: "Error",
|
|
|
|
}}
|
|
|
|
className={classes.confirmButton}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function Instructions() {
|
|
|
|
const { appBridge } = useAppBridge();
|
|
|
|
|
|
|
|
const { data } = useAppApi({
|
|
|
|
url: "/api/slack-app-manifest",
|
|
|
|
});
|
|
|
|
|
|
|
|
const slackUrl = new URL("https://api.slack.com/apps");
|
|
|
|
slackUrl.searchParams.append("new_app", "1");
|
|
|
|
slackUrl.searchParams.append("manifest_json", JSON.stringify(data));
|
|
|
|
|
|
|
|
const openExternalUrl = (to: string) => {
|
|
|
|
appBridge?.dispatch({
|
|
|
|
type: "redirect",
|
|
|
|
payload: {
|
|
|
|
newContext: true,
|
|
|
|
actionId: "redirect_from_slack_app",
|
|
|
|
to,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<Typography>How to configure</Typography>
|
|
|
|
<List>
|
|
|
|
<ListItem>
|
|
|
|
<Link
|
|
|
|
onClick={(e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
openExternalUrl(slackUrl.href);
|
|
|
|
}}
|
|
|
|
href={slackUrl.href}
|
|
|
|
>
|
|
|
|
Install Slack application
|
|
|
|
</Link>
|
|
|
|
</ListItem>
|
|
|
|
<ListItem>
|
|
|
|
Copy incoming Webhook URL from Slack app configuration and paste it below into
|
|
|
|
`WEBHOOK_URL` field
|
|
|
|
</ListItem>
|
|
|
|
<ListItem>Save configuration</ListItem>
|
|
|
|
</List>
|
|
|
|
<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
|
|
|
|
</Link>
|
|
|
|
</ListItem>
|
|
|
|
</List>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const ConfigurationWithAuth = withAuthorization({
|
|
|
|
notIframe: <AccessWarning cause="not_in_iframe" />,
|
|
|
|
unmounted: null,
|
|
|
|
noDashboardToken: <AccessWarning cause="missing_access_token" />,
|
|
|
|
dashboardTokenInvalid: <AccessWarning cause="invalid_access_token" />,
|
|
|
|
})(Configuration);
|
|
|
|
|
|
|
|
ConfigurationWithAuth.getLayout = (page: ReactElement) => (
|
2023-02-28 19:18:34 +00:00
|
|
|
<AppColumnsLayout>
|
|
|
|
<div />
|
|
|
|
<Card>
|
|
|
|
<CardHeader title="Configuration" />
|
|
|
|
<CardContent>{page}</CardContent>
|
|
|
|
</Card>
|
|
|
|
<Card style={{ marginBottom: 40 }}>
|
|
|
|
<CardHeader title="Instructions" />
|
|
|
|
<CardContent>
|
|
|
|
<Instructions />
|
|
|
|
</CardContent>
|
|
|
|
</Card>
|
|
|
|
</AppColumnsLayout>
|
2023-02-09 10:58:20 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
export default ConfigurationWithAuth;
|