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:
Krzysztof Wolski 2023-03-15 09:31:07 +01:00 committed by GitHub
parent 36f42a72fc
commit 3b694d16bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 127 additions and 131 deletions

View 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.

View file

@ -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>
); );
}; };

View file

@ -25,6 +25,7 @@ const useStyles = makeStyles((theme) => {
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
gap: 20, gap: 20,
maxWidth: 600,
}, },
}; };
}); });

View file

@ -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>
); );
}; };

View file

@ -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<{}>;

View file

@ -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}

View file

@ -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} />
))} ))}