Saleor 1712 dashboard unable to handle empty channel list (#924)

* Use query hook on home page

* Handle no channel

* Handle no channel on pages

* Add navigate channels

* Move messages

* Refactor

* Update storybook and locale

* Remove comment

* Refactor

* Update storybook

* Fix skip

* Fix undefined channel

* Update storybook
This commit is contained in:
Marek Choiński 2021-01-13 10:33:38 +01:00 committed by GitHub
parent 3f5cacb8a0
commit d0be941ade
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 272 additions and 406 deletions

View file

@ -3023,6 +3023,9 @@
"src_dot_home_dot_components_dot_HomeActivityCard_dot_placed": { "src_dot_home_dot_components_dot_HomeActivityCard_dot_placed": {
"string": "Order #{orderId} was placed" "string": "Order #{orderId} was placed"
}, },
"src_dot_home_dot_components_dot_HomeNotificationTable_dot_createNewChannel": {
"string": "Create new channel"
},
"src_dot_hooks_dot_3382262667": { "src_dot_hooks_dot_3382262667": {
"string": "Variant {name} has been set as default." "string": "Variant {name} has been set as default."
}, },

View file

@ -4,6 +4,7 @@ import DeleteIcon from "@material-ui/icons/Delete";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import useAppChannel from "@saleor/components/AppLayout/AppChannelContext"; import useAppChannel from "@saleor/components/AppLayout/AppChannelContext";
import NotFoundPage from "@saleor/components/NotFoundPage"; import NotFoundPage from "@saleor/components/NotFoundPage";
import Skeleton from "@saleor/components/Skeleton";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
@ -207,6 +208,10 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
variables => updatePrivateMetadata({ variables }) variables => updatePrivateMetadata({ variables })
); );
if (typeof channel === "undefined") {
return <Skeleton />;
}
return ( return (
<> <>
<WindowTitle title={maybe(() => data.category.name)} /> <WindowTitle title={maybe(() => data.category.name)} />
@ -256,7 +261,7 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
data.category.products.edges.map(edge => edge.node) data.category.products.edges.map(edge => edge.node)
)} )}
saveButtonBarState={updateResult.status} saveButtonBarState={updateResult.status}
selectedChannelId={channel.id} selectedChannelId={channel?.id}
subcategories={maybe(() => subcategories={maybe(() =>
data.category.children.edges.map(edge => edge.node) data.category.children.edges.map(edge => edge.node)
)} )}

View file

@ -146,8 +146,8 @@ const CollectionList: React.FC<CollectionListProps> = props => {
collections, collections,
collection => { collection => {
const isSelected = collection ? isChecked(collection.id) : false; const isSelected = collection ? isChecked(collection.id) : false;
const channel = collection?.channelListings.find( const channel = collection?.channelListings?.find(
listing => listing.channel.id === selectedChannelId listing => listing?.channel?.id === selectedChannelId
); );
return ( return (
<TableRow <TableRow
@ -184,11 +184,13 @@ const CollectionList: React.FC<CollectionListProps> = props => {
{collection && !collection?.channelListings?.length ? ( {collection && !collection?.channelListings?.length ? (
"-" "-"
) : collection?.channelListings !== undefined ? ( ) : collection?.channelListings !== undefined ? (
<ChannelsAvailabilityDropdown channel ? (
allChannelsCount={channelsCount} <ChannelsAvailabilityDropdown
currentChannel={channel} allChannelsCount={channelsCount}
channels={collection?.channelListings} currentChannel={channel}
/> channels={collection?.channelListings}
/>
) : null
) : ( ) : (
<Skeleton /> <Skeleton />
)} )}

View file

@ -184,7 +184,7 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
toggle={toggle} toggle={toggle}
toggleAll={toggleAll} toggleAll={toggleAll}
channelsCount={availableChannels?.length} channelsCount={availableChannels?.length}
selectedChannelId={channel.id} selectedChannelId={channel?.id}
/> />
<ActionDialog <ActionDialog
open={params.action === "remove" && maybe(() => params.ids.length > 0)} open={params.action === "remove" && maybe(() => params.ids.length > 0)}

View file

@ -30,6 +30,7 @@ export const AppChannelProvider: React.FC = ({ children }) => {
const { data: channelData, refetch } = useChannelsList({ const { data: channelData, refetch } = useChannelsList({
skip: !isAuthenticated skip: !isAuthenticated
}); });
const [isPickerActive, setPickerActive] = React.useState(false); const [isPickerActive, setPickerActive] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
if (!selectedChannel) { if (!selectedChannel) {
@ -38,9 +39,10 @@ export const AppChannelProvider: React.FC = ({ children }) => {
}, [channelData]); }, [channelData]);
const availableChannels = channelData?.channels || []; const availableChannels = channelData?.channels || [];
const channel = availableChannels.find(
channel => channel.id === selectedChannel const channel =
); channelData &&
(availableChannels.find(channel => channel.id === selectedChannel) || null);
return ( return (
<AppChannelContext.Provider <AppChannelContext.Provider
@ -57,6 +59,7 @@ export const AppChannelProvider: React.FC = ({ children }) => {
</AppChannelContext.Provider> </AppChannelContext.Provider>
); );
}; };
AppChannelProvider.displayName = "AppChannelProvider"; AppChannelProvider.displayName = "AppChannelProvider";
function useAppChannel(enablePicker = true): UseAppChannel { function useAppChannel(enablePicker = true): UseAppChannel {

View file

@ -215,12 +215,14 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
.includes("mac")} .includes("mac")}
onClick={() => setNavigatorVisibility(true)} onClick={() => setNavigatorVisibility(true)}
/> />
<AppChannelSelect {channel && (
channels={availableChannels} <AppChannelSelect
disabled={!isPickerActive} channels={availableChannels}
selectedChannelId={channel.id} disabled={!isPickerActive}
onChannelSelect={setChannel} selectedChannelId={channel.id}
/> onChannelSelect={setChannel}
/>
)}
<UserChip <UserChip
isDarkThemeEnabled={isDark} isDarkThemeEnabled={isDark}
user={user} user={user}

View file

@ -78,7 +78,7 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
triggerChange triggerChange
); );
const formDisabled = data.channelListings?.some(channel => const formDisabled = data.channelListings?.some(channel =>
validatePrice(channel.discountValue) validatePrice(channel?.discountValue)
); );
return ( return (
<Container> <Container>

View file

@ -51,12 +51,12 @@ const SaleSummary: React.FC<SaleSummaryProps> = ({
sale.type === SaleType.FIXED && channel?.discountValue ? ( sale.type === SaleType.FIXED && channel?.discountValue ? (
<Money <Money
money={{ money={{
amount: channel.discountValue, amount: channel?.discountValue,
currency: channel.currency currency: channel?.currency
}} }}
/> />
) : channel?.discountValue ? ( ) : channel?.discountValue ? (
<Percent amount={channel.discountValue} /> <Percent amount={channel?.discountValue} />
) : ( ) : (
"-" "-"
) )

View file

@ -69,12 +69,12 @@ const VoucherSummary: React.FC<VoucherSummaryProps> = ({
channel?.discountValue ? ( channel?.discountValue ? (
<Money <Money
money={{ money={{
amount: channel.discountValue, amount: channel?.discountValue,
currency: channel.channel.currencyCode currency: channel?.channel.currencyCode
}} }}
/> />
) : channel?.discountValue ? ( ) : channel?.discountValue ? (
<Percent amount={channel.discountValue} /> <Percent amount={channel?.discountValue} />
) : ( ) : (
"-" "-"
) )

View file

@ -199,7 +199,7 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
} }
selectedChannelId={channel.id} selectedChannelId={channel?.id}
/> />
<ActionDialog <ActionDialog
confirmButtonState={saleBulkDeleteOpts.status} confirmButtonState={saleBulkDeleteOpts.status}

View file

@ -298,7 +298,7 @@ export const VoucherDetails: React.FC<VoucherDetailsProps> = ({
...(updateChannelsOpts.data ...(updateChannelsOpts.data
?.voucherChannelListingUpdate.errors || []) ?.voucherChannelListingUpdate.errors || [])
]} ]}
selectedChannelId={channel.id} selectedChannelId={channel?.id}
pageInfo={pageInfo} pageInfo={pageInfo}
onNextPage={loadNextPage} onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage} onPreviousPage={loadPreviousPage}

View file

@ -200,7 +200,7 @@ export const VoucherList: React.FC<VoucherListProps> = ({ params }) => {
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
} }
selectedChannelId={channel.id} selectedChannelId={channel?.id}
/> />
<ActionDialog <ActionDialog
confirmButtonState={voucherBulkDeleteOpts.status} confirmButtonState={voucherBulkDeleteOpts.status}

View file

@ -11,7 +11,40 @@ import Skeleton from "@saleor/components/Skeleton";
import { UserPermissionProps } from "@saleor/types"; import { UserPermissionProps } from "@saleor/types";
import { PermissionEnum } from "@saleor/types/globalTypes"; import { PermissionEnum } from "@saleor/types/globalTypes";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { defineMessages, useIntl } from "react-intl";
const messages = defineMessages({
createNewChannel: {
defaultMessage: "Create new channel"
},
noOrders: {
defaultMessage: "No orders ready to fulfill",
id: "homeNotificationTableNoOrders"
},
noPaymentWaiting: {
defaultMessage: "No payments waiting for capture",
id: "homeNotificationsNoPayments"
},
noProductsOut: {
defaultMessage: "No products out of stock",
id: "homeNotificationsTableNoProducts"
},
orderReady: {
defaultMessage:
"{amount, plural,one {One order is ready to fulfill} other {{amount} Orders are ready to fulfill}}",
id: "homeNotificationTableOrders"
},
paymentCapture: {
defaultMessage:
"{amount, plural,one {One payment to capture}other {{amount} Payments to capture}}",
id: "homeNotificationTablePayments"
},
productOut: {
defaultMessage:
"{amount, plural,one {One product out of stock}other {{amount} Products out of stock}}",
id: "homeNotificationTableProducts"
}
});
const useStyles = makeStyles( const useStyles = makeStyles(
() => ({ () => ({
@ -33,28 +66,46 @@ interface HomeNotificationTableProps extends UserPermissionProps {
ordersToCapture: number; ordersToCapture: number;
ordersToFulfill: number; ordersToFulfill: number;
productsOutOfStock: number; productsOutOfStock: number;
onCreateNewChannelClick: () => void;
onOrdersToFulfillClick: () => void; onOrdersToFulfillClick: () => void;
onOrdersToCaptureClick: () => void; onOrdersToCaptureClick: () => void;
onProductsOutOfStockClick: () => void; onProductsOutOfStockClick: () => void;
noChannel: boolean;
} }
const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => { const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
const { const {
onCreateNewChannelClick,
onOrdersToCaptureClick, onOrdersToCaptureClick,
onOrdersToFulfillClick, onOrdersToFulfillClick,
onProductsOutOfStockClick, onProductsOutOfStockClick,
ordersToCapture, ordersToCapture,
ordersToFulfill, ordersToFulfill,
productsOutOfStock, productsOutOfStock,
userPermissions userPermissions,
noChannel
} = props; } = props;
const classes = useStyles(props); const classes = useStyles(props);
const intl = useIntl();
return ( return (
<Card className={classes.tableCard}> <Card className={classes.tableCard}>
<ResponsiveTable> <ResponsiveTable>
<TableBody className={classes.tableRow}> <TableBody className={classes.tableRow}>
{noChannel && (
<TableRow hover={true} onClick={onCreateNewChannelClick}>
<TableCell>
<Typography>
{intl.formatMessage(messages.createNewChannel)}
</Typography>
</TableCell>
<TableCell className={classes.arrowIcon}>
<KeyboardArrowRight />
</TableCell>
</TableRow>
)}
<RequirePermissions <RequirePermissions
userPermissions={userPermissions} userPermissions={userPermissions}
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]} requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
@ -65,20 +116,13 @@ const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
<Skeleton /> <Skeleton />
) : ordersToFulfill === 0 ? ( ) : ordersToFulfill === 0 ? (
<Typography> <Typography>
<FormattedMessage {intl.formatMessage(messages.noOrders)}
defaultMessage="No orders ready to fulfill"
id="homeNotificationTableNoOrders"
/>
</Typography> </Typography>
) : ( ) : (
<Typography> <Typography>
<FormattedMessage {intl.formatMessage(messages.orderReady, {
defaultMessage="{amount, plural,one {One order is ready to fulfill} other {{amount} Orders are ready to fulfill}}" amount: <strong>{ordersToFulfill}</strong>
id="homeNotificationTableOrders" })}
values={{
amount: <strong>{ordersToFulfill}</strong>
}}
/>
</Typography> </Typography>
)} )}
</TableCell> </TableCell>
@ -92,20 +136,13 @@ const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
<Skeleton /> <Skeleton />
) : ordersToCapture === 0 ? ( ) : ordersToCapture === 0 ? (
<Typography> <Typography>
<FormattedMessage {intl.formatMessage(messages.noPaymentWaiting)}
defaultMessage="No payments waiting for capture"
id="homeNotificationsNoPayments"
/>
</Typography> </Typography>
) : ( ) : (
<Typography> <Typography>
<FormattedMessage {intl.formatMessage(messages.paymentCapture, {
defaultMessage="{amount, plural,one {One payment to capture}other {{amount} Payments to capture}}" amount: <strong>{ordersToCapture}</strong>
id="homeNotificationTablePayments" })}
values={{
amount: <strong>{ordersToCapture}</strong>
}}
/>
</Typography> </Typography>
)} )}
</TableCell> </TableCell>
@ -124,20 +161,13 @@ const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
<Skeleton /> <Skeleton />
) : productsOutOfStock === 0 ? ( ) : productsOutOfStock === 0 ? (
<Typography> <Typography>
<FormattedMessage {intl.formatMessage(messages.noProductsOut)}
defaultMessage="No products out of stock"
id="homeNotificationsTableNoProducts"
/>
</Typography> </Typography>
) : ( ) : (
<Typography> <Typography>
<FormattedMessage {intl.formatMessage(messages.productOut, {
defaultMessage="{amount, plural,one {One product out of stock}other {{amount} Products out of stock}}" amount: <strong>{productsOutOfStock}</strong>
id="homeNotificationTableProducts" })}
values={{
amount: <strong>{productsOutOfStock}</strong>
}}
/>
</Typography> </Typography>
)} )}
</TableCell> </TableCell>

View file

@ -46,17 +46,19 @@ const useStyles = makeStyles(
export interface HomePageProps extends UserPermissionProps { export interface HomePageProps extends UserPermissionProps {
activities: Home_activities_edges_node[]; activities: Home_activities_edges_node[];
orders: number; orders: number | null;
ordersToCapture: number; ordersToCapture: number | null;
ordersToFulfill: number; ordersToFulfill: number | null;
productsOutOfStock: number; productsOutOfStock: number;
sales: Home_salesToday_gross; sales: Home_salesToday_gross;
topProducts: Home_productTopToday_edges_node[]; topProducts: Home_productTopToday_edges_node[] | null;
userName: string; userName: string;
onCreateNewChannelClick: () => void;
onOrdersToCaptureClick: () => void; onOrdersToCaptureClick: () => void;
onOrdersToFulfillClick: () => void; onOrdersToFulfillClick: () => void;
onProductClick: (productId: string, variantId: string) => void; onProductClick: (productId: string, variantId: string) => void;
onProductsOutOfStockClick: () => void; onProductsOutOfStockClick: () => void;
noChannel: boolean;
} }
const HomePage: React.FC<HomePageProps> = props => { const HomePage: React.FC<HomePageProps> = props => {
@ -67,13 +69,15 @@ const HomePage: React.FC<HomePageProps> = props => {
topProducts, topProducts,
onProductClick, onProductClick,
activities, activities,
onCreateNewChannelClick,
onOrdersToCaptureClick, onOrdersToCaptureClick,
onOrdersToFulfillClick, onOrdersToFulfillClick,
onProductsOutOfStockClick, onProductsOutOfStockClick,
ordersToCapture, ordersToCapture = 0,
ordersToFulfill, ordersToFulfill = 0,
productsOutOfStock, productsOutOfStock = 0,
userPermissions userPermissions = [],
noChannel
} = props; } = props;
const classes = useStyles(props); const classes = useStyles(props);
@ -99,7 +103,9 @@ const HomePage: React.FC<HomePageProps> = props => {
/> />
} }
> >
{sales ? ( {noChannel ? (
0
) : sales ? (
<Money money={sales} /> <Money money={sales} />
) : ( ) : (
<Skeleton style={{ width: "5em" }} /> <Skeleton style={{ width: "5em" }} />
@ -115,15 +121,18 @@ const HomePage: React.FC<HomePageProps> = props => {
/> />
} }
> >
{orders === undefined ? ( {noChannel ? (
<Skeleton style={{ width: "5em" }} /> 0
) : ( ) : orders !== undefined ? (
orders orders
) : (
<Skeleton style={{ width: "5em" }} />
)} )}
</HomeAnalyticsCard> </HomeAnalyticsCard>
</div> </div>
</RequirePermissions> </RequirePermissions>
<HomeNotificationTable <HomeNotificationTable
onCreateNewChannelClick={onCreateNewChannelClick}
onOrdersToCaptureClick={onOrdersToCaptureClick} onOrdersToCaptureClick={onOrdersToCaptureClick}
onOrdersToFulfillClick={onOrdersToFulfillClick} onOrdersToFulfillClick={onOrdersToFulfillClick}
onProductsOutOfStockClick={onProductsOutOfStockClick} onProductsOutOfStockClick={onProductsOutOfStockClick}
@ -131,30 +140,35 @@ const HomePage: React.FC<HomePageProps> = props => {
ordersToFulfill={ordersToFulfill} ordersToFulfill={ordersToFulfill}
productsOutOfStock={productsOutOfStock} productsOutOfStock={productsOutOfStock}
userPermissions={userPermissions} userPermissions={userPermissions}
noChannel={noChannel}
/> />
<CardSpacer /> <CardSpacer />
<RequirePermissions {topProducts && (
userPermissions={userPermissions} <RequirePermissions
requiredPermissions={[ userPermissions={userPermissions}
PermissionEnum.MANAGE_ORDERS, requiredPermissions={[
PermissionEnum.MANAGE_PRODUCTS PermissionEnum.MANAGE_ORDERS,
]} PermissionEnum.MANAGE_PRODUCTS
> ]}
<HomeProductListCard >
onRowClick={onProductClick} <HomeProductListCard
topProducts={topProducts} onRowClick={onProductClick}
/> topProducts={topProducts}
<CardSpacer /> />
</RequirePermissions> <CardSpacer />
</div> </RequirePermissions>
<div> )}
<RequirePermissions
userPermissions={userPermissions}
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
>
<HomeActivityCard activities={activities} />
</RequirePermissions>
</div> </div>
{activities && (
<div>
<RequirePermissions
userPermissions={userPermissions}
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
>
<HomeActivityCard activities={activities} />
</RequirePermissions>
</div>
)}
</Grid> </Grid>
</Container> </Container>
); );

View file

@ -1,7 +1,7 @@
import makeQuery from "@saleor/hooks/makeQuery";
import gql from "graphql-tag"; import gql from "graphql-tag";
import { TypedQuery } from "../queries"; import { Home, HomeVariables } from "./types/Home";
import { Home } from "./types/Home";
const home = gql` const home = gql`
query Home($channel: String!) { query Home($channel: String!) {
@ -80,4 +80,5 @@ const home = gql`
} }
} }
`; `;
export const HomePageQuery = TypedQuery<Home, {}>(home);
export const useHomePage = makeQuery<Home, HomeVariables>(home);

View file

@ -1,64 +1,69 @@
import { channelsListUrl } from "@saleor/channels/urls";
import useAppChannel from "@saleor/components/AppLayout/AppChannelContext"; import useAppChannel from "@saleor/components/AppLayout/AppChannelContext";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useUser from "@saleor/hooks/useUser"; import useUser from "@saleor/hooks/useUser";
import React from "react"; import React from "react";
import { getUserName, maybe } from "../../misc"; import { getUserName } from "../../misc";
import { orderListUrl } from "../../orders/urls"; import { orderListUrl } from "../../orders/urls";
import { productListUrl, productVariantEditUrl } from "../../products/urls"; import { productListUrl, productVariantEditUrl } from "../../products/urls";
import { OrderStatusFilter, StockAvailability } from "../../types/globalTypes"; import { OrderStatusFilter, StockAvailability } from "../../types/globalTypes";
import HomePage from "../components/HomePage"; import HomePage from "../components/HomePage";
import { HomePageQuery } from "../queries"; import { useHomePage } from "../queries";
const HomeSection = () => { const HomeSection = () => {
const navigate = useNavigator(); const navigate = useNavigator();
const { user } = useUser(); const { user } = useUser();
const { channel } = useAppChannel(); const { channel } = useAppChannel();
const noChannel = !channel && typeof channel !== "undefined";
const { data } = useHomePage({
displayLoader: true,
skip: noChannel,
variables: { channel: channel?.slug }
});
return ( return (
<HomePageQuery displayLoader variables={{ channel: channel.slug }}> <HomePage
{({ data }) => ( activities={data?.activities.edges.map(edge => edge.node).reverse()}
<HomePage orders={data?.ordersToday.totalCount}
activities={maybe(() => sales={data?.salesToday.gross}
data.activities.edges.map(edge => edge.node).reverse() topProducts={data?.productTopToday.edges.map(edge => edge.node)}
)} onProductClick={(productId, variantId) =>
orders={maybe(() => data.ordersToday.totalCount)} navigate(productVariantEditUrl(productId, variantId))
sales={maybe(() => data.salesToday.gross)} }
topProducts={maybe(() => onCreateNewChannelClick={() => {
data.productTopToday.edges.map(edge => edge.node) navigate(channelsListUrl());
)} }}
onProductClick={(productId, variantId) => onOrdersToCaptureClick={() =>
navigate(productVariantEditUrl(productId, variantId)) navigate(
} orderListUrl({
onOrdersToCaptureClick={() => status: [OrderStatusFilter.READY_TO_CAPTURE]
navigate( })
orderListUrl({ )
status: [OrderStatusFilter.READY_TO_CAPTURE] }
}) onOrdersToFulfillClick={() =>
) navigate(
} orderListUrl({
onOrdersToFulfillClick={() => status: [OrderStatusFilter.READY_TO_FULFILL]
navigate( })
orderListUrl({ )
status: [OrderStatusFilter.READY_TO_FULFILL] }
}) onProductsOutOfStockClick={() =>
) navigate(
} productListUrl({
onProductsOutOfStockClick={() => stockStatus: StockAvailability.OUT_OF_STOCK
navigate( })
productListUrl({ )
stockStatus: StockAvailability.OUT_OF_STOCK }
}) ordersToCapture={data?.ordersToCapture.totalCount}
) ordersToFulfill={data?.ordersToFulfill.totalCount}
} productsOutOfStock={data?.productsOutOfStock.totalCount}
ordersToCapture={maybe(() => data.ordersToCapture.totalCount)} userName={getUserName(user, true)}
ordersToFulfill={maybe(() => data.ordersToFulfill.totalCount)} userPermissions={user?.userPermissions}
productsOutOfStock={maybe(() => data.productsOutOfStock.totalCount)} noChannel={noChannel}
userName={getUserName(user, true)} />
userPermissions={user?.userPermissions || []}
/>
)}
</HomePageQuery>
); );
}; };

View file

@ -142,13 +142,21 @@ const Routes: React.FC = () => {
} = useAuth(); } = useAuth();
const { channel } = useAppChannel(false); const { channel } = useAppChannel(false);
const channelLoaded = typeof channel !== "undefined";
const homePageLoaded =
channelLoaded &&
isAuthenticated &&
!tokenAuthLoading &&
!tokenVerifyLoading;
const homePageLoading =
(isAuthenticated && !channelLoaded) || (hasToken && tokenVerifyLoading);
return ( return (
<> <>
<WindowTitle title={intl.formatMessage(commonMessages.dashboard)} /> <WindowTitle title={intl.formatMessage(commonMessages.dashboard)} />
{channel && {homePageLoaded ? (
isAuthenticated &&
!tokenAuthLoading &&
!tokenVerifyLoading ? (
<AppLayout> <AppLayout>
<ErrorBoundary <ErrorBoundary
onError={() => onError={() =>
@ -284,7 +292,7 @@ const Routes: React.FC = () => {
</Switch> </Switch>
</ErrorBoundary> </ErrorBoundary>
</AppLayout> </AppLayout>
) : (isAuthenticated && !channel) || (hasToken && tokenVerifyLoading) ? ( ) : homePageLoading ? (
<LoginLoading /> <LoginLoading />
) : ( ) : (
<Auth /> <Auth />

View file

@ -20,6 +20,7 @@ import { ListViews } from "@saleor/types";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createSortHandler from "@saleor/utils/handlers/sortHandler";
import { mapNodeToChoice } from "@saleor/utils/maps";
import { getSortParams } from "@saleor/utils/sort"; import { getSortParams } from "@saleor/utils/sort";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
@ -259,12 +260,9 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
tabName={maybe(() => tabs[currentTab - 1].name, "...")} tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/> />
<ChannelPickerDialog <ChannelPickerDialog
channelsChoices={availableChannels.map(channel => ({ channelsChoices={mapNodeToChoice(availableChannels)}
label: channel.name,
value: channel.id
}))}
confirmButtonState="success" confirmButtonState="success"
defaultChoice={channel.id} defaultChoice={channel?.id}
open={params.action === "create-order"} open={params.action === "create-order"}
onClose={closeModal} onClose={closeModal}
onConfirm={channel => onConfirm={channel =>

View file

@ -71,6 +71,8 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
const { channel, availableChannels } = useAppChannel(); const { channel, availableChannels } = useAppChannel();
const noChannel = !channel && typeof channel !== "undefined";
const tabs = getFilterTabs(); const tabs = getFilterTabs();
const currentTab = const currentTab =
@ -176,23 +178,25 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
onSubmit={handleFilterTabDelete} onSubmit={handleFilterTabDelete}
tabName={getStringOrPlaceholder(tabs[currentTab - 1]?.name)} tabName={getStringOrPlaceholder(tabs[currentTab - 1]?.name)}
/> />
<ChannelPickerDialog {!noChannel && (
channelsChoices={availableChannels.map(channel => ({ <ChannelPickerDialog
label: channel.name, channelsChoices={availableChannels.map(channel => ({
value: channel.id label: channel.name,
}))} value: channel.id
confirmButtonState="success" }))}
defaultChoice={channel.id} confirmButtonState="success"
open={params.action === "create-order"} defaultChoice={channel.id}
onClose={closeModal} open={params.action === "create-order"}
onConfirm={channel => onClose={closeModal}
createOrder({ onConfirm={channel =>
variables: { createOrder({
input: { channel } variables: {
} input: { channel }
}) }
} })
/> }
/>
)}
</> </>
); );
}; };

View file

@ -127,6 +127,8 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
}); });
const { availableChannels, channel } = useAppChannel(); const { availableChannels, channel } = useAppChannel();
const noChannel = !channel && typeof channel !== "undefined";
const [openModal, closeModal] = createDialogActionHandlers< const [openModal, closeModal] = createDialogActionHandlers<
ProductListUrlDialog, ProductListUrlDialog,
ProductListUrlQueryParams ProductListUrlQueryParams
@ -222,8 +224,8 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
); );
const paginationState = createPaginationState(settings.rowNumber, params); const paginationState = createPaginationState(settings.rowNumber, params);
const filter = getFilterVariables(params, channel.slug); const filter = !noChannel ? getFilterVariables(params, channel.slug) : null;
const sort = getSortQueryVariables(params, channel.slug); const sort = !noChannel ? getSortQueryVariables(params, channel.slug) : null;
const queryVariables = React.useMemo<ProductListVariables>( const queryVariables = React.useMemo<ProductListVariables>(
() => ({ () => ({
...paginationState, ...paginationState,
@ -302,7 +304,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
() => attributes.data.availableInGrid.edges.map(edge => edge.node), () => attributes.data.availableInGrid.edges.map(edge => edge.node),
[] []
)} )}
currencySymbol={channel?.currencyCode} currencySymbol={channel?.currencyCode || ""}
currentTab={currentTab} currentTab={currentTab}
defaultSettings={defaultListSettings[ListViews.PRODUCT_LIST]} defaultSettings={defaultListSettings[ListViews.PRODUCT_LIST]}
filterOpts={filterOpts} filterOpts={filterOpts}
@ -380,7 +382,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
tabs={getFilterTabs().map(tab => tab.name)} tabs={getFilterTabs().map(tab => tab.name)}
onExport={() => openModal("export")} onExport={() => openModal("export")}
channelsCount={availableChannels?.length} channelsCount={availableChannels?.length}
selectedChannelId={channel.id} selectedChannelId={channel?.id}
/> />
<ActionDialog <ActionDialog
open={params.action === "delete"} open={params.action === "delete"}

View file

@ -423,7 +423,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
loading: searchCollectionsOpts.loading, loading: searchCollectionsOpts.loading,
onFetchMore: loadMoreCollections onFetchMore: loadMoreCollections
}} }}
selectedChannelId={channel.id} selectedChannelId={channel?.id}
openChannelsModal={handleChannelsModalOpen} openChannelsModal={handleChannelsModalOpen}
onChannelsChange={setCurrentChannels} onChannelsChange={setCurrentChannels}
/> />

View file

@ -52100,21 +52100,7 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id" class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id"
data-test="availability" data-test="availability"
data-test-availability="true" data-test-availability="true"
> />
<div>
<div
aria-controls="availability-menu"
aria-haspopup="true"
role="button"
>
<div
class="StatusLabel-root-id undefined StatusLabel-errorDot-id"
>
Available in 1/2
</div>
</div>
</div>
</td>
</tr> </tr>
<tr <tr
class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id" class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id"
@ -52170,21 +52156,7 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id" class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id"
data-test="availability" data-test="availability"
data-test-availability="true" data-test-availability="true"
> />
<div>
<div
aria-controls="availability-menu"
aria-haspopup="true"
role="button"
>
<div
class="StatusLabel-root-id undefined StatusLabel-errorDot-id"
>
Available in 1/2
</div>
</div>
</div>
</td>
</tr> </tr>
<tr <tr
class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id" class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id"
@ -52240,21 +52212,7 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id" class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id"
data-test="availability" data-test="availability"
data-test-availability="true" data-test-availability="true"
> />
<div>
<div
aria-controls="availability-menu"
aria-haspopup="true"
role="button"
>
<div
class="StatusLabel-root-id undefined StatusLabel-errorDot-id"
>
Available in 1/2
</div>
</div>
</div>
</td>
</tr> </tr>
<tr <tr
class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id" class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id"
@ -52310,21 +52268,7 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id" class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id"
data-test="availability" data-test="availability"
data-test-availability="true" data-test-availability="true"
> />
<div>
<div
aria-controls="availability-menu"
aria-haspopup="true"
role="button"
>
<div
class="StatusLabel-root-id undefined StatusLabel-errorDot-id"
>
Available in 1/2
</div>
</div>
</div>
</td>
</tr> </tr>
<tr <tr
class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id" class="MuiTableRow-root-id CollectionList-tableRow-id MuiTableRow-hover-id"
@ -52380,21 +52324,7 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id" class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id"
data-test="availability" data-test="availability"
data-test-availability="true" data-test-availability="true"
> />
<div>
<div
aria-controls="availability-menu"
aria-haspopup="true"
role="button"
>
<div
class="StatusLabel-root-id undefined StatusLabel-errorDot-id"
>
Available in 1/2
</div>
</div>
</div>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -91661,11 +91591,11 @@ exports[`Storyshots Views / HomePage loading 1`] = `
<td <td
class="MuiTableCell-root-id MuiTableCell-body-id" class="MuiTableCell-root-id MuiTableCell-body-id"
> >
<span <div
class="Skeleton-skeleton-id" class="MuiTypography-root-id MuiTypography-body1-id"
> >
No orders ready to fulfill
</span> </div>
</td> </td>
<td <td
class="MuiTableCell-root-id MuiTableCell-body-id HomeNotificationTable-arrowIcon-id" class="MuiTableCell-root-id MuiTableCell-body-id HomeNotificationTable-arrowIcon-id"
@ -91689,11 +91619,11 @@ exports[`Storyshots Views / HomePage loading 1`] = `
<td <td
class="MuiTableCell-root-id MuiTableCell-body-id" class="MuiTableCell-root-id MuiTableCell-body-id"
> >
<span <div
class="Skeleton-skeleton-id" class="MuiTypography-root-id MuiTypography-body1-id"
> >
No payments waiting for capture
</span> </div>
</td> </td>
<td <td
class="MuiTableCell-root-id MuiTableCell-body-id HomeNotificationTable-arrowIcon-id" class="MuiTableCell-root-id MuiTableCell-body-id HomeNotificationTable-arrowIcon-id"
@ -91717,11 +91647,11 @@ exports[`Storyshots Views / HomePage loading 1`] = `
<td <td
class="MuiTableCell-root-id MuiTableCell-body-id" class="MuiTableCell-root-id MuiTableCell-body-id"
> >
<span <div
class="Skeleton-skeleton-id" class="MuiTypography-root-id MuiTypography-body1-id"
> >
No products out of stock
</span> </div>
</td> </td>
<td <td
class="MuiTableCell-root-id MuiTableCell-body-id HomeNotificationTable-arrowIcon-id" class="MuiTableCell-root-id MuiTableCell-body-id HomeNotificationTable-arrowIcon-id"
@ -91746,149 +91676,6 @@ exports[`Storyshots Views / HomePage loading 1`] = `
<div <div
class="CardSpacer-spacer-id" class="CardSpacer-spacer-id"
/> />
<div
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
>
<div
class="CardTitle-root-id"
>
<span
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
Top Products
</span>
<div
class="CardTitle-toolbar-id"
/>
</div>
<div
class="CardTitle-children-id"
/>
<hr
class="CardTitle-hr-id"
/>
<div
class="ResponsiveTable-root-id"
>
<table
class="MuiTable-root-id"
>
<colgroup>
<col
class="HomeProductListCard-colAvatar-id"
/>
<col
class="HomeProductListCard-colName-id"
/>
<col />
</colgroup>
<tbody
class="MuiTableBody-root-id"
>
<tr
class="MuiTableRow-root-id"
>
<td
class="MuiTableCell-root-id MuiTableCell-body-id TableCellAvatar-root-id HomeProductListCard-colAvatar-id"
>
<div
class="TableCellAvatar-content-id"
>
<div
class="MuiAvatar-root-id MuiAvatar-circle-id TableCellAvatar-avatar-id HomeProductListCard-avatarProps-id MuiAvatar-colorDefault-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id MuiSvgIcon-colorPrimary-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z"
/>
</svg>
</div>
<div
class="TableCellAvatar-children-id"
/>
</div>
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id HomeProductListCard-label-id"
>
<span
class="Skeleton-skeleton-id"
>
</span>
</td>
<td
class="MuiTableCell-root-id MuiTableCell-body-id"
>
<div
class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-alignRight-id"
>
<span
class="Skeleton-skeleton-id"
>
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div
class="CardSpacer-spacer-id"
/>
</div>
<div>
<div
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
>
<div
class="CardTitle-root-id"
>
<span
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
Activity
</span>
<div
class="CardTitle-toolbar-id"
/>
</div>
<div
class="CardTitle-children-id"
/>
<hr
class="CardTitle-hr-id"
/>
<ul
class="MuiList-root-id MuiList-dense-id MuiList-padding-id"
>
<li
class="MuiListItem-root-id MuiListItem-dense-id MuiListItem-gutters-id"
>
<div
class="MuiListItemText-root-id HomeActivityCard-loadingProducts-id MuiListItemText-dense-id"
>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
>
<span
class="Skeleton-skeleton-id"
>
</span>
</div>
</div>
</li>
</ul>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -13,6 +13,8 @@ const shop = shopFixture(placeholderImage);
const homePageProps: Omit<HomePageProps, "classes"> = { const homePageProps: Omit<HomePageProps, "classes"> = {
activities: shop.activities.edges.map(edge => edge.node), activities: shop.activities.edges.map(edge => edge.node),
noChannel: false,
onCreateNewChannelClick: () => undefined,
onOrdersToCaptureClick: () => undefined, onOrdersToCaptureClick: () => undefined,
onOrdersToFulfillClick: () => undefined, onOrdersToFulfillClick: () => undefined,
onProductClick: () => undefined, onProductClick: () => undefined,