diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 8ebae72dffa..46b8393fa01 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -1966,6 +1966,10 @@ "context": "unassign multiple attributes from item", "string": "{counter,plural,one{Are you sure you want to unassign this attribute from {itemTypeName}?} other{Are you sure you want to unassign {attributeQuantity} attributes from {itemTypeName}?}}" }, + "src_dot_components_dot_CardMenu_dot_cardMenuItemLoading": { + "context": "menu item loading", + "string": "working..." + }, "src_dot_components_dot_ChannelsAvailabilityCard_dot_3326160357": { "context": "section header", "string": "Availability" @@ -3820,6 +3824,10 @@ "context": "used by filter label", "string": "Used by" }, + "src_dot_giftCards_dot_GiftCardsList_dot_GiftCardsListTable_dot_GiftCardsListTableHeader_dot_deleteLabel": { + "context": "GiftCardEnableDisableSection enable label", + "string": "Delete" + }, "src_dot_giftCards_dot_GiftCardsList_dot_GiftCardsListTable_dot_GiftCardsListTableHeader_dot_disableLabel": { "context": "GiftCardEnableDisableSection enable label", "string": "Deactivate" @@ -3924,6 +3932,26 @@ "context": "GiftCardsListHeader menu item settings", "string": "Settings" }, + "src_dot_giftCards_dot_components_dot_GiftCardCustomerCard_dot_customerGiftCardsAbsentSubtitle": { + "context": "subtitle", + "string": "There are no gift cards assigned to this customer" + }, + "src_dot_giftCards_dot_components_dot_GiftCardCustomerCard_dot_customerGiftCardsCardTitle": { + "context": "title", + "string": "Gift Cards" + }, + "src_dot_giftCards_dot_components_dot_GiftCardCustomerCard_dot_customerGiftCardsIssueNewCardButton": { + "context": "button", + "string": "Issue new card" + }, + "src_dot_giftCards_dot_components_dot_GiftCardCustomerCard_dot_customerGiftCardsPresentSubtitle": { + "context": "subtitle", + "string": "Only five newest gift cards are shown here" + }, + "src_dot_giftCards_dot_components_dot_GiftCardCustomerCard_dot_customerGiftCardsViewAllButton": { + "context": "button", + "string": "View All" + }, "src_dot_giftCards_dot_components_dot_GiftCardDeleteDialog_dot_consentLabel": { "context": "GiftCardDeleteDialog consent label", "string": "{selectedItemsCount,plural,one{I am aware that I am removing a gift card with balance} other{I am aware that I am removing gift cards with balance}}" diff --git a/package-lock.json b/package-lock.json index fdc5d69ec42..12d11b4786b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6385,23 +6385,6 @@ "requires": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" - }, - "dependencies": { - "@types/react": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.3.tgz", - "integrity": "sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "csstype": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", - "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==" - } } }, "@types/html-minifier-terser": { diff --git a/src/components/CardMenu/CardMenu.tsx b/src/components/CardMenu/CardMenu.tsx index 21b100a2d54..8f758264bf8 100644 --- a/src/components/CardMenu/CardMenu.tsx +++ b/src/components/CardMenu/CardMenu.tsx @@ -1,23 +1,32 @@ import { + CircularProgress, ClickAwayListener, Grow, IconButton, MenuItem, MenuList, Paper, - Popper + Popper, + Typography } from "@material-ui/core"; import MoreVertIcon from "@material-ui/icons/MoreVert"; import { makeStyles } from "@saleor/macaw-ui"; -import React from "react"; +import classNames from "classnames"; +import * as React from "react"; +import { useEffect, useRef, useState } from "react"; +import { FormattedMessage } from "react-intl"; -const ITEM_HEIGHT = 48; +import { cardMenuMessages as messages } from "./messages"; +const ITEM_HEIGHT = 48; export interface CardMenuItem { disabled?: boolean; label: string; testId?: string; onSelect: () => void; + loading?: boolean; + withLoading?: boolean; + hasError?: boolean; } export interface CardMenuProps { @@ -42,6 +51,14 @@ const useStyles = makeStyles( marginTop: theme.spacing(2), maxHeight: ITEM_HEIGHT * 4.5, overflowY: "scroll" + }, + loadingContent: { + width: "100%", + display: "grid", + gridTemplateColumns: "1fr 24px", + gap: theme.spacing(2), + alignItems: "center", + justifyContent: "flex-end" } }), { name: "CardMenu" } @@ -51,8 +68,8 @@ const CardMenu: React.FC = props => { const { className, disabled, menuItems, ...rest } = props; const classes = useStyles(props); - const anchorRef = React.useRef(null); - const [open, setOpen] = React.useState(false); + const anchorRef = useRef(null); + const [open, setOpen] = useState(false); const handleToggle = () => setOpen(prevOpen => !prevOpen); @@ -74,8 +91,8 @@ const CardMenu: React.FC = props => { } }; - const prevOpen = React.useRef(open); - React.useEffect(() => { + const prevOpen = useRef(open); + useEffect(() => { if (prevOpen.current === true && open === false) { anchorRef.current!.focus(); } @@ -83,11 +100,27 @@ const CardMenu: React.FC = props => { prevOpen.current = open; }, [open]); + useEffect(() => { + const hasAnyItemsLoadingOrWithError = menuItems + ?.filter(({ withLoading }) => withLoading) + ?.some(({ loading, hasError }) => loading || hasError); + + if (!hasAnyItemsLoadingOrWithError) { + setOpen(false); + } + }, [menuItems]); + const handleMenuClick = (index: number) => { - menuItems[index].onSelect(); - setOpen(false); + const selectedItem = menuItems[index]; + selectedItem.onSelect(); + + if (!selectedItem.withLoading) { + setOpen(false); + } }; + const isWithLoading = menuItems.some(({ withLoading }) => withLoading); + return (
= props => { {menuItems.map((menuItem, menuItemIndex) => ( handleMenuClick(menuItemIndex)} key={menuItem.label} data-test={menuItem.testId} > - {menuItem.label} +
+ {menuItem.loading ? ( + <> + + + + + + ) : ( + {menuItem.label} + )} +
))} diff --git a/src/components/CardMenu/messages.ts b/src/components/CardMenu/messages.ts new file mode 100644 index 00000000000..3fc26f439af --- /dev/null +++ b/src/components/CardMenu/messages.ts @@ -0,0 +1,8 @@ +import { defineMessages } from "react-intl"; + +export const cardMenuMessages = defineMessages({ + cardMenuItemLoading: { + defaultMessage: "working...", + description: "menu item loading" + } +}); diff --git a/src/components/CardTitle/CardTitle.tsx b/src/components/CardTitle/CardTitle.tsx index 6f4cad33c54..f2d77d9b1e1 100644 --- a/src/components/CardTitle/CardTitle.tsx +++ b/src/components/CardTitle/CardTitle.tsx @@ -27,6 +27,9 @@ const useStyles = makeStyles( fontWeight: 500, lineHeight: 1 }, + subtitle: { + fontWeight: 400 + }, toolbar: { marginRight: theme.spacing(-1) } @@ -39,6 +42,7 @@ interface CardTitleProps { className?: string; height?: "default" | "const"; title: string | React.ReactNode; + subtitle?: string | React.ReactNode; toolbar?: React.ReactNode; onClick?: (event: React.MouseEvent) => void; } @@ -49,6 +53,7 @@ const CardTitle: React.FC = props => { children, height, title, + subtitle, toolbar, onClick, ...rest diff --git a/src/components/CollectionWithDividers/CollectionWithDividers.tsx b/src/components/CollectionWithDividers/CollectionWithDividers.tsx index 2e4d0966264..3fd6ab2af55 100644 --- a/src/components/CollectionWithDividers/CollectionWithDividers.tsx +++ b/src/components/CollectionWithDividers/CollectionWithDividers.tsx @@ -1,10 +1,10 @@ import { Divider } from "@material-ui/core"; -import initial from "lodash/initial"; import React from "react"; interface CollectionWithDividersProps { DividerComponent?: React.FunctionComponent; renderEmpty?: (collection: T[]) => any; + withOuterDividers?: boolean; collection: T[]; renderItem: ( item: T | undefined, @@ -13,7 +13,25 @@ interface CollectionWithDividersProps { ) => any; } +const Wrapper: React.FC<{ + withOuterDividers?: boolean; + SelectedDivider?: React.FunctionComponent; +}> = ({ withOuterDividers, SelectedDivider, children }) => ( +
+ {withOuterDividers && SelectedDivider ? ( + <> + + {children} + + + ) : ( + <>{children} + )} +
+); + function CollectionWithDividers({ + withOuterDividers = false, collection, renderItem, DividerComponent, @@ -31,15 +49,20 @@ function CollectionWithDividers({ const SelectedDividerComponent = DividerComponent || Divider; - return initial( - collection.reduce( - (result, item, index) => [ - ...result, - renderItem(item, index, collection), - - ], - [] - ) + return ( + + <> + {collection.map((item, index) => ( + <> + {renderItem(item, index, collection)} + + + ))} + + ); } diff --git a/src/components/ExtendedPageHeader/ExtendedPageHeader.tsx b/src/components/ExtendedPageHeader/ExtendedPageHeader.tsx index d601066dfdf..6c4dacfd44f 100644 --- a/src/components/ExtendedPageHeader/ExtendedPageHeader.tsx +++ b/src/components/ExtendedPageHeader/ExtendedPageHeader.tsx @@ -54,6 +54,7 @@ const useStyles = makeStyles( interface ExtendedPageHeaderProps { children?: React.ReactNode; className?: string; + childrenWrapperClassName?: string; inline?: boolean; underline?: boolean; title?: React.ReactNode; @@ -61,7 +62,15 @@ interface ExtendedPageHeaderProps { } const ExtendedPageHeader: React.FC = props => { - const { children, className, inline, underline, title, testId } = props; + const { + children, + className, + childrenWrapperClassName, + inline, + underline, + title, + testId + } = props; const classes = useStyles(props); @@ -75,7 +84,9 @@ const ExtendedPageHeader: React.FC = props => { })} > {title} -
{children}
+
+ {children} +
{underline && (
diff --git a/src/components/PageTitleWithStatusChip/PageTitleWithStatusChip.tsx b/src/components/PageTitleWithStatusChip/PageTitleWithStatusChip.tsx index 8f0dd9a3446..adbc0ac311d 100644 --- a/src/components/PageTitleWithStatusChip/PageTitleWithStatusChip.tsx +++ b/src/components/PageTitleWithStatusChip/PageTitleWithStatusChip.tsx @@ -1,13 +1,10 @@ import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer"; -import StatusChip from "@saleor/components/StatusChip"; -import { StatusType } from "@saleor/components/StatusChip/types"; import { makeStyles } from "@saleor/macaw-ui"; import React from "react"; +import ExtendedPageHeader from "../ExtendedPageHeader"; export interface PageTitleWithStatusChipProps { title: string; - statusLabel: string; - statusType: StatusType; } const useStyles = makeStyles( @@ -22,17 +19,20 @@ const useStyles = makeStyles( const PageTitleWithStatusChip: React.FC = ({ title, - statusLabel, - statusType + children }) => { const classes = useStyles({}); return ( -
- {title} - - -
+ + <> + + {children} + + ); }; diff --git a/src/customers/components/CustomerDetailsPage/CustomerDetailsPage.tsx b/src/customers/components/CustomerDetailsPage/CustomerDetailsPage.tsx index 3f52167ab06..f5412278e20 100644 --- a/src/customers/components/CustomerDetailsPage/CustomerDetailsPage.tsx +++ b/src/customers/components/CustomerDetailsPage/CustomerDetailsPage.tsx @@ -8,6 +8,7 @@ import { MetadataFormData } from "@saleor/components/Metadata/types"; import PageHeader from "@saleor/components/PageHeader"; import Savebar from "@saleor/components/Savebar"; import { AccountErrorFragment } from "@saleor/fragments/types/AccountErrorFragment"; +import CustomerGiftCardsCard from "@saleor/giftCards/components/GiftCardCustomerCard/CustomerGiftCardsCard"; import { SubmitPromise } from "@saleor/hooks/useForm"; import { sectionNames } from "@saleor/intl"; import { Backlink } from "@saleor/macaw-ui"; @@ -117,6 +118,8 @@ const CustomerDetailsPage: React.FC = ({ /> + +
{ + const customerDetails = useContext(CustomerDetailsContext); + + return customerDetails; +}; diff --git a/src/customers/providers/CustomerDetailsProvider.tsx b/src/customers/providers/CustomerDetailsProvider.tsx new file mode 100644 index 00000000000..13c82ce567b --- /dev/null +++ b/src/customers/providers/CustomerDetailsProvider.tsx @@ -0,0 +1,40 @@ +import React, { createContext } from "react"; + +import { useCustomerDetails } from "../queries"; +import { CustomerDetails } from "../types/CustomerDetails"; + +export interface CustomerDetailsProviderProps { + id: string; +} + +interface CustomerDetailsConsumerProps { + customer: CustomerDetails | null; + loading: boolean | null; +} + +export const CustomerDetailsContext = createContext< + CustomerDetailsConsumerProps +>(null); + +export const CustomerDetailsProvider: React.FC = ({ + children, + id +}) => { + const { data, loading } = useCustomerDetails({ + displayLoader: true, + variables: { + id + } + }); + + const providerValues: CustomerDetailsConsumerProps = { + customer: data, + loading + }; + + return ( + + {children} + + ); +}; diff --git a/src/customers/queries.ts b/src/customers/queries.ts index ea45c37a96f..803d430817d 100644 --- a/src/customers/queries.ts +++ b/src/customers/queries.ts @@ -90,7 +90,8 @@ const customerDetails = gql` } } `; -export const TypedCustomerDetailsQuery = TypedQuery< + +export const useCustomerDetails = makeQuery< CustomerDetails, CustomerDetailsVariables >(customerDetails); diff --git a/src/customers/views/CustomerDetails.tsx b/src/customers/views/CustomerDetails.tsx index a44f9e6221f..476d1cfcbb1 100644 --- a/src/customers/views/CustomerDetails.tsx +++ b/src/customers/views/CustomerDetails.tsx @@ -1,36 +1,18 @@ -import { DialogContentText } from "@material-ui/core"; -import ActionDialog from "@saleor/components/ActionDialog"; -import NotFoundPage from "@saleor/components/NotFoundPage"; -import { WindowTitle } from "@saleor/components/WindowTitle"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import { commonMessages } from "@saleor/intl"; -import { getStringOrPlaceholder } from "@saleor/misc"; -import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler"; -import { - useMetadataUpdate, - usePrivateMetadataUpdate -} from "@saleor/utils/metadata/updateMetadata"; import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; +import { useIntl } from "react-intl"; -import { orderListUrl, orderUrl } from "../../orders/urls"; -import CustomerDetailsPage, { - CustomerDetailsPageFormData -} from "../components/CustomerDetailsPage/CustomerDetailsPage"; import { TypedRemoveCustomerMutation, TypedUpdateCustomerMutation } from "../mutations"; -import { TypedCustomerDetailsQuery } from "../queries"; +import { CustomerDetailsProvider } from "../providers/CustomerDetailsProvider"; import { RemoveCustomer } from "../types/RemoveCustomer"; import { UpdateCustomer } from "../types/UpdateCustomer"; -import { - customerAddressesUrl, - customerListUrl, - customerUrl, - CustomerUrlQueryParams -} from "../urls"; +import { customerListUrl, CustomerUrlQueryParams } from "../urls"; +import { CustomerDetailsContent } from "./CustomerDetailsContent"; interface CustomerDetailsViewProps { id: string; @@ -75,109 +57,18 @@ export const CustomerDetailsView: React.FC = ({ {(removeCustomer, removeCustomerOpts) => ( {(updateCustomer, updateCustomerOpts) => ( - - {customerDetails => { - const user = customerDetails.data?.user; - - if (user === null) { - return ; - } - - const [updateMetadata] = useMetadataUpdate({}); - const [updatePrivateMetadata] = usePrivateMetadataUpdate({}); - - const updateData = async ( - data: CustomerDetailsPageFormData - ) => { - const result = await updateCustomer({ - variables: { - id, - input: { - email: data.email, - firstName: data.firstName, - isActive: data.isActive, - lastName: data.lastName, - note: data.note - } - } - }); - - return result.data.customerUpdate.errors; - }; - - const handleSubmit = createMetadataUpdateHandler( - user, - updateData, - variables => updateMetadata({ variables }), - variables => updatePrivateMetadata({ variables }) - ); - - return ( - <> - - - navigate(customerAddressesUrl(id)) - } - onBack={handleBack} - onRowClick={id => navigate(orderUrl(id))} - onSubmit={handleSubmit} - onDelete={() => - navigate( - customerUrl(id, { - action: "remove" - }) - ) - } - onViewAllOrdersClick={() => - navigate( - orderListUrl({ - customer: user?.email - }) - ) - } - /> - - navigate(customerUrl(id), { replace: true }) - } - onConfirm={() => removeCustomer()} - title={intl.formatMessage({ - defaultMessage: "Delete Customer", - description: "dialog header" - })} - variant="delete" - open={params.action === "remove"} - > - - - {getStringOrPlaceholder(user?.email)} - - ) - }} - /> - - - - ); - }} - + + + )} )} diff --git a/src/customers/views/CustomerDetailsContent.tsx b/src/customers/views/CustomerDetailsContent.tsx new file mode 100644 index 00000000000..231b20ed578 --- /dev/null +++ b/src/customers/views/CustomerDetailsContent.tsx @@ -0,0 +1,150 @@ +import { DialogContentText } from "@material-ui/core"; +import ActionDialog from "@saleor/components/ActionDialog"; +import NotFoundPage from "@saleor/components/NotFoundPage"; +import { WindowTitle } from "@saleor/components/WindowTitle"; +import { UseNavigatorResult } from "@saleor/hooks/useNavigator"; +import { getStringOrPlaceholder } from "@saleor/misc"; +import { MutationResultAdditionalProps } from "@saleor/types"; +import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler"; +import { + useMetadataUpdate, + usePrivateMetadataUpdate +} from "@saleor/utils/metadata/updateMetadata"; +import React, { useContext } from "react"; +import { MutationFunction, MutationResult } from "react-apollo"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { orderListUrl, orderUrl } from "../../orders/urls"; +import CustomerDetailsPage, { + CustomerDetailsPageFormData +} from "../components/CustomerDetailsPage/CustomerDetailsPage"; +import { CustomerDetailsContext } from "../providers/CustomerDetailsProvider"; +import { + RemoveCustomer, + RemoveCustomerVariables +} from "../types/RemoveCustomer"; +import { + UpdateCustomer, + UpdateCustomerVariables +} from "../types/UpdateCustomer"; +import { + customerAddressesUrl, + customerUrl, + CustomerUrlQueryParams +} from "../urls"; + +export interface CustomerDetailsContentProps { + handleBack: () => void; + updateCustomer: MutationFunction; + removeCustomer: MutationFunction; + id: string; + updateCustomerOpts: MutationResult & + MutationResultAdditionalProps; + removeCustomerOpts: MutationResult & + MutationResultAdditionalProps; + navigate: UseNavigatorResult; + params: CustomerUrlQueryParams; +} + +export const CustomerDetailsContent: React.FC = ({ + handleBack, + updateCustomer, + id, + updateCustomerOpts, + removeCustomerOpts, + navigate, + removeCustomer, + params +}) => { + const customerDetails = useContext(CustomerDetailsContext); + const user = customerDetails?.customer?.user; + const customerDetailsLoading = customerDetails?.loading; + + const intl = useIntl(); + + if (user === null) { + return ; + } + + const [updateMetadata] = useMetadataUpdate({}); + const [updatePrivateMetadata] = usePrivateMetadataUpdate({}); + + const updateData = async (data: CustomerDetailsPageFormData) => { + const result = await updateCustomer({ + variables: { + id, + input: { + email: data.email, + firstName: data.firstName, + isActive: data.isActive, + lastName: data.lastName, + note: data.note + } + } + }); + + return result.data.customerUpdate.errors; + }; + + const handleSubmit = createMetadataUpdateHandler( + user, + updateData, + variables => updateMetadata({ variables }), + variables => updatePrivateMetadata({ variables }) + ); + + return ( + <> + + navigate(customerAddressesUrl(id))} + onBack={handleBack} + onRowClick={id => navigate(orderUrl(id))} + onSubmit={handleSubmit} + onDelete={() => + navigate( + customerUrl(id, { + action: "remove" + }) + ) + } + onViewAllOrdersClick={() => + navigate( + orderListUrl({ + customer: user?.email + }) + ) + } + /> + navigate(customerUrl(id), true)} + onConfirm={() => removeCustomer()} + title={intl.formatMessage({ + defaultMessage: "Delete Customer", + description: "dialog header" + })} + variant="delete" + open={params.action === "remove"} + > + + {getStringOrPlaceholder(user?.email)} + }} + /> + + + + ); +}; diff --git a/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialog.tsx b/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialog.tsx index ba36de4e770..accf5ea46ff 100644 --- a/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialog.tsx +++ b/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialog.tsx @@ -8,7 +8,6 @@ import commonErrorMessages from "@saleor/utils/errors/common"; import React, { useState } from "react"; import { useIntl } from "react-intl"; -import { GIFT_CARD_LIST_QUERY } from "../GiftCardsList/types"; import ContentWithProgress from "./ContentWithProgress"; import GiftCardCreateDialogCodeContent from "./GiftCardCreateDialogCodeContent"; import GiftCardCreateDialogForm, { @@ -17,10 +16,21 @@ import GiftCardCreateDialogForm, { import { giftCardCreateMessages as messages } from "./messages"; import { useGiftCardCreateMutation } from "./mutations"; import { useChannelCurrencies } from "./queries"; +import { GiftCardCreateFormCustomer } from "./types"; import { GiftCardCreate } from "./types/GiftCardCreate"; import { getGiftCardExpiryInputData } from "./utils"; -const GiftCardCreateDialog: React.FC = ({ onClose, open }) => { +interface GiftCardCreateDialogProps extends DialogProps { + refetchQueries: string[]; + initialCustomer?: GiftCardCreateFormCustomer | null; +} + +const GiftCardCreateDialog: React.FC = ({ + onClose, + open, + refetchQueries, + initialCustomer +}) => { const intl = useIntl(); const notify = useNotifier(); @@ -80,7 +90,7 @@ const GiftCardCreateDialog: React.FC = ({ onClose, open }) => { const [createGiftCard, createGiftCardOpts] = useGiftCardCreateMutation({ onCompleted, - refetchQueries: [GIFT_CARD_LIST_QUERY] + refetchQueries }); const handleSubmit = (data: GiftCardCreateFormData) => { @@ -114,6 +124,7 @@ const GiftCardCreateDialog: React.FC = ({ onClose, open }) => { onClose={handleClose} apiErrors={createGiftCardOpts?.data?.giftCardCreate?.errors} onSubmit={handleSubmit} + initialCustomer={initialCustomer} /> ))} diff --git a/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialogForm.tsx b/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialogForm.tsx index 5fdc57fb36a..07b76984879 100644 --- a/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialogForm.tsx +++ b/src/giftCards/GiftCardCreateDialog/GiftCardCreateDialogForm.tsx @@ -36,8 +36,6 @@ export interface GiftCardCreateFormData extends GiftCardCreateCommonFormData { channelSlug?: string; } -const initialCustomer = { email: "", name: "" }; - export const initialData: GiftCardCreateFormData = { tag: "", balanceAmount: 1, @@ -51,19 +49,22 @@ export const initialData: GiftCardCreateFormData = { expiryPeriodAmount: 12, requiresActivation: true }; - interface GiftCardCreateDialogFormProps { opts: { status: ConfirmButtonTransitionState }; apiErrors: GiftCardError[]; onSubmit: (data: GiftCardCreateFormData) => void; onClose: () => void; + initialCustomer?: GiftCardCreateFormCustomer | null; } +const defaultInitialCustomer = { email: "", name: "" }; + const GiftCardCreateDialogForm: React.FC = ({ onSubmit, opts, onClose, - apiErrors + apiErrors, + initialCustomer }) => { const intl = useIntl(); const classes = useStyles({}); @@ -75,7 +76,7 @@ const GiftCardCreateDialogForm: React.FC = ({ const [selectedCustomer, setSelectedCustomer] = useState< GiftCardCreateFormCustomer - >(initialCustomer); + >(initialCustomer || defaultInitialCustomer); const handleSubmit = (data: GiftCardCreateFormData) => onSubmit({ ...data, selectedCustomer }); @@ -103,7 +104,8 @@ const GiftCardCreateDialogForm: React.FC = ({ ...initialData, ...getInitialExpirySettingsData(), balanceCurrency: "", - channelSlug: "" + channelSlug: "", + sendToCustomerSelected: !!initialCustomer }, handleSubmit ); @@ -161,6 +163,7 @@ const GiftCardCreateDialogForm: React.FC = ({ sendToCustomerSelected={sendToCustomerSelected} selectedCustomer={selectedCustomer} setSelectedCustomer={setSelectedCustomer} + disabled={!!initialCustomer} /> diff --git a/src/giftCards/GiftCardCreateDialog/GiftCardCustomerSelectField.tsx b/src/giftCards/GiftCardCreateDialog/GiftCardCustomerSelectField.tsx index 69a91ba5bd7..8a0f2b7493e 100644 --- a/src/giftCards/GiftCardCreateDialog/GiftCardCustomerSelectField.tsx +++ b/src/giftCards/GiftCardCreateDialog/GiftCardCustomerSelectField.tsx @@ -13,11 +13,13 @@ import { GiftCardCreateFormCustomer } from "./types"; export interface GiftCardCustomerSelectFieldProps { selectedCustomer: GiftCardCreateFormCustomer; setSelectedCustomer: (customer: GiftCardCreateFormCustomer) => void; + disabled?: boolean; } const GiftCardCustomerSelectField: React.FC = ({ selectedCustomer, - setSelectedCustomer + setSelectedCustomer, + disabled = false }) => { const intl = useIntl(); @@ -54,6 +56,7 @@ const GiftCardCustomerSelectField: React.FC = fetchChoices={search} onChange={handleSelect} onFetchMore={loadMore} + disabled={disabled} /> ); }; diff --git a/src/giftCards/GiftCardUpdate/GiftCardUpdatePageHeader/GiftCardEnableDisableSection.tsx b/src/giftCards/GiftCardUpdate/GiftCardUpdatePageHeader/GiftCardEnableDisableSection.tsx index 81654ae13d5..fa35b5f059b 100644 --- a/src/giftCards/GiftCardUpdate/GiftCardUpdatePageHeader/GiftCardEnableDisableSection.tsx +++ b/src/giftCards/GiftCardUpdate/GiftCardUpdatePageHeader/GiftCardEnableDisableSection.tsx @@ -1,22 +1,13 @@ -import useNotifier from "@saleor/hooks/useNotifier"; import { commonMessages } from "@saleor/intl"; import { ConfirmButton } from "@saleor/macaw-ui"; -import commonErrorMessages from "@saleor/utils/errors/common"; import React from "react"; import { useIntl } from "react-intl"; import { bulkEnableDisableSectionMessages as buttonMessages } from "../../GiftCardsList/GiftCardsListTable/GiftCardsListTableHeader/messages"; import useGiftCardDetails from "../providers/GiftCardDetailsProvider/hooks/useGiftCardDetails"; -import { giftCardEnableDisableSectionMessages as messages } from "./messages"; -import { - useGiftCardActivateMutation, - useGiftCardDeactivateMutation -} from "./mutations"; -import { GiftCardActivate } from "./types/GiftCardActivate"; -import { GiftCardDeactivate } from "./types/GiftCardDeactivate"; +import useGiftCardActivateToggle from "./hooks/useGiftCardActivateToggle"; const GiftCardEnableDisableSection: React.FC = () => { - const notify = useNotifier(); const intl = useIntl(); const { @@ -27,50 +18,12 @@ const GiftCardEnableDisableSection: React.FC = () => { return null; } - const onActivateCompleted = (data: GiftCardActivate) => { - const errors = data?.giftCardActivate?.errors; - - if (!!errors?.length) { - notify({ - status: "error", - text: intl.formatMessage(commonErrorMessages.unknownError) - }); - - return; - } - - notify({ - status: "success", - text: intl.formatMessage(messages.successfullyEnabledTitle) - }); - }; - - const onDeactivateCompleted = (data: GiftCardDeactivate) => { - const errors = data?.giftCardDeactivate?.errors; - - if (!!errors?.length) { - notify({ - status: "error", - text: intl.formatMessage(commonErrorMessages.unknownError) - }); - return; - } - - notify({ - status: "success", - text: intl.formatMessage(messages.successfullyDisabledTitle) - }); - }; - - const [giftCardActivate, giftCardActivateOpts] = useGiftCardActivateMutation({ - onCompleted: onActivateCompleted - }); - - const [ + const { + giftCardActivate, giftCardDeactivate, - giftCardDeactivateOpts - ] = useGiftCardDeactivateMutation({ - onCompleted: onDeactivateCompleted + currentOpts + } = useGiftCardActivateToggle({ + isActive }); const handleClick = () => @@ -82,8 +35,6 @@ const GiftCardEnableDisableSection: React.FC = () => { ? buttonMessages.disableLabel : buttonMessages.enableLabel; - const currentOpts = isActive ? giftCardDeactivateOpts : giftCardActivateOpts; - return ( { const { openResendCodeDialog } = useGiftCardUpdateDialogs(); - const { displayCode, isActive, isExpired } = giftCard; + const { displayCode, isExpired } = giftCard; const title = intl.formatMessage(tableMessages.codeEndingWithLabel, { displayCode }); - const getPageTitle = () => { - if (isExpired) { - return ( - - ); - } - - if (!isActive) { - return ( - - ); - } - - return title; - }; - return ( <> {intl.formatMessage(sectionNames.giftCards)} - + + + + } + > {!isExpired && ( diff --git a/src/giftCards/GiftCardUpdate/GiftCardUpdatePageHeader/hooks/useGiftCardActivateToggle.ts b/src/giftCards/GiftCardUpdate/GiftCardUpdatePageHeader/hooks/useGiftCardActivateToggle.ts new file mode 100644 index 00000000000..8b395a27840 --- /dev/null +++ b/src/giftCards/GiftCardUpdate/GiftCardUpdatePageHeader/hooks/useGiftCardActivateToggle.ts @@ -0,0 +1,92 @@ +import useNotifier from "@saleor/hooks/useNotifier"; +import commonErrorMessages from "@saleor/utils/errors/common"; +import { useIntl } from "react-intl"; + +import { giftCardEnableDisableSectionMessages as messages } from "../messages"; +import { + useGiftCardActivateMutation, + useGiftCardDeactivateMutation +} from "../mutations"; +import { GiftCardActivate } from "../types/GiftCardActivate"; +import { GiftCardDeactivate } from "../types/GiftCardDeactivate"; + +interface useGiftCardActivateToggleProps { + onActivateActionComplete?: () => void | undefined; + onDeactivateActionComplete?: () => void | undefined; + isActive?: boolean; +} + +const useGiftCardActivateToggle = ({ + onActivateActionComplete, + onDeactivateActionComplete, + isActive +}: useGiftCardActivateToggleProps) => { + const intl = useIntl(); + const notify = useNotifier(); + + const onActivateCompleted = (data: GiftCardActivate) => { + const errors = data?.giftCardActivate?.errors; + + if (!!errors?.length) { + notify({ + status: "error", + text: intl.formatMessage(commonErrorMessages.unknownError) + }); + + return; + } + + notify({ + status: "success", + text: intl.formatMessage(messages.successfullyEnabledTitle) + }); + + if (!!onActivateActionComplete) { + onActivateActionComplete(); + } + }; + + const onDeactivateCompleted = (data: GiftCardDeactivate) => { + const errors = data?.giftCardDeactivate?.errors; + + if (!!errors?.length) { + notify({ + status: "error", + text: intl.formatMessage(commonErrorMessages.unknownError) + }); + return; + } + + notify({ + status: "success", + text: intl.formatMessage(messages.successfullyDisabledTitle) + }); + + if (!!onDeactivateActionComplete) { + onDeactivateActionComplete(); + } + }; + + const [giftCardActivate, giftCardActivateOpts] = useGiftCardActivateMutation({ + onCompleted: onActivateCompleted + }); + + const [ + giftCardDeactivate, + giftCardDeactivateOpts + ] = useGiftCardDeactivateMutation({ + onCompleted: onDeactivateCompleted + }); + + const currentOpts = isActive ? giftCardDeactivateOpts : giftCardActivateOpts; + + return { + giftCardActivate, + giftCardActivateOpts, + giftCardDeactivate, + giftCardDeactivateOpts, + currentOpts + }; +}; + +export default useGiftCardActivateToggle; diff --git a/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTable.tsx b/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTable.tsx index 8927db43a08..6f796e4218e 100644 --- a/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTable.tsx +++ b/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTable.tsx @@ -11,9 +11,8 @@ import DeleteIconButton from "@saleor/components/DeleteIconButton"; import Link from "@saleor/components/Link"; import ResponsiveTable from "@saleor/components/ResponsiveTable"; import Skeleton from "@saleor/components/Skeleton"; -import StatusChip from "@saleor/components/StatusChip"; -import { StatusType } from "@saleor/components/StatusChip/types"; import { customerUrl } from "@saleor/customers/urls"; +import GiftCardStatusChip from "@saleor/giftCards/components/GiftCardStatusChip/GiftCardStatusChip"; import { PLACEHOLDER } from "@saleor/giftCards/GiftCardUpdate/types"; import { giftCardUrl } from "@saleor/giftCards/urls"; import useNavigator from "@saleor/hooks/useNavigator"; @@ -22,7 +21,6 @@ import { productUrl } from "@saleor/products/urls"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import { giftCardUpdatePageHeaderMessages as giftCardStatusChipMessages } from "../../GiftCardUpdate/GiftCardUpdatePageHeader/messages"; import GiftCardListSearchAndFilters from "../GiftCardListSearchAndFilters"; import { giftCardsListTableMessages as messages } from "../messages"; import useGiftCardListDialogs from "../providers/GiftCardListDialogsProvider/hooks/useGiftCardListDialogs"; @@ -44,38 +42,6 @@ const GiftCardsListTable: React.FC = () => { const redirectToGiftCardUpdate = (id: string) => () => navigate(giftCardUrl(id)); - const selectGiftCardStatusChip = ({ - isActive, - isExpired - }: { - isActive: boolean; - isExpired: boolean; - }) => { - if (isExpired) { - return ( - - ); - } - - if (!isActive) { - return ( - - ); - } - }; - return ( @@ -85,82 +51,86 @@ const GiftCardsListTable: React.FC = () => { {renderCollection( giftCards, - ({ - id, - displayCode, - usedBy, - usedByEmail, - tag, - isActive, - product, - currentBalance, - isExpired - }) => ( - - - toggle(id)} - /> - - -
- - {intl.formatMessage(messages.codeEndingWithLabel, { - displayCode - })} - - <> - - {selectGiftCardStatusChip({ isActive, isExpired })} - -
-
- - {tag || PLACEHOLDER} - - - {product ? ( - navigate(productUrl(product?.id))}> - {product?.name} - - ) : ( - PLACEHOLDER - )} - - - {usedBy ? ( - navigate(customerUrl(usedBy?.id))}> - {`${usedBy?.firstName} ${usedBy?.lastName}`} - - ) : ( - {usedByEmail || PLACEHOLDER} - )} - - -
- - {currentBalance.currency} - - - {currentBalance.amount} -
-
- - { - event.stopPropagation(); - openDeleteDialog(id); - }} - /> - -
- ), + giftCard => { + const { + id, + displayCode, + usedBy, + usedByEmail, + tag, + product, + currentBalance + } = giftCard; + + return ( + + + toggle(id)} + /> + + +
+ + {intl.formatMessage(messages.codeEndingWithLabel, { + displayCode + })} + + <> + + + +
+
+ + {tag || PLACEHOLDER} + + + {product ? ( + navigate(productUrl(product?.id))}> + {product?.name} + + ) : ( + PLACEHOLDER + )} + + + {usedBy ? ( + navigate(customerUrl(usedBy?.id))}> + {`${usedBy?.firstName} ${usedBy?.lastName}`} + + ) : ( + + {usedByEmail || PLACEHOLDER} + + )} + + +
+ + {currentBalance.currency} + + + {currentBalance.amount} +
+
+ + { + event.stopPropagation(); + openDeleteDialog(id); + }} + /> + +
+ ); + }, () => ( diff --git a/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTableHeader/messages.ts b/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTableHeader/messages.ts index 1755a4eb391..1a240bc9580 100644 --- a/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTableHeader/messages.ts +++ b/src/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTableHeader/messages.ts @@ -9,6 +9,10 @@ export const bulkEnableDisableSectionMessages = defineMessages({ defaultMessage: "Deactivate", description: "GiftCardEnableDisableSection enable label" }, + deleteLabel: { + defaultMessage: "Delete", + description: "GiftCardEnableDisableSection enable label" + }, successActivateAlertText: { defaultMessage: "Successfully activated gift {count,plural,one{card} other{cards}}", diff --git a/src/giftCards/GiftCardsList/providers/GiftCardListDialogsProvider/GiftCardListDialogsProvider.tsx b/src/giftCards/GiftCardsList/providers/GiftCardListDialogsProvider/GiftCardListDialogsProvider.tsx index fc64aac24cf..a040b281c72 100644 --- a/src/giftCards/GiftCardsList/providers/GiftCardListDialogsProvider/GiftCardListDialogsProvider.tsx +++ b/src/giftCards/GiftCardsList/providers/GiftCardListDialogsProvider/GiftCardListDialogsProvider.tsx @@ -8,6 +8,7 @@ import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandl import React, { createContext } from "react"; import { + GIFT_CARD_LIST_QUERY, GiftCardListActionParamsEnum, GiftCardListUrlQueryParams } from "../../types"; @@ -77,7 +78,11 @@ const GiftCardListDialogsProvider: React.FC = return ( {children} - + { + const intl = useIntl(); + const [openCreateDialog, setOpenCreateDialog] = useState(false); + const customerDetails = useCustomerDetails(); + const customer = customerDetails?.customer?.user; + const id = customer?.id; + + const { data, loading } = useCustomerGiftCardQuery({ + variables: { + first: 5, + filter: { + usedBy: [id] + } + } + }); + + const closeCreateDialog = () => setOpenCreateDialog(false); + + const giftCards = mapEdgesToItems(data?.giftCards); + + const classes = useCardActionsStyles({ + buttonPosition: giftCards?.length > 0 ? "right" : "left" + }); + + const handleViewAllButton = () => { + // history push to filtered gift cards + }; + + const handleCreateNewCardButton = () => { + setOpenCreateDialog(true); + }; + + return ( + <> + + + + + ) + } + > + + + + + {!loading && giftCards && ( + ( + + )} + withOuterDividers + /> + )} + + + + + + + + ); +}; + +export default CustomerGiftCardsCard; diff --git a/src/giftCards/components/GiftCardCustomerCard/CustomerGiftCardsCardListItem.tsx b/src/giftCards/components/GiftCardCustomerCard/CustomerGiftCardsCardListItem.tsx new file mode 100644 index 00000000000..f1d842877fd --- /dev/null +++ b/src/giftCards/components/GiftCardCustomerCard/CustomerGiftCardsCardListItem.tsx @@ -0,0 +1,125 @@ +import CardMenu, { CardMenuItem } from "@saleor/components/CardMenu"; +import { bulkEnableDisableSectionMessages } from "@saleor/giftCards/GiftCardsList/GiftCardsListTable/GiftCardsListTableHeader/messages"; +import { giftCardsListTableMessages } from "@saleor/giftCards/GiftCardsList/messages"; +import useGiftCardActivateToggle from "@saleor/giftCards/GiftCardUpdate/GiftCardUpdatePageHeader/hooks/useGiftCardActivateToggle"; +import { ExtendedGiftCard } from "@saleor/giftCards/GiftCardUpdate/providers/GiftCardDetailsProvider/types"; +import * as React from "react"; +import { useState } from "react"; +import { FormattedMessage } from "react-intl"; +import { useIntl } from "react-intl"; + +import GiftCardDeleteDialogContent from "../GiftCardDeleteDialog/GiftCardDeleteDialogContent"; +import useGiftCardSingleDelete from "../GiftCardDeleteDialog/useGiftCardSingleDelete"; +import GiftCardStatusChip from "../GiftCardStatusChip/GiftCardStatusChip"; +import { CUSTOMER_GIFT_CARD_LIST_QUERY } from "./queries"; +import { useListWrapperStyles } from "./styles"; +import { CustomerGiftCardList_giftCards_edges_node } from "./types/CustomerGiftCardList"; +import { getGiftCardDisplayCode } from "./utils"; + +interface CustomerGiftCardsCardListItemProps { + giftCard: ExtendedGiftCard; +} + +const CustomerGiftCardsCardListItem: React.FC = ({ + giftCard +}) => { + const intl = useIntl(); + const classes = useListWrapperStyles(); + const [openDeleteGiftCard, setOpenDeleteGiftCard] = useState(false); + const { isExpired, isActive } = giftCard; + + const onGiftCardDeleteDialogClose = () => setOpenDeleteGiftCard(false); + + const { + giftCardActivate, + giftCardDeactivate, + giftCardActivateOpts, + giftCardDeactivateOpts + } = useGiftCardActivateToggle({ + isActive + }); + + const handleGiftCardActivate = () => { + giftCardActivate({ + variables: { + id: giftCard.id + } + }); + }; + + const handleGiftCardDeactivate = () => { + giftCardDeactivate({ + variables: { + id: giftCard.id + } + }); + }; + + const handleGiftCardDelete = () => setOpenDeleteGiftCard(true); + + const getMenuItems = (): CardMenuItem[] => { + const items = [ + { + label: intl.formatMessage(bulkEnableDisableSectionMessages.deleteLabel), + onSelect: handleGiftCardDelete + } + ]; + + if (isExpired) { + return items; + } + + const statusButton = isActive + ? { + label: intl.formatMessage( + bulkEnableDisableSectionMessages.disableLabel + ), + onSelect: handleGiftCardDeactivate, + loading: giftCardDeactivateOpts.loading, + withLoading: true, + hasError: !!giftCardDeactivateOpts.error + } + : { + label: intl.formatMessage( + bulkEnableDisableSectionMessages.enableLabel + ), + onSelect: handleGiftCardActivate, + loading: giftCardActivateOpts.loading, + withLoading: true, + hasError: !!giftCardActivateOpts.error + }; + + return [...items, statusButton]; + }; + + const { onDeleteGiftCard, deleteGiftCardOpts } = useGiftCardSingleDelete({ + id: giftCard?.id, + onClose: onGiftCardDeleteDialogClose, + refetchQueries: [CUSTOMER_GIFT_CARD_LIST_QUERY] + }); + + return ( + <> +
+ + + +
+ + + ); +}; + +export default CustomerGiftCardsCardListItem; diff --git a/src/giftCards/components/GiftCardCustomerCard/messages.ts b/src/giftCards/components/GiftCardCustomerCard/messages.ts new file mode 100644 index 00000000000..f4ada7e5857 --- /dev/null +++ b/src/giftCards/components/GiftCardCustomerCard/messages.ts @@ -0,0 +1,24 @@ +import { defineMessages } from "react-intl"; + +export const giftCardCustomerCardMessages = defineMessages({ + customerGiftCardsPresentSubtitle: { + defaultMessage: "Only five newest gift cards are shown here", + description: "subtitle" + }, + customerGiftCardsAbsentSubtitle: { + defaultMessage: "There are no gift cards assigned to this customer", + description: "subtitle" + }, + customerGiftCardsViewAllButton: { + defaultMessage: "View All", + description: "button" + }, + customerGiftCardsIssueNewCardButton: { + defaultMessage: "Issue new card", + description: "button" + }, + customerGiftCardsCardTitle: { + defaultMessage: "Gift Cards", + description: "title" + } +}); diff --git a/src/giftCards/components/GiftCardCustomerCard/queries.ts b/src/giftCards/components/GiftCardCustomerCard/queries.ts new file mode 100644 index 00000000000..bea382d2af8 --- /dev/null +++ b/src/giftCards/components/GiftCardCustomerCard/queries.ts @@ -0,0 +1,34 @@ +import { fragmentMoney } from "@saleor/fragments/products"; +import makeQuery from "@saleor/hooks/makeQuery"; +import gql from "graphql-tag"; + +import { + CustomerGiftCardList, + CustomerGiftCardListVariables +} from "./types/CustomerGiftCardList"; + +const customerGiftCardListQuery = gql` + ${fragmentMoney} + query CustomerGiftCardList($first: Int, $filter: GiftCardFilterInput) { + giftCards(first: $first, filter: $filter) { + edges { + node { + id + displayCode + expiryDate + isActive + currentBalance { + ...Money + } + } + } + } + } +`; + +export const useCustomerGiftCardQuery = makeQuery< + CustomerGiftCardList, + CustomerGiftCardListVariables +>(customerGiftCardListQuery); + +export const CUSTOMER_GIFT_CARD_LIST_QUERY = "CustomerGiftCardList"; diff --git a/src/giftCards/components/GiftCardCustomerCard/styles.ts b/src/giftCards/components/GiftCardCustomerCard/styles.ts new file mode 100644 index 00000000000..e0c17be450e --- /dev/null +++ b/src/giftCards/components/GiftCardCustomerCard/styles.ts @@ -0,0 +1,32 @@ +import { makeStyles } from "@material-ui/core"; + +interface CustomerGiftCardsCardActionsProps { + buttonPosition: "left" | "right"; +} + +export const useCardActionsStyles = makeStyles( + theme => ({ + cardActions: { + padding: `${theme.spacing(2)} ${theme.spacing(3)}`, + flexDirection: ({ buttonPosition }: CustomerGiftCardsCardActionsProps) => + buttonPosition === "left" ? "row" : "row-reverse" + } + }), + { name: "CustomerGiftCardsCard" } +); + +export const useListWrapperStyles = makeStyles( + theme => ({ + listingWrapper: { + display: "grid", + gridTemplateColumns: "max-content 1fr min-content", + margin: theme.spacing(2, 3), + alignItems: "center", + justifyItems: "center" + }, + listingMenu: { + gridColumn: "3" + } + }), + { name: "CustomerGiftCardListCard" } +); diff --git a/src/giftCards/components/GiftCardCustomerCard/types/CustomerGiftCardList.ts b/src/giftCards/components/GiftCardCustomerCard/types/CustomerGiftCardList.ts new file mode 100644 index 00000000000..996d7e44b74 --- /dev/null +++ b/src/giftCards/components/GiftCardCustomerCard/types/CustomerGiftCardList.ts @@ -0,0 +1,44 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { GiftCardFilterInput } from "./../../../../types/globalTypes"; + +// ==================================================== +// GraphQL query operation: CustomerGiftCardList +// ==================================================== + +export interface CustomerGiftCardList_giftCards_edges_node_currentBalance { + __typename: "Money"; + amount: number; + currency: string; +} + +export interface CustomerGiftCardList_giftCards_edges_node { + __typename: "GiftCard"; + id: string; + displayCode: string; + expiryDate: any | null; + isActive: boolean; + currentBalance: CustomerGiftCardList_giftCards_edges_node_currentBalance | null; +} + +export interface CustomerGiftCardList_giftCards_edges { + __typename: "GiftCardCountableEdge"; + node: CustomerGiftCardList_giftCards_edges_node; +} + +export interface CustomerGiftCardList_giftCards { + __typename: "GiftCardCountableConnection"; + edges: CustomerGiftCardList_giftCards_edges[]; +} + +export interface CustomerGiftCardList { + giftCards: CustomerGiftCardList_giftCards | null; +} + +export interface CustomerGiftCardListVariables { + first?: number | null; + filter?: GiftCardFilterInput | null; +} diff --git a/src/giftCards/components/GiftCardCustomerCard/utils.ts b/src/giftCards/components/GiftCardCustomerCard/utils.ts new file mode 100644 index 00000000000..98f67729018 --- /dev/null +++ b/src/giftCards/components/GiftCardCustomerCard/utils.ts @@ -0,0 +1,7 @@ +import { ExtendedGiftCard } from "@saleor/giftCards/GiftCardUpdate/providers/GiftCardDetailsProvider/types"; + +import { CustomerGiftCardList_giftCards_edges_node } from "./types/CustomerGiftCardList"; + +export const getGiftCardDisplayCode = ( + giftCard: ExtendedGiftCard +) => giftCard.displayCode.slice(4, giftCard.displayCode.length); diff --git a/src/giftCards/components/GiftCardDeleteDialog/GiftCardDeleteDialogContent.tsx b/src/giftCards/components/GiftCardDeleteDialog/GiftCardDeleteDialogContent.tsx index 11462f07180..e84c06d970c 100644 --- a/src/giftCards/components/GiftCardDeleteDialog/GiftCardDeleteDialogContent.tsx +++ b/src/giftCards/components/GiftCardDeleteDialog/GiftCardDeleteDialogContent.tsx @@ -9,7 +9,8 @@ import ActionDialog, { import DeleteWarningDialogConsentContent from "@saleor/components/TypeDeleteWarningDialog/DeleteWarningDialogConsentContent"; import { UseGiftCardListProps } from "@saleor/giftCards/GiftCardsList/providers/GiftCardListProvider/hooks/useGiftCardList"; import { UseGiftCardListBulkActionsProps } from "@saleor/giftCards/GiftCardsList/providers/GiftCardListProvider/hooks/useGiftCardListBulkActions"; -import { GiftCardDetailsConsumerProps } from "@saleor/giftCards/GiftCardUpdate/providers/GiftCardDetailsProvider"; +import { ExtendedGiftCard } from "@saleor/giftCards/GiftCardUpdate/providers/GiftCardDetailsProvider/types"; +import { GiftCardDetails_giftCard } from "@saleor/giftCards/GiftCardUpdate/types/GiftCardDetails"; import { getById } from "@saleor/orders/components/OrderReturnPage/utils"; import React, { useEffect, useState } from "react"; import { useIntl } from "react-intl"; @@ -19,7 +20,14 @@ import { useGiftCardDeleteDialogContentStyles as useStyles } from "./styles"; export const SINGLE = 1; -export interface GiftCardDeleteDialogContentProps +type DeleteDialogContentGiftCard = Pick< + ExtendedGiftCard, + "currentBalance" | "id" +>; + +export interface GiftCardDeleteDialogContentProps< + TGiftCard extends DeleteDialogContentGiftCard +> extends Pick< ActionDialogProps, "open" | "onClose" | "onConfirm" | "confirmButtonState" @@ -30,13 +38,15 @@ export interface GiftCardDeleteDialogContentProps UseGiftCardListBulkActionsProps, "listElements" | "selectedItemsCount" > - >, - Partial> { + > { id?: string; + giftCard?: TGiftCard; singleDeletion: boolean; } -const GiftCardDeleteDialogContent: React.FC = ({ +function GiftCardDeleteDialogContent< + TGiftCard extends DeleteDialogContentGiftCard +>({ id, open, onClose, @@ -48,7 +58,7 @@ const GiftCardDeleteDialogContent: React.FC = giftCards, giftCard, loading -}) => { +}: GiftCardDeleteDialogContentProps) { const intl = useIntl(); const classes = useStyles({}); @@ -118,6 +128,6 @@ const GiftCardDeleteDialogContent: React.FC = )} ); -}; +} export default GiftCardDeleteDialogContent; diff --git a/src/giftCards/components/GiftCardDeleteDialog/GiftCardListPageDeleteDialog.tsx b/src/giftCards/components/GiftCardDeleteDialog/GiftCardListPageDeleteDialog.tsx index de63ff846a1..98a7c2df5b0 100644 --- a/src/giftCards/components/GiftCardDeleteDialog/GiftCardListPageDeleteDialog.tsx +++ b/src/giftCards/components/GiftCardDeleteDialog/GiftCardListPageDeleteDialog.tsx @@ -1,3 +1,4 @@ +import { ActionDialogProps } from "@saleor/components/ActionDialog"; import useGiftCardListDialogs from "@saleor/giftCards/GiftCardsList/providers/GiftCardListDialogsProvider/hooks/useGiftCardListDialogs"; import useGiftCardList from "@saleor/giftCards/GiftCardsList/providers/GiftCardListProvider/hooks/useGiftCardList"; import useGiftCardListBulkActions from "@saleor/giftCards/GiftCardsList/providers/GiftCardListProvider/hooks/useGiftCardListBulkActions"; @@ -6,13 +7,20 @@ import { DialogProps } from "@saleor/types"; import React from "react"; import GiftCardDeleteDialogContent, { - GiftCardDeleteDialogContentProps, SINGLE } from "./GiftCardDeleteDialogContent"; import useGiftCardBulkDelete from "./useGiftCardBulkDelete"; import useGiftCardSingleDelete from "./useGiftCardSingleDelete"; -const GiftCardDeleteDialog: React.FC = ({ open, onClose }) => { +interface GiftCardDeleteDialogProps extends DialogProps { + refetchQueries?: string[]; +} + +const GiftCardDeleteDialog: React.FC = ({ + open, + onClose, + refetchQueries = [] +}) => { const giftCardBulkActionsProps = useGiftCardListBulkActions(); const { selectedItemsCount } = giftCardBulkActionsProps; @@ -25,7 +33,7 @@ const GiftCardDeleteDialog: React.FC = ({ open, onClose }) => { const { onDeleteGiftCard, deleteGiftCardOpts } = useGiftCardSingleDelete({ id, onClose, - refetchQueries: [GIFT_CARD_LIST_QUERY] + refetchQueries: [GIFT_CARD_LIST_QUERY, ...refetchQueries] }); const { @@ -33,11 +41,11 @@ const GiftCardDeleteDialog: React.FC = ({ open, onClose }) => { bulkDeleteGiftCardOpts } = useGiftCardBulkDelete({ onClose, - refetchQueries: [GIFT_CARD_LIST_QUERY] + refetchQueries: [GIFT_CARD_LIST_QUERY, ...refetchQueries] }); const dialogProps: Pick< - GiftCardDeleteDialogContentProps, + ActionDialogProps, "onConfirm" | "confirmButtonState" > = singleDeletion ? { diff --git a/src/giftCards/components/GiftCardSendToCustomer/GiftCardSendToCustomer.tsx b/src/giftCards/components/GiftCardSendToCustomer/GiftCardSendToCustomer.tsx index d3844297d4f..b1441229334 100644 --- a/src/giftCards/components/GiftCardSendToCustomer/GiftCardSendToCustomer.tsx +++ b/src/giftCards/components/GiftCardSendToCustomer/GiftCardSendToCustomer.tsx @@ -18,6 +18,7 @@ interface GiftCardSendToCustomerProps { sendToCustomerSelected: boolean; selectedCustomer: GiftCardCreateFormCustomer; setSelectedCustomer: (customer: GiftCardCreateFormCustomer) => void; + disabled?: boolean; } const GiftCardSendToCustomer: React.FC = ({ @@ -25,7 +26,8 @@ const GiftCardSendToCustomer: React.FC = ({ sendToCustomerSelected, selectedChannelSlug, selectedCustomer, - setSelectedCustomer + setSelectedCustomer, + disabled = false }) => { const { channel, availableChannels } = useAppChannel(false); @@ -46,6 +48,7 @@ const GiftCardSendToCustomer: React.FC = ({ label={intl.formatMessage(messages.sendToCustomerSelectedLabel)} checked={sendToCustomerSelected} onChange={change} + disabled={disabled} /> {sendToCustomerSelected && ( <> @@ -53,6 +56,7 @@ const GiftCardSendToCustomer: React.FC = ({