diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 2f7279e2382..e41cdf42915 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -3012,6 +3012,9 @@ "src_dot_home_dot_components_dot_HomeActivityCard_dot_placed": { "string": "Order #{orderId} was placed" }, + "src_dot_home_dot_components_dot_HomeNotificationTable_dot_createNewChannel": { + "string": "Create new channel" + }, "src_dot_hooks_dot_3382262667": { "string": "Variant {name} has been set as default." }, diff --git a/src/categories/views/CategoryDetails.tsx b/src/categories/views/CategoryDetails.tsx index bf002ae9aae..30a97ff0264 100644 --- a/src/categories/views/CategoryDetails.tsx +++ b/src/categories/views/CategoryDetails.tsx @@ -4,6 +4,7 @@ import DeleteIcon from "@material-ui/icons/Delete"; import ActionDialog from "@saleor/components/ActionDialog"; import useAppChannel from "@saleor/components/AppLayout/AppChannelContext"; import NotFoundPage from "@saleor/components/NotFoundPage"; +import Skeleton from "@saleor/components/Skeleton"; import { WindowTitle } from "@saleor/components/WindowTitle"; import useBulkActions from "@saleor/hooks/useBulkActions"; import useNavigator from "@saleor/hooks/useNavigator"; @@ -207,6 +208,10 @@ export const CategoryDetails: React.FC = ({ variables => updatePrivateMetadata({ variables }) ); + if (typeof channel === "undefined") { + return ; + } + return ( <> data.category.name)} /> @@ -256,7 +261,7 @@ export const CategoryDetails: React.FC = ({ data.category.products.edges.map(edge => edge.node) )} saveButtonBarState={updateResult.status} - selectedChannelId={channel.id} + selectedChannelId={channel?.id} subcategories={maybe(() => data.category.children.edges.map(edge => edge.node) )} diff --git a/src/collections/components/CollectionList/CollectionList.tsx b/src/collections/components/CollectionList/CollectionList.tsx index a42b4c57fc6..fd8cf3aea94 100644 --- a/src/collections/components/CollectionList/CollectionList.tsx +++ b/src/collections/components/CollectionList/CollectionList.tsx @@ -146,8 +146,8 @@ const CollectionList: React.FC = props => { collections, collection => { const isSelected = collection ? isChecked(collection.id) : false; - const channel = collection?.channelListings.find( - listing => listing.channel.id === selectedChannelId + const channel = collection?.channelListings?.find( + listing => listing?.channel?.id === selectedChannelId ); return ( = props => { {collection && !collection?.channelListings?.length ? ( "-" ) : collection?.channelListings !== undefined ? ( - + channel ? ( + + ) : null ) : ( )} diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index c91004e5d39..4e404ae7f60 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -184,7 +184,7 @@ export const CollectionList: React.FC = ({ params }) => { toggle={toggle} toggleAll={toggleAll} channelsCount={availableChannels?.length} - selectedChannelId={channel.id} + selectedChannelId={channel?.id} /> params.ids.length > 0)} diff --git a/src/components/AppLayout/AppChannelContext.tsx b/src/components/AppLayout/AppChannelContext.tsx index 27aee5a0201..930cacfd019 100644 --- a/src/components/AppLayout/AppChannelContext.tsx +++ b/src/components/AppLayout/AppChannelContext.tsx @@ -30,6 +30,7 @@ export const AppChannelProvider: React.FC = ({ children }) => { const { data: channelData, refetch } = useChannelsList({ skip: !isAuthenticated }); + const [isPickerActive, setPickerActive] = React.useState(false); React.useEffect(() => { if (!selectedChannel) { @@ -38,9 +39,10 @@ export const AppChannelProvider: React.FC = ({ children }) => { }, [channelData]); const availableChannels = channelData?.channels || []; - const channel = availableChannels.find( - channel => channel.id === selectedChannel - ); + + const channel = + channelData && + (availableChannels.find(channel => channel.id === selectedChannel) || null); return ( { ); }; + AppChannelProvider.displayName = "AppChannelProvider"; function useAppChannel(enablePicker = true): UseAppChannel { diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index b093a8c147e..4be74a99fa3 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -215,12 +215,14 @@ const AppLayout: React.FC = ({ children }) => { .includes("mac")} onClick={() => setNavigatorVisibility(true)} /> - + {channel && ( + + )} = ({ triggerChange ); const formDisabled = data.channelListings?.some(channel => - validatePrice(channel.discountValue) + validatePrice(channel?.discountValue) ); return ( diff --git a/src/discounts/components/SaleSummary/SaleSummary.tsx b/src/discounts/components/SaleSummary/SaleSummary.tsx index 08bca53afa7..bf77c571dc6 100644 --- a/src/discounts/components/SaleSummary/SaleSummary.tsx +++ b/src/discounts/components/SaleSummary/SaleSummary.tsx @@ -51,12 +51,12 @@ const SaleSummary: React.FC = ({ sale.type === SaleType.FIXED && channel?.discountValue ? ( ) : channel?.discountValue ? ( - + ) : ( "-" ) diff --git a/src/discounts/components/VoucherSummary/VoucherSummary.tsx b/src/discounts/components/VoucherSummary/VoucherSummary.tsx index d66c1837cbe..2e1233191a1 100644 --- a/src/discounts/components/VoucherSummary/VoucherSummary.tsx +++ b/src/discounts/components/VoucherSummary/VoucherSummary.tsx @@ -69,12 +69,12 @@ const VoucherSummary: React.FC = ({ channel?.discountValue ? ( ) : channel?.discountValue ? ( - + ) : ( "-" ) diff --git a/src/discounts/views/SaleList/SaleList.tsx b/src/discounts/views/SaleList/SaleList.tsx index f32a4271487..a71ee51a42e 100644 --- a/src/discounts/views/SaleList/SaleList.tsx +++ b/src/discounts/views/SaleList/SaleList.tsx @@ -199,7 +199,7 @@ export const SaleList: React.FC = ({ params }) => { } - selectedChannelId={channel.id} + selectedChannelId={channel?.id} /> = ({ ...(updateChannelsOpts.data ?.voucherChannelListingUpdate.errors || []) ]} - selectedChannelId={channel.id} + selectedChannelId={channel?.id} pageInfo={pageInfo} onNextPage={loadNextPage} onPreviousPage={loadPreviousPage} diff --git a/src/discounts/views/VoucherList/VoucherList.tsx b/src/discounts/views/VoucherList/VoucherList.tsx index d4e72cdeba8..84c804ca785 100644 --- a/src/discounts/views/VoucherList/VoucherList.tsx +++ b/src/discounts/views/VoucherList/VoucherList.tsx @@ -200,7 +200,7 @@ export const VoucherList: React.FC = ({ params }) => { } - selectedChannelId={channel.id} + selectedChannelId={channel?.id} /> ({ @@ -33,28 +66,46 @@ interface HomeNotificationTableProps extends UserPermissionProps { ordersToCapture: number; ordersToFulfill: number; productsOutOfStock: number; + onCreateNewChannelClick: () => void; onOrdersToFulfillClick: () => void; onOrdersToCaptureClick: () => void; onProductsOutOfStockClick: () => void; + noChannel: boolean; } const HomeNotificationTable: React.FC = props => { const { + onCreateNewChannelClick, onOrdersToCaptureClick, onOrdersToFulfillClick, onProductsOutOfStockClick, ordersToCapture, ordersToFulfill, productsOutOfStock, - userPermissions + userPermissions, + noChannel } = props; const classes = useStyles(props); + const intl = useIntl(); + return ( + {noChannel && ( + + + + {intl.formatMessage(messages.createNewChannel)} + + + + + + + )} = props => { ) : ordersToFulfill === 0 ? ( - + {intl.formatMessage(messages.noOrders)} ) : ( - {ordersToFulfill} - }} - /> + {intl.formatMessage(messages.orderReady, { + amount: {ordersToFulfill} + })} )} @@ -92,20 +136,13 @@ const HomeNotificationTable: React.FC = props => { ) : ordersToCapture === 0 ? ( - + {intl.formatMessage(messages.noPaymentWaiting)} ) : ( - {ordersToCapture} - }} - /> + {intl.formatMessage(messages.paymentCapture, { + amount: {ordersToCapture} + })} )} @@ -124,20 +161,13 @@ const HomeNotificationTable: React.FC = props => { ) : productsOutOfStock === 0 ? ( - + {intl.formatMessage(messages.noProductsOut)} ) : ( - {productsOutOfStock} - }} - /> + {intl.formatMessage(messages.productOut, { + amount: {productsOutOfStock} + })} )} diff --git a/src/home/components/HomePage/HomePage.tsx b/src/home/components/HomePage/HomePage.tsx index 072173bcdb2..dbae636707b 100644 --- a/src/home/components/HomePage/HomePage.tsx +++ b/src/home/components/HomePage/HomePage.tsx @@ -46,17 +46,19 @@ const useStyles = makeStyles( export interface HomePageProps extends UserPermissionProps { activities: Home_activities_edges_node[]; - orders: number; - ordersToCapture: number; - ordersToFulfill: number; + orders: number | null; + ordersToCapture: number | null; + ordersToFulfill: number | null; productsOutOfStock: number; sales: Home_salesToday_gross; - topProducts: Home_productTopToday_edges_node[]; + topProducts: Home_productTopToday_edges_node[] | null; userName: string; + onCreateNewChannelClick: () => void; onOrdersToCaptureClick: () => void; onOrdersToFulfillClick: () => void; onProductClick: (productId: string, variantId: string) => void; onProductsOutOfStockClick: () => void; + noChannel: boolean; } const HomePage: React.FC = props => { @@ -67,13 +69,15 @@ const HomePage: React.FC = props => { topProducts, onProductClick, activities, + onCreateNewChannelClick, onOrdersToCaptureClick, onOrdersToFulfillClick, onProductsOutOfStockClick, - ordersToCapture, - ordersToFulfill, - productsOutOfStock, - userPermissions + ordersToCapture = 0, + ordersToFulfill = 0, + productsOutOfStock = 0, + userPermissions = [], + noChannel } = props; const classes = useStyles(props); @@ -99,7 +103,9 @@ const HomePage: React.FC = props => { /> } > - {sales ? ( + {noChannel ? ( + 0 + ) : sales ? ( ) : ( @@ -115,15 +121,18 @@ const HomePage: React.FC = props => { /> } > - {orders === undefined ? ( - - ) : ( + {noChannel ? ( + 0 + ) : orders !== undefined ? ( orders + ) : ( + )} = props => { ordersToFulfill={ordersToFulfill} productsOutOfStock={productsOutOfStock} userPermissions={userPermissions} + noChannel={noChannel} /> - - - - - -
- - - + {topProducts && ( + + + + + )}
+ {activities && ( +
+ + + +
+ )}
); diff --git a/src/home/queries.ts b/src/home/queries.ts index a286a72f2db..ba18119c0d1 100644 --- a/src/home/queries.ts +++ b/src/home/queries.ts @@ -1,7 +1,7 @@ +import makeQuery from "@saleor/hooks/makeQuery"; import gql from "graphql-tag"; -import { TypedQuery } from "../queries"; -import { Home } from "./types/Home"; +import { Home, HomeVariables } from "./types/Home"; const home = gql` query Home($channel: String!) { @@ -80,4 +80,5 @@ const home = gql` } } `; -export const HomePageQuery = TypedQuery(home); + +export const useHomePage = makeQuery(home); diff --git a/src/home/views/index.tsx b/src/home/views/index.tsx index 957ed0c8ff8..d873ce5ff67 100644 --- a/src/home/views/index.tsx +++ b/src/home/views/index.tsx @@ -1,64 +1,69 @@ +import { channelsListUrl } from "@saleor/channels/urls"; import useAppChannel from "@saleor/components/AppLayout/AppChannelContext"; import useNavigator from "@saleor/hooks/useNavigator"; import useUser from "@saleor/hooks/useUser"; import React from "react"; -import { getUserName, maybe } from "../../misc"; +import { getUserName } from "../../misc"; import { orderListUrl } from "../../orders/urls"; import { productListUrl, productVariantEditUrl } from "../../products/urls"; import { OrderStatusFilter, StockAvailability } from "../../types/globalTypes"; import HomePage from "../components/HomePage"; -import { HomePageQuery } from "../queries"; +import { useHomePage } from "../queries"; const HomeSection = () => { const navigate = useNavigator(); const { user } = useUser(); const { channel } = useAppChannel(); + const noChannel = !channel && typeof channel !== "undefined"; + + const { data } = useHomePage({ + displayLoader: true, + skip: noChannel, + variables: { channel: channel?.slug } + }); + return ( - - {({ data }) => ( - - data.activities.edges.map(edge => edge.node).reverse() - )} - orders={maybe(() => data.ordersToday.totalCount)} - sales={maybe(() => data.salesToday.gross)} - topProducts={maybe(() => - data.productTopToday.edges.map(edge => edge.node) - )} - onProductClick={(productId, variantId) => - navigate(productVariantEditUrl(productId, variantId)) - } - onOrdersToCaptureClick={() => - navigate( - orderListUrl({ - status: [OrderStatusFilter.READY_TO_CAPTURE] - }) - ) - } - onOrdersToFulfillClick={() => - navigate( - orderListUrl({ - status: [OrderStatusFilter.READY_TO_FULFILL] - }) - ) - } - onProductsOutOfStockClick={() => - navigate( - productListUrl({ - stockStatus: StockAvailability.OUT_OF_STOCK - }) - ) - } - ordersToCapture={maybe(() => data.ordersToCapture.totalCount)} - ordersToFulfill={maybe(() => data.ordersToFulfill.totalCount)} - productsOutOfStock={maybe(() => data.productsOutOfStock.totalCount)} - userName={getUserName(user, true)} - userPermissions={user?.userPermissions || []} - /> - )} - + edge.node).reverse()} + orders={data?.ordersToday.totalCount} + sales={data?.salesToday.gross} + topProducts={data?.productTopToday.edges.map(edge => edge.node)} + onProductClick={(productId, variantId) => + navigate(productVariantEditUrl(productId, variantId)) + } + onCreateNewChannelClick={() => { + navigate(channelsListUrl()); + }} + onOrdersToCaptureClick={() => + navigate( + orderListUrl({ + status: [OrderStatusFilter.READY_TO_CAPTURE] + }) + ) + } + onOrdersToFulfillClick={() => + navigate( + orderListUrl({ + status: [OrderStatusFilter.READY_TO_FULFILL] + }) + ) + } + onProductsOutOfStockClick={() => + navigate( + productListUrl({ + stockStatus: StockAvailability.OUT_OF_STOCK + }) + ) + } + ordersToCapture={data?.ordersToCapture.totalCount} + ordersToFulfill={data?.ordersToFulfill.totalCount} + productsOutOfStock={data?.productsOutOfStock.totalCount} + userName={getUserName(user, true)} + userPermissions={user?.userPermissions} + noChannel={noChannel} + /> ); }; diff --git a/src/index.tsx b/src/index.tsx index 766332330ea..b199afe7031 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -142,13 +142,21 @@ const Routes: React.FC = () => { } = useAuth(); const { channel } = useAppChannel(false); + const channelLoaded = typeof channel !== "undefined"; + + const homePageLoaded = + channelLoaded && + isAuthenticated && + !tokenAuthLoading && + !tokenVerifyLoading; + + const homePageLoading = + (isAuthenticated && !channelLoaded) || (hasToken && tokenVerifyLoading); + return ( <> - {channel && - isAuthenticated && - !tokenAuthLoading && - !tokenVerifyLoading ? ( + {homePageLoaded ? ( @@ -284,7 +292,7 @@ const Routes: React.FC = () => { - ) : (isAuthenticated && !channel) || (hasToken && tokenVerifyLoading) ? ( + ) : homePageLoading ? ( ) : ( diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index 2316c9d2545..a10d9cee59d 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -20,6 +20,7 @@ import { ListViews } from "@saleor/types"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import { mapNodeToChoice } from "@saleor/utils/maps"; import { getSortParams } from "@saleor/utils/sort"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -259,12 +260,9 @@ export const OrderDraftList: React.FC = ({ params }) => { tabName={maybe(() => tabs[currentTab - 1].name, "...")} /> ({ - label: channel.name, - value: channel.id - }))} + channelsChoices={mapNodeToChoice(availableChannels)} confirmButtonState="success" - defaultChoice={channel.id} + defaultChoice={channel?.id} open={params.action === "create-order"} onClose={closeModal} onConfirm={channel => diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index 8c48e961c43..f9ac6b9fbb7 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -71,6 +71,8 @@ export const OrderList: React.FC = ({ params }) => { const { channel, availableChannels } = useAppChannel(); + const noChannel = !channel && typeof channel !== "undefined"; + const tabs = getFilterTabs(); const currentTab = @@ -176,23 +178,25 @@ export const OrderList: React.FC = ({ params }) => { onSubmit={handleFilterTabDelete} tabName={getStringOrPlaceholder(tabs[currentTab - 1]?.name)} /> - ({ - label: channel.name, - value: channel.id - }))} - confirmButtonState="success" - defaultChoice={channel.id} - open={params.action === "create-order"} - onClose={closeModal} - onConfirm={channel => - createOrder({ - variables: { - input: { channel } - } - }) - } - /> + {!noChannel && ( + ({ + label: channel.name, + value: channel.id + }))} + confirmButtonState="success" + defaultChoice={channel.id} + open={params.action === "create-order"} + onClose={closeModal} + onConfirm={channel => + createOrder({ + variables: { + input: { channel } + } + }) + } + /> + )} ); }; diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index f47c86ccc66..98cbce0d4fd 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -127,6 +127,8 @@ export const ProductList: React.FC = ({ params }) => { }); const { availableChannels, channel } = useAppChannel(); + const noChannel = !channel && typeof channel !== "undefined"; + const [openModal, closeModal] = createDialogActionHandlers< ProductListUrlDialog, ProductListUrlQueryParams @@ -222,8 +224,8 @@ export const ProductList: React.FC = ({ params }) => { ); const paginationState = createPaginationState(settings.rowNumber, params); - const filter = getFilterVariables(params, channel.slug); - const sort = getSortQueryVariables(params, channel.slug); + const filter = !noChannel ? getFilterVariables(params, channel.slug) : null; + const sort = !noChannel ? getSortQueryVariables(params, channel.slug) : null; const queryVariables = React.useMemo( () => ({ ...paginationState, @@ -302,7 +304,7 @@ export const ProductList: React.FC = ({ params }) => { () => attributes.data.availableInGrid.edges.map(edge => edge.node), [] )} - currencySymbol={channel?.currencyCode} + currencySymbol={channel?.currencyCode || ""} currentTab={currentTab} defaultSettings={defaultListSettings[ListViews.PRODUCT_LIST]} filterOpts={filterOpts} @@ -380,7 +382,7 @@ export const ProductList: React.FC = ({ params }) => { tabs={getFilterTabs().map(tab => tab.name)} onExport={() => openModal("export")} channelsCount={availableChannels?.length} - selectedChannelId={channel.id} + selectedChannelId={channel?.id} /> = ({ id, params }) => { loading: searchCollectionsOpts.loading, onFetchMore: loadMoreCollections }} - selectedChannelId={channel.id} + selectedChannelId={channel?.id} openChannelsModal={handleChannelsModalOpen} onChannelsChange={setCurrentChannels} /> diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 7f790fbf860..bbfeede454c 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -51858,21 +51858,7 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = ` class="MuiTableCell-root-id MuiTableCell-body-id CollectionList-colAvailability-id" data-test="availability" data-test-availability="true" - > -
-
-
- Available in 1/2 -
-
-
- + /> -
-
-
- Available in 1/2 -
-
-
- + /> -
-
-
- Available in 1/2 -
-
-
- + /> -
-
-
- Available in 1/2 -
-
-
- + /> -
-
-
- Available in 1/2 -
-
-
- + /> @@ -90429,11 +90359,11 @@ exports[`Storyshots Views / HomePage loading 1`] = ` - - ‌ - + No orders ready to fulfill + - - ‌ - + No payments waiting for capture + - - ‌ - + No products out of stock + -
-
- - Top Products - -
-
-
-
-
- - - - - - - - - - - - - -
-
-
- -
-
-
-
- - ‌ - - -
- - ‌ - -
-
-
-
-
-
-
-
-
- - Activity - -
-
-
-
-
    -
  • -
    -
    - - ‌ - -
    -
    -
  • -
-
diff --git a/src/storybook/stories/home/HomePage.tsx b/src/storybook/stories/home/HomePage.tsx index 44b528c0c81..b150068499e 100644 --- a/src/storybook/stories/home/HomePage.tsx +++ b/src/storybook/stories/home/HomePage.tsx @@ -13,6 +13,8 @@ const shop = shopFixture(placeholderImage); const homePageProps: Omit = { activities: shop.activities.edges.map(edge => edge.node), + noChannel: false, + onCreateNewChannelClick: () => undefined, onOrdersToCaptureClick: () => undefined, onOrdersToFulfillClick: () => undefined, onProductClick: () => undefined,