parent
2f94183dc6
commit
5fad97c6d2
11 changed files with 214 additions and 114 deletions
5
.changeset/pretty-months-mix.md
Normal file
5
.changeset/pretty-months-mix.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-products-feed": patch
|
||||
---
|
||||
|
||||
Update the UI to the common theme
|
|
@ -1,22 +1,22 @@
|
|||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { LinearProgress, Paper } from "@material-ui/core";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import { EditIcon, IconButton, makeStyles } from "@saleor/macaw-ui";
|
||||
import { AppConfigContainer } from "../app-config-container";
|
||||
import { UrlConfigurationForm } from "./url-configuration-form";
|
||||
import { ChannelsList } from "./channels-list";
|
||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||
import { AppColumnsLayout } from "../../ui/app-columns-layout";
|
||||
import { FeedPreviewCard } from "./feed-preview-card";
|
||||
import { Instructions } from "./instructions";
|
||||
import SideMenu from "./side-menu";
|
||||
|
||||
const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
header: { marginBottom: 20 },
|
||||
grid: { display: "grid", gridTemplateColumns: "1fr 1fr", alignItems: "start", gap: 40 },
|
||||
formContainer: {
|
||||
top: 0,
|
||||
position: "sticky",
|
||||
grid: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr",
|
||||
alignItems: "start",
|
||||
gap: 40,
|
||||
},
|
||||
instructionsContainer: {
|
||||
padding: 15,
|
||||
|
@ -78,16 +78,30 @@ export const ChannelsConfiguration = () => {
|
|||
|
||||
return (
|
||||
<AppColumnsLayout>
|
||||
<ChannelsList
|
||||
channels={channels.data}
|
||||
activeChannelSlug={activeChannel.slug}
|
||||
onChannelClick={setActiveChannelSlug}
|
||||
<SideMenu
|
||||
title="Channels"
|
||||
selectedItemId={activeChannel?.slug}
|
||||
headerToolbar={
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
appBridge?.dispatch(
|
||||
actions.Redirect({
|
||||
to: `/channels/`,
|
||||
})
|
||||
);
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
}
|
||||
onClick={(id) => setActiveChannelSlug(id)}
|
||||
items={channels.data.map((c) => ({ label: c.name, id: c.slug })) || []}
|
||||
/>
|
||||
|
||||
{activeChannel ? (
|
||||
<div className={styles.configurationColumn}>
|
||||
<FeedPreviewCard channelSlug={activeChannel.slug} />
|
||||
<Paper elevation={0} className={styles.formContainer}>
|
||||
<Paper elevation={0}>
|
||||
<UrlConfigurationForm
|
||||
channelID={activeChannel.id}
|
||||
key={activeChannelSlug}
|
||||
|
@ -106,6 +120,7 @@ export const ChannelsConfiguration = () => {
|
|||
/>
|
||||
{saveError && <span>{saveError.message}</span>}
|
||||
</Paper>
|
||||
<FeedPreviewCard channelSlug={activeChannel.slug} />
|
||||
</div>
|
||||
) : null}
|
||||
<Instructions />
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
import {
|
||||
makeStyles,
|
||||
OffsettedList,
|
||||
OffsettedListBody,
|
||||
OffsettedListHeader,
|
||||
OffsettedListItem,
|
||||
OffsettedListItemCell,
|
||||
} from "@saleor/macaw-ui";
|
||||
import clsx from "clsx";
|
||||
import { Typography } from "@material-ui/core";
|
||||
import React from "react";
|
||||
import { ChannelFragment } from "../../../../generated/graphql";
|
||||
|
||||
const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
listItem: {
|
||||
cursor: "pointer",
|
||||
height: "auto !important",
|
||||
border: `1px solid transparent`,
|
||||
},
|
||||
listItemActive: {
|
||||
border: `1px solid ${
|
||||
theme.palette.type === "light" ? theme.palette.divider : theme.palette.grey.A200
|
||||
}`,
|
||||
},
|
||||
cellSlug: {
|
||||
fontFamily: "monospace",
|
||||
opacity: 0.8,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type Props = {
|
||||
channels: ChannelFragment[];
|
||||
activeChannelSlug: string;
|
||||
onChannelClick(channelSlug: string): void;
|
||||
};
|
||||
|
||||
export const ChannelsList = ({ channels, activeChannelSlug, onChannelClick }: Props) => {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<OffsettedList gridTemplate={["1fr"]}>
|
||||
<OffsettedListBody>
|
||||
{channels.map((c) => {
|
||||
return (
|
||||
<OffsettedListItem
|
||||
className={clsx(styles.listItem, {
|
||||
[styles.listItemActive]: c.slug === activeChannelSlug,
|
||||
})}
|
||||
key={c.slug}
|
||||
onClick={() => {
|
||||
onChannelClick(c.slug);
|
||||
}}
|
||||
>
|
||||
<OffsettedListItemCell>
|
||||
{c.name}
|
||||
<Typography variant="caption">
|
||||
<code>{c.slug}</code>
|
||||
</Typography>
|
||||
</OffsettedListItemCell>
|
||||
</OffsettedListItem>
|
||||
);
|
||||
})}
|
||||
</OffsettedListBody>
|
||||
</OffsettedList>
|
||||
);
|
||||
};
|
|
@ -38,8 +38,8 @@ export const FeedPreviewCard = ({ channelSlug }: FeedPreviewCardProps) => {
|
|||
};
|
||||
return (
|
||||
<Paper elevation={0} className={styles.instructionsContainer}>
|
||||
<Typography paragraph variant="h4">
|
||||
Your Google Merchant Feed
|
||||
<Typography paragraph variant="h3">
|
||||
Your Google Merchant Feed preview
|
||||
</Typography>
|
||||
<TextField
|
||||
label="Google feed URL"
|
||||
|
@ -47,6 +47,7 @@ export const FeedPreviewCard = ({ channelSlug }: FeedPreviewCardProps) => {
|
|||
value={googleFeedUrl}
|
||||
disabled={true}
|
||||
className={styles.field}
|
||||
helperText="Dedicated URL for your Google Merchant Feed"
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
import { Card, CardContent, CardHeader, Divider } from "@material-ui/core";
|
||||
("@material-ui/icons");
|
||||
import { DeleteIcon, IconButton, List, ListItem, ListItemCell } from "@saleor/macaw-ui";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import { Skeleton } from "@material-ui/lab";
|
||||
|
||||
export const useStyles = makeStyles((theme) => ({
|
||||
menu: {
|
||||
height: "fit-content",
|
||||
},
|
||||
clickable: {
|
||||
cursor: "pointer",
|
||||
},
|
||||
selected: {
|
||||
"&&&&::before": {
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
width: "4px",
|
||||
height: "100%",
|
||||
backgroundColor: theme.palette.saleor.active[1],
|
||||
},
|
||||
},
|
||||
spaceBetween: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
},
|
||||
tableRow: {
|
||||
minHeight: "48px",
|
||||
"&::after": {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
greyText: {
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
link: {
|
||||
all: "inherit",
|
||||
display: "contents",
|
||||
},
|
||||
}));
|
||||
|
||||
interface SideMenuProps {
|
||||
title: string;
|
||||
noItemsText?: string;
|
||||
items: { id: string; label: string }[];
|
||||
selectedItemId?: string;
|
||||
headerToolbar?: React.ReactNode;
|
||||
onDelete?: (itemId: string) => void;
|
||||
onClick: (itemId: string) => void;
|
||||
}
|
||||
|
||||
export const SideMenu: React.FC<SideMenuProps> = ({
|
||||
title,
|
||||
items,
|
||||
headerToolbar,
|
||||
selectedItemId,
|
||||
noItemsText,
|
||||
onDelete,
|
||||
onClick,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
|
||||
const isNoItems = !items || !items.length;
|
||||
return (
|
||||
<Card className={classes.menu}>
|
||||
<CardHeader title={title} action={headerToolbar} />
|
||||
{isNoItems ? (
|
||||
!!noItemsText && <CardContent className={classes.greyText}>{noItemsText}</CardContent>
|
||||
) : (
|
||||
<List gridTemplate={["1fr"]}>
|
||||
{items.map((item) => (
|
||||
<React.Fragment key={item.id}>
|
||||
<Divider />
|
||||
<ListItem
|
||||
className={clsx(classes.clickable, classes.tableRow, {
|
||||
[classes.selected]: item.id === selectedItemId,
|
||||
})}
|
||||
onClick={() => onClick(item.id)}
|
||||
>
|
||||
<ListItemCell>
|
||||
<div className={classes.spaceBetween}>
|
||||
{item.label}
|
||||
{!!onDelete && (
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
onDelete(item.id);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
</ListItemCell>
|
||||
</ListItem>
|
||||
</React.Fragment>
|
||||
)) ?? <Skeleton />}
|
||||
<Divider />
|
||||
</List>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SideMenu;
|
|
@ -53,12 +53,12 @@ export const UrlConfigurationForm = (props: Props) => {
|
|||
})}
|
||||
className={styles.form}
|
||||
>
|
||||
<Typography variant="body1" paragraph>
|
||||
<Typography variant="h3" paragraph>
|
||||
Configure
|
||||
<strong onClick={handleChannelNameClick} className={styles.channelName}>
|
||||
{` ${props.channelName} `}
|
||||
</strong>
|
||||
channel:
|
||||
channel
|
||||
</Typography>
|
||||
<TextField label="Storefront home URL" {...CommonFieldProps} {...register("storefrontUrl")} />
|
||||
<TextField
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "280px auto 400px",
|
||||
alignItems: "start",
|
||||
gap: 32,
|
||||
maxWidth: 1180,
|
||||
margin: "0 auto",
|
||||
gap: theme.spacing(3),
|
||||
marginTop: theme.spacing(3),
|
||||
marginLeft: theme.spacing(3),
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
type Props = PropsWithChildren<{}>;
|
||||
type AppColumnsLayoutProps = PropsWithChildren<{}>;
|
||||
|
||||
export const AppColumnsLayout = ({ children }: Props) => {
|
||||
export const AppColumnsLayout = ({ children }: AppColumnsLayoutProps) => {
|
||||
const styles = useStyles();
|
||||
|
||||
return <div className={styles.root}>{children}</div>;
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import "../styles/globals.css";
|
||||
|
||||
import { Theme } from "@material-ui/core/styles";
|
||||
import { createGenerateClassName, Theme } from "@material-ui/core/styles";
|
||||
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
||||
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
||||
import { ThemeProvider as MacawUIThemeProvider } from "@saleor/macaw-ui";
|
||||
import {
|
||||
dark,
|
||||
light,
|
||||
SaleorThemeColors,
|
||||
ThemeProvider as MacawUIThemeProvider,
|
||||
} from "@saleor/macaw-ui";
|
||||
import React, { PropsWithChildren, useEffect } from "react";
|
||||
import { AppProps } from "next/app";
|
||||
|
||||
|
@ -11,12 +16,49 @@ import { ThemeSynchronizer } from "../lib/theme-synchronizer";
|
|||
import { NoSSRWrapper } from "../lib/no-ssr-wrapper";
|
||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
||||
|
||||
const themeOverrides: Partial<Theme> = {
|
||||
type PalettesOverride = Record<"light" | "dark", SaleorThemeColors>;
|
||||
|
||||
/**
|
||||
* You can override MacawUI theme here
|
||||
* 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)",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const themeOverrides: Partial<Theme> = {
|
||||
overrides: {
|
||||
MuiTableCell: {
|
||||
body: {
|
||||
paddingBottom: 8,
|
||||
paddingTop: 8,
|
||||
},
|
||||
root: {
|
||||
height: 56,
|
||||
paddingBottom: 4,
|
||||
paddingTop: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const generateClassName = createGenerateClassName({
|
||||
productionPrefix: "c",
|
||||
disableGlobal: true,
|
||||
});
|
||||
/**
|
||||
* Ensure instance is a singleton.
|
||||
* TODO: This is React 18 issue, consider hiding this workaround inside app-sdk
|
||||
|
@ -27,7 +69,7 @@ export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge()
|
|||
* That's a hack required by Macaw-UI incompatibility with React@18
|
||||
*/
|
||||
const ThemeProvider = MacawUIThemeProvider as React.FC<
|
||||
PropsWithChildren<{ overrides?: Partial<Theme>; ssr: boolean }>
|
||||
PropsWithChildren<{ overrides?: Partial<Theme>; ssr: boolean; palettes: PalettesOverride }>
|
||||
>;
|
||||
|
||||
function NextApp({ Component, pageProps }: AppProps) {
|
||||
|
@ -44,7 +86,7 @@ function NextApp({ Component, pageProps }: AppProps) {
|
|||
return (
|
||||
<NoSSRWrapper>
|
||||
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
||||
<ThemeProvider overrides={themeOverrides} ssr={false}>
|
||||
<ThemeProvider palettes={palettes} overrides={themeOverrides} ssr={false}>
|
||||
<ThemeSynchronizer />
|
||||
<RoutePropagator />
|
||||
<Component {...pageProps} />
|
||||
|
|
|
@ -3,23 +3,11 @@ import React, { useEffect } from "react";
|
|||
import { ChannelsConfiguration } from "../modules/app-configuration/ui/channels-configuration";
|
||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
||||
import { useRouter } from "next/router";
|
||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||
|
||||
const ConfigurationPage: NextPage = () => {
|
||||
const channels = trpcClient.channels.fetch.useQuery();
|
||||
const router = useRouter();
|
||||
|
||||
const { appBridge } = useAppBridge();
|
||||
|
||||
const openInNewTab = (url: string) => {
|
||||
appBridge?.dispatch(
|
||||
actions.Redirect({
|
||||
to: url,
|
||||
newContext: true,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (channels.isSuccess && channels.data.length === 0) {
|
||||
router.push("/not-ready");
|
||||
|
|
|
@ -23,7 +23,7 @@ const IndexPage: NextPage = () => {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<h1>Saleor Merchant Feed</h1>
|
||||
<h1>Saleor Product Feed</h1>
|
||||
<p>This is Saleor App that allows product feed generation</p>
|
||||
<p>Install app in your Saleor instance and open in with Dashboard</p>
|
||||
</div>
|
||||
|
|
|
@ -2,10 +2,11 @@ body {
|
|||
font-family: Inter, -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
color: #111;
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f6f8fa;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
|
@ -13,3 +14,8 @@ code {
|
|||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
code::before {
|
||||
content: "$ ";
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue