Migrate Home page to new macaw (#3737)
This commit is contained in:
parent
1c7486818b
commit
b66af99477
26 changed files with 606 additions and 620 deletions
5
.changeset/shiny-moons-wave.md
Normal file
5
.changeset/shiny-moons-wave.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-dashboard": minor
|
||||
---
|
||||
|
||||
Migrate Home page to new macaw components
|
|
@ -427,10 +427,6 @@
|
|||
"context": "section header",
|
||||
"string": "Refunded Amount"
|
||||
},
|
||||
"0opVvi": {
|
||||
"context": "number of ordered products",
|
||||
"string": "{amount, plural,one {One ordered}other {{amount} Ordered}}"
|
||||
},
|
||||
"0qg33z": {
|
||||
"context": "table column header",
|
||||
"string": "Return"
|
||||
|
@ -1053,9 +1049,6 @@
|
|||
"context": "vat not included in order price",
|
||||
"string": "does not apply"
|
||||
},
|
||||
"5LRkEs": {
|
||||
"string": "The new dashboard and the GraphQL API are preview-quality software."
|
||||
},
|
||||
"5ObBlW": {
|
||||
"string": "Dark Mode"
|
||||
},
|
||||
|
@ -1198,10 +1191,6 @@
|
|||
"context": "button",
|
||||
"string": "Create page type"
|
||||
},
|
||||
"6L6Fy2": {
|
||||
"context": "header",
|
||||
"string": "Disclaimer"
|
||||
},
|
||||
"6QjMei": {
|
||||
"string": "Preorder end time needs to be set in the future"
|
||||
},
|
||||
|
@ -2500,9 +2489,6 @@
|
|||
"context": "tooltip",
|
||||
"string": "Checkout reservation time threshold is enabled in settings."
|
||||
},
|
||||
"G7mu0y": {
|
||||
"string": "The GraphQL API is beta quality. It is not fully optimized and some mutations or queries may be missing."
|
||||
},
|
||||
"GAmGog": {
|
||||
"context": "value input label",
|
||||
"string": "Discount value"
|
||||
|
@ -5859,6 +5845,10 @@
|
|||
"context": "current balance filter label",
|
||||
"string": "Current balance"
|
||||
},
|
||||
"e08xWz": {
|
||||
"context": "header",
|
||||
"string": "Top products"
|
||||
},
|
||||
"e0RKe+": {
|
||||
"context": "generate invoice button",
|
||||
"string": "Generate"
|
||||
|
@ -7041,6 +7031,10 @@
|
|||
"context": "order history message",
|
||||
"string": "Fulfilled {quantity} items"
|
||||
},
|
||||
"nII/qB": {
|
||||
"context": "number of ordered products",
|
||||
"string": "{amount, plural,one {One ordered}other {{amount} ordered}}"
|
||||
},
|
||||
"nIrjSR": {
|
||||
"context": "section header",
|
||||
"string": "Ongoing Installations"
|
||||
|
@ -7616,10 +7610,6 @@
|
|||
"context": "order status",
|
||||
"string": "Ready to capture"
|
||||
},
|
||||
"rr8fyf": {
|
||||
"context": "header",
|
||||
"string": "Top Products"
|
||||
},
|
||||
"rs815i": {
|
||||
"context": "text field label",
|
||||
"string": "Group name"
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
savebarHeight,
|
||||
} from "@dashboard/components/AppLayout/consts";
|
||||
import { Box, Sprinkles } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
|
||||
interface DetailPageLayoutProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -19,17 +19,39 @@ export const RootLayout: React.FC<DetailPageLayoutProps> = ({
|
|||
children,
|
||||
gridTemplateColumns = 12,
|
||||
withSavebar = true,
|
||||
}) => (
|
||||
}) => {
|
||||
const gridTemplateColumnsValue =
|
||||
useMemo((): Sprinkles["gridTemplateColumns"] => {
|
||||
if (gridTemplateColumns instanceof Object) {
|
||||
return {
|
||||
mobile: gridTemplateColumns.mobile ?? 1,
|
||||
...gridTemplateColumns,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
mobile: 1,
|
||||
desktop: gridTemplateColumns,
|
||||
};
|
||||
}, [gridTemplateColumns]);
|
||||
|
||||
const heightValue = useMemo(() => {
|
||||
return withSavebar ? contentWithSidebarHeight : contentWithoutSidebarHeight;
|
||||
}, [withSavebar]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
// TODO: Use custom value media query when it will be ready
|
||||
// https://github.com/saleor/macaw-ui/issues/498
|
||||
className="mobile-full-height"
|
||||
display="grid"
|
||||
margin="auto"
|
||||
gridTemplateColumns={gridTemplateColumns}
|
||||
gridTemplateColumns={gridTemplateColumnsValue}
|
||||
__gridTemplateRows="auto 1fr"
|
||||
__maxWidth={contentMaxWidth}
|
||||
__height={
|
||||
withSavebar ? contentWithSidebarHeight : contentWithoutSidebarHeight
|
||||
}
|
||||
__height={heightValue}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,104 +1,84 @@
|
|||
// @ts-strict-ignore
|
||||
import CardTitle from "@dashboard/components/CardTitle";
|
||||
import { DashboardCard } from "@dashboard/components/Card";
|
||||
import { DateTime } from "@dashboard/components/Date";
|
||||
import Skeleton from "@dashboard/components/Skeleton";
|
||||
import { HomeQuery } from "@dashboard/graphql";
|
||||
import { RelayToFlat } from "@dashboard/types";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import { Activities } from "@dashboard/home/types";
|
||||
import { Box, List, Text, useTheme } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { renderCollection } from "../../../misc";
|
||||
import { getActivityMessage } from "./activityMessages";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
{
|
||||
loadingProducts: {
|
||||
paddingBottom: "10px",
|
||||
paddingTop: "10px",
|
||||
},
|
||||
noProducts: {
|
||||
paddingBottom: "16px",
|
||||
paddingTop: "16px",
|
||||
},
|
||||
listItem: {
|
||||
paddingLeft: 0,
|
||||
},
|
||||
},
|
||||
{ name: "HomeActivityCard" },
|
||||
);
|
||||
|
||||
interface HomeActivityCardProps {
|
||||
activities: RelayToFlat<HomeQuery["activities"]>;
|
||||
activities: Activities;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
const HomeActivityCard: React.FC<HomeActivityCardProps> = props => {
|
||||
const { activities, testId } = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
export const HomeActivityCard = ({
|
||||
activities,
|
||||
testId,
|
||||
}: HomeActivityCardProps) => {
|
||||
const intl = useIntl();
|
||||
const { themeValues } = useTheme();
|
||||
|
||||
return (
|
||||
<Card data-test-id={testId}>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
<DashboardCard data-test-id={testId}>
|
||||
<DashboardCard.Title>
|
||||
{intl.formatMessage({
|
||||
id: "BXkF8Z",
|
||||
defaultMessage: "Activity",
|
||||
description: "header",
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<List dense>
|
||||
</DashboardCard.Title>
|
||||
<DashboardCard.Content>
|
||||
<List>
|
||||
{renderCollection(
|
||||
activities,
|
||||
(activity, activityId) => (
|
||||
<ListItem key={activityId} className={classes.listItem}>
|
||||
<List.Item
|
||||
key={activityId}
|
||||
flexDirection="column"
|
||||
alignItems="flex-start"
|
||||
cursor="auto"
|
||||
paddingY={1}
|
||||
paddingX={6}
|
||||
__marginLeft={"-" + themeValues.spacing[6]}
|
||||
__marginRight={"-" + themeValues.spacing[6]}
|
||||
marginBottom={3}
|
||||
>
|
||||
{activity ? (
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography>
|
||||
<>
|
||||
<Text variant="body" size="small">
|
||||
{getActivityMessage(activity, intl)}
|
||||
</Typography>
|
||||
}
|
||||
secondary={<DateTime date={activity.date} plain />}
|
||||
/>
|
||||
</Text>
|
||||
<Text
|
||||
variant="body"
|
||||
size="small"
|
||||
color="textNeutralSubdued"
|
||||
>
|
||||
<DateTime date={activity.date} plain />
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<ListItemText className={classes.loadingProducts}>
|
||||
<Typography>
|
||||
<Box paddingY={4}>
|
||||
<Skeleton />
|
||||
</Typography>
|
||||
</ListItemText>
|
||||
</Box>
|
||||
)}
|
||||
</ListItem>
|
||||
</List.Item>
|
||||
),
|
||||
() => (
|
||||
<ListItem className={classes.noProducts}>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography>
|
||||
<Box paddingY={4}>
|
||||
<Text variant="body" size="small">
|
||||
<FormattedMessage
|
||||
id="wWTUrM"
|
||||
defaultMessage="No activities found"
|
||||
/>
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
</Text>
|
||||
</Box>
|
||||
),
|
||||
)}
|
||||
</List>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</DashboardCard.Content>
|
||||
</DashboardCard>
|
||||
);
|
||||
};
|
||||
HomeActivityCard.displayName = "HomeActivityCard";
|
||||
export default HomeActivityCard;
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export { default } from "./HomeActivityCard";
|
||||
export * from "./HomeActivityCard";
|
||||
|
|
|
@ -8,16 +8,18 @@ interface HomeAnalyticsCardProps {
|
|||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const HomeAnalyticsCard: React.FC<HomeAnalyticsCardProps> = props => {
|
||||
const { children, title, testId } = props;
|
||||
|
||||
return (
|
||||
export const HomeAnalyticsCard = ({
|
||||
children,
|
||||
title,
|
||||
testId,
|
||||
}: HomeAnalyticsCardProps) => (
|
||||
<Box
|
||||
borderWidth={1}
|
||||
borderStyle="solid"
|
||||
borderColor="neutralPlain"
|
||||
borderRadius={3}
|
||||
padding={5}
|
||||
paddingX={3}
|
||||
paddingY={5}
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
data-test-id={testId}
|
||||
|
@ -35,6 +37,3 @@ const HomeAnalyticsCard: React.FC<HomeAnalyticsCardProps> = props => {
|
|||
</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
HomeAnalyticsCard.displayName = "HomeAnalyticsCard";
|
||||
export default HomeAnalyticsCard;
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export { default } from "./HomeAnalyticsCard";
|
||||
export * from "./HomeAnalyticsCard";
|
||||
|
|
|
@ -1,42 +1,21 @@
|
|||
import Skeleton from "@dashboard/components/Skeleton";
|
||||
import { Typography } from "@material-ui/core";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import { Text } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
headerContainer: {
|
||||
alignItems: "flex-end",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: theme.spacing(6),
|
||||
},
|
||||
pageHeader: {
|
||||
fontWeight: 600 as 600,
|
||||
},
|
||||
subtitle: {
|
||||
color: theme.typography.caption.color,
|
||||
},
|
||||
}),
|
||||
{ name: "HomeHeader" },
|
||||
);
|
||||
|
||||
interface HomeOrdersCardProps {
|
||||
interface HomeHeaderProps {
|
||||
userName: string;
|
||||
}
|
||||
|
||||
const HomeOrdersCard: React.FC<HomeOrdersCardProps> = props => {
|
||||
const { userName } = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
export const HomeHeader: React.FC<HomeHeaderProps> = ({
|
||||
userName,
|
||||
}: HomeHeaderProps) => (
|
||||
<div data-test-id="home-header">
|
||||
<div>
|
||||
<Typography
|
||||
className={classes.pageHeader}
|
||||
variant="h4"
|
||||
<Text
|
||||
variant="heading"
|
||||
lineHeight="captionSmall"
|
||||
size="small"
|
||||
as="h4"
|
||||
data-test-id="welcome-header"
|
||||
>
|
||||
{userName ? (
|
||||
|
@ -51,8 +30,8 @@ const HomeOrdersCard: React.FC<HomeOrdersCardProps> = props => {
|
|||
) : (
|
||||
<Skeleton style={{ width: "10em" }} />
|
||||
)}
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle}>
|
||||
</Text>
|
||||
<Text variant="caption" size="large">
|
||||
{userName ? (
|
||||
<FormattedMessage
|
||||
id="aCX8rl"
|
||||
|
@ -62,10 +41,6 @@ const HomeOrdersCard: React.FC<HomeOrdersCardProps> = props => {
|
|||
) : (
|
||||
<Skeleton style={{ width: "10em" }} />
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
HomeOrdersCard.displayName = "HomeOrdersCard";
|
||||
export default HomeOrdersCard;
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export { default } from "./HomeHeader";
|
||||
export * from "./HomeHeader";
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import RequirePermissions from "@dashboard/components/RequirePermissions";
|
||||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
import { List } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { HomeNotificationListItem } from "./HomeNotificationListItem";
|
||||
import { homeNotificationTableMessages as messages } from "./messages";
|
||||
import {
|
||||
getOrdersToCaptureText,
|
||||
getOrderToFulfillText,
|
||||
getProductsOutOfStockText,
|
||||
} from "./utils";
|
||||
|
||||
interface HomeNotificationTableProps {
|
||||
ordersToCapture: number;
|
||||
ordersToFulfill: number;
|
||||
productsOutOfStock: number;
|
||||
createNewChannelHref: string;
|
||||
ordersToFulfillHref: string;
|
||||
ordersToCaptureHref: string;
|
||||
productsOutOfStockHref: string;
|
||||
noChannel: boolean;
|
||||
}
|
||||
|
||||
export const HomeNotificationList = ({
|
||||
createNewChannelHref,
|
||||
ordersToFulfillHref,
|
||||
ordersToCaptureHref,
|
||||
productsOutOfStockHref,
|
||||
ordersToCapture,
|
||||
ordersToFulfill,
|
||||
productsOutOfStock,
|
||||
noChannel,
|
||||
}: HomeNotificationTableProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<List>
|
||||
{noChannel && (
|
||||
<RequirePermissions
|
||||
requiredPermissions={[PermissionEnum.MANAGE_CHANNELS]}
|
||||
>
|
||||
<HomeNotificationListItem linkUrl={createNewChannelHref}>
|
||||
{intl.formatMessage(messages.createNewChannel)}
|
||||
</HomeNotificationListItem>
|
||||
</RequirePermissions>
|
||||
)}
|
||||
|
||||
<RequirePermissions requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}>
|
||||
<HomeNotificationListItem
|
||||
linkUrl={ordersToFulfillHref}
|
||||
dataTestId="orders-to-fulfill"
|
||||
>
|
||||
{getOrderToFulfillText(ordersToFulfill, intl)}
|
||||
</HomeNotificationListItem>
|
||||
|
||||
<HomeNotificationListItem
|
||||
linkUrl={ordersToCaptureHref}
|
||||
dataTestId="orders-to-capture"
|
||||
>
|
||||
{getOrdersToCaptureText(ordersToCapture, intl)}
|
||||
</HomeNotificationListItem>
|
||||
</RequirePermissions>
|
||||
|
||||
<RequirePermissions
|
||||
requiredPermissions={[PermissionEnum.MANAGE_PRODUCTS]}
|
||||
>
|
||||
<HomeNotificationListItem
|
||||
linkUrl={productsOutOfStockHref}
|
||||
dataTestId="products-out-of-stock"
|
||||
>
|
||||
{getProductsOutOfStockText(productsOutOfStock, intl)}
|
||||
</HomeNotificationListItem>
|
||||
</RequirePermissions>
|
||||
</List>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
import {
|
||||
Box,
|
||||
ChevronRightIcon,
|
||||
List,
|
||||
sprinkles,
|
||||
Text,
|
||||
} from "@saleor/macaw-ui/next";
|
||||
import React, { ReactNode } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
interface HomeNotificationListItemProps {
|
||||
dataTestId?: string;
|
||||
linkUrl: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const HomeNotificationListItem = ({
|
||||
dataTestId,
|
||||
linkUrl,
|
||||
children,
|
||||
}: HomeNotificationListItemProps) => (
|
||||
<List.Item
|
||||
borderColor="neutralPlain"
|
||||
borderWidth={1}
|
||||
borderBottomStyle="solid"
|
||||
data-test-id={dataTestId}
|
||||
>
|
||||
<Link
|
||||
className={sprinkles({ width: "100%", paddingX: 3, paddingY: 4 })}
|
||||
to={linkUrl}
|
||||
>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
width="100%"
|
||||
>
|
||||
<Text size="small">{children}</Text>
|
||||
<ChevronRightIcon />
|
||||
</Box>
|
||||
</Link>
|
||||
</List.Item>
|
||||
);
|
1
src/home/components/HomeNotificationList/index.ts
Normal file
1
src/home/components/HomeNotificationList/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./HomeNotificationList";
|
56
src/home/components/HomeNotificationList/utils.tsx
Normal file
56
src/home/components/HomeNotificationList/utils.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import Skeleton from "@dashboard/components/Skeleton";
|
||||
import React from "react";
|
||||
import { IntlShape } from "react-intl";
|
||||
|
||||
import { homeNotificationTableMessages as messages } from "./messages";
|
||||
|
||||
export const getOrderToFulfillText = (
|
||||
ordersToFulfill: number | undefined,
|
||||
intl: IntlShape,
|
||||
) => {
|
||||
if (ordersToFulfill === undefined) {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
if (ordersToFulfill === 0) {
|
||||
return intl.formatMessage(messages.noOrders);
|
||||
}
|
||||
|
||||
return intl.formatMessage(messages.orderReady, {
|
||||
amount: <strong>{ordersToFulfill}</strong>,
|
||||
});
|
||||
};
|
||||
|
||||
export const getOrdersToCaptureText = (
|
||||
ordersToCapture: number | undefined,
|
||||
intl: IntlShape,
|
||||
) => {
|
||||
if (ordersToCapture === undefined) {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
if (ordersToCapture === 0) {
|
||||
return intl.formatMessage(messages.noPaymentWaiting);
|
||||
}
|
||||
|
||||
return intl.formatMessage(messages.paymentCapture, {
|
||||
amount: <strong>{ordersToCapture}</strong>,
|
||||
});
|
||||
};
|
||||
|
||||
export const getProductsOutOfStockText = (
|
||||
productsOutOfStock: number | undefined,
|
||||
intl: IntlShape,
|
||||
) => {
|
||||
if (productsOutOfStock === undefined) {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
if (productsOutOfStock === 0) {
|
||||
return intl.formatMessage(messages.noProductsOut);
|
||||
}
|
||||
|
||||
return intl.formatMessage(messages.productOut, {
|
||||
amount: <strong>{productsOutOfStock}</strong>,
|
||||
});
|
||||
};
|
|
@ -1,172 +0,0 @@
|
|||
import RequirePermissions from "@dashboard/components/RequirePermissions";
|
||||
import ResponsiveTable from "@dashboard/components/ResponsiveTable";
|
||||
import Skeleton from "@dashboard/components/Skeleton";
|
||||
import TableRowLink from "@dashboard/components/TableRowLink";
|
||||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
TableBody,
|
||||
TableCell,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import { vars } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { homeNotificationTableMessages as messages } from "./messages";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
() => ({
|
||||
arrowIcon: {
|
||||
textAlign: "right",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
tableCard: {
|
||||
overflow: "hidden",
|
||||
borderRadius: 0,
|
||||
},
|
||||
tableRow: {
|
||||
cursor: "pointer",
|
||||
/* Table to be replaced with Box */
|
||||
"& .MuiTableCell-root": {
|
||||
paddingLeft: `${vars.spacing[5]} !important`,
|
||||
paddingRight: `${vars.spacing[5]} !important`,
|
||||
},
|
||||
},
|
||||
cardContent: {
|
||||
padding: 0,
|
||||
},
|
||||
}),
|
||||
{ name: "HomeNotificationTable" },
|
||||
);
|
||||
|
||||
interface HomeNotificationTableProps {
|
||||
ordersToCapture: number;
|
||||
ordersToFulfill: number;
|
||||
productsOutOfStock: number;
|
||||
createNewChannelHref: string;
|
||||
ordersToFulfillHref: string;
|
||||
ordersToCaptureHref: string;
|
||||
productsOutOfStockHref: string;
|
||||
noChannel: boolean;
|
||||
}
|
||||
|
||||
const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
|
||||
const {
|
||||
createNewChannelHref,
|
||||
ordersToFulfillHref,
|
||||
ordersToCaptureHref,
|
||||
productsOutOfStockHref,
|
||||
ordersToCapture,
|
||||
ordersToFulfill,
|
||||
productsOutOfStock,
|
||||
noChannel,
|
||||
} = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card className={classes.tableCard}>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<ResponsiveTable>
|
||||
<TableBody className={classes.tableRow}>
|
||||
{noChannel && (
|
||||
<RequirePermissions
|
||||
requiredPermissions={[PermissionEnum.MANAGE_CHANNELS]}
|
||||
>
|
||||
<TableRowLink hover={true} href={createNewChannelHref}>
|
||||
<TableCell>
|
||||
<Typography>
|
||||
{intl.formatMessage(messages.createNewChannel)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell className={classes.arrowIcon}>
|
||||
<KeyboardArrowRight />
|
||||
</TableCell>
|
||||
</TableRowLink>
|
||||
</RequirePermissions>
|
||||
)}
|
||||
<RequirePermissions
|
||||
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
|
||||
>
|
||||
<TableRowLink hover={true} href={ordersToFulfillHref}>
|
||||
<TableCell data-test-id="orders-to-fulfill">
|
||||
{ordersToFulfill === undefined ? (
|
||||
<Skeleton />
|
||||
) : ordersToFulfill === 0 ? (
|
||||
<Typography>
|
||||
{intl.formatMessage(messages.noOrders)}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography>
|
||||
{intl.formatMessage(messages.orderReady, {
|
||||
amount: <strong>{ordersToFulfill}</strong>,
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.arrowIcon}>
|
||||
<KeyboardArrowRight />
|
||||
</TableCell>
|
||||
</TableRowLink>
|
||||
<TableRowLink hover={true} href={ordersToCaptureHref}>
|
||||
<TableCell data-test-id="orders-to-capture">
|
||||
{ordersToCapture === undefined ? (
|
||||
<Skeleton />
|
||||
) : ordersToCapture === 0 ? (
|
||||
<Typography>
|
||||
{intl.formatMessage(messages.noPaymentWaiting)}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography>
|
||||
{intl.formatMessage(messages.paymentCapture, {
|
||||
amount: <strong>{ordersToCapture}</strong>,
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.arrowIcon}>
|
||||
<KeyboardArrowRight />
|
||||
</TableCell>
|
||||
</TableRowLink>
|
||||
</RequirePermissions>
|
||||
<RequirePermissions
|
||||
requiredPermissions={[PermissionEnum.MANAGE_PRODUCTS]}
|
||||
>
|
||||
<TableRowLink hover={true} href={productsOutOfStockHref}>
|
||||
<TableCell data-test-id="products-out-of-stock">
|
||||
{productsOutOfStock === undefined ? (
|
||||
<Skeleton />
|
||||
) : productsOutOfStock === 0 ? (
|
||||
<Typography>
|
||||
{intl.formatMessage(messages.noProductsOut)}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography>
|
||||
{intl.formatMessage(messages.productOut, {
|
||||
amount: <strong>{productsOutOfStock}</strong>,
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.arrowIcon}>
|
||||
<KeyboardArrowRight />
|
||||
</TableCell>
|
||||
</TableRowLink>
|
||||
</RequirePermissions>
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
HomeNotificationTable.displayName = "HomeNotificationTable";
|
||||
export default HomeNotificationTable;
|
|
@ -1,2 +0,0 @@
|
|||
export { default } from "./HomeNotificationTable";
|
||||
export * from "./HomeNotificationTable";
|
|
@ -1,4 +1,3 @@
|
|||
// @ts-strict-ignore
|
||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||
import CardSpacer from "@dashboard/components/CardSpacer";
|
||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||
|
@ -6,26 +5,26 @@ import Money from "@dashboard/components/Money";
|
|||
import RequirePermissions from "@dashboard/components/RequirePermissions";
|
||||
import Skeleton from "@dashboard/components/Skeleton";
|
||||
import { HomeQuery, PermissionEnum } from "@dashboard/graphql";
|
||||
import { RelayToFlat } from "@dashboard/types";
|
||||
import { Activities, ProductTopToday } from "@dashboard/home/types";
|
||||
import { Box } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import HomeActivityCard from "../HomeActivityCard";
|
||||
import HomeAnalyticsCard from "../HomeAnalyticsCard";
|
||||
import HomeHeader from "../HomeHeader";
|
||||
import HomeNotificationTable from "../HomeNotificationTable/HomeNotificationTable";
|
||||
import HomeProductListCard from "../HomeProductListCard";
|
||||
import { HomeActivityCard } from "../HomeActivityCard";
|
||||
import { HomeAnalyticsCard } from "../HomeAnalyticsCard";
|
||||
import { HomeHeader } from "../HomeHeader";
|
||||
import { HomeNotificationList } from "../HomeNotificationList";
|
||||
import { HomeProductList } from "../HomeProductList";
|
||||
import { homePageMessages } from "./messages";
|
||||
|
||||
export interface HomePageProps {
|
||||
activities: RelayToFlat<HomeQuery["activities"]>;
|
||||
activities: Activities;
|
||||
orders: number | null;
|
||||
ordersToCapture: number | null;
|
||||
ordersToFulfill: number | null;
|
||||
productsOutOfStock: number;
|
||||
sales: HomeQuery["salesToday"]["gross"];
|
||||
topProducts: RelayToFlat<HomeQuery["productTopToday"]> | null;
|
||||
sales: NonNullable<HomeQuery["salesToday"]>["gross"];
|
||||
topProducts: ProductTopToday | null;
|
||||
userName: string;
|
||||
createNewChannelHref: string;
|
||||
ordersToFulfillHref: string;
|
||||
|
@ -93,13 +92,13 @@ const HomePage: React.FC<HomePageProps> = props => {
|
|||
</HomeAnalyticsCard>
|
||||
</Box>
|
||||
</RequirePermissions>
|
||||
<HomeNotificationTable
|
||||
<HomeNotificationList
|
||||
createNewChannelHref={createNewChannelHref}
|
||||
ordersToFulfillHref={ordersToFulfillHref}
|
||||
ordersToCaptureHref={ordersToCaptureHref}
|
||||
productsOutOfStockHref={productsOutOfStockHref}
|
||||
ordersToCapture={ordersToCapture}
|
||||
ordersToFulfill={ordersToFulfill}
|
||||
ordersToCapture={ordersToCapture ?? 0}
|
||||
ordersToFulfill={ordersToFulfill ?? 0}
|
||||
productsOutOfStock={productsOutOfStock}
|
||||
noChannel={noChannel}
|
||||
/>
|
||||
|
@ -111,7 +110,7 @@ const HomePage: React.FC<HomePageProps> = props => {
|
|||
PermissionEnum.MANAGE_PRODUCTS,
|
||||
]}
|
||||
>
|
||||
<HomeProductListCard
|
||||
<HomeProductList
|
||||
testId="top-products"
|
||||
topProducts={topProducts}
|
||||
/>
|
||||
|
|
118
src/home/components/HomeProductList/HomeProductList.tsx
Normal file
118
src/home/components/HomeProductList/HomeProductList.tsx
Normal file
|
@ -0,0 +1,118 @@
|
|||
import Money from "@dashboard/components/Money";
|
||||
import Skeleton from "@dashboard/components/Skeleton";
|
||||
import { ProductTopToday } from "@dashboard/home/types";
|
||||
import { productVariantEditUrl } from "@dashboard/products/urls";
|
||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { renderCollection } from "../../../misc";
|
||||
import { HomeProductListItem } from "./HomeProductListItem";
|
||||
|
||||
interface HomeProductListProps {
|
||||
testId?: string;
|
||||
topProducts: ProductTopToday;
|
||||
}
|
||||
|
||||
export const HomeProductList = ({
|
||||
topProducts,
|
||||
testId,
|
||||
}: HomeProductListProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Box data-test-id={testId}>
|
||||
<Text variant="heading" display="block" paddingTop={7} marginBottom={2}>
|
||||
{intl.formatMessage({
|
||||
id: "e08xWz",
|
||||
defaultMessage: "Top products",
|
||||
description: "header",
|
||||
})}
|
||||
</Text>
|
||||
<Box>
|
||||
{renderCollection(
|
||||
topProducts,
|
||||
variant => (
|
||||
<HomeProductListItem
|
||||
key={variant ? variant.id : "skeleton"}
|
||||
linkUrl={
|
||||
variant
|
||||
? productVariantEditUrl(variant.product.id, variant.id)
|
||||
: ""
|
||||
}
|
||||
>
|
||||
{variant ? (
|
||||
<>
|
||||
<Box display="flex" gap={3} alignItems="center">
|
||||
<Box
|
||||
borderColor="neutralHighlight"
|
||||
borderStyle="solid"
|
||||
borderWidth={1}
|
||||
borderRadius={3}
|
||||
as="img"
|
||||
width={16}
|
||||
height={16}
|
||||
padding={0.5}
|
||||
alt={variant.product.name}
|
||||
objectFit="scale-down"
|
||||
src={variant.product.thumbnail?.url}
|
||||
/>
|
||||
|
||||
<Box display="flex" flexDirection="column">
|
||||
<Text size="small">{variant.product.name}</Text>
|
||||
|
||||
<Text size="small" color="textNeutralSubdued">
|
||||
{variant.attributes
|
||||
.map(attribute => attribute.values[0].name)
|
||||
.join(" / ")}
|
||||
</Text>
|
||||
|
||||
<Text size="small" color="textNeutralSubdued">
|
||||
<FormattedMessage
|
||||
id="nII/qB"
|
||||
defaultMessage="{amount, plural,one {One ordered}other {{amount} ordered}}"
|
||||
description="number of ordered products"
|
||||
values={{
|
||||
amount: variant.quantityOrdered,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Text textAlign="right">
|
||||
{variant.revenue ? (
|
||||
<Money money={variant.revenue.gross} />
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</HomeProductListItem>
|
||||
),
|
||||
() => (
|
||||
<Box
|
||||
borderColor="neutralPlain"
|
||||
borderWidth={1}
|
||||
paddingY={5}
|
||||
borderBottomStyle="solid"
|
||||
>
|
||||
<Text size="small">
|
||||
<FormattedMessage
|
||||
id="Q1Uzbb"
|
||||
defaultMessage="No products found"
|
||||
/>
|
||||
</Text>
|
||||
</Box>
|
||||
),
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
HomeProductList.displayName = "HomeProductList";
|
||||
export default HomeProductList;
|
33
src/home/components/HomeProductList/HomeProductListItem.tsx
Normal file
33
src/home/components/HomeProductList/HomeProductListItem.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { Box, List, sprinkles } from "@saleor/macaw-ui/next";
|
||||
import React, { ReactNode } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
interface HomeNotificationListItemProps {
|
||||
dataTestId?: string;
|
||||
linkUrl: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const HomeProductListItem = ({
|
||||
dataTestId,
|
||||
linkUrl,
|
||||
children,
|
||||
}: HomeNotificationListItemProps) => (
|
||||
<List.Item
|
||||
borderColor="neutralPlain"
|
||||
borderWidth={1}
|
||||
borderBottomStyle="solid"
|
||||
data-test-id={dataTestId}
|
||||
>
|
||||
<Link className={sprinkles({ width: "100%", padding: 3 })} to={linkUrl}>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
width="100%"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Link>
|
||||
</List.Item>
|
||||
);
|
1
src/home/components/HomeProductList/index.ts
Normal file
1
src/home/components/HomeProductList/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./HomeProductList";
|
|
@ -1,175 +0,0 @@
|
|||
// @ts-strict-ignore
|
||||
import CardTitle from "@dashboard/components/CardTitle";
|
||||
import Money from "@dashboard/components/Money";
|
||||
import ResponsiveTable from "@dashboard/components/ResponsiveTable";
|
||||
import Skeleton from "@dashboard/components/Skeleton";
|
||||
import TableCellAvatar from "@dashboard/components/TableCellAvatar";
|
||||
import TableRowLink from "@dashboard/components/TableRowLink";
|
||||
import { HomeQuery } from "@dashboard/graphql";
|
||||
import { productVariantEditUrl } from "@dashboard/products/urls";
|
||||
import { RelayToFlat } from "@dashboard/types";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
TableBody,
|
||||
TableCell,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import { vars } from "@saleor/macaw-ui/next";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { maybe, renderCollection } from "../../../misc";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
avatarProps: {
|
||||
height: 64,
|
||||
width: 64,
|
||||
},
|
||||
colAvatar: {
|
||||
paddingBottom: theme.spacing(2),
|
||||
paddingRight: theme.spacing(),
|
||||
paddingTop: theme.spacing(2),
|
||||
width: 112,
|
||||
},
|
||||
colName: {
|
||||
width: "auto",
|
||||
},
|
||||
label: {
|
||||
paddingLeft: 0,
|
||||
},
|
||||
noProducts: {
|
||||
paddingBottom: 20,
|
||||
paddingTop: 20,
|
||||
paddingLeft: "0 !important",
|
||||
},
|
||||
tableRow: {
|
||||
cursor: "pointer",
|
||||
/* Table to be replaced with Box */
|
||||
"& .MuiTableCell-root": {
|
||||
paddingLeft: `${vars.spacing[5]} !important`,
|
||||
paddingRight: `${vars.spacing[5]} !important`,
|
||||
},
|
||||
},
|
||||
cardContent: {
|
||||
padding: 0,
|
||||
},
|
||||
cardTitle: {
|
||||
padding: 0,
|
||||
},
|
||||
}),
|
||||
{ name: "HomeProductListCard" },
|
||||
);
|
||||
|
||||
interface HomeProductListProps {
|
||||
testId?: string;
|
||||
topProducts: RelayToFlat<HomeQuery["productTopToday"]>;
|
||||
}
|
||||
|
||||
export const HomeProductList: React.FC<HomeProductListProps> = props => {
|
||||
const { topProducts, testId } = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card data-test-id={testId}>
|
||||
<CardTitle
|
||||
className={classes.cardTitle}
|
||||
title={intl.formatMessage({
|
||||
id: "rr8fyf",
|
||||
defaultMessage: "Top Products",
|
||||
description: "header",
|
||||
})}
|
||||
/>
|
||||
<CardContent className={classes.cardContent}>
|
||||
<ResponsiveTable>
|
||||
<colgroup>
|
||||
<col className={classes.colAvatar} />
|
||||
<col className={classes.colName} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
topProducts,
|
||||
variant => (
|
||||
<TableRowLink
|
||||
key={variant ? variant.id : "skeleton"}
|
||||
hover={!!variant}
|
||||
className={clsx({
|
||||
[classes.tableRow]: !!variant,
|
||||
})}
|
||||
href={productVariantEditUrl(variant.product.id, variant.id)}
|
||||
>
|
||||
<TableCellAvatar
|
||||
className={classes.colAvatar}
|
||||
thumbnail={maybe(() => variant.product.thumbnail.url)}
|
||||
avatarProps={classes.avatarProps}
|
||||
/>
|
||||
|
||||
<TableCell className={classes.label}>
|
||||
{variant ? (
|
||||
<>
|
||||
<Typography color={"primary"}>
|
||||
{variant.product.name}
|
||||
</Typography>
|
||||
<Typography color={"textSecondary"}>
|
||||
{maybe(() =>
|
||||
variant.attributes
|
||||
.map(attribute => attribute.values[0].name)
|
||||
.join(" / "),
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color={"textSecondary"}>
|
||||
<FormattedMessage
|
||||
id="0opVvi"
|
||||
defaultMessage="{amount, plural,one {One ordered}other {{amount} Ordered}}"
|
||||
description="number of ordered products"
|
||||
values={{
|
||||
amount: variant.quantityOrdered,
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Typography align={"right"}>
|
||||
{maybe(
|
||||
() => (
|
||||
<Money money={variant.revenue.gross} />
|
||||
),
|
||||
<Skeleton />,
|
||||
)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRowLink>
|
||||
),
|
||||
() => (
|
||||
<TableRowLink>
|
||||
<TableCell colSpan={3} className={classes.noProducts}>
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
id="Q1Uzbb"
|
||||
defaultMessage="No products found"
|
||||
/>
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRowLink>
|
||||
),
|
||||
)}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
HomeProductList.displayName = "HomeProductList";
|
||||
export default HomeProductList;
|
|
@ -1,2 +0,0 @@
|
|||
export { default } from "./HomeProductListCard";
|
||||
export * from "./HomeProductListCard";
|
|
@ -1,53 +0,0 @@
|
|||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||
import CardTitle from "@dashboard/components/CardTitle";
|
||||
import { Card, CardContent, Typography } from "@material-ui/core";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
interface HomeScreenProps {
|
||||
user: {
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const HomeScreen: React.FC<HomeScreenProps> = ({ user }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopNav
|
||||
title={intl.formatMessage(
|
||||
{
|
||||
id: "By5ZBp",
|
||||
defaultMessage: "Hello there, {userName}",
|
||||
description: "header",
|
||||
},
|
||||
{ userName: user.email },
|
||||
)}
|
||||
/>
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
id: "6L6Fy2",
|
||||
defaultMessage: "Disclaimer",
|
||||
description: "header",
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
id="5LRkEs"
|
||||
defaultMessage="The new dashboard and the GraphQL API are preview-quality software."
|
||||
/>
|
||||
</Typography>
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
id="G7mu0y"
|
||||
defaultMessage="The GraphQL API is beta quality. It is not fully optimized and some mutations or queries may be missing."
|
||||
/>
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -316,7 +316,7 @@ export const shop: (placeholderImage: string) => HomeQuery = (
|
|||
{
|
||||
__typename: "AttributeValue",
|
||||
id: "QXR0cmlidXRlVmFsdWU6OTI=",
|
||||
name: "XS",
|
||||
name: "XL",
|
||||
sortOrder: 0,
|
||||
},
|
||||
],
|
||||
|
@ -326,7 +326,83 @@ export const shop: (placeholderImage: string) => HomeQuery = (
|
|||
product: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDo4",
|
||||
name: "Gardner-Martin",
|
||||
name: "Black Hoodie",
|
||||
thumbnail: {
|
||||
__typename: "Image",
|
||||
url: placeholderImage,
|
||||
},
|
||||
},
|
||||
quantityOrdered: 1,
|
||||
revenue: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 37.65,
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: "ProductVariantCountableEdge",
|
||||
node: {
|
||||
__typename: "ProductVariant",
|
||||
attributes: [
|
||||
{
|
||||
__typename: "SelectedAttribute",
|
||||
values: [
|
||||
{
|
||||
__typename: "AttributeValue",
|
||||
id: "QXR0cmlidXRlVmFsdWU6OTI2=",
|
||||
name: "2l",
|
||||
sortOrder: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6NDM=2",
|
||||
product: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDo4",
|
||||
name: "Bean Juice",
|
||||
thumbnail: {
|
||||
__typename: "Image",
|
||||
url: placeholderImage,
|
||||
},
|
||||
},
|
||||
quantityOrdered: 1,
|
||||
revenue: {
|
||||
__typename: "TaxedMoney",
|
||||
gross: {
|
||||
__typename: "Money",
|
||||
amount: 37.65,
|
||||
currency: "USD",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: "ProductVariantCountableEdge",
|
||||
node: {
|
||||
__typename: "ProductVariant",
|
||||
attributes: [
|
||||
{
|
||||
__typename: "SelectedAttribute",
|
||||
values: [
|
||||
{
|
||||
__typename: "AttributeValue",
|
||||
id: "QXR0cmlidXRlVmFsdWU6OTI=3",
|
||||
name: "L",
|
||||
sortOrder: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6NDM=3",
|
||||
product: {
|
||||
__typename: "Product",
|
||||
id: "UHJvZHVjdDo4",
|
||||
name: "Black Hoodie",
|
||||
thumbnail: {
|
||||
__typename: "Image",
|
||||
url: placeholderImage,
|
||||
|
|
7
src/home/types.ts
Normal file
7
src/home/types.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { HomeQuery } from "@dashboard/graphql";
|
||||
import { RelayToFlat } from "@dashboard/types";
|
||||
|
||||
export type Activities = RelayToFlat<NonNullable<HomeQuery["activities"]>>;
|
||||
export type ProductTopToday = RelayToFlat<
|
||||
NonNullable<HomeQuery["productTopToday"]>
|
||||
>;
|
|
@ -48,3 +48,13 @@ body {
|
|||
[id*="ScrollableDialog"] .infinite-scroll-component {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: Remove it when macaw will handle media queries in custom properties
|
||||
https://github.com/saleor/macaw-ui/issues/498
|
||||
*/
|
||||
@media screen and (max-width: 1024px) {
|
||||
.mobile-full-height {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue