Improve styles and layout of the emails app (#287)
* Use whole width of the screen * Make template editor responsive * Use whole screen in configuration view
This commit is contained in:
parent
36f42a72fc
commit
3b694d16bc
7 changed files with 127 additions and 131 deletions
5
.changeset/eight-eagles-sin.md
Normal file
5
.changeset/eight-eagles-sin.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-app-emails-and-messages": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Improve styles and layout of the emails app configuration views. Make event template editing view responsive.
|
|
@ -21,7 +21,6 @@ const useStyles = makeStyles((theme) => ({
|
||||||
sectionHeader: {
|
sectionHeader: {
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
},
|
},
|
||||||
form: {},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -35,12 +34,11 @@ export const MjmlConfigurationForm = (props: Props) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { appBridge } = useAppBridge();
|
const { appBridge } = useAppBridge();
|
||||||
|
|
||||||
const { handleSubmit, control, setValue, getValues, reset, setError } =
|
const { handleSubmit, control, reset, setError } = useForm<MjmlConfiguration>({
|
||||||
useForm<MjmlConfiguration>({
|
defaultValues: props.initialData,
|
||||||
defaultValues: props.initialData,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const { mutate: createOrUpdateConfiguration, error: createOrUpdateError } =
|
const { mutate: createOrUpdateConfiguration } =
|
||||||
trpcClient.mjmlConfiguration.updateOrCreateConfiguration.useMutation({
|
trpcClient.mjmlConfiguration.updateOrCreateConfiguration.useMutation({
|
||||||
onSuccess(data, variables) {
|
onSuccess(data, variables) {
|
||||||
router.replace(mjmlUrls.configuration(data.id));
|
router.replace(mjmlUrls.configuration(data.id));
|
||||||
|
@ -97,7 +95,6 @@ export const MjmlConfigurationForm = (props: Props) => {
|
||||||
...data,
|
...data,
|
||||||
});
|
});
|
||||||
})}
|
})}
|
||||||
className={styles.form}
|
|
||||||
>
|
>
|
||||||
{isNewConfiguration ? (
|
{isNewConfiguration ? (
|
||||||
<Typography variant="h2" paragraph>
|
<Typography variant="h2" paragraph>
|
||||||
|
@ -268,8 +265,6 @@ export const MjmlConfigurationForm = (props: Props) => {
|
||||||
<Button type="submit" fullWidth variant="primary">
|
<Button type="submit" fullWidth variant="primary">
|
||||||
Save configuration
|
Save configuration
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{createOrUpdateError && <span>{createOrUpdateError.message}</span>}
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,7 @@ const useStyles = makeStyles((theme) => {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
gap: 20,
|
gap: 20,
|
||||||
|
maxWidth: 600,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { CircularProgress, TextField, TextFieldProps, Typography } from "@material-ui/core";
|
import { CircularProgress, Grid, TextField, TextFieldProps, Typography } from "@material-ui/core";
|
||||||
import {
|
import {
|
||||||
BackSmallIcon,
|
BackSmallIcon,
|
||||||
Button,
|
Button,
|
||||||
|
@ -26,13 +26,15 @@ import { examplePayloads } from "../../../event-handlers/default-payloads";
|
||||||
const PREVIEW_DEBOUNCE_DELAY = 500;
|
const PREVIEW_DEBOUNCE_DELAY = 500;
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
viewContainer: {
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
},
|
||||||
header: {
|
header: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(2),
|
||||||
padding: theme.spacing(3),
|
marginBottom: theme.spacing(2),
|
||||||
maxWidth: 1180,
|
|
||||||
margin: "0 auto",
|
margin: "0 auto",
|
||||||
},
|
},
|
||||||
previewHeader: {
|
previewHeader: {
|
||||||
|
@ -40,7 +42,8 @@ const useStyles = makeStyles((theme) => ({
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
marginBottom: theme.spacing(3),
|
marginTop: theme.spacing(2),
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
},
|
},
|
||||||
|
|
||||||
field: {
|
field: {
|
||||||
|
@ -53,15 +56,7 @@ const useStyles = makeStyles((theme) => ({
|
||||||
marginBottom: theme.spacing(3),
|
marginBottom: theme.spacing(3),
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
padding: theme.spacing(3),
|
maxWidth: 800,
|
||||||
},
|
|
||||||
formAndPreviewSection: {
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "2fr 1fr",
|
|
||||||
alignItems: "start",
|
|
||||||
gap: 32,
|
|
||||||
maxWidth: 1180,
|
|
||||||
margin: "0 auto",
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -115,7 +110,7 @@ export const EventConfigurationForm = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: updateEventConfiguration, isLoading: isFetchingUpdateEvent } =
|
const { mutate: updateEventConfiguration } =
|
||||||
trpcClient.mjmlConfiguration.updateEventConfiguration.useMutation({
|
trpcClient.mjmlConfiguration.updateEventConfiguration.useMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
appBridge?.dispatch(
|
appBridge?.dispatch(
|
||||||
|
@ -164,7 +159,7 @@ export const EventConfigurationForm = ({
|
||||||
}, [debouncedPayload, debouncedSubject, debouncedTemplate, fetchTemplatePreview]);
|
}, [debouncedPayload, debouncedSubject, debouncedTemplate, fetchTemplatePreview]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={styles.viewContainer}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
@ -178,100 +173,104 @@ export const EventConfigurationForm = ({
|
||||||
{messageEventTypesLabels[eventType]} event configuration
|
{messageEventTypesLabels[eventType]} event configuration
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.formAndPreviewSection}>
|
<Grid container spacing={2}>
|
||||||
<form
|
<Grid item xs={12} lg={7}>
|
||||||
onSubmit={handleSubmit((data, event) => {
|
<form
|
||||||
updateEventConfiguration({ ...data, configurationId });
|
onSubmit={handleSubmit((data, event) => {
|
||||||
})}
|
updateEventConfiguration({ ...data, configurationId });
|
||||||
className={styles.form}
|
})}
|
||||||
>
|
className={styles.form}
|
||||||
<Controller
|
>
|
||||||
control={control}
|
<Controller
|
||||||
name="active"
|
control={control}
|
||||||
render={({ field: { value, name, onChange } }) => (
|
name="active"
|
||||||
<div className={styles.field}>
|
render={({ field: { value, name, onChange } }) => (
|
||||||
{/* TODO: fix types in the MacawUI */}
|
<div className={styles.field}>
|
||||||
{/* @ts-ignore: MacawUI use wrong type for */}
|
{/* TODO: fix types in the MacawUI */}
|
||||||
<SwitchSelector key={name} className={styles.field}>
|
{/* @ts-ignore: MacawUI use wrong type for */}
|
||||||
{[
|
<SwitchSelector key={name} className={styles.field}>
|
||||||
{ label: "Active", value: true },
|
{[
|
||||||
{ label: "Disabled", value: false },
|
{ label: "Active", value: true },
|
||||||
].map((button) => (
|
{ label: "Disabled", value: false },
|
||||||
// @ts-ignore: MacawUI use wrong type for SwitchSelectorButton
|
].map((button) => (
|
||||||
<SwitchSelectorButton
|
// @ts-ignore: MacawUI use wrong type for SwitchSelectorButton
|
||||||
value={button.value.toString()}
|
<SwitchSelectorButton
|
||||||
onClick={() => onChange(button.value)}
|
value={button.value.toString()}
|
||||||
activeTab={value.toString()}
|
onClick={() => onChange(button.value)}
|
||||||
key={button.label}
|
activeTab={value.toString()}
|
||||||
>
|
key={button.label}
|
||||||
{button.label}
|
>
|
||||||
</SwitchSelectorButton>
|
{button.label}
|
||||||
))}
|
</SwitchSelectorButton>
|
||||||
</SwitchSelector>
|
))}
|
||||||
</div>
|
</SwitchSelector>
|
||||||
)}
|
</div>
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="subject"
|
name="subject"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||||
<TextField
|
<TextField
|
||||||
label="Email subject"
|
label="Email subject"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={
|
helperText={
|
||||||
!error
|
!error
|
||||||
? "You can use variables like {{ order.number }} or {{ order.userEmail }}"
|
? "You can use variables like {{ order.number }} or {{ order.userEmail }}"
|
||||||
: error.message
|
: error.message
|
||||||
}
|
}
|
||||||
{...CommonFieldProps}
|
{...CommonFieldProps}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="template"
|
name="template"
|
||||||
render={({ field: { value, onChange } }) => {
|
render={({ field: { value, onChange } }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.editor}>
|
<div className={styles.editor}>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
initialTemplate={value}
|
initialTemplate={value}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
language="xml"
|
language="xml"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button type="submit" fullWidth variant="primary">
|
<Button type="submit" fullWidth variant="primary">
|
||||||
Save configuration
|
Save configuration
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
<div>
|
</Grid>
|
||||||
<div className={styles.previewHeader}>
|
<Grid item xs={12} lg={5}>
|
||||||
<Typography variant="h2">Preview</Typography>
|
<div>
|
||||||
{isFetchingTemplatePreview && <CircularProgress size="3rem" color="primary" />}
|
<div className={styles.previewHeader}>
|
||||||
|
<Typography variant="h2">Preview</Typography>
|
||||||
|
{isFetchingTemplatePreview && <CircularProgress size="3rem" color="primary" />}
|
||||||
|
</div>
|
||||||
|
<Typography variant="h3" paragraph>
|
||||||
|
Subject: {lastValidRenderedSubject}
|
||||||
|
</Typography>
|
||||||
|
<div className={styles.preview}>
|
||||||
|
<MjmlPreview value={lastValidRenderedTemplate} />
|
||||||
|
</div>
|
||||||
|
<CodeEditor
|
||||||
|
initialTemplate={payload}
|
||||||
|
value={payload}
|
||||||
|
onChange={setPayload}
|
||||||
|
language="json"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Typography variant="h3" paragraph>
|
</Grid>
|
||||||
Subject: {lastValidRenderedSubject}
|
</Grid>
|
||||||
</Typography>
|
|
||||||
<div className={styles.preview}>
|
|
||||||
<MjmlPreview value={lastValidRenderedTemplate} />
|
|
||||||
</div>
|
|
||||||
<CodeEditor
|
|
||||||
initialTemplate={payload}
|
|
||||||
value={payload}
|
|
||||||
onChange={setPayload}
|
|
||||||
language="json"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: "280px auto",
|
gridTemplateColumns: "280px auto",
|
||||||
alignItems: "start",
|
alignItems: "start",
|
||||||
gap: 32,
|
gap: theme.spacing(3),
|
||||||
maxWidth: 1180,
|
|
||||||
margin: "0 auto",
|
|
||||||
padding: "20px 0",
|
padding: "20px 0",
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
|
|
||||||
type AppColumnsLayoutProps = PropsWithChildren<{}>;
|
type AppColumnsLayoutProps = PropsWithChildren<{}>;
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,7 @@ export const CodeEditor = ({ initialTemplate, onChange, value, language }: Props
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Editor
|
<Editor
|
||||||
height="40vh"
|
height="600px"
|
||||||
width="100%"
|
|
||||||
value={value}
|
value={value}
|
||||||
theme={themeType === "dark" ? "vs-dark" : "vs-light"}
|
theme={themeType === "dark" ? "vs-dark" : "vs-light"}
|
||||||
defaultLanguage={language}
|
defaultLanguage={language}
|
||||||
|
|
|
@ -2,9 +2,12 @@ import React, { PropsWithChildren } from "react";
|
||||||
import { makeStyles, PageTab, PageTabs } from "@saleor/macaw-ui";
|
import { makeStyles, PageTab, PageTabs } from "@saleor/macaw-ui";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles((theme) => ({
|
||||||
appContainer: { marginTop: 20 },
|
appContainer: {
|
||||||
});
|
marginTop: theme.spacing(3),
|
||||||
|
marginLeft: theme.spacing(3),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
type Props = PropsWithChildren<{}>;
|
type Props = PropsWithChildren<{}>;
|
||||||
|
|
||||||
|
@ -37,11 +40,7 @@ export const ConfigurationPageBaseLayout = ({ children }: Props) => {
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className={styles.appContainer}>
|
<div className={styles.appContainer}>
|
||||||
<PageTabs
|
<PageTabs value={activePath} onChange={navigateToTab}>
|
||||||
value={activePath}
|
|
||||||
onChange={navigateToTab}
|
|
||||||
style={{ maxWidth: 1180, margin: "0 auto" }}
|
|
||||||
>
|
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<PageTab key={tab.key} value={tab.key} label={tab.label} disabled={tab.disabled} />
|
<PageTab key={tab.key} value={tab.key} label={tab.label} disabled={tab.disabled} />
|
||||||
))}
|
))}
|
||||||
|
|
Loading…
Reference in a new issue