Feature - Gift Cards (#1366)

* Add deletion to gift cards (#1298)

* Add gift cards section to menu and add empty list component

* Update messages

* Change styling of app wide page header to match design

* Add gift cards list table wip

* Update prop name for status chip component to make it more consistent with other components

* Replace old trash icon with new one

* Add Size type based on action dialog sizes to be used app wide

* Add delete icon button

* Add new sizes option to status chip component

* Add / update gift cards list components

* Add bulk actions type

* Work on gift cards list WIP

* Small refactor

* Fix styling of gift cards table

* Remove temp files

* Remove unnecessary type

* Add gift cards section to menu and add empty list component

* Update schema and types

* Add link to gift card update page to gift cards list and add route to gift cards index

* Extract order page title with status chip into a separate generic component and use it in order page title

* wip

* Update money component

* Add gift card details card balance section

* Refactor gift card details

* Add vertical spacer component

* Update schema and types

* Add gift card tag input component along with necessary queries

* Add gift card tag input to gift card update page

* Add gift card update details card expiry section WIP

* Add time period select field WIP

* Post rebase refactor

* Add time period select field to gift card update view

* Update schema, types and gift cards query

* Add getFullName util function and replace existing manual usages

* Add text with select field component

* Add gift card update info card and refactor

* Fixes after review

* Fix import

* Add displaying order link in gift card update

* Refactor

* Connect gift card list to api

* refactor

* Add gift card create dialog

* Fix gift card list styles, change location for gift card list query, minor refactor

* Fix menu structure data for gift cards

* Add channel currencies type to shop

* Refactor text with select field

* Add gift card expiry select component

* Add gift card error type and fragment

* Update global types

* Add default prop to getFormErrors function

* Move gift card details provider to providers dir

* Update global utils with mapSingleValueNodeToChoice function

* Update gift card tag input

* Move and refactor time period field

* Update schema

* move format money function to other money ulities

* Update gift card urls

* Add content or skeleton component

* Add gift card create util for extracting expiry settings input data

* Remove content or skeleton component and move displaying logic to existing skeleton

* Move displaying logic of gift card create dialog to list

* Refactor

* Add hooks for gift card bulk actions and gift card list to be used instead of context directly

* Fix types for text with select field + add parsing for number typed field

* Add initial currency to gift card create form

* Fix gift card create dialog closing animation

* Add gift card update info card

* Refactor gift card update details card

* Add gift card balance dialog

* Move gift card update form providers to providers dir

* Connect gift card update page to api, add necessary contexts etc.

* Refactor

* Refactor

* Add hooks to use instead of gift card contexts directly

* Fix types

* Fix text field target name missing in passed event in text with select field

* Add minimal value option to text with select field, add to gift card inputs

* Fix gift card update balance dialog not changing hasChanged prop after submit

* Refactor

* Add enable / disable section to gift card update

* Refactor

* Refactor

* Fix update balance dialog crashing the app when enetered wrong amount

* Fix gift card list table header styles

* Refactor

* Add metadata to gift card update

* Update messages ids

* Refactor

* Refactor

* Refactor

* Refactor

* Update schema and types, add gift card delete and bulk delete mutations

* Fix url change after gift card delete

* Refactor

* Update messages

* Change gift card list providers order

* Refactor/fix after merge

* Add gift card resend mutation and update types

* Add use dialog form reset hook and make update balance dialog use it, to fix it displaying form errors after modal close and open

* Add gift card resend code dialog

* Add button to open gift card resend code dialog

* Update messages

* Add gift card list bulk enable disable section

* Add refetching gift card list query after bulk activate / deactivate, refactor

* Refactor

* Refactor

* Update messages

* Update schema

* Change gift card update expiry settings

* Refactor

* Add gift card settings view (#1300)

* Add gift card settings view

* Refactor

* Create gift card sttings expiry select

* Update test snapshots

* Update schema

* Update gift card settings page title

* Refactor to match rest of the gift cards flow (#1308)

Co-authored-by: Magdalena Markusik <magdalena.markusik@mirumee.com>

* Refactor

* Refactor

* Set common error codes in string union

Co-authored-by: Magdalena Markusik <magdalena.markusik@mirumee.com>

* Add gift card kind to product type (#1307)

* Add gift card kind to product type

* Fix radio label alignment

* Update test snapshots

* Refactor

* Set default kind in product type create

* Change styling of activate / deactivate button in gift card update

* Add week option to time period select field

* Update messages

* Change labels and update messages

* Add auto fulfill non shippable gift cards to order settings

* Fix fixture

* Update messages

* Update snapshots

* Refactor

* Update types

* Add gift card used in order money amount in order payment section

* Remove unnecessary schema changes

* Change types

* Update fixtures and messages

* Refactor

* Add top card to gift card list (#1327)

* Add top card to gift card  list

* Update no gift card products notification messages

* Use alert instead of notification

* Update prodduct and product  type counts

* Return null if none of the conditions pass

* Update messages

* Fix unused product type deletion

* Update types

* Update imports

* Update messages

* Update test snapshots

* Change gift card dialog expiry settings

* Update form of create gift card dialog

* Add activation option to create gift card dialog

* Update event handling

* Update test snapshots

* Use date context provider for moment time

* Update types

* Refactor

* Update messages

* Update snapshots

* Add channel picker to gift card create modal

* Fix styling

* Add channel picker to gift card resend code dialog

* Update schema and types

* Refactor

* Update messages

* Refactor

* Update messages and snapshots

* Fix order gift card money amount (#1371)

* Update messages and snapshots

* Fix order gift card money in payment card when multiple gift cards used

* Fix missing order id check in gift card events in order gift card money used util

* Fix channel selection in gift card create dialog

* Fix order number not showing in gift card update view

* Add gift card settings to gift card create dialog (#1372)

* Fix gift cards number inputs to accept floats

* Fix order number showing as null in gift card update info card

* Fix channel slug adding in gift card create dialog

* Fix gift card list header top card links to display correctly

* Remove unused imports

Co-authored-by: Dawid Tarasiuk <tarasiukdawid@gmail.com>
This commit is contained in:
Magdalena Markusik 2021-09-14 15:57:02 +02:00 committed by GitHub
parent 9b3d90e9c7
commit aaa0a9f309
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
205 changed files with 6354 additions and 1172 deletions

View file

@ -3407,6 +3407,22 @@
"context": "gift cards section name",
"string": "Gift Cards"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_GiftCardCreateExpirySelect_dot_expiryDateLabel": {
"context": "GiftCarUpdateDetailsExpirySection expiry date label",
"string": "Exact date"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_GiftCardCreateExpirySelect_dot_expiryOnLabel": {
"context": "GiftCarUpdateDetailsExpirySection expiry on label",
"string": "Will expire on:"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_GiftCardCreateExpirySelect_dot_expiryPeriodLabel": {
"context": "GiftCarUpdateDetailsExpirySection expiry period label",
"string": "Expires in"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_GiftCardCreateExpirySelect_dot_expirySelectedLabel": {
"context": "GiftCarUpdateDetailsExpirySection expiry selected label",
"string": "Set gift card expiry date"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_amountLabel": {
"context": "GiftCardCreateDialog amount label",
"string": "Enter amount"
@ -3431,10 +3447,6 @@
"context": "GiftCardCreateDialog customer label",
"string": "Customer"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_customerSubtitle": {
"context": "GiftCardCreateDialog customer subtitle",
"string": "Selected customer will be sent the generated gift card code. Someone else can redeem the gift card code. Gift card will be assigned to account which redeemed the code."
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_issueButtonLabel": {
"context": "GiftCardCreateDialog issue button label",
"string": "Issue"
@ -3447,10 +3459,57 @@
"context": "GiftCardCreateDialog note subtitle",
"string": "Why was this gift card issued. This note will not be shown to the customer. Note will be stored in gift card history"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_requiresActivationCaption": {
"context": "GiftCarUpdateDetailsExpirySection requires activation caption",
"string": "All issued cards require activation by staff before use."
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_requiresActivationLabel": {
"context": "GiftCarUpdateDetailsExpirySection requires activation label",
"string": "Requires activation"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_title": {
"context": "GiftCardCreateDialog title",
"string": "Issue gift card"
},
"src_dot_giftCards_dot_GiftCardSettings_dot_GiftCardExpirySettingsCard_dot_expiryDateSectionDescription": {
"string": "You can set gift cards to expire after a certain time after their purchase. Remember that in some countries, gift cards expiry is prohibited by law."
},
"src_dot_giftCards_dot_GiftCardSettings_dot_GiftCardExpirySettingsCard_dot_expiryDateTitle": {
"context": "section header",
"string": "Expiry date"
},
"src_dot_giftCards_dot_GiftCardSettings_dot_title": {
"context": "header",
"string": "Gift Cards Settings"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardResendCodeDialog_dot_consentCheckboxLabel": {
"context": "GiftCardResendCodeDialog consentCheckboxLabel",
"string": "Yes, I want to send gift card to different address"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardResendCodeDialog_dot_description": {
"context": "GiftCardResendCodeDialog description",
"string": "Gift Card Code will be resent to email provided during checkout. You can provide a different email address if you want to:"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardResendCodeDialog_dot_emailInputPlaceholder": {
"context": "GiftCardResendCodeDialog emailInputPlaceholder",
"string": "Provided email address"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardResendCodeDialog_dot_sendToChannelSelectLabel": {
"context": "ChannelPickerSelectField sendToChannelLabel",
"string": "Send to channel"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardResendCodeDialog_dot_submitButtonLabel": {
"context": "GiftCardResendCodeDialog submitButtonLabel",
"string": "Resend"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardResendCodeDialog_dot_successResendAlertText": {
"context": "GiftCardResendCodeDialog successResendAlertText",
"string": "Successfully resent code to customer!"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardResendCodeDialog_dot_title": {
"context": "GiftCardResendCodeDialog title",
"string": "Resend code to customer"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateBalanceDialog_dot_changeButtonLabel": {
"context": "GiftCardUpdateDetailsCard set balance dialog change button label",
"string": "Change"
@ -3479,6 +3538,14 @@
"context": "GiftCardUpdateDetailsCard title",
"string": "Details"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateExpirySelect_dot_expiryDateCheckboxLabel": {
"context": "GiftCarUpdateDetailsExpirySection expiry date checkbox label",
"string": "Gift card expires"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateExpirySelect_dot_expiryDateLabel": {
"context": "GiftCarUpdateDetailsExpirySection expiry date label",
"string": "Expiration date"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateInfoCard_dot_boughtByLabel": {
"context": "GiftCardUpdateInfoCard bought by label",
"string": "Bought by"
@ -3511,13 +3578,9 @@
"context": "GiftCardUpdateInfoCard used by label",
"string": "Used by"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdatePageHeader_dot_disableLabel": {
"context": "GiftCardEnableDisableSection enable label",
"string": "Disable"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdatePageHeader_dot_enableLabel": {
"context": "GiftCardEnableDisableSection enable label",
"string": "Enable"
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdatePageHeader_dot_resendButtonLabel": {
"context": "giftCardUpdatePageHeader resendButtonLabel",
"string": "Resend code"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdatePageHeader_dot_successfullyDisabledTitle": {
"context": "GiftCardEnableDisableSection disable success",
@ -3527,10 +3590,38 @@
"context": "GiftCardEnableDisableSection enable success",
"string": "Successfully enabled gift card"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_notFound": {
"context": "giftCardErrorMessages not found",
"string": "Couldn't find gift card"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_title": {
"context": "GiftCardUpdateDetailsCard title",
"string": "Details"
},
"src_dot_giftCards_dot_GiftCardsList_dot_GiftCardsListTable_dot_GiftCardsListTableHeader_dot_disableLabel": {
"context": "GiftCardEnableDisableSection enable label",
"string": "Deactivate"
},
"src_dot_giftCards_dot_GiftCardsList_dot_GiftCardsListTable_dot_GiftCardsListTableHeader_dot_enableLabel": {
"context": "GiftCardEnableDisableSection enable label",
"string": "Activate"
},
"src_dot_giftCards_dot_GiftCardsList_dot_GiftCardsListTable_dot_GiftCardsListTableHeader_dot_errorActivateAlertText": {
"context": "GiftCardEnableDisableSection error activate alert text",
"string": "Error activating gift {count,plural,one{card} other{cards}}"
},
"src_dot_giftCards_dot_GiftCardsList_dot_GiftCardsListTable_dot_GiftCardsListTableHeader_dot_errorDeactivateAlertText": {
"context": "GiftCardEnableDisableSection error activate alert text",
"string": "Errors deactivating gift {count,plural,one{card} other{cards}}"
},
"src_dot_giftCards_dot_GiftCardsList_dot_GiftCardsListTable_dot_GiftCardsListTableHeader_dot_successActivateAlertText": {
"context": "GiftCardEnableDisableSection success activate alert text",
"string": "Successfully activated gift {count,plural,one{card} other{cards}}"
},
"src_dot_giftCards_dot_GiftCardsList_dot_GiftCardsListTable_dot_GiftCardsListTableHeader_dot_successDeactivateAlertText": {
"context": "GiftCardEnableDisableSection success activate alert text",
"string": "Successfully deactivated gift {count,plural,one{card} other{cards}}"
},
"src_dot_giftCards_dot_GiftCardsList_dot_bulkIssue": {
"context": "GiftCardsListHeader menu item settings",
"string": "Bulk Issue"
@ -3539,6 +3630,14 @@
"context": "GiftCardsListTable code ending with label",
"string": "Code ending with {displayCode}"
},
"src_dot_giftCards_dot_GiftCardsList_dot_createGiftCardProduct": {
"context": "GiftCardsListHeader alert",
"string": "Create a gift card product"
},
"src_dot_giftCards_dot_GiftCardsList_dot_createGiftCardProductType": {
"context": "GiftCardsListHeader alert",
"string": "Create a gift card product type"
},
"src_dot_giftCards_dot_GiftCardsList_dot_exportCodes": {
"context": "GiftCardsListHeader menu item settings",
"string": "Export card codes"
@ -3547,6 +3646,10 @@
"context": "GiftCardsListTable disabled label",
"string": "Disabled"
},
"src_dot_giftCards_dot_GiftCardsList_dot_giftCardProduct": {
"context": "GiftCardsListHeader alert",
"string": "gift card product"
},
"src_dot_giftCards_dot_GiftCardsList_dot_giftCardsTableColumnBalanceTitle": {
"context": "GiftCardsListTable column title balance",
"string": "Balance"
@ -3571,29 +3674,73 @@
"context": "GiftCardsListHeader issue button label",
"string": "Issue card"
},
"src_dot_giftCards_dot_GiftCardsList_dot_noGiftCardsAlertTitle": {
"context": "GiftCardsListHeader alert",
"string": "You havent defined a gift card product!"
},
"src_dot_giftCards_dot_GiftCardsList_dot_noGiftCardsFound": {
"context": "GiftCardsListTable no cards found title",
"string": "No gift cards found"
},
"src_dot_giftCards_dot_GiftCardsList_dot_noGiftCardsProductTypes": {
"context": "GiftCardsListHeader alert",
"string": "{createGiftCardProductType} to start selling gift cards in your store."
},
"src_dot_giftCards_dot_GiftCardsList_dot_noGiftCardsProducts": {
"context": "GiftCardsListHeader alert",
"string": "{createGiftCardProduct} to start selling gift cards in your store."
},
"src_dot_giftCards_dot_GiftCardsList_dot_noGiftCardsProductsAndProductTypes": {
"context": "GiftCardsListHeader alert",
"string": "{createGiftCardProductType} and {giftCardProduct} to start selling gift cards in your store."
},
"src_dot_giftCards_dot_GiftCardsList_dot_settings": {
"context": "GiftCardsListHeader menu item settings",
"string": "Settings"
},
"src_dot_giftCards_dot_components_dot_GiftCardExpirySelect_dot_expirationDateLabel": {
"context": "GiftCarUpdateDetailsExpirySection expiration date label",
"string": "Expiration date"
"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}}"
},
"src_dot_giftCards_dot_components_dot_GiftCardExpirySelect_dot_expiryDateLabel": {
"context": "GiftCarUpdateDetailsExpirySection expiry date label",
"string": "Expiration date"
"src_dot_giftCards_dot_components_dot_GiftCardDeleteDialog_dot_defaultDescription": {
"context": "GiftCardDeleteDialog default description",
"string": "{selectedItemsCount,plural,one{Are you sure you want to delete this gift card?} other{Are you sure you want to delete {selectedItemsCount} giftCards?}}"
},
"src_dot_giftCards_dot_components_dot_GiftCardExpirySelect_dot_expiryPeriodLabel": {
"context": "GiftCarUpdateDetailsExpirySection expiry period label",
"string": "Expiry period"
"src_dot_giftCards_dot_components_dot_GiftCardDeleteDialog_dot_deleteSuccessAlertText": {
"context": "GiftCardDeleteDialog success alert text",
"string": "{selectedItemsCount,plural,one{Successfully deleted gift card} other{Successfully deleted gift cards}}"
},
"src_dot_giftCards_dot_components_dot_GiftCardExpirySelect_dot_neverExpireLabel": {
"context": "GiftCarUpdateDetailsExpirySection never expire label",
"string": "Never expire"
"src_dot_giftCards_dot_components_dot_GiftCardDeleteDialog_dot_title": {
"context": "GiftCardDeleteDialog single title",
"string": "{selectedItemsCount,plural,one{Delete Gift Card} other{Delete Gift Cards}}"
},
"src_dot_giftCards_dot_components_dot_GiftCardDeleteDialog_dot_withBalanceDescription": {
"context": "GiftCardDeleteDialog with balance description",
"string": "{selectedItemsCount,plural,one{The gift card you are about to delete has available balance. By deleting this card you may remove balance available to your customer.} other{You are about to delete gift cards with available balance. Are you sure you want to do that?}}"
},
"src_dot_giftCards_dot_components_dot_GiftCardSendToCustomer_dot_channelSelectLabel": {
"context": "GiftCardCreateDialog channel select label",
"string": "Channel"
},
"src_dot_giftCards_dot_components_dot_GiftCardSendToCustomer_dot_customerChannelSubtitle": {
"context": "GiftCardCreateDialog customer channel subtitle",
"string": "Customer will be sent the gift card code via this channels email address"
},
"src_dot_giftCards_dot_components_dot_GiftCardSendToCustomer_dot_customerSubtitle": {
"context": "GiftCardCreateDialog customer subtitle",
"string": "Selected customer will be sent the generated gift card code. Someone else can redeem the gift card code. Gift card will be assigned to account which redeemed the code."
},
"src_dot_giftCards_dot_components_dot_GiftCardSendToCustomer_dot_sendToCustomerSelectedLabel": {
"context": "GiftCardSendToCustomer send to customer selected label",
"string": "Send gift card to customer"
},
"src_dot_giftCards_dot_components_dot_GiftCardSettingsExpirySelect_dot_setExpirationPeriodDescription": {
"context": "checkbox label description",
"string": "Expiration date will be automatically set, once gift card is issued"
},
"src_dot_giftCards_dot_components_dot_GiftCardSettingsExpirySelect_dot_setExpirationPeriodTitle": {
"context": "checkbox label",
"string": "Set gift card expiration period"
},
"src_dot_giftCards_dot_components_dot_GiftCardTagInput_dot_label": {
"context": "GiftCardTagInput tag label",
@ -3605,15 +3752,19 @@
},
"src_dot_giftCards_dot_components_dot_TimePeriodField_dot_dayLabel": {
"context": "TimePeriodTextWithSelectField day label",
"string": "days after usage"
"string": "days after issue"
},
"src_dot_giftCards_dot_components_dot_TimePeriodField_dot_monthLabel": {
"context": "TimePeriodTextWithSelectField month label",
"string": "months after usage"
"string": "months after issue"
},
"src_dot_giftCards_dot_components_dot_TimePeriodField_dot_weekLabel": {
"context": "TimePeriodTextWithSelectField day label",
"string": "weeks after issue"
},
"src_dot_giftCards_dot_components_dot_TimePeriodField_dot_yearLabel": {
"context": "TimePeriodTextWithSelectField year label",
"string": "years after usage"
"string": "years after issue"
},
"src_dot_home": {
"context": "home section name",
@ -4411,6 +4562,10 @@
"context": "order discount",
"string": "Discount"
},
"src_dot_orders_dot_components_dot_OrderPayment_dot_372187363": {
"context": "order payment",
"string": "Paid with Gift Card"
},
"src_dot_orders_dot_components_dot_OrderPayment_dot_3768782744": {
"context": "order payment",
"string": "Preauthorized amount"
@ -4752,6 +4907,14 @@
"context": "section header",
"string": "Settings"
},
"src_dot_orders_dot_components_dot_OrderSettings_dot_2116402092": {
"context": "checkbox gift cards label",
"string": "Automatically fulfill non shippable gift cards"
},
"src_dot_orders_dot_components_dot_OrderSettings_dot_2879928595": {
"context": "checkbox gift cards label description",
"string": "when activated non-shippable gift cards will be automatically set as fulfilled and sent to customer"
},
"src_dot_orders_dot_components_dot_OrderSettings_dot_3281882935": {
"context": "checkbox label description",
"string": "All orders will be automatically confirmed and all payments will be captured."
@ -5402,7 +5565,24 @@
"context": "switch button",
"string": "Product type uses Variant Attributes"
},
"src_dot_productTypes_dot_components_dot_ProductTypeDetails_dot_1007996279": {
"src_dot_productTypes_dot_components_dot_ProductTypeDetails_dot_optionGiftCardDescription": {
"context": "option description",
"string": "This product type can be used to create voucher/gift card type products that user will be able to pay with during checkout process"
},
"src_dot_productTypes_dot_components_dot_ProductTypeDetails_dot_optionGiftCardTitle": {
"context": "option",
"string": "Products of this type can be used as gift card/voucher"
},
"src_dot_productTypes_dot_components_dot_ProductTypeDetails_dot_optionNormalDescription": {
"context": "option description",
"string": "This product type can be used to create shipping passes for your customers"
},
"src_dot_productTypes_dot_components_dot_ProductTypeDetails_dot_optionNormalTitle": {
"context": "option",
"string": "This is a normal product"
},
"src_dot_productTypes_dot_components_dot_ProductTypeDetails_dot_productTypeName": {
"context": "label",
"string": "Product Type Name"
},
"src_dot_productTypes_dot_components_dot_ProductTypeListPage_dot_1776073799": {
@ -7594,6 +7774,10 @@
"context": "event",
"string": "Product Variant out of stock"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_2315238863": {
"context": "event",
"string": "Product variant out of stock"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_2454751033": {
"context": "event",
"string": "All events"
@ -7690,6 +7874,10 @@
"context": "event",
"string": "Page created"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_69923590": {
"context": "event",
"string": "Product variant back in stock"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_759562905": {
"context": "event",
"string": "Authorize payment"

View file

@ -1111,6 +1111,7 @@ enum CheckoutErrorCode {
TAX_ERROR
UNIQUE
VOUCHER_NOT_APPLICABLE
GIFT_CARD_NOT_APPLICABLE
ZERO_QUANTITY
MISSING_CHANNEL_SLUG
CHANNEL_INACTIVE
@ -2253,7 +2254,6 @@ type GiftCard implements Node & ObjectWithMetadata {
code: String!
isActive: Boolean!
expiryDate: Date
expiryType: GiftCardExpiryTypeEnum!
tag: String
created: DateTime!
lastUsedOn: DateTime
@ -2268,9 +2268,9 @@ type GiftCard implements Node & ObjectWithMetadata {
createdByEmail: String
usedByEmail: String
app: App
expiryPeriod: TimePeriod
product: Product
events: [GiftCardEvent!]!
boughtInChannel: String
user: User @deprecated(reason: "This field will be removed in Saleor 4.0. Use `createdBy` field instead.")
endDate: DateTime @deprecated(reason: "This field will be removed in Saleor 4.0. Use `expiryDate` field instead.")
startDate: DateTime @deprecated(reason: "This field will be removed in Saleor 4.0.")
@ -2282,6 +2282,31 @@ type GiftCardActivate {
errors: [GiftCardError!]!
}
type GiftCardAddNote {
giftCard: GiftCard
event: GiftCardEvent
errors: [GiftCardError!]!
}
input GiftCardAddNoteInput {
message: String!
}
type GiftCardBulkActivate {
count: Int!
errors: [GiftCardError!]!
}
type GiftCardBulkDeactivate {
count: Int!
errors: [GiftCardError!]!
}
type GiftCardBulkDelete {
count: Int!
errors: [GiftCardError!]!
}
type GiftCardCountableConnection {
pageInfo: PageInfo!
edges: [GiftCardCountableEdge!]!
@ -2301,11 +2326,13 @@ type GiftCardCreate {
input GiftCardCreateInput {
tag: String
expiryDate: Date
startDate: Date
endDate: Date
balance: PriceInput!
userEmail: String
expirySettings: GiftCardExpirySettingsInput!
channel: String
isActive: Boolean!
code: String
note: String
}
@ -2350,25 +2377,17 @@ type GiftCardEvent implements Node {
tag: String
oldTag: String
balance: GiftCardEventBalance
expiry: GiftCardEventExpiry
expiryDate: Date
oldExpiryDate: Date
}
type GiftCardEventBalance {
initialBalance: Money!
initialBalance: Money
currentBalance: Money!
oldInitialBalance: Money
oldCurrentBalance: Money
}
type GiftCardEventExpiry {
expiryType: GiftCardExpiryTypeEnum
expiryPeriod: TimePeriod
expiryDate: Date
oldExpiryType: GiftCardExpiryTypeEnum
oldExpiryPeriod: TimePeriod
oldExpiryDate: Date
}
enum GiftCardEventsEnum {
ISSUED
BOUGHT
@ -2376,25 +2395,77 @@ enum GiftCardEventsEnum {
ACTIVATED
DEACTIVATED
BALANCE_RESET
EXPIRY_SETTINGS_UPDATED
EXPIRY_DATE_UPDATED
SENT_TO_CUSTOMER
RESENT
}
input GiftCardExpirySettingsInput {
expiryType: GiftCardExpiryTypeEnum!
expiryDate: Date
expiryPeriod: TimePeriodInputType
}
enum GiftCardExpiryTypeEnum {
NEVER_EXPIRE
EXPIRY_PERIOD
EXPIRY_DATE
NOTE_ADDED
USED_IN_ORDER
}
input GiftCardFilterInput {
isActive: Boolean
tag: String
tags: [String]
products: [ID]
usedBy: [ID]
currency: String
currentBalance: PriceRangeInput
initialBalance: PriceRangeInput
}
type GiftCardResend {
giftCard: GiftCard
errors: [GiftCardError!]!
}
input GiftCardResendInput {
id: ID!
email: String
channel: String!
}
type GiftCardSettings {
expiryType: GiftCardSettingsExpiryTypeEnum!
expiryPeriod: TimePeriod
}
type GiftCardSettingsError {
field: String
message: String
code: GiftCardSettingsErrorCode!
}
enum GiftCardSettingsErrorCode {
INVALID
REQUIRED
GRAPHQL_ERROR
}
enum GiftCardSettingsExpiryTypeEnum {
NEVER_EXPIRE
EXPIRY_PERIOD
}
type GiftCardSettingsUpdate {
giftCardSettings: GiftCardSettings
errors: [GiftCardSettingsError!]!
}
input GiftCardSettingsUpdateInput {
expiryType: GiftCardSettingsExpiryTypeEnum
expiryPeriod: TimePeriodInputType
}
enum GiftCardSortField {
TAG
PRODUCT
USED_BY
CURRENT_BALANCE
}
input GiftCardSortingInput {
direction: OrderDirection!
field: GiftCardSortField!
}
type GiftCardUpdate {
@ -2405,10 +2476,10 @@ type GiftCardUpdate {
input GiftCardUpdateInput {
tag: String
expiryDate: Date
startDate: Date
endDate: Date
balanceAmount: PositiveDecimal
expirySettings: GiftCardExpirySettingsInput
}
type Group implements Node {
@ -3645,6 +3716,7 @@ type Mutation {
shopSettingsTranslate(input: ShopSettingsTranslationInput!, languageCode: LanguageCodeEnum!): ShopSettingsTranslate
shopAddressUpdate(input: AddressInput): ShopAddressUpdate
orderSettingsUpdate(input: OrderSettingsUpdateInput!): OrderSettingsUpdate
giftCardSettingsUpdate(input: GiftCardSettingsUpdateInput!): GiftCardSettingsUpdate
shippingMethodChannelListingUpdate(id: ID!, input: ShippingMethodChannelListingInput!): ShippingMethodChannelListingUpdate
shippingPriceCreate(input: ShippingPriceInput!): ShippingPriceCreate
shippingPriceDelete(id: ID!): ShippingPriceDelete
@ -3783,6 +3855,11 @@ type Mutation {
giftCardDelete(id: ID!): GiftCardDelete
giftCardDeactivate(id: ID!): GiftCardDeactivate
giftCardUpdate(id: ID!, input: GiftCardUpdateInput!): GiftCardUpdate
giftCardResend(input: GiftCardResendInput!): GiftCardResend
giftCardAddNote(id: ID!, input: GiftCardAddNoteInput!): GiftCardAddNote
giftCardBulkDelete(ids: [ID]!): GiftCardBulkDelete
giftCardBulkActivate(ids: [ID]!): GiftCardBulkActivate
giftCardBulkDeactivate(ids: [ID]!): GiftCardBulkDeactivate
pluginUpdate(channelId: ID, id: ID!, input: PluginUpdateInput!): PluginUpdate
externalNotificationTrigger(channel: String!, input: ExternalNotificationTriggerInput!, pluginId: String): ExternalNotificationTrigger
saleCreate(input: SaleInput!): SaleCreate
@ -4383,6 +4460,7 @@ input OrderReturnProductsInput {
type OrderSettings {
automaticallyConfirmAllNewOrders: Boolean!
automaticallyFulfillNonShippableGiftCard: Boolean!
}
type OrderSettingsError {
@ -4402,7 +4480,8 @@ type OrderSettingsUpdate {
}
input OrderSettingsUpdateInput {
automaticallyConfirmAllNewOrders: Boolean!
automaticallyConfirmAllNewOrders: Boolean
automaticallyFulfillNonShippableGiftCard: Boolean
}
enum OrderSortField {
@ -5235,6 +5314,7 @@ input ProductFilterInput {
price: PriceRangeInput
minimalPrice: PriceRangeInput
productTypes: [ID]
giftCard: Boolean
ids: [ID]
channel: String
}
@ -5397,6 +5477,7 @@ type ProductType implements Node & ObjectWithMetadata {
weight: Weight
privateMetadata: [MetadataItem]!
metadata: [MetadataItem]!
kind: ProductTypeKindEnum!
products(channel: String, before: String, after: String, first: Int, last: Int): ProductCountableConnection @deprecated(reason: "This field will be removed in Saleor 4.0. Use the top-level `products` query with the `productTypes` filter.")
taxType: TaxType
variantAttributes(variantSelection: VariantAttributeScope): [Attribute]
@ -5448,12 +5529,14 @@ input ProductTypeFilterInput {
configurable: ProductTypeConfigurable
productType: ProductTypeEnum
metadata: [MetadataFilter]
kind: ProductTypeKindEnum
ids: [ID]
}
input ProductTypeInput {
name: String
slug: String
kind: ProductTypeKindEnum
hasVariants: Boolean
productAttributes: [ID]
variantAttributes: [ID]
@ -5463,6 +5546,11 @@ input ProductTypeInput {
taxCode: String
}
enum ProductTypeKindEnum {
NORMAL
GIFT_CARD
}
type ProductTypeReorderAttributes {
productType: ProductType
productErrors: [ProductError!]! @deprecated(reason: "This field will be removed in Saleor 4.0. Use `errors` field instead.")
@ -5682,6 +5770,7 @@ type Query {
stocks(filter: StockFilterInput, before: String, after: String, first: Int, last: Int): StockCountableConnection
shop: Shop!
orderSettings: OrderSettings
giftCardSettings: GiftCardSettings!
shippingZone(id: ID!, channel: String): ShippingZone
shippingZones(filter: ShippingZoneFilterInput, channel: String, before: String, after: String, first: Int, last: Int): ShippingZoneCountableConnection
digitalContent(id: ID!): DigitalContent
@ -5714,7 +5803,7 @@ type Query {
menuItem(id: ID!, channel: String): MenuItem
menuItems(channel: String, sortBy: MenuItemSortingInput, filter: MenuItemFilterInput, before: String, after: String, first: Int, last: Int): MenuItemCountableConnection
giftCard(id: ID!): GiftCard
giftCards(filter: GiftCardFilterInput, before: String, after: String, first: Int, last: Int): GiftCardCountableConnection
giftCards(sortBy: GiftCardSortingInput, filter: GiftCardFilterInput, before: String, after: String, first: Int, last: Int): GiftCardCountableConnection
plugin(id: ID!): Plugin
plugins(filter: PluginFilterInput, sortBy: PluginSortingInput, before: String, after: String, first: Int, last: Int): PluginCountableConnection
sale(id: ID!, channel: String): Sale
@ -6471,6 +6560,7 @@ input TimePeriodInputType {
enum TimePeriodTypeEnum {
DAY
WEEK
MONTH
YEAR
}

View file

@ -6,7 +6,7 @@ import { ConfirmButtonTransitionState } from "../ConfirmButton";
import DialogButtons from "./DialogButtons";
import { ActionDialogVariant, Size } from "./types";
interface ActionDialogProps extends DialogProps {
export interface ActionDialogProps extends DialogProps {
children?: React.ReactNode;
confirmButtonLabel?: string;
confirmButtonState: ConfirmButtonTransitionState;

View file

@ -57,8 +57,7 @@ export const AppChannelProvider: React.FC = ({ children }) => {
const availableChannels = channelData?.channels || [];
const channel =
channelData &&
(availableChannels.find(channel => channel.id === selectedChannel) || null);
channelData && (availableChannels.find(getById(selectedChannel)) || null);
return (
<AppChannelContext.Provider

View file

@ -3,13 +3,13 @@ import { ChannelData } from "@saleor/channels/utils";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import Hr from "@saleor/components/Hr";
import RadioSwitchField from "@saleor/components/RadioSwitchField";
import useCurrentDate from "@saleor/hooks/useCurrentDate";
import useDateLocalize from "@saleor/hooks/useDateLocalize";
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
import classNames from "classnames";
import React, { useState } from "react";
import { useIntl } from "react-intl";
import { DateContext } from "../../Date/DateContext";
import { useStyles } from "../styles";
import { ChannelOpts, ChannelsAvailabilityError, Messages } from "../types";
export interface ChannelContentProps {
@ -43,7 +43,7 @@ const ChannelContent: React.FC<ChannelContentProps> = ({
publicationDate,
...(visibleInListings !== undefined ? { visibleInListings } : {})
};
const dateNow = React.useContext(DateContext);
const dateNow = useCurrentDate();
const localizeDate = useDateLocalize();
const hasAvailableProps =
isAvailable !== undefined && availableForPurchase !== undefined;

View file

@ -1,3 +1,4 @@
import { Divider } from "@material-ui/core";
import { makeStyles } from "@saleor/macaw-ui";
import classNames from "classnames";
import React from "react";
@ -17,6 +18,9 @@ const useStyles = makeStyles(
}
}
},
underline: {
marginBottom: theme.spacing(4)
},
grid: {
padding: theme.spacing(2)
},
@ -51,25 +55,34 @@ interface ExtendedPageHeaderProps {
children?: React.ReactNode;
className?: string;
inline?: boolean;
underline?: boolean;
title?: React.ReactNode;
testId?: string;
}
const ExtendedPageHeader: React.FC<ExtendedPageHeaderProps> = props => {
const { children, className, inline, title, testId } = props;
const { children, className, inline, underline, title, testId } = props;
const classes = useStyles(props);
return (
<div
data-test-id={testId}
className={classNames(classes.root, className, {
[classes.block]: !inline
})}
>
{title}
<div className={classes.action}>{children}</div>
</div>
<>
<div
data-test-id={testId}
className={classNames(classes.root, className, {
[classes.block]: !inline,
[classes.underline]: underline
})}
>
{title}
<div className={classes.action}>{children}</div>
</div>
{underline && (
<div className={classes.underline}>
<Divider />
</div>
)}
</>
);
};
ExtendedPageHeader.displayName = "ExtendedPageHeader";

View file

@ -43,12 +43,13 @@ interface PageHeaderProps {
children?: React.ReactNode;
className?: string;
inline?: boolean;
underline?: boolean;
limitText?: string;
title?: React.ReactNode;
}
const PageHeader: React.FC<PageHeaderProps> = props => {
const { children, className, inline, limitText, title } = props;
const { children, className, inline, underline, limitText, title } = props;
const classes = useStyles(props);
@ -57,6 +58,7 @@ const PageHeader: React.FC<PageHeaderProps> = props => {
testId="page-header"
className={className}
inline={inline}
underline={underline}
title={
<Typography className={classes.title} variant="h5">
{title !== undefined ? title : <Skeleton style={{ width: "10em" }} />}

View file

@ -7,47 +7,11 @@ import {
Radio,
RadioGroup
} from "@material-ui/core";
import { makeStyles } from "@saleor/macaw-ui";
import classNames from "classnames";
import React from "react";
import { FormattedMessage } from "react-intl";
const useStyles = makeStyles(
theme => ({
alignTop: {
alignSelf: "baseline",
position: "relative",
top: -6
},
formLabel: {
marginBottom: theme.spacing(1)
},
radioGroupInline: {
flexDirection: "row"
},
radioLabel: {
marginBottom: theme.spacing(-0.5)
},
radioLabelInline: {
marginRight: theme.spacing(4)
},
root: {
"& $radioLabel": {
"&:last-of-type": {
marginBottom: 0
}
},
padding: 0,
width: "100%"
},
rootNoLabel: {
marginTop: theme.spacing(-1.5)
}
}),
{
name: "RadioGroupField"
}
);
import { useStyles } from "./styles";
export interface RadioGroupFieldChoice<
T extends string | number = string | number
@ -119,6 +83,9 @@ export const RadioGroupField: React.FC<RadioGroupFieldProps> = props => {
[classes.radioLabel]: variant !== "inline",
[classes.radioLabelInline]: variant === "inline"
})}
classes={{
label: classes.label
}}
control={
<Radio
className={classNames({

View file

@ -0,0 +1,43 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useStyles = makeStyles(
theme => ({
alignTop: {
alignSelf: "baseline",
position: "relative",
top: -6
},
formLabel: {
marginBottom: theme.spacing(1)
},
radioGroupInline: {
flexDirection: "row"
},
radioLabel: {
alignItems: "start",
marginBottom: theme.spacing(-0.5)
},
radioLabelInline: {
alignItems: "start",
marginRight: theme.spacing(4)
},
label: {
marginTop: theme.spacing(1.5)
},
root: {
"& $radioLabel": {
"&:last-of-type": {
marginBottom: 0
}
},
padding: 0,
width: "100%"
},
rootNoLabel: {
marginTop: theme.spacing(-1.5)
}
}),
{
name: "RadioGroupField"
}
);

View file

@ -0,0 +1,46 @@
import { Typography } from "@material-ui/core";
import React, { ChangeEvent } from "react";
import CardSpacer from "../CardSpacer";
import ControlledCheckbox from "../ControlledCheckbox";
import { useTypeDeleteWarningDialogStyles as useStyles } from "./styles";
interface DeleteWarningDialogConsentContentProps {
description: string | React.ReactNode[];
consentLabel: string;
isConsentChecked: boolean;
onConsentChange: (value: boolean) => void;
}
const DeleteWarningDialogConsentContent: React.FC<DeleteWarningDialogConsentContentProps> = ({
description,
consentLabel,
isConsentChecked,
onConsentChange
}) => {
const classes = useStyles();
const handleConsentChange = ({ target }: ChangeEvent<any>) =>
onConsentChange(target.value);
return (
<>
<Typography>{description}</Typography>
<CardSpacer />
{consentLabel && (
<ControlledCheckbox
name="delete-assigned-items-consent"
checked={isConsentChecked}
onChange={handleConsentChange}
label={
<Typography className={classes.consentLabel}>
{consentLabel}
</Typography>
}
/>
)}
</>
);
};
export default DeleteWarningDialogConsentContent;

View file

@ -1,13 +1,13 @@
import { CardContent, Typography } from "@material-ui/core";
import { CardContent } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import CardSpacer from "@saleor/components/CardSpacer";
import ConfirmButton from "@saleor/components/ConfirmButton";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import DeleteButton from "@saleor/components/DeleteButton";
import useNavigator from "@saleor/hooks/useNavigator";
import React, { ChangeEvent, useState } from "react";
import React, { useState } from "react";
import { MessageDescriptor, useIntl } from "react-intl";
import DeleteButton from "../DeleteButton";
import DeleteWarningDialogConsentContent from "./DeleteWarningDialogConsentContent";
import { useTypeDeleteWarningDialogStyles as useStyles } from "./styles";
interface TypeDeleteWarningDialogContentProps {
@ -40,9 +40,6 @@ const TypeDeleteWarningDialogContent: React.FC<TypeDeleteWarningDialogContentPro
const [isConsentChecked, setIsConsentChecked] = useState(false);
const handleConsentChange = ({ target }: ChangeEvent<any>) =>
setIsConsentChecked(target.value);
const handleViewAssignedItems = () => navigate(viewAssignedItemsUrl);
const isDisbled = hasAssignedItems ? !isConsentChecked : false;
@ -52,26 +49,16 @@ const TypeDeleteWarningDialogContent: React.FC<TypeDeleteWarningDialogContentPro
return (
<CardContent>
<Typography>
{intl.formatMessage(description, {
<DeleteWarningDialogConsentContent
description={intl.formatMessage(description, {
typeName: singleItemSelectedName,
assignedItemsCount,
b: (...chunks) => <b>{chunks}</b>
})}
</Typography>
<CardSpacer />
{consentLabel && (
<ControlledCheckbox
name="delete-assigned-items-consent"
checked={isConsentChecked}
onChange={handleConsentChange}
label={
<Typography className={classes.consentLabel}>
{intl.formatMessage(consentLabel)}
</Typography>
}
/>
)}
consentLabel={consentLabel && intl.formatMessage(consentLabel)}
isConsentChecked={isConsentChecked}
onConsentChange={setIsConsentChecked}
/>
<CardSpacer />
<div className={classes.buttonsSection}>
{shouldShowViewAssignedItemsButton && (

View file

@ -3,6 +3,7 @@ import CardTitle from "@saleor/components/CardTitle";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import Hr from "@saleor/components/Hr";
import RadioSwitchField from "@saleor/components/RadioSwitchField";
import useCurrentDate from "@saleor/hooks/useCurrentDate";
import useDateLocalize from "@saleor/hooks/useDateLocalize";
import { ChangeEvent } from "@saleor/hooks/useForm";
import { makeStyles } from "@saleor/macaw-ui";
@ -12,7 +13,6 @@ import classNames from "classnames";
import React from "react";
import { useIntl } from "react-intl";
import { DateContext } from "../Date/DateContext";
import FormSpacer from "../FormSpacer";
import DateVisibilitySelector from "./DateVisibilitySelector";
@ -103,7 +103,7 @@ export const VisibilityCard: React.FC<VisibilityCardProps> = props => {
const classes = useStyles(props);
const intl = useIntl();
const localizeDate = useDateLocalize();
const dateNow = React.useContext(DateContext);
const dateNow = useCurrentDate();
const hasAvailableProps =
isAvailable !== undefined && availableForPurchase !== undefined;

View file

@ -222,3 +222,10 @@ export const giftCardErrorFragment = gql`
field
}
`;
export const giftCardSettingsErrorFragment = gql`
fragment GiftCardSettingsErrorFragment on GiftCardSettingsError {
code
field
}
`;

View file

@ -0,0 +1,11 @@
import gql from "graphql-tag";
export const fragmentGiftCardsSettings = gql`
fragment GiftCardsSettingsFragment on GiftCardSettings {
expiryType
expiryPeriod {
type
amount
}
}
`;

View file

@ -182,6 +182,27 @@ export const fragmentOrderDetails = gql`
billingAddress {
...AddressFragment
}
giftCards {
events {
id
type
orderId
balance {
initialBalance {
...Money
}
currentBalance {
...Money
}
oldInitialBalance {
...Money
}
oldCurrentBalance {
...Money
}
}
}
}
isShippingRequired
canFinalize
created
@ -296,6 +317,7 @@ export const fragmentOrderDetails = gql`
export const fragmentOrderSettings = gql`
fragment OrderSettingsFragment on OrderSettings {
automaticallyConfirmAllNewOrders
automaticallyFulfillNonShippableGiftCard
}
`;

View file

@ -7,6 +7,7 @@ export const productTypeFragment = gql`
fragment ProductTypeFragment on ProductType {
id
name
kind
hasVariants
isShippingRequired
taxType {

View file

@ -0,0 +1,16 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardSettingsErrorCode } from "./../../types/globalTypes";
// ====================================================
// GraphQL fragment: GiftCardSettingsErrorFragment
// ====================================================
export interface GiftCardSettingsErrorFragment {
__typename: "GiftCardSettingsError";
code: GiftCardSettingsErrorCode;
field: string | null;
}

View file

@ -0,0 +1,22 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardSettingsExpiryTypeEnum, TimePeriodTypeEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL fragment: GiftCardsSettingsFragment
// ====================================================
export interface GiftCardsSettingsFragment_expiryPeriod {
__typename: "TimePeriod";
type: TimePeriodTypeEnum;
amount: number;
}
export interface GiftCardsSettingsFragment {
__typename: "GiftCardSettings";
expiryType: GiftCardSettingsExpiryTypeEnum;
expiryPeriod: GiftCardsSettingsFragment_expiryPeriod | null;
}

View file

@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { OrderDiscountType, DiscountValueTypeEnum, OrderEventsEmailsEnum, OrderEventsEnum, FulfillmentStatus, PaymentChargeStatusEnum, WarehouseClickAndCollectOptionEnum, OrderStatus, OrderAction, JobStatusEnum } from "./../../types/globalTypes";
import { GiftCardEventsEnum, OrderDiscountType, DiscountValueTypeEnum, OrderEventsEmailsEnum, OrderEventsEnum, FulfillmentStatus, PaymentChargeStatusEnum, WarehouseClickAndCollectOptionEnum, OrderStatus, OrderAction, JobStatusEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL fragment: OrderDetailsFragment
@ -43,6 +43,51 @@ export interface OrderDetailsFragment_billingAddress {
streetAddress2: string;
}
export interface OrderDetailsFragment_giftCards_events_balance_initialBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface OrderDetailsFragment_giftCards_events_balance_currentBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface OrderDetailsFragment_giftCards_events_balance_oldInitialBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface OrderDetailsFragment_giftCards_events_balance_oldCurrentBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface OrderDetailsFragment_giftCards_events_balance {
__typename: "GiftCardEventBalance";
initialBalance: OrderDetailsFragment_giftCards_events_balance_initialBalance | null;
currentBalance: OrderDetailsFragment_giftCards_events_balance_currentBalance;
oldInitialBalance: OrderDetailsFragment_giftCards_events_balance_oldInitialBalance | null;
oldCurrentBalance: OrderDetailsFragment_giftCards_events_balance_oldCurrentBalance | null;
}
export interface OrderDetailsFragment_giftCards_events {
__typename: "GiftCardEvent";
id: string;
type: GiftCardEventsEnum | null;
orderId: string | null;
balance: OrderDetailsFragment_giftCards_events_balance | null;
}
export interface OrderDetailsFragment_giftCards {
__typename: "GiftCard";
events: OrderDetailsFragment_giftCards_events[];
}
export interface OrderDetailsFragment_discounts_amount {
__typename: "Money";
amount: number;
@ -497,6 +542,7 @@ export interface OrderDetailsFragment {
metadata: (OrderDetailsFragment_metadata | null)[];
privateMetadata: (OrderDetailsFragment_privateMetadata | null)[];
billingAddress: OrderDetailsFragment_billingAddress | null;
giftCards: (OrderDetailsFragment_giftCards | null)[] | null;
isShippingRequired: boolean;
canFinalize: boolean;
created: any;

View file

@ -10,4 +10,5 @@
export interface OrderSettingsFragment {
__typename: "OrderSettings";
automaticallyConfirmAllNewOrders: boolean;
automaticallyFulfillNonShippableGiftCard: boolean;
}

View file

@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AttributeTypeEnum, MeasurementUnitsEnum, AttributeInputTypeEnum, WeightUnitsEnum } from "./../../types/globalTypes";
import { ProductTypeKindEnum, AttributeTypeEnum, MeasurementUnitsEnum, AttributeInputTypeEnum, WeightUnitsEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL fragment: ProductTypeDetailsFragment
@ -63,6 +63,7 @@ export interface ProductTypeDetailsFragment {
__typename: "ProductType";
id: string;
name: string;
kind: ProductTypeKindEnum;
hasVariants: boolean;
isShippingRequired: boolean;
taxType: ProductTypeDetailsFragment_taxType | null;

View file

@ -3,6 +3,8 @@
// @generated
// This file was automatically generated and should not be edited.
import { ProductTypeKindEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL fragment: ProductTypeFragment
// ====================================================
@ -17,6 +19,7 @@ export interface ProductTypeFragment {
__typename: "ProductType";
id: string;
name: string;
kind: ProductTypeKindEnum;
hasVariants: boolean;
isShippingRequired: boolean;
taxType: ProductTypeFragment_taxType | null;

View file

@ -1,11 +1,14 @@
import { Dialog, DialogTitle } from "@material-ui/core";
import { IMessage } from "@saleor/components/messages";
import useCurrentDate from "@saleor/hooks/useCurrentDate";
import useNotifier from "@saleor/hooks/useNotifier";
import { GiftCardCreateInput } from "@saleor/types/globalTypes";
import commonErrorMessages from "@saleor/utils/errors/common";
import { DialogActionHandlersProps } from "@saleor/utils/handlers/dialogActionHandlers";
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, {
@ -15,15 +18,10 @@ import { giftCardCreateDialogMessages as messages } from "./messages";
import { useGiftCardCreateMutation } from "./mutations";
import { useChannelCurrencies } from "./queries";
import { GiftCardCreate } from "./types/GiftCardCreate";
import { getGiftCardExpirySettingsInputData } from "./utils";
import { getGiftCardExpiryInputData } from "./utils";
interface GiftCardCreateDialogProps {
onClose: () => void;
open: boolean;
}
const GiftCardCreateDialog: React.FC<GiftCardCreateDialogProps> = ({
onClose,
const GiftCardCreateDialog: React.FC<DialogActionHandlersProps> = ({
closeDialog,
open
}) => {
const intl = useIntl();
@ -56,6 +54,8 @@ const GiftCardCreateDialog: React.FC<GiftCardCreateDialogProps> = ({
}
};
const currentDate = useCurrentDate();
const getParsedSubmitInputData = (
formData: GiftCardCreateFormData
): GiftCardCreateInput => {
@ -64,23 +64,29 @@ const GiftCardCreateDialog: React.FC<GiftCardCreateDialogProps> = ({
balanceCurrency,
note,
tag,
selectedCustomer
sendToCustomerSelected,
selectedCustomer,
requiresActivation,
channelSlug
} = formData;
return {
note: note || null,
tag: tag || null,
userEmail: selectedCustomer.email || null,
userEmail: (sendToCustomerSelected && selectedCustomer.email) || null,
channel: (sendToCustomerSelected && channelSlug) || null,
balance: {
amount: balanceAmount,
currency: balanceCurrency
},
expirySettings: getGiftCardExpirySettingsInputData(formData)
expiryDate: getGiftCardExpiryInputData(formData, currentDate),
isActive: !requiresActivation
};
};
const [createGiftCard, createGiftCardOpts] = useGiftCardCreateMutation({
onCompleted
onCompleted,
refetchQueries: [GIFT_CARD_LIST_QUERY]
});
const handleSubmit = (data: GiftCardCreateFormData) => {
@ -92,7 +98,7 @@ const GiftCardCreateDialog: React.FC<GiftCardCreateDialogProps> = ({
};
const handleClose = () => {
onClose();
closeDialog();
// dialog closing animation runs slower than prop change
// and we don't want to show the form for a split second
setTimeout(() => setCardCode(null), 0);

View file

@ -1,33 +1,51 @@
import { DialogContent, Divider, TextField } from "@material-ui/core";
import {
DialogContent,
Divider,
TextField,
Typography
} from "@material-ui/core";
import VerticalSpacer from "@saleor/apps/components/VerticalSpacer";
import DialogButtons from "@saleor/components/ActionDialog/DialogButtons";
import CardSpacer from "@saleor/components/CardSpacer";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import TextWithSelectField from "@saleor/components/TextWithSelectField";
import { GiftCardError } from "@saleor/fragments/types/GiftCardError";
import GiftCardExpirySelect from "@saleor/giftCards/components/GiftCardExpirySelect";
import GiftCardTagInput from "@saleor/giftCards/components/GiftCardTagInput";
import useForm from "@saleor/hooks/useForm";
import { commonMessages } from "@saleor/intl";
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
import Label from "@saleor/orders/components/OrderHistory/Label";
import {
GiftCardExpiryTypeEnum,
GiftCardSettingsExpiryTypeEnum,
TimePeriodTypeEnum
} from "@saleor/types/globalTypes";
import { getFormErrors } from "@saleor/utils/errors";
import { mapSingleValueNodeToChoice } from "@saleor/utils/maps";
import React, { useState } from "react";
import { useIntl } from "react-intl";
import { FormattedMessage, useIntl } from "react-intl";
import GiftCardSendToCustomer from "../components/GiftCardSendToCustomer/GiftCardSendToCustomer";
import { useGiftCardSettingsQuery } from "../GiftCardSettings/queries";
import { getGiftCardErrorMessage } from "../GiftCardUpdate/messages";
import GiftCardCustomerSelectField from "./GiftCardCustomerSelectField";
import GiftCardCreateExpirySelect from "./GiftCardCreateExpirySelect";
import { giftCardCreateDialogMessages as messages } from "./messages";
import { useGiftCardCreateDialogFormStyles as useStyles } from "./styles";
import { GiftCardCommonFormData, GiftCardCreateFormCustomer } from "./types";
import {
GiftCardCommonFormData,
GiftCardCreateFormCustomer,
GiftCardExpiryType
} from "./types";
export interface GiftCardCreateFormData extends GiftCardCommonFormData {
note: string;
sendToCustomerSelected: boolean;
selectedCustomer?: GiftCardCreateFormCustomer;
channelSlug?: string;
expirySelected: boolean;
expiryType: GiftCardExpiryType;
expiryPeriodType: TimePeriodTypeEnum;
expiryPeriodAmount: number;
requiresActivation: boolean;
}
const initialCustomer = { email: "", name: "" };
@ -37,10 +55,13 @@ export const initialData: GiftCardCreateFormData = {
balanceAmount: 1,
balanceCurrency: null,
note: "",
sendToCustomerSelected: false,
expirySelected: false,
expiryType: "EXPIRY_PERIOD",
expiryDate: "",
expiryType: GiftCardExpiryTypeEnum.EXPIRY_PERIOD,
expiryPeriodType: TimePeriodTypeEnum.YEAR,
expiryPeriodAmount: 1
expiryPeriodType: TimePeriodTypeEnum.MONTH,
expiryPeriodAmount: 12,
requiresActivation: true
};
interface GiftCardCreateDialogFormProps {
@ -63,6 +84,11 @@ const GiftCardCreateDialogForm: React.FC<GiftCardCreateDialogFormProps> = ({
const initialCurrency = channelCurrencies[0];
const {
data: settingsData,
loading: loadingSettings
} = useGiftCardSettingsQuery();
const [selectedCustomer, setSelectedCustomer] = useState<
GiftCardCreateFormCustomer
>(initialCustomer);
@ -70,33 +96,65 @@ const GiftCardCreateDialogForm: React.FC<GiftCardCreateDialogFormProps> = ({
const handleSubmit = (data: GiftCardCreateFormData) =>
onSubmit({ ...data, selectedCustomer });
const getInitialExpirySettingsData = (): Partial<GiftCardCreateFormData> => {
if (loadingSettings) {
return {};
}
const { expiryType, expiryPeriod } = settingsData?.giftCardSettings;
if (expiryType === GiftCardSettingsExpiryTypeEnum.NEVER_EXPIRE) {
return {};
}
return {
expiryType,
expiryPeriodType: expiryPeriod?.type,
expiryPeriodAmount: expiryPeriod?.amount
};
};
const { submit, change, data } = useForm(
{ ...initialData, balanceCurrency: initialCurrency },
{
...initialData,
...getInitialExpirySettingsData(),
balanceCurrency: initialCurrency,
channelSlug: ""
},
handleSubmit
);
const formErrors = getFormErrors(
[
"tag",
"expiryDate",
"expiryPeriod",
"customer",
"currency",
"amount",
"balance"
],
["tag", "expiryDate", "customer", "currency", "amount", "balance"],
apiErrors
);
const {
tag,
sendToCustomerSelected,
channelSlug,
balanceAmount,
balanceCurrency,
expirySelected,
expiryType,
expiryPeriodAmount,
expiryPeriodType,
expiryType,
balanceAmount,
balanceCurrency
expiryDate,
requiresActivation
} = data;
const shouldEnableSubmitButton = () => {
if (!balanceAmount) {
return false;
}
if (expirySelected && expiryType === "EXPIRY_DATE") {
return !!expiryDate;
}
return true;
};
return (
<>
<DialogContent>
@ -107,7 +165,7 @@ const GiftCardCreateDialogForm: React.FC<GiftCardCreateDialogFormProps> = ({
choices={mapSingleValueNodeToChoice(channelCurrencies)}
containerClassName={classes.balanceContainer}
textFieldProps={{
type: "number",
type: "float",
label: intl.formatMessage(messages.amountLabel),
name: "balanceAmount",
value: balanceAmount,
@ -128,24 +186,25 @@ const GiftCardCreateDialogForm: React.FC<GiftCardCreateDialogFormProps> = ({
/>
<CardSpacer />
<Divider />
<CardSpacer />
<GiftCardCustomerSelectField
<GiftCardSendToCustomer
selectedChannelSlug={channelSlug}
change={change}
sendToCustomerSelected={sendToCustomerSelected}
selectedCustomer={selectedCustomer}
setSelectedCustomer={setSelectedCustomer}
/>
<VerticalSpacer />
<Label text={intl.formatMessage(messages.customerSubtitle)} />
<CardSpacer />
<Divider />
<CardSpacer />
<GiftCardExpirySelect
<VerticalSpacer />
<GiftCardCreateExpirySelect
errors={formErrors}
change={change}
expirySelected={expirySelected}
expiryType={expiryType}
expiryPeriodAmount={expiryPeriodAmount}
expiryPeriodType={expiryPeriodType}
expiryDate={expiryDate}
/>
<CardSpacer />
<VerticalSpacer />
<TextField
name="note"
onChange={change}
@ -157,8 +216,23 @@ const GiftCardCreateDialogForm: React.FC<GiftCardCreateDialogFormProps> = ({
/>
<VerticalSpacer />
<Label text={intl.formatMessage(messages.noteSubtitle)} />
<VerticalSpacer spacing={2} />
<ControlledCheckbox
name="requiresActivation"
label={
<>
<FormattedMessage {...messages.requiresActivationLabel} />
<Typography variant="caption">
<FormattedMessage {...messages.requiresActivationCaption} />
</Typography>
</>
}
checked={requiresActivation}
onChange={change}
/>
</DialogContent>
<DialogButtons
disabled={!shouldEnableSubmitButton()}
onConfirm={submit}
confirmButtonLabel={intl.formatMessage(messages.issueButtonLabel)}
confirmButtonState={opts?.status}

View file

@ -0,0 +1,137 @@
import { TextField, Typography } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import VerticalSpacer from "@saleor/apps/components/VerticalSpacer";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import RadioGroupField from "@saleor/components/RadioGroupField";
import { GiftCardError } from "@saleor/fragments/types/GiftCardError";
import TimePeriodField from "@saleor/giftCards/components/TimePeriodField";
import { GiftCardExpiryType } from "@saleor/giftCards/GiftCardCreateDialog/types";
import { getExpiryPeriodTerminationDate } from "@saleor/giftCards/GiftCardCreateDialog/utils";
import { getGiftCardErrorMessage } from "@saleor/giftCards/GiftCardUpdate/messages";
import useCurrentDate from "@saleor/hooks/useCurrentDate";
import { FormChange } from "@saleor/hooks/useForm";
import { TimePeriodTypeEnum } from "@saleor/types/globalTypes";
import React from "react";
import { FormattedMessage } from "react-intl";
import { MessageDescriptor, useIntl } from "react-intl";
import { giftCardCreateExpirySelectMessages as messages } from "./messages";
import { useGiftCardCreateExpirySelectStyles as useStyles } from "./styles";
interface UntranslatedOption {
label: MessageDescriptor;
value: GiftCardExpiryType;
}
const options: UntranslatedOption[] = [
{
label: messages.expiryPeriodLabel,
value: "EXPIRY_PERIOD"
},
{
label: messages.expiryDateLabel,
value: "EXPIRY_DATE"
}
];
interface GiftCardCreateExpirySelectProps {
change: FormChange;
expirySelected: boolean;
expiryPeriodType: TimePeriodTypeEnum;
expiryPeriodAmount: number;
expiryType: GiftCardExpiryType;
customOptions?: UntranslatedOption[];
errors?: Record<"expiryDate", GiftCardError>;
expiryDate?: string;
}
const GiftCardCreateExpirySelect: React.FC<GiftCardCreateExpirySelectProps> = ({
errors,
change,
expirySelected,
expiryPeriodType,
expiryPeriodAmount,
expiryType,
expiryDate
}) => {
const intl = useIntl();
const classes = useStyles({});
const translatedOptions = options.map(({ label, value }) => ({
value,
label: intl.formatMessage(label)
}));
const currentDate = useCurrentDate();
return (
<>
<ControlledCheckbox
name={"expirySelected"}
label={intl.formatMessage(messages.expirySelectedLabel)}
checked={expirySelected}
onChange={change}
/>
{expirySelected && (
<>
<VerticalSpacer spacing={2} />
<RadioGroupField
innerContainerClassName={classes.radioGroupContainer}
choices={translatedOptions}
onChange={change}
name={"expiryType"}
value={expiryType}
variant="inline"
/>
<VerticalSpacer spacing={2} />
{expiryType === "EXPIRY_DATE" && (
<TextField
error={!!errors?.expiryDate}
helperText={getGiftCardErrorMessage(errors?.expiryDate, intl)}
onChange={change}
name={"expiryDate"}
className={classes.dateField}
label={intl.formatMessage(messages.expiryDateLabel)}
value={expiryDate}
InputLabelProps={{
shrink: true
}}
type="date"
/>
)}
{expiryType === "EXPIRY_PERIOD" && (
<div className={classes.periodField}>
<TimePeriodField
isError={!!errors?.expiryDate}
helperText={getGiftCardErrorMessage(errors?.expiryDate, intl)}
change={change}
periodType={expiryPeriodType}
periodAmount={expiryPeriodAmount}
amountFieldName={"expiryPeriodAmount"}
typeFieldName={"expiryPeriodType"}
/>
<HorizontalSpacer spacing={2} />
<div className={classes.dateText}>
<Typography variant="caption">
<FormattedMessage {...messages.expiryOnLabel} />
</Typography>
<Typography>
{getExpiryPeriodTerminationDate(
currentDate,
expiryPeriodType,
expiryPeriodAmount
)?.format("L")}
</Typography>
</div>
</div>
)}
<VerticalSpacer spacing={2} />
</>
)}
</>
);
};
export default GiftCardCreateExpirySelect;

View file

@ -0,0 +1,2 @@
export * from "./GiftCardCreateExpirySelect";
export { default } from "./GiftCardCreateExpirySelect";

View file

@ -0,0 +1,20 @@
import { defineMessages } from "react-intl";
export const giftCardCreateExpirySelectMessages = defineMessages({
expirySelectedLabel: {
defaultMessage: "Set gift card expiry date",
description: "GiftCarUpdateDetailsExpirySection expiry selected label"
},
expiryPeriodLabel: {
defaultMessage: "Expires in",
description: "GiftCarUpdateDetailsExpirySection expiry period label"
},
expiryDateLabel: {
defaultMessage: "Exact date",
description: "GiftCarUpdateDetailsExpirySection expiry date label"
},
expiryOnLabel: {
defaultMessage: "Will expire on:",
description: "GiftCarUpdateDetailsExpirySection expiry on label"
}
});

View file

@ -0,0 +1,20 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useGiftCardCreateExpirySelectStyles = makeStyles(
theme => ({
radioGroupContainer: {
display: "flex",
flexDirection: "row"
},
dateField: {
width: 400
},
periodField: {
display: "flex"
},
dateText: {
marginTop: theme.spacing(0.5)
}
}),
{ name: "GiftCardExpirySelect" }
);

View file

@ -17,11 +17,6 @@ export const giftCardCreateDialogMessages = defineMessages({
defaultMessage: "Customer",
description: "GiftCardCreateDialog customer label"
},
customerSubtitle: {
defaultMessage:
"Selected customer will be sent the generated gift card code. Someone else can redeem the gift card code. Gift card will be assigned to account which redeemed the code.",
description: "GiftCardCreateDialog customer subtitle"
},
noteLabel: {
defaultMessage: "Note",
description: "GiftCardCreateDialog note label"
@ -46,5 +41,13 @@ export const giftCardCreateDialogMessages = defineMessages({
createdSuccessAlertTitle: {
defaultMessage: "Successfully created gift card",
description: "GiftCardCreateDialog createdSuccessAlertTitle"
},
requiresActivationLabel: {
defaultMessage: "Requires activation",
description: "GiftCarUpdateDetailsExpirySection requires activation label"
},
requiresActivationCaption: {
defaultMessage: "All issued cards require activation by staff before use.",
description: "GiftCarUpdateDetailsExpirySection requires activation caption"
}
});

View file

@ -1,9 +1,9 @@
import { GiftCardError } from "@saleor/fragments/types/GiftCardError";
import { FormChange } from "@saleor/hooks/useForm";
import {
GiftCardExpiryTypeEnum,
TimePeriodTypeEnum
} from "@saleor/types/globalTypes";
import { GiftCardCreateFormData } from "./GiftCardCreateDialogForm";
export type GiftCardExpiryType = "EXPIRY_DATE" | "EXPIRY_PERIOD";
export interface GiftCardCreateFormCustomer {
name: string;
@ -15,16 +15,22 @@ export interface GiftCardCommonFormData {
balanceAmount: number;
balanceCurrency: string;
expiryDate: string;
expiryType: GiftCardExpiryTypeEnum;
expiryPeriodType: TimePeriodTypeEnum;
expiryPeriodAmount: number;
}
export type GiftCardCreateFormErrors = Record<
"tag" | "expiryDate" | "expiryPeriod" | "customer" | "currency" | "amount",
"tag" | "expiryDate" | "customer" | "currency" | "amount",
GiftCardError
>;
export type GiftCardCreateInputData = Pick<
GiftCardCreateFormData,
| "expirySelected"
| "expiryDate"
| "expiryPeriodAmount"
| "expiryPeriodType"
| "expiryType"
>;
export interface GiftCardCreateFormCommonProps {
change: FormChange;
errors: GiftCardCreateFormErrors;

View file

@ -1,41 +1,54 @@
import {
GiftCardExpirySettingsInput,
GiftCardExpiryTypeEnum
} from "@saleor/types/globalTypes";
import { TimePeriodTypeEnum } from "@saleor/types/globalTypes";
import moment from "moment-timezone";
import { GiftCardCommonFormData } from "./types";
import { GiftCardCreateFormData } from "./GiftCardCreateDialogForm";
export const getGiftCardExpirySettingsInputData = ({
expiryType,
expiryDate,
expiryPeriodAmount,
expiryPeriodType
}: Pick<
GiftCardCommonFormData,
"expiryDate" | "expiryPeriodAmount" | "expiryPeriodType" | "expiryType"
>): GiftCardExpirySettingsInput => {
switch (expiryType) {
case GiftCardExpiryTypeEnum.EXPIRY_DATE: {
return {
expiryType,
expiryDate
};
}
const addToCurrentDate = (
currentDate: number,
expiryPeriodAmount: number,
unit: moment.unitOfTime.DurationConstructor
) => moment(currentDate).add(expiryPeriodAmount, unit);
case GiftCardExpiryTypeEnum.EXPIRY_PERIOD: {
return {
expiryType,
expiryPeriod: {
amount: expiryPeriodAmount,
type: expiryPeriodType
}
};
}
default: {
return {
expiryType
};
}
export const getExpiryPeriodTerminationDate = (
currentDate: number,
expiryPeriodType: TimePeriodTypeEnum,
expiryPeriodAmount: number = 0
): moment.Moment | null => {
switch (expiryPeriodType) {
case TimePeriodTypeEnum.DAY:
return addToCurrentDate(currentDate, expiryPeriodAmount, "d");
case TimePeriodTypeEnum.WEEK:
return addToCurrentDate(currentDate, expiryPeriodAmount, "w");
case TimePeriodTypeEnum.MONTH:
return addToCurrentDate(currentDate, expiryPeriodAmount, "M");
case TimePeriodTypeEnum.YEAR:
return addToCurrentDate(currentDate, expiryPeriodAmount, "y");
default:
return null;
}
};
export const getGiftCardExpiryInputData = (
{
expirySelected,
expiryType,
expiryDate,
expiryPeriodAmount,
expiryPeriodType
}: GiftCardCreateFormData,
currentDate: number
): string => {
if (!expirySelected) {
return;
}
if (expiryType === "EXPIRY_PERIOD") {
return getExpiryPeriodTerminationDate(
currentDate,
expiryPeriodType,
expiryPeriodAmount
)?.format("YYYY-MM-DD");
}
return expiryDate;
};

View file

@ -0,0 +1,45 @@
import { Card, CardContent } from "@material-ui/core";
import CardTitle from "@saleor/components/CardTitle";
import GiftCardSettingsExpirySelect, {
GiftCardSettingsExpirySelectProps
} from "@saleor/giftCards/components/GiftCardSettingsExpirySelect";
import React from "react";
import { useIntl } from "react-intl";
import { GiftCardSettingsFormData } from "../types";
import { giftCardExpirySettingsCard as messages } from "./messages";
export interface GiftCardExpirySettingsCardProps
extends Pick<GiftCardSettingsExpirySelectProps, "errors"> {
data: GiftCardSettingsFormData;
disabled: boolean;
onChange: (event: React.ChangeEvent<any>) => void;
}
const GiftCardExpirySettingsCard: React.FC<GiftCardExpirySettingsCardProps> = ({
data,
disabled,
errors,
onChange
}) => {
const intl = useIntl();
return (
<Card data-test="giftCardSettings">
<CardTitle title={intl.formatMessage(messages.expiryDateTitle)} />
<CardContent>
<GiftCardSettingsExpirySelect
expiryPeriodActive={data.expiryPeriodActive}
expiryPeriodType={data.expiryPeriodType}
expiryPeriodAmount={data.expiryPeriodAmount}
change={onChange}
disabled={disabled}
errors={errors}
/>
</CardContent>
</Card>
);
};
GiftCardExpirySettingsCard.displayName = "GiftCardExpirySettingsCard";
export default GiftCardExpirySettingsCard;

View file

@ -0,0 +1,2 @@
export * from "./GiftCardExpirySettingsCard";
export { default } from "./GiftCardExpirySettingsCard";

View file

@ -0,0 +1,12 @@
import { defineMessages } from "react-intl";
export const giftCardExpirySettingsCard = defineMessages({
expiryDateSectionDescription: {
defaultMessage:
"You can set gift cards to expire after a certain time after their purchase. Remember that in some countries, gift cards expiry is prohibited by law."
},
expiryDateTitle: {
defaultMessage: "Expiry date",
description: "section header"
}
});

View file

@ -0,0 +1,101 @@
import { Typography } from "@material-ui/core";
import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import { Grid } from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader";
import Savebar from "@saleor/components/Savebar";
import useNavigator from "@saleor/hooks/useNavigator";
import { sectionNames } from "@saleor/intl";
import { Backlink } from "@saleor/macaw-ui";
import {
GiftCardSettingsExpiryTypeEnum,
TimePeriodTypeEnum
} from "@saleor/types/globalTypes";
import { getFormErrors } from "@saleor/utils/errors";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { giftCardsListPath } from "../urls";
import GiftCardExpirySettingsCard from "./GiftCardExpirySettingsCard";
import { giftCardExpirySettingsCard as expirySettingsMessages } from "./GiftCardExpirySettingsCard/messages";
import { giftCardSettingsPageMessages as messages } from "./messages";
import { useGiftCardSettingsUpdateMutation } from "./mutations";
import { useGiftCardSettingsQuery } from "./queries";
import { GiftCardSettingsFormData } from "./types";
import { getGiftCardSettingsInputData } from "./utils";
const GiftCardSettingsPage: React.FC = () => {
const intl = useIntl();
const navigate = useNavigator();
const { data, loading } = useGiftCardSettingsQuery();
const settingsData = data?.giftCardSettings;
const initialData: GiftCardSettingsFormData = {
expiryPeriodActive:
settingsData?.expiryType === GiftCardSettingsExpiryTypeEnum.EXPIRY_PERIOD,
expiryPeriodType:
settingsData?.expiryPeriod?.type || TimePeriodTypeEnum.YEAR,
expiryPeriodAmount: settingsData?.expiryPeriod?.amount || 1
};
const [
updateGiftCardSettings,
updateGiftCardSettingsOpts
] = useGiftCardSettingsUpdateMutation({});
const navigateBack = () => navigate(giftCardsListPath);
const handleSubmit = (formData: GiftCardSettingsFormData) => {
updateGiftCardSettings({
variables: {
input: getGiftCardSettingsInputData(formData)
}
});
};
const formLoading = loading || updateGiftCardSettingsOpts?.loading;
const apiErrors =
updateGiftCardSettingsOpts?.data?.giftCardSettingsUpdate?.errors;
const formErrors = getFormErrors(["expiryPeriod"], apiErrors);
return (
<Container>
<Backlink onClick={navigateBack}>
{intl.formatMessage(sectionNames.giftCards)}
</Backlink>
<PageHeader title={intl.formatMessage(messages.title)} underline={true} />
<Form initial={initialData} onSubmit={handleSubmit}>
{({ data: formData, submit, hasChanged, change }) => (
<Grid variant="inverted">
<div>
<Typography>
<FormattedMessage
{...expirySettingsMessages.expiryDateSectionDescription}
/>
</Typography>
</div>
<GiftCardExpirySettingsCard
data={formData}
disabled={formLoading}
onChange={change}
errors={formErrors}
/>
<Savebar
onCancel={navigateBack}
onSubmit={submit}
disabled={formLoading || !hasChanged}
state={updateGiftCardSettingsOpts?.status}
/>
</Grid>
)}
</Form>
</Container>
);
};
export default GiftCardSettingsPage;

View file

@ -0,0 +1,2 @@
export * from "./GiftCardSettingsPage";
export { default } from "./GiftCardSettingsPage";

View file

@ -0,0 +1,17 @@
import { GiftCardSettingsErrorFragment } from "@saleor/fragments/types/GiftCardSettingsErrorFragment";
import { getCommonFormFieldErrorMessage } from "@saleor/utils/errors/common";
import { defineMessages, IntlShape } from "react-intl";
export const giftCardSettingsPageMessages = defineMessages({
title: {
defaultMessage: "Gift Cards Settings",
description: "header"
}
});
export function getGiftCardSettingsErrorMessage(
error: Omit<GiftCardSettingsErrorFragment, "__typename"> | undefined,
intl: IntlShape
): string {
return getCommonFormFieldErrorMessage(error, intl);
}

View file

@ -0,0 +1,29 @@
import { giftCardSettingsErrorFragment } from "@saleor/fragments/errors";
import { fragmentGiftCardsSettings } from "@saleor/fragments/giftCards";
import makeMutation from "@saleor/hooks/makeMutation";
import gql from "graphql-tag";
import {
GiftCardSettingsUpdate,
GiftCardSettingsUpdateVariables
} from "./types/GiftCardSettingsUpdate";
const giftCardSettingsUpdate = gql`
${giftCardSettingsErrorFragment}
${fragmentGiftCardsSettings}
mutation GiftCardSettingsUpdate($input: GiftCardSettingsUpdateInput!) {
giftCardSettingsUpdate(input: $input) {
errors {
...GiftCardSettingsErrorFragment
}
giftCardSettings {
...GiftCardsSettingsFragment
}
}
}
`;
export const useGiftCardSettingsUpdateMutation = makeMutation<
GiftCardSettingsUpdate,
GiftCardSettingsUpdateVariables
>(giftCardSettingsUpdate);

View file

@ -0,0 +1,18 @@
import { fragmentGiftCardsSettings } from "@saleor/fragments/giftCards";
import makeQuery from "@saleor/hooks/makeQuery";
import gql from "graphql-tag";
import { GiftCardSettings } from "./types/GiftCardSettings";
export const giftCardSettings = gql`
${fragmentGiftCardsSettings}
query GiftCardSettings {
giftCardSettings {
...GiftCardsSettingsFragment
}
}
`;
export const useGiftCardSettingsQuery = makeQuery<GiftCardSettings, never>(
giftCardSettings
);

View file

@ -0,0 +1,7 @@
import { TimePeriodTypeEnum } from "@saleor/types/globalTypes";
export interface GiftCardSettingsFormData {
expiryPeriodActive: boolean;
expiryPeriodType: TimePeriodTypeEnum;
expiryPeriodAmount: number;
}

View file

@ -0,0 +1,26 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardSettingsExpiryTypeEnum, TimePeriodTypeEnum } from "./../../../types/globalTypes";
// ====================================================
// GraphQL query operation: GiftCardSettings
// ====================================================
export interface GiftCardSettings_giftCardSettings_expiryPeriod {
__typename: "TimePeriod";
type: TimePeriodTypeEnum;
amount: number;
}
export interface GiftCardSettings_giftCardSettings {
__typename: "GiftCardSettings";
expiryType: GiftCardSettingsExpiryTypeEnum;
expiryPeriod: GiftCardSettings_giftCardSettings_expiryPeriod | null;
}
export interface GiftCardSettings {
giftCardSettings: GiftCardSettings_giftCardSettings;
}

View file

@ -0,0 +1,42 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardSettingsUpdateInput, GiftCardSettingsErrorCode, GiftCardSettingsExpiryTypeEnum, TimePeriodTypeEnum } from "./../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: GiftCardSettingsUpdate
// ====================================================
export interface GiftCardSettingsUpdate_giftCardSettingsUpdate_errors {
__typename: "GiftCardSettingsError";
code: GiftCardSettingsErrorCode;
field: string | null;
}
export interface GiftCardSettingsUpdate_giftCardSettingsUpdate_giftCardSettings_expiryPeriod {
__typename: "TimePeriod";
type: TimePeriodTypeEnum;
amount: number;
}
export interface GiftCardSettingsUpdate_giftCardSettingsUpdate_giftCardSettings {
__typename: "GiftCardSettings";
expiryType: GiftCardSettingsExpiryTypeEnum;
expiryPeriod: GiftCardSettingsUpdate_giftCardSettingsUpdate_giftCardSettings_expiryPeriod | null;
}
export interface GiftCardSettingsUpdate_giftCardSettingsUpdate {
__typename: "GiftCardSettingsUpdate";
errors: GiftCardSettingsUpdate_giftCardSettingsUpdate_errors[];
giftCardSettings: GiftCardSettingsUpdate_giftCardSettingsUpdate_giftCardSettings | null;
}
export interface GiftCardSettingsUpdate {
giftCardSettingsUpdate: GiftCardSettingsUpdate_giftCardSettingsUpdate | null;
}
export interface GiftCardSettingsUpdateVariables {
input: GiftCardSettingsUpdateInput;
}

View file

@ -0,0 +1,32 @@
import {
GiftCardSettingsExpiryTypeEnum,
GiftCardSettingsUpdateInput
} from "@saleor/types/globalTypes";
import { GiftCardSettingsFormData } from "./types";
export const getGiftCardSettingsInputData = ({
expiryPeriodActive,
expiryPeriodType,
expiryPeriodAmount
}: Pick<
GiftCardSettingsFormData,
"expiryPeriodActive" | "expiryPeriodType" | "expiryPeriodAmount"
>): GiftCardSettingsUpdateInput => {
const expiryType = expiryPeriodActive
? GiftCardSettingsExpiryTypeEnum.EXPIRY_PERIOD
: GiftCardSettingsExpiryTypeEnum.NEVER_EXPIRE;
const expiryPeriod =
expiryPeriodActive && expiryPeriodType && expiryPeriodAmount
? {
type: expiryPeriodType,
amount: expiryPeriodAmount
}
: undefined;
return {
expiryType,
expiryPeriod
};
};

View file

@ -0,0 +1,179 @@
import { CircularProgress, TextField, Typography } from "@material-ui/core";
import VerticalSpacer from "@saleor/apps/components/VerticalSpacer";
import { useChannelsList } from "@saleor/channels/queries";
import ActionDialog from "@saleor/components/ActionDialog";
import { useChannelsSearch } from "@saleor/components/ChannelsAvailabilityDialog/utils";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import { IMessage } from "@saleor/components/messages";
import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField";
import useForm from "@saleor/hooks/useForm";
import useNotifier from "@saleor/hooks/useNotifier";
import { getBySlug } from "@saleor/products/components/ProductVariantCreatorPage/utils";
import commonErrorMessages from "@saleor/utils/errors/common";
import { DialogActionHandlersProps } from "@saleor/utils/handlers/dialogActionHandlers";
import { mapSlugNodeToChoice } from "@saleor/utils/maps";
import React, { useEffect, useState } from "react";
import { useIntl } from "react-intl";
import { useGiftCardDeleteDialogContentStyles as useProgressStyles } from "../../components/GiftCardDeleteDialog/styles";
import { useUpdateBalanceDialogStyles as useStyles } from "../GiftCardUpdateBalanceDialog/styles";
import { getGiftCardErrorMessage } from "../messages";
import useGiftCardDetails from "../providers/GiftCardDetailsProvider/hooks/useGiftCardDetails";
import { giftCardResendCodeDialogMessages as messages } from "./messages";
import { useGiftCardResendCodeMutation } from "./mutations";
import { GiftCardResend } from "./types/GiftCardResend";
import { useDialogFormReset } from "./utils";
export interface GiftCardResendCodeFormData {
email: string;
channelSlug: string;
}
const GiftCardResendCodeDialog: React.FC<DialogActionHandlersProps> = ({
open,
closeDialog
}) => {
const intl = useIntl();
const notify = useNotifier();
const classes = useStyles();
const progressClasses = useProgressStyles();
const {
giftCard: { boughtInChannel: initialChannelSlug }
} = useGiftCardDetails();
const [consentSelected, setConsentSelected] = useState(false);
const { data: channelsData, loading: loadingChannels } = useChannelsList({});
const channels = channelsData?.channels;
const activeChannels = channels?.filter(({ isActive }) => isActive);
const { onQueryChange, filteredChannels } = useChannelsSearch(activeChannels);
const initialFormData: GiftCardResendCodeFormData = {
email: "",
channelSlug: initialChannelSlug || ""
};
const {
giftCard: { id }
} = useGiftCardDetails();
const handleSubmit = async ({
email,
channelSlug
}: GiftCardResendCodeFormData) => {
const result = await resendGiftCardCode({
variables: {
input: {
id,
email: email ? email : null,
channel: channelSlug
}
}
});
return result?.data?.giftCardResend?.errors;
};
const { data, change, submit, reset } = useForm(
initialFormData,
handleSubmit
);
const onCompleted = (data: GiftCardResend) => {
const errors = data?.giftCardResend?.errors;
const notifierData: IMessage = !!errors?.length
? {
status: "error",
text: intl.formatMessage(commonErrorMessages.unknownError)
}
: {
status: "success",
text: intl.formatMessage(messages.successResendAlertText)
};
notify(notifierData);
if (!errors.length) {
closeDialog();
reset();
}
};
const [
resendGiftCardCode,
resendGiftCardCodeOpts
] = useGiftCardResendCodeMutation({
onCompleted
});
const { loading, status, data: submitData } = resendGiftCardCodeOpts;
const { formErrors } = useDialogFormReset({
open,
reset,
apiErrors: submitData?.giftCardResend?.errors,
keys: ["email"]
});
useEffect(reset, [consentSelected]);
return (
<ActionDialog
maxWidth="sm"
open={open}
onConfirm={submit}
confirmButtonLabel={intl.formatMessage(messages.submitButtonLabel)}
onClose={closeDialog}
title={intl.formatMessage(messages.title)}
confirmButtonState={status}
disabled={loading}
>
{loadingChannels ? (
<div className={progressClasses.progressContainer}>
<CircularProgress />
</div>
) : (
<>
<Typography>{intl.formatMessage(messages.description)}</Typography>
<VerticalSpacer />
<SingleAutocompleteSelectField
choices={mapSlugNodeToChoice(filteredChannels)}
name="channelSlug"
label={intl.formatMessage(messages.sendToChannelSelectLabel)}
value={data?.channelSlug}
onChange={change}
displayValue={channels.find(getBySlug(data?.channelSlug))?.name}
fetchChoices={onQueryChange}
/>
<VerticalSpacer />
<ControlledCheckbox
name="differentMailConsent"
label={intl.formatMessage(messages.consentCheckboxLabel)}
checked={consentSelected}
onChange={(event: React.ChangeEvent<any>) =>
setConsentSelected(event.target.value)
}
/>
<VerticalSpacer />
<TextField
disabled={!consentSelected}
error={!!formErrors?.email}
helperText={getGiftCardErrorMessage(formErrors?.email, intl)}
name="email"
value={data.email}
onChange={change}
className={classes.inputContainer}
label={intl.formatMessage(messages.emailInputPlaceholder)}
/>
</>
)}
</ActionDialog>
);
};
export default GiftCardResendCodeDialog;

View file

@ -0,0 +1,2 @@
export * from "./GiftCardResendCodeDialog";
export { default } from "./GiftCardResendCodeDialog";

View file

@ -0,0 +1,33 @@
import { defineMessages } from "react-intl";
export const giftCardResendCodeDialogMessages = defineMessages({
title: {
defaultMessage: "Resend code to customer",
description: "GiftCardResendCodeDialog title"
},
description: {
defaultMessage:
"Gift Card Code will be resent to email provided during checkout. You can provide a different email address if you want to:",
description: "GiftCardResendCodeDialog description"
},
consentCheckboxLabel: {
defaultMessage: "Yes, I want to send gift card to different address",
description: "GiftCardResendCodeDialog consentCheckboxLabel"
},
submitButtonLabel: {
defaultMessage: "Resend",
description: "GiftCardResendCodeDialog submitButtonLabel"
},
emailInputPlaceholder: {
defaultMessage: "Provided email address",
description: "GiftCardResendCodeDialog emailInputPlaceholder"
},
successResendAlertText: {
defaultMessage: "Successfully resent code to customer!",
description: "GiftCardResendCodeDialog successResendAlertText"
},
sendToChannelSelectLabel: {
defaultMessage: "Send to channel",
description: "ChannelPickerSelectField sendToChannelLabel"
}
});

View file

@ -0,0 +1,29 @@
import { giftCardErrorFragment } from "@saleor/fragments/errors";
import makeMutation from "@saleor/hooks/makeMutation";
import gql from "graphql-tag";
import { giftCardDataFragment } from "../queries";
import {
GiftCardResend,
GiftCardResendVariables
} from "./types/GiftCardResend";
const giftCardResend = gql`
${giftCardDataFragment}
${giftCardErrorFragment}
mutation GiftCardResend($input: GiftCardResendInput!) {
giftCardResend(input: $input) {
errors {
...GiftCardError
}
giftCard {
...GiftCardData
}
}
}
`;
export const useGiftCardResendCodeMutation = makeMutation<
GiftCardResend,
GiftCardResendVariables
>(giftCardResend);

View file

@ -0,0 +1,102 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardResendInput, GiftCardErrorCode } from "./../../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: GiftCardResend
// ====================================================
export interface GiftCardResend_giftCardResend_errors {
__typename: "GiftCardError";
code: GiftCardErrorCode;
field: string | null;
}
export interface GiftCardResend_giftCardResend_giftCard_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardResend_giftCardResend_giftCard_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardResend_giftCardResend_giftCard_createdBy {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardResend_giftCardResend_giftCard_product {
__typename: "Product";
id: string;
name: string;
}
export interface GiftCardResend_giftCardResend_giftCard_usedBy {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardResend_giftCardResend_giftCard_app {
__typename: "App";
id: string;
name: string | null;
}
export interface GiftCardResend_giftCardResend_giftCard_initialBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardResend_giftCardResend_giftCard_currentBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardResend_giftCardResend_giftCard {
__typename: "GiftCard";
metadata: (GiftCardResend_giftCardResend_giftCard_metadata | null)[];
privateMetadata: (GiftCardResend_giftCardResend_giftCard_privateMetadata | null)[];
displayCode: string;
boughtInChannel: string | null;
createdBy: GiftCardResend_giftCardResend_giftCard_createdBy | null;
product: GiftCardResend_giftCardResend_giftCard_product | null;
usedBy: GiftCardResend_giftCardResend_giftCard_usedBy | null;
usedByEmail: string | null;
createdByEmail: string | null;
app: GiftCardResend_giftCardResend_giftCard_app | null;
created: any;
expiryDate: any | null;
lastUsedOn: any | null;
isActive: boolean;
initialBalance: GiftCardResend_giftCardResend_giftCard_initialBalance | null;
currentBalance: GiftCardResend_giftCardResend_giftCard_currentBalance | null;
id: string;
tag: string | null;
}
export interface GiftCardResend_giftCardResend {
__typename: "GiftCardResend";
errors: GiftCardResend_giftCardResend_errors[];
giftCard: GiftCardResend_giftCardResend_giftCard | null;
}
export interface GiftCardResend {
giftCardResend: GiftCardResend_giftCardResend | null;
}
export interface GiftCardResendVariables {
input: GiftCardResendInput;
}

View file

@ -0,0 +1,34 @@
import { UserError } from "@saleor/types";
import { FormErrors, getFormErrors } from "@saleor/utils/errors";
import { useEffect, useState } from "react";
export function useDialogFormReset<
TError extends UserError,
TKey extends string
>({
reset,
apiErrors,
keys,
open
}: {
reset: () => void;
apiErrors: TError[];
keys: TKey[];
open: boolean;
}) {
const [formErrors, setFormErrors] = useState<FormErrors<TKey, TError>>(null);
useEffect(() => {
if (!open) {
setFormErrors(null);
reset();
}
}, [open]);
useEffect(() => {
const errors = getFormErrors(keys, apiErrors);
setFormErrors(errors);
}, [apiErrors]);
return { formErrors };
}

View file

@ -1,16 +1,16 @@
import { TextField, Typography } from "@material-ui/core";
import ActionDialog from "@saleor/components/ActionDialog";
import CardSpacer from "@saleor/components/CardSpacer";
import Form from "@saleor/components/Form";
import { IMessage } from "@saleor/components/messages";
import useForm from "@saleor/hooks/useForm";
import useNotifier from "@saleor/hooks/useNotifier";
import { getFormErrors } from "@saleor/utils/errors";
import commonErrorMessages from "@saleor/utils/errors/common";
import { DialogActionHandlers } from "@saleor/utils/handlers/dialogActionHandlers";
import { DialogActionHandlersProps } from "@saleor/utils/handlers/dialogActionHandlers";
import React from "react";
import { useIntl } from "react-intl";
import { giftCardsListTableMessages as tableMessages } from "../../GiftCardsList/messages";
import { useDialogFormReset } from "../GiftCardResendCodeDialog/utils";
import { getGiftCardErrorMessage } from "../messages";
import { useGiftCardUpdateMutation } from "../mutations";
import useGiftCardDetails from "../providers/GiftCardDetailsProvider/hooks/useGiftCardDetails";
@ -22,9 +22,9 @@ export interface GiftCardBalanceUpdateFormData {
balanceAmount: number;
}
const GiftCardUpdateBalanceDialog: React.FC<DialogActionHandlers> = ({
const GiftCardUpdateBalanceDialog: React.FC<DialogActionHandlersProps> = ({
open,
onClose
closeDialog
}) => {
const intl = useIntl();
const classes = useStyles({});
@ -57,7 +57,7 @@ const GiftCardUpdateBalanceDialog: React.FC<DialogActionHandlers> = ({
notify(notifierData);
if (!errors.length) {
onClose();
closeDialog();
}
};
@ -83,54 +83,57 @@ const GiftCardUpdateBalanceDialog: React.FC<DialogActionHandlers> = ({
return result?.data?.giftCardUpdate?.errors;
};
const { loading, status, data } = updateGiftCardBalanceOpts;
const formErrors = getFormErrors(
["initialBalanceAmount"],
data?.giftCardUpdate?.errors
const { data, change, submit, reset, hasChanged } = useForm(
initialFormData,
handleSubmit
);
const { loading, status, data: submitData } = updateGiftCardBalanceOpts;
const { formErrors } = useDialogFormReset({
open,
reset,
keys: ["initialBalanceAmount"],
apiErrors: submitData?.giftCardUpdate?.errors
});
return (
<Form initial={initialFormData} onSubmit={handleSubmit}>
{({ data, change, submit, hasChanged }) => (
<ActionDialog
maxWidth="sm"
open={open}
onConfirm={submit}
confirmButtonLabel={intl.formatMessage(messages.changeButtonLabel)}
onClose={onClose}
title={intl.formatMessage(messages.title)}
confirmButtonState={status}
disabled={loading || !hasChanged}
>
<Typography>{intl.formatMessage(messages.subtitle)}</Typography>
<CardSpacer />
<TextField
inputProps={{ min: 0 }}
error={!!formErrors?.initialBalanceAmount}
helperText={getGiftCardErrorMessage(
formErrors?.initialBalanceAmount,
intl
)}
name="balanceAmount"
value={data.balanceAmount}
onChange={change}
className={classes.inputContainer}
label={intl.formatMessage(
tableMessages.giftCardsTableColumnBalanceTitle
)}
type="number"
InputProps={{
startAdornment: (
<div className={classes.currencyCodeContainer}>
<Typography variant="caption">{currency}</Typography>
</div>
)
}}
/>
</ActionDialog>
)}
</Form>
<ActionDialog
maxWidth="sm"
open={open}
onConfirm={submit}
confirmButtonLabel={intl.formatMessage(messages.changeButtonLabel)}
onClose={closeDialog}
title={intl.formatMessage(messages.title)}
confirmButtonState={status}
disabled={loading || !hasChanged}
>
<Typography>{intl.formatMessage(messages.subtitle)}</Typography>
<CardSpacer />
<TextField
inputProps={{ min: 0 }}
error={!!formErrors?.initialBalanceAmount}
helperText={getGiftCardErrorMessage(
formErrors?.initialBalanceAmount,
intl
)}
name="balanceAmount"
value={data.balanceAmount}
onChange={change}
className={classes.inputContainer}
label={intl.formatMessage(
tableMessages.giftCardsTableColumnBalanceTitle
)}
type="float"
InputProps={{
startAdornment: (
<div className={classes.currencyCodeContainer}>
<Typography variant="caption">{currency}</Typography>
</div>
)
}}
/>
</ActionDialog>
);
};

View file

@ -2,8 +2,8 @@ import { Button, Card, CardContent, Divider } from "@material-ui/core";
import CardSpacer from "@saleor/components/CardSpacer";
import CardTitle from "@saleor/components/CardTitle";
import Skeleton from "@saleor/components/Skeleton";
import GiftCardExpirySelect from "@saleor/giftCards/components/GiftCardExpirySelect";
import GiftCardTagInput from "@saleor/giftCards/components/GiftCardTagInput";
import GiftCardUpdateExpirySelect from "@saleor/giftCards/GiftCardUpdate/GiftCardUpdateExpirySelect";
import React from "react";
import { useIntl } from "react-intl";
@ -21,7 +21,7 @@ const GiftCardUpdateDetailsCard: React.FC = () => {
const {
change,
data: { expiryType, expiryPeriodAmount, expiryPeriodType, tag, expiryDate },
data: { tag },
formErrors
} = useGiftCardUpdateForm();
@ -55,14 +55,7 @@ const GiftCardUpdateDetailsCard: React.FC = () => {
change={change}
/>
<CardSpacer />
<GiftCardExpirySelect
expiryDate={expiryDate}
errors={formErrors}
change={change}
expiryType={expiryType}
expiryPeriodAmount={expiryPeriodAmount}
expiryPeriodType={expiryPeriodType}
/>
<GiftCardUpdateExpirySelect />
</>
)}
</Skeleton>

View file

@ -0,0 +1,69 @@
import { TextField, Typography } from "@material-ui/core";
import VerticalSpacer from "@saleor/apps/components/VerticalSpacer";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import { getGiftCardErrorMessage } from "@saleor/giftCards/GiftCardUpdate/messages";
import useGiftCardUpdateForm from "@saleor/giftCards/GiftCardUpdate/providers/GiftCardUpdateFormProvider/hooks/useGiftCardUpdateForm";
import useStateFromProps from "@saleor/hooks/useStateFromProps";
import React, { useEffect } from "react";
import { useIntl } from "react-intl";
import { giftCardExpirySelectMessages as messages } from "./messages";
import { useGiftCardExpirySelectStyles as useStyles } from "./styles";
const GiftCardUpdateExpirySelect: React.FC = () => {
const intl = useIntl();
const classes = useStyles({});
const {
change,
data: { expiryDate },
formErrors
} = useGiftCardUpdateForm();
const [cardExpiresSelected, setCardExpiresSelected] = useStateFromProps(
!!expiryDate
);
useEffect(() => {
if (!cardExpiresSelected) {
change({
target: {
name: "expiryDate",
value: null
}
});
}
}, [cardExpiresSelected]);
return (
<>
<Typography>{intl.formatMessage(messages.expiryDateLabel)}</Typography>
<VerticalSpacer />
<ControlledCheckbox
name="cardExpires"
label={intl.formatMessage(messages.expiryDateCheckboxLabel)}
checked={cardExpiresSelected}
onChange={event => setCardExpiresSelected(event.target.value)}
/>
<VerticalSpacer spacing={2} />
{cardExpiresSelected && (
<TextField
error={!!formErrors?.expiryDate}
helperText={getGiftCardErrorMessage(formErrors?.expiryDate, intl)}
onChange={change}
name={"expiryDate"}
className={classes.dateField}
label={intl.formatMessage(messages.expiryDateLabel)}
value={expiryDate}
InputLabelProps={{
shrink: true
}}
type="date"
/>
)}
</>
);
};
export default GiftCardUpdateExpirySelect;

View file

@ -0,0 +1,2 @@
export * from "./GiftCardUpdateExpirySelect";
export { default } from "./GiftCardUpdateExpirySelect";

View file

@ -0,0 +1,12 @@
import { defineMessages } from "react-intl";
export const giftCardExpirySelectMessages = defineMessages({
expiryDateCheckboxLabel: {
defaultMessage: "Gift card expires",
description: "GiftCarUpdateDetailsExpirySection expiry date checkbox label"
},
expiryDateLabel: {
defaultMessage: "Expiration date",
description: "GiftCarUpdateDetailsExpirySection expiry date label"
}
});

View file

@ -1,13 +1,19 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useGiftCardExpirySelectStyles = makeStyles(
() => ({
theme => ({
radioGroupContainer: {
display: "flex",
flexDirection: "row"
},
dateField: {
width: 400
},
periodField: {
display: "flex"
},
dateText: {
marginTop: theme.spacing(0.5)
}
}),
{ name: "GiftCardUpdateDetailsExpirySection" }

View file

@ -8,6 +8,7 @@ import useNavigator from "@saleor/hooks/useNavigator";
import { getFullName, getStringOrPlaceholder } from "@saleor/misc";
import Label from "@saleor/orders/components/OrderHistory/Label";
import { getOrderNumberLinkObject } from "@saleor/orders/components/OrderHistory/utils";
import { getByType } from "@saleor/orders/components/OrderReturnPage/utils";
import { productUrl } from "@saleor/products/urls";
import { staffMemberDetailsUrl } from "@saleor/staff/urls";
import { GiftCardEventsEnum } from "@saleor/types/globalTypes";
@ -37,9 +38,8 @@ const GiftCardUpdateInfoCardContent: React.FC = () => {
events
} = giftCard;
const cardIssuedEvent = events.find(
({ type }) => type === GiftCardEventsEnum.ISSUED
);
const cardIssuedEvent = events.find(getByType(GiftCardEventsEnum.ISSUED));
const cardBoughtEvent = events.find(getByType(GiftCardEventsEnum.BOUGHT));
const getBuyerFieldData = (): {
label: MessageDescriptor;
@ -80,13 +80,31 @@ const GiftCardUpdateInfoCardContent: React.FC = () => {
};
};
const orderData =
cardIssuedEvent && cardIssuedEvent.orderId
? getOrderNumberLinkObject({
id: cardIssuedEvent.orderId,
number: cardIssuedEvent.orderNumber
})
: null;
const getOrderData = () => {
if (cardIssuedEvent) {
const { orderId, orderNumber } = cardIssuedEvent;
if (!orderId) {
return null;
}
return getOrderNumberLinkObject({
id: orderId,
number: orderNumber
});
}
if (cardBoughtEvent) {
const { orderId, orderNumber } = cardBoughtEvent;
return getOrderNumberLinkObject({
id: orderId,
number: orderNumber
});
}
return null;
};
const {
label: buyerLabelMessage,
@ -94,6 +112,8 @@ const GiftCardUpdateInfoCardContent: React.FC = () => {
url: buyerUrl
} = getBuyerFieldData();
const orderData = getOrderData();
return (
<>
<Label text={intl.formatMessage(messages.creationLabel)} />

View file

@ -13,7 +13,7 @@ import useGiftCardUpdate from "./providers/GiftCardUpdateFormProvider/hooks/useG
import useGiftCardUpdateForm from "./providers/GiftCardUpdateFormProvider/hooks/useGiftCardUpdateForm";
const GiftCardUpdatePage: React.FC = () => {
const { navigateBack } = useGiftCardUpdateDialogs();
const { navigateBack, openDeleteDialog } = useGiftCardUpdateDialogs();
const {
hasChanged,
@ -44,6 +44,7 @@ const GiftCardUpdatePage: React.FC = () => {
disabled={loadingUpdate || !hasChanged}
onCancel={navigateBack}
onSubmit={submit}
onDelete={openDeleteDialog}
/>
</Container>
);

View file

@ -2,22 +2,20 @@ 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 classNames from "classnames";
import React, { useEffect, useState } from "react";
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 { useGiftCardEnableDisableSectionStyles as useStyles } from "./styles";
import { GiftCardActivate } from "./types/GiftCardActivate";
import { GiftCardDeactivate } from "./types/GiftCardDeactivate";
const GiftCardEnableDisableSection: React.FC = () => {
const classes = useStyles({});
const notify = useNotifier();
const intl = useIntl();
@ -25,10 +23,6 @@ const GiftCardEnableDisableSection: React.FC = () => {
giftCard: { id, isActive }
} = useGiftCardDetails();
const [showButtonGreen, setShowButtonGreen] = useState(!isActive);
useEffect(() => setShowButtonGreen(!isActive), [isActive]);
const onActivateCompleted = (data: GiftCardActivate) => {
const errors = data?.giftCardActivate?.errors;
@ -38,8 +32,6 @@ const GiftCardEnableDisableSection: React.FC = () => {
text: intl.formatMessage(commonErrorMessages.unknownError)
});
setShowButtonGreen(false);
setTimeout(() => setShowButtonGreen(true), 3000);
return;
}
@ -82,16 +74,14 @@ const GiftCardEnableDisableSection: React.FC = () => {
? giftCardDeactivate({ variables: { id } })
: giftCardActivate({ variables: { id } });
const buttonLabel = isActive ? messages.disableLabel : messages.enableLabel;
const buttonLabel = isActive
? buttonMessages.disableLabel
: buttonMessages.enableLabel;
const currentOpts = isActive ? giftCardDeactivateOpts : giftCardActivateOpts;
return (
<ConfirmButton
className={classNames(classes.button, {
[classes.buttonRed]: isActive || currentOpts?.status === "error",
[classes.buttonGreen]: showButtonGreen
})}
onClick={handleClick}
transitionState={currentOpts?.status}
labels={{

View file

@ -1,3 +1,5 @@
import { Button } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import PageHeader from "@saleor/components/PageHeader";
import PageTitleWithStatusChip from "@saleor/components/PageTitleWithStatusChip";
import { StatusType } from "@saleor/components/StatusChip/types";
@ -10,6 +12,7 @@ import { giftCardsListTableMessages as tableMessages } from "../../GiftCardsList
import useGiftCardDetails from "../providers/GiftCardDetailsProvider/hooks/useGiftCardDetails";
import useGiftCardUpdateDialogs from "../providers/GiftCardUpdateDialogsProvider/hooks/useGiftCardUpdateDialogs";
import GiftCardEnableDisableSection from "./GiftCardEnableDisableSection";
import { giftCardUpdatePageHeaderMessages as messages } from "./messages";
const GiftCardUpdatePageHeader: React.FC = () => {
const intl = useIntl();
@ -20,6 +23,8 @@ const GiftCardUpdatePageHeader: React.FC = () => {
return null;
}
const { openResendCodeDialog } = useGiftCardUpdateDialogs();
const { displayCode, isActive } = giftCard;
const title = intl.formatMessage(tableMessages.codeEndingWithLabel, {
@ -48,6 +53,14 @@ const GiftCardUpdatePageHeader: React.FC = () => {
}
>
<GiftCardEnableDisableSection />
<HorizontalSpacer />
<Button
color="primary"
variant="contained"
onClick={openResendCodeDialog}
>
{intl.formatMessage(messages.resendButtonLabel)}
</Button>
</PageHeader>
</>
);

View file

@ -1,14 +1,6 @@
import { defineMessages } from "react-intl";
export const giftCardEnableDisableSectionMessages = defineMessages({
enableLabel: {
defaultMessage: "Enable",
description: "GiftCardEnableDisableSection enable label"
},
disableLabel: {
defaultMessage: "Disable",
description: "GiftCardEnableDisableSection enable label"
},
successfullyEnabledTitle: {
defaultMessage: "Successfully enabled gift card",
description: "GiftCardEnableDisableSection enable success"
@ -18,3 +10,10 @@ export const giftCardEnableDisableSectionMessages = defineMessages({
description: "GiftCardEnableDisableSection disable success"
}
});
export const giftCardUpdatePageHeaderMessages = defineMessages({
resendButtonLabel: {
defaultMessage: "Resend code",
description: "giftCardUpdatePageHeader resendButtonLabel"
}
});

View file

@ -1,27 +0,0 @@
import { darken } from "@material-ui/core";
import { statusChipStyles } from "@saleor/components/StatusChip/StatusChip";
import { makeStyles } from "@saleor/macaw-ui";
export const useGiftCardEnableDisableSectionStyles = makeStyles(
theme => ({
button: {
transition: "backgroundColor 0ms"
},
buttonRed: {
backgroundColor: theme.palette.error.main,
color: "#ffffff",
"&:hover": {
backgroundColor: darken(theme.palette.error.main, 0.1)
}
},
buttonGreen: {
backgroundColor: statusChipStyles.successLabel.color,
"&:hover": {
backgroundColor: darken(statusChipStyles.successLabel.color, 0.1)
}
}
}),
{ name: "GiftCardEnableDisableSection" }
);

View file

@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardErrorCode, GiftCardExpiryTypeEnum, TimePeriodTypeEnum } from "./../../../../types/globalTypes";
import { GiftCardErrorCode } from "./../../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: GiftCardActivate
@ -40,13 +40,6 @@ export interface GiftCardActivate_giftCardActivate_giftCard_product {
name: string;
}
export interface GiftCardActivate_giftCardActivate_giftCard_user {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardActivate_giftCardActivate_giftCard_usedBy {
__typename: "User";
id: string;
@ -60,12 +53,6 @@ export interface GiftCardActivate_giftCardActivate_giftCard_app {
name: string | null;
}
export interface GiftCardActivate_giftCardActivate_giftCard_expiryPeriod {
__typename: "TimePeriod";
amount: number;
type: TimePeriodTypeEnum;
}
export interface GiftCardActivate_giftCardActivate_giftCard_initialBalance {
__typename: "Money";
amount: number;
@ -83,17 +70,15 @@ export interface GiftCardActivate_giftCardActivate_giftCard {
metadata: (GiftCardActivate_giftCardActivate_giftCard_metadata | null)[];
privateMetadata: (GiftCardActivate_giftCardActivate_giftCard_privateMetadata | null)[];
displayCode: string;
boughtInChannel: string | null;
createdBy: GiftCardActivate_giftCardActivate_giftCard_createdBy | null;
product: GiftCardActivate_giftCardActivate_giftCard_product | null;
user: GiftCardActivate_giftCardActivate_giftCard_user | null;
usedBy: GiftCardActivate_giftCardActivate_giftCard_usedBy | null;
usedByEmail: string | null;
createdByEmail: string | null;
app: GiftCardActivate_giftCardActivate_giftCard_app | null;
created: any;
expiryDate: any | null;
expiryType: GiftCardExpiryTypeEnum;
expiryPeriod: GiftCardActivate_giftCardActivate_giftCard_expiryPeriod | null;
lastUsedOn: any | null;
isActive: boolean;
initialBalance: GiftCardActivate_giftCardActivate_giftCard_initialBalance | null;

View file

@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardErrorCode, GiftCardExpiryTypeEnum, TimePeriodTypeEnum } from "./../../../../types/globalTypes";
import { GiftCardErrorCode } from "./../../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: GiftCardDeactivate
@ -40,13 +40,6 @@ export interface GiftCardDeactivate_giftCardDeactivate_giftCard_product {
name: string;
}
export interface GiftCardDeactivate_giftCardDeactivate_giftCard_user {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardDeactivate_giftCardDeactivate_giftCard_usedBy {
__typename: "User";
id: string;
@ -60,12 +53,6 @@ export interface GiftCardDeactivate_giftCardDeactivate_giftCard_app {
name: string | null;
}
export interface GiftCardDeactivate_giftCardDeactivate_giftCard_expiryPeriod {
__typename: "TimePeriod";
amount: number;
type: TimePeriodTypeEnum;
}
export interface GiftCardDeactivate_giftCardDeactivate_giftCard_initialBalance {
__typename: "Money";
amount: number;
@ -83,17 +70,15 @@ export interface GiftCardDeactivate_giftCardDeactivate_giftCard {
metadata: (GiftCardDeactivate_giftCardDeactivate_giftCard_metadata | null)[];
privateMetadata: (GiftCardDeactivate_giftCardDeactivate_giftCard_privateMetadata | null)[];
displayCode: string;
boughtInChannel: string | null;
createdBy: GiftCardDeactivate_giftCardDeactivate_giftCard_createdBy | null;
product: GiftCardDeactivate_giftCardDeactivate_giftCard_product | null;
user: GiftCardDeactivate_giftCardDeactivate_giftCard_user | null;
usedBy: GiftCardDeactivate_giftCardDeactivate_giftCard_usedBy | null;
usedByEmail: string | null;
createdByEmail: string | null;
app: GiftCardDeactivate_giftCardDeactivate_giftCard_app | null;
created: any;
expiryDate: any | null;
expiryType: GiftCardExpiryTypeEnum;
expiryPeriod: GiftCardDeactivate_giftCardDeactivate_giftCard_expiryPeriod | null;
lastUsedOn: any | null;
isActive: boolean;
initialBalance: GiftCardDeactivate_giftCardDeactivate_giftCard_initialBalance | null;

View file

@ -1,10 +1,8 @@
import { commonMessages } from "@saleor/intl";
import { GiftCardError } from "@saleor/fragments/types/GiftCardError";
import { GiftCardErrorCode } from "@saleor/types/globalTypes";
import commonErrorMessages from "@saleor/utils/errors/common";
import { getCommonFormFieldErrorMessage } from "@saleor/utils/errors/common";
import { defineMessages, IntlShape } from "react-intl";
import { GiftCardUpdate_giftCardUpdate_errors } from "./types/GiftCardUpdate";
export const giftCardUpdateDetailsCardMessages = defineMessages({
title: {
defaultMessage: "Details",
@ -12,23 +10,23 @@ export const giftCardUpdateDetailsCardMessages = defineMessages({
}
});
const giftCardErrorMessages = defineMessages({
notFound: {
defaultMessage: "Couldn't find gift card",
description: "giftCardErrorMessages not found"
}
});
export function getGiftCardErrorMessage(
error: Omit<GiftCardUpdate_giftCardUpdate_errors, "__typename"> | undefined,
error: Omit<GiftCardError, "__typename"> | undefined,
intl: IntlShape
): string {
if (error) {
switch (error.code) {
case GiftCardErrorCode.GRAPHQL_ERROR:
return intl.formatMessage(commonErrorMessages.graphqlError);
case GiftCardErrorCode.REQUIRED:
return intl.formatMessage(commonMessages.requiredField);
case GiftCardErrorCode.INVALID:
return intl.formatMessage(commonErrorMessages.invalid);
default:
return intl.formatMessage(commonErrorMessages.unknownError);
case GiftCardErrorCode.NOT_FOUND:
return intl.formatMessage(giftCardErrorMessages.notFound);
}
}
return undefined;
return getCommonFormFieldErrorMessage(error, intl);
}

View file

@ -1,8 +1,10 @@
import GiftCardUpdatePageDeleteDialog from "@saleor/giftCards/components/GiftCardDeleteDialog/GiftCardUpdatePageDeleteDialog";
import { giftCardsListPath, giftCardUrl } from "@saleor/giftCards/urls";
import useNavigator from "@saleor/hooks/useNavigator";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import React, { createContext } from "react";
import GiftCardResendCodeDialog from "../../GiftCardResendCodeDialog";
import GiftCardUpdateBalanceDialog from "../../GiftCardUpdateBalanceDialog";
import {
GiftCardUpdatePageActionParamsEnum,
@ -18,8 +20,10 @@ interface GiftCardUpdateDialogsProviderProps {
export interface GiftCardUpdateDialogsConsumerProps {
navigateBack: () => void;
openSetBalanceDialog: () => void;
closeDialog: () => void;
openSetBalanceDialog: () => void;
openDeleteDialog: () => void;
openResendCodeDialog: () => void;
}
export const GiftCardUpdateDialogsContext = createContext<
@ -35,21 +39,26 @@ const GiftCardUpdateDialogsProvider: React.FC<GiftCardUpdateDialogsProviderProps
const { loading: loadingGiftCard } = useGiftCardDetails();
const {
SET_BALANCE,
DELETE,
RESEND_CODE
} = GiftCardUpdatePageActionParamsEnum;
const [openDialog, closeDialog] = createDialogActionHandlers<
GiftCardUpdatePageActionParamsEnum,
GiftCardUpdatePageUrlQueryParams
>(navigate, params => giftCardUrl(id, params), params);
const openSetBalanceDialog = () =>
openDialog(GiftCardUpdatePageActionParamsEnum.SET_BALANCE);
const isSetBalanceDialogOpen =
params?.action === GiftCardUpdatePageActionParamsEnum.SET_BALANCE;
const isDialogOpen = (action: GiftCardUpdatePageActionParamsEnum) =>
params?.action === action;
const navigateBack = () => navigate(giftCardsListPath);
const providerValues: GiftCardUpdateDialogsConsumerProps = {
openSetBalanceDialog,
openSetBalanceDialog: () => openDialog(SET_BALANCE),
openDeleteDialog: () => openDialog(DELETE),
openResendCodeDialog: () => openDialog(RESEND_CODE),
closeDialog,
navigateBack
};
@ -58,10 +67,21 @@ const GiftCardUpdateDialogsProvider: React.FC<GiftCardUpdateDialogsProviderProps
<GiftCardUpdateDialogsContext.Provider value={providerValues}>
{children}
{!loadingGiftCard && (
<GiftCardUpdateBalanceDialog
onClose={closeDialog}
open={isSetBalanceDialogOpen}
/>
<>
<GiftCardUpdateBalanceDialog
closeDialog={closeDialog}
open={isDialogOpen(SET_BALANCE)}
/>
<GiftCardUpdatePageDeleteDialog
closeDialog={closeDialog}
open={isDialogOpen(DELETE)}
navigateBack={navigateBack}
/>
<GiftCardResendCodeDialog
open={isDialogOpen(RESEND_CODE)}
closeDialog={closeDialog}
/>
</>
)}
</GiftCardUpdateDialogsContext.Provider>
);

View file

@ -1,12 +1,10 @@
import { MetadataFormData } from "@saleor/components/Metadata";
import { GiftCardError } from "@saleor/fragments/types/GiftCardError";
import { GiftCardCommonFormData } from "@saleor/giftCards/GiftCardCreateDialog/types";
import { getGiftCardExpirySettingsInputData } from "@saleor/giftCards/GiftCardCreateDialog/utils";
import { MutationResultWithOpts } from "@saleor/hooks/makeMutation";
import useForm, { FormChange, UseFormResult } from "@saleor/hooks/useForm";
import useNotifier from "@saleor/hooks/useNotifier";
import { getDefaultNotifierSuccessErrorData } from "@saleor/hooks/useNotifier/utils";
import { TimePeriodTypeEnum } from "@saleor/types/globalTypes";
import { getFormErrors } from "@saleor/utils/errors";
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
@ -38,7 +36,7 @@ export interface GiftCardUpdateFormConsumerData
}
export interface GiftCardUpdateFormErrors {
formErrors: Record<"tag" | "expiryDate" | "expiryPeriod", GiftCardError>;
formErrors: Record<"tag" | "expiryDate", GiftCardError>;
handlers: { changeMetadata: FormChange };
}
@ -66,21 +64,11 @@ const GiftCardUpdateFormProvider: React.FC<GiftCardUpdateFormProviderProps> = ({
return { ...emptyFormData, metadata: [], privateMetadata: [] };
}
const {
tag,
expiryDate,
expiryType,
expiryPeriod,
privateMetadata,
metadata
} = giftCard;
const { tag, expiryDate, privateMetadata, metadata } = giftCard;
return {
tag,
expiryDate,
expiryType,
expiryPeriodType: expiryPeriod?.type || TimePeriodTypeEnum.YEAR,
expiryPeriodAmount: expiryPeriod?.amount || 1,
privateMetadata: privateMetadata?.map(mapMetadataItemToInput),
metadata: metadata?.map(mapMetadataItemToInput)
};
@ -96,13 +84,13 @@ const GiftCardUpdateFormProvider: React.FC<GiftCardUpdateFormProviderProps> = ({
onCompleted: onSubmit
});
const submit = async (formData: GiftCardUpdateFormData) => {
const submit = async ({ tag, expiryDate }: GiftCardUpdateFormData) => {
const result = await updateGiftCard({
variables: {
id: giftCard?.id,
input: {
tag: formData.tag,
expirySettings: getGiftCardExpirySettingsInputData(formData)
tag,
expiryDate
}
}
});
@ -138,7 +126,7 @@ const GiftCardUpdateFormProvider: React.FC<GiftCardUpdateFormProviderProps> = ({
handleFormSubmit(submitData, handleSubmit, setChanged);
const formErrors = getFormErrors(
["tag", "expiryDate", "expiryPeriod"],
["tag", "expiryDate"],
updateGiftCardOpts?.data?.giftCardUpdate?.errors
);

View file

@ -1,7 +1,6 @@
import { fragmentUserBase } from "@saleor/fragments/auth";
import { metadataFragment } from "@saleor/fragments/metadata";
import { fragmentMoney } from "@saleor/fragments/products";
import { fragmentTimePeriod } from "@saleor/fragments/timePeriod";
import makeQuery from "@saleor/hooks/makeQuery";
import gql from "graphql-tag";
@ -14,10 +13,10 @@ export const giftCardDataFragment = gql`
${fragmentMoney}
${metadataFragment}
${fragmentUserBase}
${fragmentTimePeriod}
fragment GiftCardData on GiftCard {
...MetadataFragment
displayCode
boughtInChannel
createdBy {
...UserBase
}
@ -25,7 +24,7 @@ export const giftCardDataFragment = gql`
id
name
}
user {
createdBy {
...UserBase
}
usedBy {
@ -39,10 +38,6 @@ export const giftCardDataFragment = gql`
}
created
expiryDate
expiryType
expiryPeriod {
...TimePeriod
}
lastUsedOn
isActive
initialBalance {
@ -63,18 +58,8 @@ export const giftCardDetails = gql`
giftCard(id: $id) {
...GiftCardData
events {
expiry {
expiryType
expiryPeriod {
...TimePeriod
}
expiryDate
oldExpiryType
oldExpiryPeriod {
...TimePeriod
}
oldExpiryDate
}
expiryDate
oldExpiryDate
id
date
type

View file

@ -1,7 +1,9 @@
import { Dialog } from "@saleor/types";
export enum GiftCardUpdatePageActionParamsEnum {
SET_BALANCE = "set-balance"
SET_BALANCE = "set-balance",
DELETE = "delete",
RESEND_CODE = "resend-code"
}
export type GiftCardUpdatePageUrlQueryParams = Dialog<

View file

@ -3,8 +3,6 @@
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardExpiryTypeEnum, TimePeriodTypeEnum } from "./../../../types/globalTypes";
// ====================================================
// GraphQL fragment: GiftCardData
// ====================================================
@ -34,13 +32,6 @@ export interface GiftCardData_product {
name: string;
}
export interface GiftCardData_user {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardData_usedBy {
__typename: "User";
id: string;
@ -54,12 +45,6 @@ export interface GiftCardData_app {
name: string | null;
}
export interface GiftCardData_expiryPeriod {
__typename: "TimePeriod";
amount: number;
type: TimePeriodTypeEnum;
}
export interface GiftCardData_initialBalance {
__typename: "Money";
amount: number;
@ -77,17 +62,15 @@ export interface GiftCardData {
metadata: (GiftCardData_metadata | null)[];
privateMetadata: (GiftCardData_privateMetadata | null)[];
displayCode: string;
boughtInChannel: string | null;
createdBy: GiftCardData_createdBy | null;
product: GiftCardData_product | null;
user: GiftCardData_user | null;
usedBy: GiftCardData_usedBy | null;
usedByEmail: string | null;
createdByEmail: string | null;
app: GiftCardData_app | null;
created: any;
expiryDate: any | null;
expiryType: GiftCardExpiryTypeEnum;
expiryPeriod: GiftCardData_expiryPeriod | null;
lastUsedOn: any | null;
isActive: boolean;
initialBalance: GiftCardData_initialBalance | null;

View file

@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardExpiryTypeEnum, TimePeriodTypeEnum, GiftCardEventsEnum } from "./../../../types/globalTypes";
import { GiftCardEventsEnum } from "./../../../types/globalTypes";
// ====================================================
// GraphQL query operation: GiftCardDetails
@ -34,13 +34,6 @@ export interface GiftCardDetails_giftCard_product {
name: string;
}
export interface GiftCardDetails_giftCard_user {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardDetails_giftCard_usedBy {
__typename: "User";
id: string;
@ -54,12 +47,6 @@ export interface GiftCardDetails_giftCard_app {
name: string | null;
}
export interface GiftCardDetails_giftCard_expiryPeriod {
__typename: "TimePeriod";
amount: number;
type: TimePeriodTypeEnum;
}
export interface GiftCardDetails_giftCard_initialBalance {
__typename: "Money";
amount: number;
@ -72,28 +59,6 @@ export interface GiftCardDetails_giftCard_currentBalance {
currency: string;
}
export interface GiftCardDetails_giftCard_events_expiry_expiryPeriod {
__typename: "TimePeriod";
amount: number;
type: TimePeriodTypeEnum;
}
export interface GiftCardDetails_giftCard_events_expiry_oldExpiryPeriod {
__typename: "TimePeriod";
amount: number;
type: TimePeriodTypeEnum;
}
export interface GiftCardDetails_giftCard_events_expiry {
__typename: "GiftCardEventExpiry";
expiryType: GiftCardExpiryTypeEnum | null;
expiryPeriod: GiftCardDetails_giftCard_events_expiry_expiryPeriod | null;
expiryDate: any | null;
oldExpiryType: GiftCardExpiryTypeEnum | null;
oldExpiryPeriod: GiftCardDetails_giftCard_events_expiry_oldExpiryPeriod | null;
oldExpiryDate: any | null;
}
export interface GiftCardDetails_giftCard_events_user {
__typename: "User";
id: string;
@ -133,7 +98,7 @@ export interface GiftCardDetails_giftCard_events_balance_oldCurrentBalance {
export interface GiftCardDetails_giftCard_events_balance {
__typename: "GiftCardEventBalance";
initialBalance: GiftCardDetails_giftCard_events_balance_initialBalance;
initialBalance: GiftCardDetails_giftCard_events_balance_initialBalance | null;
currentBalance: GiftCardDetails_giftCard_events_balance_currentBalance;
oldInitialBalance: GiftCardDetails_giftCard_events_balance_oldInitialBalance | null;
oldCurrentBalance: GiftCardDetails_giftCard_events_balance_oldCurrentBalance | null;
@ -141,7 +106,8 @@ export interface GiftCardDetails_giftCard_events_balance {
export interface GiftCardDetails_giftCard_events {
__typename: "GiftCardEvent";
expiry: GiftCardDetails_giftCard_events_expiry | null;
expiryDate: any | null;
oldExpiryDate: any | null;
id: string;
date: any | null;
type: GiftCardEventsEnum | null;
@ -161,17 +127,15 @@ export interface GiftCardDetails_giftCard {
metadata: (GiftCardDetails_giftCard_metadata | null)[];
privateMetadata: (GiftCardDetails_giftCard_privateMetadata | null)[];
displayCode: string;
boughtInChannel: string | null;
createdBy: GiftCardDetails_giftCard_createdBy | null;
product: GiftCardDetails_giftCard_product | null;
user: GiftCardDetails_giftCard_user | null;
usedBy: GiftCardDetails_giftCard_usedBy | null;
usedByEmail: string | null;
createdByEmail: string | null;
app: GiftCardDetails_giftCard_app | null;
created: any;
expiryDate: any | null;
expiryType: GiftCardExpiryTypeEnum;
expiryPeriod: GiftCardDetails_giftCard_expiryPeriod | null;
lastUsedOn: any | null;
isActive: boolean;
initialBalance: GiftCardDetails_giftCard_initialBalance | null;

View file

@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardUpdateInput, GiftCardErrorCode, GiftCardExpiryTypeEnum, TimePeriodTypeEnum } from "./../../../types/globalTypes";
import { GiftCardUpdateInput, GiftCardErrorCode } from "./../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: GiftCardUpdate
@ -40,13 +40,6 @@ export interface GiftCardUpdate_giftCardUpdate_giftCard_product {
name: string;
}
export interface GiftCardUpdate_giftCardUpdate_giftCard_user {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardUpdate_giftCardUpdate_giftCard_usedBy {
__typename: "User";
id: string;
@ -60,12 +53,6 @@ export interface GiftCardUpdate_giftCardUpdate_giftCard_app {
name: string | null;
}
export interface GiftCardUpdate_giftCardUpdate_giftCard_expiryPeriod {
__typename: "TimePeriod";
amount: number;
type: TimePeriodTypeEnum;
}
export interface GiftCardUpdate_giftCardUpdate_giftCard_initialBalance {
__typename: "Money";
amount: number;
@ -83,17 +70,15 @@ export interface GiftCardUpdate_giftCardUpdate_giftCard {
metadata: (GiftCardUpdate_giftCardUpdate_giftCard_metadata | null)[];
privateMetadata: (GiftCardUpdate_giftCardUpdate_giftCard_privateMetadata | null)[];
displayCode: string;
boughtInChannel: string | null;
createdBy: GiftCardUpdate_giftCardUpdate_giftCard_createdBy | null;
product: GiftCardUpdate_giftCardUpdate_giftCard_product | null;
user: GiftCardUpdate_giftCardUpdate_giftCard_user | null;
usedBy: GiftCardUpdate_giftCardUpdate_giftCard_usedBy | null;
usedByEmail: string | null;
createdByEmail: string | null;
app: GiftCardUpdate_giftCardUpdate_giftCard_app | null;
created: any;
expiryDate: any | null;
expiryType: GiftCardExpiryTypeEnum;
expiryPeriod: GiftCardUpdate_giftCardUpdate_giftCard_expiryPeriod | null;
lastUsedOn: any | null;
isActive: boolean;
initialBalance: GiftCardUpdate_giftCardUpdate_giftCard_initialBalance | null;

View file

@ -0,0 +1,14 @@
import Container from "@saleor/components/Container";
import React from "react";
import GiftCardsListHeader from "./GiftCardsListHeader";
import GiftCardsListTable from "./GiftCardsListTable";
const GiftCardsListPage: React.FC = () => (
<Container>
<GiftCardsListHeader />
<GiftCardsListTable />
</Container>
);
export default GiftCardsListPage;

View file

@ -1,46 +1,20 @@
import Container from "@saleor/components/Container";
import useNavigator from "@saleor/hooks/useNavigator";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import React from "react";
import GiftCardCreateDialog from "../GiftCardCreateDialog";
import { giftCardsListUrl } from "../urls";
import GiftCardsListHeader from "./GiftCardsListHeader";
import GiftCardsListTable from "./GiftCardsListTable";
import { GiftCardsListProvider } from "./providers/GiftCardsListProvider";
import {
GiftCardListActionParamsEnum,
GiftCardListUrlQueryParams
} from "./types";
import GiftCardListPage from "./GiftCardListPage";
import GiftCardListDialogsProvider from "./providers/GiftCardListDialogsProvider";
import { GiftCardsListProvider } from "./providers/GiftCardListProvider";
import { GiftCardListUrlQueryParams } from "./types";
interface GiftCardsListProps {
params: GiftCardListUrlQueryParams;
}
const GiftCardsList: React.FC<GiftCardsListProps> = ({ params }) => {
const navigate = useNavigator();
const [openModal, closeModal] = createDialogActionHandlers<
GiftCardListActionParamsEnum,
GiftCardListUrlQueryParams
>(navigate, giftCardsListUrl, params);
const openCreateModal = () => openModal(GiftCardListActionParamsEnum.CREATE);
return (
<>
<GiftCardsListProvider params={params}>
<Container>
<GiftCardsListHeader onIssueButtonClick={openCreateModal} />
<GiftCardsListTable />
</Container>
</GiftCardsListProvider>
<GiftCardCreateDialog
open={params?.action === GiftCardListActionParamsEnum.CREATE}
onClose={closeModal}
/>
</>
);
};
const GiftCardsList: React.FC<GiftCardsListProps> = ({ params }) => (
<GiftCardsListProvider params={params}>
<GiftCardListDialogsProvider params={params}>
<GiftCardListPage />
</GiftCardListDialogsProvider>
</GiftCardsListProvider>
);
export default GiftCardsList;

View file

@ -1,54 +0,0 @@
import { Button } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
// import CardMenu, { CardMenuItem } from "@saleor/components/CardMenu";
import PageHeader from "@saleor/components/PageHeader";
import { sectionNames } from "@saleor/intl";
import React from "react";
import { useIntl } from "react-intl";
import { giftCardsListHeaderMenuItemsMessages as messages } from "./messages";
interface GiftCardsListHeaderProps {
onIssueButtonClick: () => void;
}
const GiftCardsListHeader: React.FC<GiftCardsListHeaderProps> = ({
onIssueButtonClick
}) => {
const intl = useIntl();
// const menuItems: CardMenuItem[] = [
// {
// label: intl.formatMessage(messages.settings),
// testId: "settingsMenuItem"
// // onSelect:
// },
// {
// label: intl.formatMessage(messages.bulkIssue),
// testId: "bulkIssueMenuItem"
// // onSelect:
// },
// {
// label: intl.formatMessage(messages.exportCodes),
// testId: "exportCodesMenuItem"
// // onSelect:
// }
// ];
return (
<PageHeader title={intl.formatMessage(sectionNames.giftCards)}>
{/* <CardMenu menuItems={menuItems} data-test="menu" /> */}
<HorizontalSpacer spacing={2} />
<Button
color="primary"
variant="contained"
onClick={onIssueButtonClick}
data-test-id="issueCardButton"
>
{intl.formatMessage(messages.issueButtonLabel)}
</Button>
</PageHeader>
);
};
export default GiftCardsListHeader;

View file

@ -0,0 +1,103 @@
import { Button } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import VerticalSpacer from "@saleor/apps/components/VerticalSpacer";
import CardMenu, { CardMenuItem } from "@saleor/components/CardMenu";
import PageHeader from "@saleor/components/PageHeader";
import useNavigator from "@saleor/hooks/useNavigator";
import { sectionNames } from "@saleor/intl";
import { Alert } from "@saleor/macaw-ui";
import { productAddUrl } from "@saleor/products/urls";
import { productTypeAddUrl } from "@saleor/productTypes/urls";
import { ProductTypeKindEnum } from "@saleor/types/globalTypes";
import React from "react";
import { useIntl } from "react-intl";
import { giftCardSettingsUrl } from "../../urls";
import { giftCardsListHeaderMenuItemsMessages as messages } from "../messages";
import useGiftCardListDialogs from "../providers/GiftCardListDialogsProvider/hooks/useGiftCardListDialogs";
import { useGiftCardProductsCountQuery } from "../queries";
import GiftCardsListHeaderAlertContent from "./GiftCardsListHeaderAlertContent";
const GiftCardsListHeader: React.FC = () => {
const intl = useIntl();
const navigate = useNavigator();
const { openCreateDialog } = useGiftCardListDialogs();
const {
data: giftCardProductsCount,
loading: giftCardProductsCountLoading
} = useGiftCardProductsCountQuery();
const openSettings = () => navigate(giftCardSettingsUrl);
const menuItems: CardMenuItem[] = [
{
label: intl.formatMessage(messages.settings),
testId: "settingsMenuItem",
onSelect: openSettings
}
// {
// label: intl.formatMessage(messages.bulkIssue),
// testId: "bulkIssueMenuItem"
// // onSelect:
// },
// {
// label: intl.formatMessage(messages.exportCodes),
// testId: "exportCodesMenuItem"
// // onSelect:
// }
];
const giftCardProductTypesExist =
giftCardProductsCount?.giftCardProductTypes.totalCount > 0;
const giftCardProductsExist =
giftCardProductsCount?.giftCardProducts.totalCount > 0;
const showNoGiftCardProductsAlert =
!giftCardProductsCountLoading &&
(!giftCardProductTypesExist || !giftCardProductsExist);
const handleCreateGiftCardProductType = () =>
navigate(
productTypeAddUrl({
kind: ProductTypeKindEnum.GIFT_CARD
})
);
const handleCreateGiftCardProduct = () => navigate(productAddUrl());
return (
<>
<PageHeader title={intl.formatMessage(sectionNames.giftCards)}>
<CardMenu menuItems={menuItems} data-test="menu" />
<HorizontalSpacer spacing={2} />
<Button
color="primary"
variant="contained"
onClick={openCreateDialog}
data-test-id="issueCardButton"
>
{intl.formatMessage(messages.issueButtonLabel)}
</Button>
</PageHeader>
{showNoGiftCardProductsAlert && (
<Alert
title={intl.formatMessage(messages.noGiftCardsAlertTitle)}
variant="warning"
close={false}
>
<GiftCardsListHeaderAlertContent
giftCardProductTypesExist={giftCardProductTypesExist}
giftCardProductsExist={giftCardProductsExist}
handleCreateGiftCardProductType={handleCreateGiftCardProductType}
handleCreateGiftCardProduct={handleCreateGiftCardProduct}
/>
</Alert>
)}
<VerticalSpacer spacing={2} />
</>
);
};
export default GiftCardsListHeader;

View file

@ -0,0 +1,62 @@
import Link from "@saleor/components/Link";
import React from "react";
import { FormattedMessage } from "react-intl";
import { giftCardsListHeaderMenuItemsMessages as messages } from "../messages";
import { useHeaderStyles as useStyles } from "../styles";
interface GiftCardsListHeaderAlertContentProps {
giftCardProductTypesExist: boolean;
giftCardProductsExist: boolean;
handleCreateGiftCardProductType: () => void;
handleCreateGiftCardProduct: () => void;
}
const GiftCardsListHeaderAlertContent: React.FC<GiftCardsListHeaderAlertContentProps> = ({
giftCardProductTypesExist,
giftCardProductsExist,
handleCreateGiftCardProductType,
handleCreateGiftCardProduct
}) => {
const classes = useStyles({});
if (!giftCardProductTypesExist) {
return (
<FormattedMessage
{...messages.noGiftCardsProductTypes}
values={{
createGiftCardProductType: (
<Link
onClick={handleCreateGiftCardProductType}
className={classes.alertLink}
>
<FormattedMessage {...messages.createGiftCardProductType} />
</Link>
)
}}
/>
);
}
if (!giftCardProductsExist) {
return (
<FormattedMessage
{...messages.noGiftCardsProducts}
values={{
createGiftCardProduct: (
<Link
onClick={handleCreateGiftCardProduct}
className={classes.alertLink}
>
<FormattedMessage {...messages.createGiftCardProduct} />
</Link>
)
}}
/>
);
}
return null;
};
export default GiftCardsListHeaderAlertContent;

View file

@ -0,0 +1,2 @@
export * from "./GiftCardsListHeader";
export { default } from "./GiftCardsListHeader";

View file

@ -22,8 +22,9 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { giftCardsListTableMessages as messages } from "../messages";
import useGiftCardList from "../providers/hooks/useGiftCardList";
import useGiftCardListBulkActions from "../providers/hooks/useGiftCardListBulkActions";
import useGiftCardListDialogs from "../providers/GiftCardListDialogsProvider/hooks/useGiftCardListDialogs";
import useGiftCardList from "../providers/GiftCardListProvider/hooks/useGiftCardList";
import useGiftCardListBulkActions from "../providers/GiftCardListProvider/hooks/useGiftCardListBulkActions";
import { useTableStyles as useStyles } from "../styles";
import GiftCardsListTableFooter from "./GiftCardsListTableFooter";
import GiftCardsListTableHeader from "./GiftCardsListTableHeader";
@ -34,8 +35,10 @@ const GiftCardsListTable: React.FC = () => {
const intl = useIntl();
const classes = useStyles({});
const navigate = useNavigator();
const { giftCards, numberOfColumns, loading } = useGiftCardList();
const { toggle, isSelected } = useGiftCardListBulkActions();
const { openDeleteDialog } = useGiftCardListDialogs();
const redirectToGiftCardUpdate = (id: string) => () =>
navigate(giftCardUrl(id));
@ -122,7 +125,12 @@ const GiftCardsListTable: React.FC = () => {
</div>
</TableCell>
<TableCell className={classes.colDelete}>
<DeleteIconButton />
<DeleteIconButton
onClick={event => {
event.stopPropagation();
openDeleteDialog(id);
}}
/>
</TableCell>
</TableRow>
),

View file

@ -3,7 +3,7 @@ import TablePagination from "@saleor/components/TablePagination";
import usePaginator from "@saleor/hooks/usePaginator";
import React from "react";
import useGiftCardList from "../providers/hooks/useGiftCardList";
import useGiftCardList from "../providers/GiftCardListProvider/hooks/useGiftCardList";
const GiftCardsListTableFooter: React.FC = () => {
const paginate = usePaginator();

View file

@ -0,0 +1,120 @@
import ConfirmButton from "@saleor/components/ConfirmButton";
import { IMessage } from "@saleor/components/messages";
import useNotifier from "@saleor/hooks/useNotifier";
import { getByIds } from "@saleor/orders/components/OrderReturnPage/utils";
import React from "react";
import { useIntl } from "react-intl";
import useGiftCardList from "../../providers/GiftCardListProvider/hooks/useGiftCardList";
import useGiftCardListBulkActions from "../../providers/GiftCardListProvider/hooks/useGiftCardListBulkActions";
import { GIFT_CARD_LIST_QUERY } from "../../types";
import { bulkEnableDisableSectionMessages as messages } from "./messages";
import {
useGiftCardBulkActivateMutation,
useGiftCardBulkDeactivateMutation
} from "./mutations";
import { GiftCardBulkActivate } from "./types/GiftCardBulkActivate";
import { GiftCardBulkDeactivate } from "./types/GiftCardBulkDeactivate";
const BulkEnableDisableSection: React.FC = () => {
const intl = useIntl();
const notify = useNotifier();
const { listElements: ids, reset } = useGiftCardListBulkActions();
const { giftCards } = useGiftCardList();
const onActivateCompleted = (data: GiftCardBulkActivate) => {
const { errors, count } = data?.giftCardBulkActivate;
const notifierData: IMessage = !!errors?.length
? {
status: "error",
text: intl.formatMessage(messages.errorActivateAlertText, { count })
}
: {
status: "success",
text: intl.formatMessage(messages.successActivateAlertText, { count })
};
notify(notifierData);
if (!errors.length) {
reset();
}
};
const onDeactivateCompleted = (data: GiftCardBulkDeactivate) => {
const { errors, count } = data?.giftCardBulkDeactivate;
const notifierData: IMessage = !!errors?.length
? {
status: "error",
text: intl.formatMessage(messages.errorDeactivateAlertText, { count })
}
: {
status: "success",
text: intl.formatMessage(messages.successDeactivateAlertText, {
count
})
};
notify(notifierData);
if (!errors.length) {
reset();
}
};
const hasAnyEnabledCardsSelected = giftCards
.filter(getByIds(ids))
.some(({ isActive }) => isActive);
const hasAnyDisabledCardsSelected = giftCards
.filter(getByIds(ids))
.some(({ isActive }) => !isActive);
const [
activateGiftCards,
activateGiftCardsOpts
] = useGiftCardBulkActivateMutation({
onCompleted: onActivateCompleted,
refetchQueries: [GIFT_CARD_LIST_QUERY]
});
const [
deactivateGiftCards,
deactivateGiftCardsOpts
] = useGiftCardBulkDeactivateMutation({
onCompleted: onDeactivateCompleted,
refetchQueries: [GIFT_CARD_LIST_QUERY]
});
const handleActivateGiftCards = () =>
activateGiftCards({ variables: { ids } });
const handleDeactivateGiftCards = () =>
deactivateGiftCards({ variables: { ids } });
return (
<>
<ConfirmButton
disabled={hasAnyEnabledCardsSelected}
onClick={handleActivateGiftCards}
variant="text"
transitionState={activateGiftCardsOpts?.status}
>
{intl.formatMessage(messages.enableLabel)}
</ConfirmButton>
<ConfirmButton
disabled={hasAnyDisabledCardsSelected}
onClick={handleDeactivateGiftCards}
variant="text"
transitionState={deactivateGiftCardsOpts?.status}
>
{intl.formatMessage(messages.disableLabel)}
</ConfirmButton>
</>
);
};
export default BulkEnableDisableSection;

View file

@ -1,4 +1,5 @@
import { TableCell } from "@material-ui/core";
import DeleteIconButton from "@saleor/components/DeleteIconButton";
import TableCellHeader, {
TableCellHeaderProps
} from "@saleor/components/TableCellHeader";
@ -9,10 +10,12 @@ import Label, {
import React from "react";
import { MessageDescriptor, useIntl } from "react-intl";
import { giftCardsListTableMessages as messages } from "../messages";
import useGiftCardList from "../providers/hooks/useGiftCardList";
import useGiftCardListBulkActions from "../providers/hooks/useGiftCardListBulkActions";
import { useTableStyles as useStyles } from "../styles";
import { giftCardsListTableMessages as messages } from "../../messages";
import useGiftCardListDialogs from "../../providers/GiftCardListDialogsProvider/hooks/useGiftCardListDialogs";
import useGiftCardList from "../../providers/GiftCardListProvider/hooks/useGiftCardList";
import useGiftCardListBulkActions from "../../providers/GiftCardListProvider/hooks/useGiftCardListBulkActions";
import { useTableStyles as useStyles } from "../../styles";
import BulkEnableDisableSection from "./BulkEnableDisableSection";
interface HeaderItem {
title?: MessageDescriptor;
@ -22,8 +25,10 @@ interface HeaderItem {
const GiftCardsListTableHeader: React.FC = () => {
const intl = useIntl();
const classes = useStyles({});
const { giftCards, numberOfColumns, loading } = useGiftCardList();
const { toggleAll, listElements } = useGiftCardListBulkActions();
const { openDeleteDialog } = useGiftCardListDialogs();
const headerItems: HeaderItem[] = [
{
@ -68,6 +73,12 @@ const GiftCardsListTableHeader: React.FC = () => {
selected={listElements.length}
items={giftCards}
toggleAll={toggleAll}
toolbar={
<>
<BulkEnableDisableSection />
<DeleteIconButton onClick={openDeleteDialog} />
</>
}
>
{headerItems.map(({ title, options }) => (
<TableCellHeader {...options}>

View file

@ -0,0 +1,2 @@
export * from "./GiftCardsListTableHeader";
export { default } from "./GiftCardsListTableHeader";

View file

@ -0,0 +1,32 @@
import { defineMessages } from "react-intl";
export const bulkEnableDisableSectionMessages = defineMessages({
enableLabel: {
defaultMessage: "Activate",
description: "GiftCardEnableDisableSection enable label"
},
disableLabel: {
defaultMessage: "Deactivate",
description: "GiftCardEnableDisableSection enable label"
},
successActivateAlertText: {
defaultMessage:
"Successfully activated gift {count,plural,one{card} other{cards}}",
description: "GiftCardEnableDisableSection success activate alert text"
},
successDeactivateAlertText: {
defaultMessage:
"Successfully deactivated gift {count,plural,one{card} other{cards}}",
description: "GiftCardEnableDisableSection success activate alert text"
},
errorActivateAlertText: {
defaultMessage:
"Error activating gift {count,plural,one{card} other{cards}}",
description: "GiftCardEnableDisableSection error activate alert text"
},
errorDeactivateAlertText: {
defaultMessage:
"Errors deactivating gift {count,plural,one{card} other{cards}}",
description: "GiftCardEnableDisableSection error activate alert text"
}
});

View file

@ -0,0 +1,46 @@
import { giftCardErrorFragment } from "@saleor/fragments/errors";
import makeMutation from "@saleor/hooks/makeMutation";
import gql from "graphql-tag";
import {
GiftCardBulkActivate,
GiftCardBulkActivateVariables
} from "./types/GiftCardBulkActivate";
import {
GiftCardBulkDeactivate,
GiftCardBulkDeactivateVariables
} from "./types/GiftCardBulkDeactivate";
const giftCardBulkActivate = gql`
${giftCardErrorFragment}
mutation GiftCardBulkActivate($ids: [ID]!) {
giftCardBulkActivate(ids: $ids) {
errors {
...GiftCardError
}
count
}
}
`;
export const useGiftCardBulkActivateMutation = makeMutation<
GiftCardBulkActivate,
GiftCardBulkActivateVariables
>(giftCardBulkActivate);
const giftCardBulkDeactivate = gql`
${giftCardErrorFragment}
mutation GiftCardBulkDeactivate($ids: [ID]!) {
giftCardBulkDeactivate(ids: $ids) {
errors {
...GiftCardError
}
count
}
}
`;
export const useGiftCardBulkDeactivateMutation = makeMutation<
GiftCardBulkDeactivate,
GiftCardBulkDeactivateVariables
>(giftCardBulkDeactivate);

View file

@ -0,0 +1,30 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardErrorCode } from "./../../../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: GiftCardBulkActivate
// ====================================================
export interface GiftCardBulkActivate_giftCardBulkActivate_errors {
__typename: "GiftCardError";
code: GiftCardErrorCode;
field: string | null;
}
export interface GiftCardBulkActivate_giftCardBulkActivate {
__typename: "GiftCardBulkActivate";
errors: GiftCardBulkActivate_giftCardBulkActivate_errors[];
count: number;
}
export interface GiftCardBulkActivate {
giftCardBulkActivate: GiftCardBulkActivate_giftCardBulkActivate | null;
}
export interface GiftCardBulkActivateVariables {
ids: (string | null)[];
}

View file

@ -0,0 +1,30 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardErrorCode } from "./../../../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: GiftCardBulkDeactivate
// ====================================================
export interface GiftCardBulkDeactivate_giftCardBulkDeactivate_errors {
__typename: "GiftCardError";
code: GiftCardErrorCode;
field: string | null;
}
export interface GiftCardBulkDeactivate_giftCardBulkDeactivate {
__typename: "GiftCardBulkDeactivate";
errors: GiftCardBulkDeactivate_giftCardBulkDeactivate_errors[];
count: number;
}
export interface GiftCardBulkDeactivate {
giftCardBulkDeactivate: GiftCardBulkDeactivate_giftCardBulkDeactivate | null;
}
export interface GiftCardBulkDeactivateVariables {
ids: (string | null)[];
}

View file

@ -16,6 +16,37 @@ export const giftCardsListHeaderMenuItemsMessages = defineMessages({
issueButtonLabel: {
defaultMessage: "Issue card",
description: "GiftCardsListHeader issue button label"
},
noGiftCardsAlertTitle: {
defaultMessage: "You havent defined a gift card product!",
description: "GiftCardsListHeader alert"
},
noGiftCardsProductsAndProductTypes: {
defaultMessage:
"{createGiftCardProductType} and {giftCardProduct} to start selling gift cards in your store.",
description: "GiftCardsListHeader alert"
},
noGiftCardsProductTypes: {
defaultMessage:
"{createGiftCardProductType} to start selling gift cards in your store.",
description: "GiftCardsListHeader alert"
},
noGiftCardsProducts: {
defaultMessage:
"{createGiftCardProduct} to start selling gift cards in your store.",
description: "GiftCardsListHeader alert"
},
createGiftCardProductType: {
defaultMessage: "Create a gift card product type",
description: "GiftCardsListHeader alert"
},
createGiftCardProduct: {
defaultMessage: "Create a gift card product",
description: "GiftCardsListHeader alert"
},
giftCardProduct: {
defaultMessage: "gift card product",
description: "GiftCardsListHeader alert"
}
});

View file

@ -0,0 +1,44 @@
import { giftCardErrorFragment } from "@saleor/fragments/errors";
import makeMutation from "@saleor/hooks/makeMutation";
import gql from "graphql-tag";
import {
BulkDeleteGiftCard,
BulkDeleteGiftCardVariables
} from "./types/BulkDeleteGiftCard";
import {
DeleteGiftCard,
DeleteGiftCardVariables
} from "./types/DeleteGiftCard";
const deleteGiftCard = gql`
${giftCardErrorFragment}
mutation DeleteGiftCard($id: ID!) {
giftCardDelete(id: $id) {
errors {
...GiftCardError
}
}
}
`;
export const useGiftCardDeleteMutation = makeMutation<
DeleteGiftCard,
DeleteGiftCardVariables
>(deleteGiftCard);
const bulkDeleteGiftCard = gql`
${giftCardErrorFragment}
mutation BulkDeleteGiftCard($ids: [ID]!) {
giftCardBulkDelete(ids: $ids) {
errors {
...GiftCardError
}
}
}
`;
export const useGiftCardBulkDeleteMutation = makeMutation<
BulkDeleteGiftCard,
BulkDeleteGiftCardVariables
>(bulkDeleteGiftCard);

View file

@ -0,0 +1,80 @@
import GiftCardListPageDeleteDialog from "@saleor/giftCards/components/GiftCardDeleteDialog/GiftCardListPageDeleteDialog";
import GiftCardCreateDialog from "@saleor/giftCards/GiftCardCreateDialog";
import { giftCardsListUrl } from "@saleor/giftCards/urls";
import useNavigator from "@saleor/hooks/useNavigator";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import React, { createContext } from "react";
import {
GiftCardListActionParamsEnum,
GiftCardListUrlQueryParams
} from "../../types";
interface GiftCardListDialogsProviderProps {
children: React.ReactNode;
params: GiftCardListUrlQueryParams;
}
export interface GiftCardListDialogsConsumerProps {
openCreateDialog: () => void;
openDeleteDialog: (id?: string | React.MouseEvent) => void;
closeDialog: () => void;
id: string;
}
export const GiftCardListDialogsContext = createContext<
GiftCardListDialogsConsumerProps
>(null);
const GiftCardListDialogsProvider: React.FC<GiftCardListDialogsProviderProps> = ({
children,
params
}) => {
const navigate = useNavigator();
const id = params?.id;
const [openDialog, closeDialog] = createDialogActionHandlers<
GiftCardListActionParamsEnum,
GiftCardListUrlQueryParams
>(navigate, giftCardsListUrl, params);
const openCreateDialog = () =>
openDialog(GiftCardListActionParamsEnum.CREATE);
const isCreateDialogOpen =
params?.action === GiftCardListActionParamsEnum.CREATE;
const isDeleteDialogOpen =
params?.action === GiftCardListActionParamsEnum.DELETE;
const handleDeleteDialogOpen = (id?: string) => {
openDialog(
GiftCardListActionParamsEnum.DELETE,
typeof id === "string" ? { id } : undefined
);
};
const providerValues: GiftCardListDialogsConsumerProps = {
openCreateDialog,
openDeleteDialog: handleDeleteDialogOpen,
closeDialog,
id
};
return (
<GiftCardListDialogsContext.Provider value={providerValues}>
{children}
<GiftCardCreateDialog
open={isCreateDialogOpen}
closeDialog={closeDialog}
/>
<GiftCardListPageDeleteDialog
open={isDeleteDialogOpen}
closeDialog={closeDialog}
/>
</GiftCardListDialogsContext.Provider>
);
};
export default GiftCardListDialogsProvider;

View file

@ -0,0 +1,14 @@
import { useContext } from "react";
import {
GiftCardListDialogsConsumerProps,
GiftCardListDialogsContext
} from "../GiftCardListDialogsProvider";
const useGiftCardListDialogs = (): GiftCardListDialogsConsumerProps => {
const giftCardListDialogsProps = useContext(GiftCardListDialogsContext);
return giftCardListDialogsProps;
};
export default useGiftCardListDialogs;

View file

@ -0,0 +1,2 @@
export * from "./GiftCardListDialogsProvider";
export { default } from "./GiftCardListDialogsProvider";

View file

@ -0,0 +1,98 @@
import useBulkActions, {
UseBulkActionsProps
} from "@saleor/hooks/useBulkActions";
import useListSettings, {
UseListSettings
} from "@saleor/hooks/useListSettings";
import {
createPaginationState,
PageInfo,
PaginationState
} from "@saleor/hooks/usePaginator";
import { ListViews } from "@saleor/types";
import { mapEdgesToItems } from "@saleor/utils/maps";
import React, { createContext } from "react";
import { useGiftCardListQuery } from "../../queries";
import { GiftCardListColummns, GiftCardListUrlQueryParams } from "../../types";
import {
GiftCardList_giftCards_edges_node,
GiftCardListVariables
} from "../../types/GiftCardList";
const numberOfColumns = 7;
interface GiftCardsListProviderProps {
children: React.ReactNode;
params: GiftCardListUrlQueryParams;
}
export interface GiftCardListDataProps {
giftCards: GiftCardList_giftCards_edges_node[];
pageInfo: PageInfo;
loading: boolean;
params: GiftCardListUrlQueryParams;
paginationState: PaginationState;
numberOfColumns: number;
}
export interface GiftCardsListConsumerProps
extends UseBulkActionsProps,
GiftCardListDataProps,
UseListSettings<GiftCardListColummns> {
selectedItemsCount: number;
}
export const GiftCardsListContext = createContext<GiftCardsListConsumerProps>(
null
);
export const GiftCardsListProvider: React.FC<GiftCardsListProviderProps> = ({
children,
params
}) => {
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
[]
);
const { updateListSettings, settings } = useListSettings<
GiftCardListColummns
>(ListViews.GIFT_CARD_LIST);
const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo<GiftCardListVariables>(
() => ({
...paginationState
}),
[params]
);
const { data, loading } = useGiftCardListQuery({
displayLoader: true,
variables: queryVariables
});
const providerValues: GiftCardsListConsumerProps = {
giftCards: mapEdgesToItems(data?.giftCards) || [],
loading,
isSelected,
listElements,
reset,
toggleAll,
toggle,
selectedItemsCount: listElements.length,
pageInfo: data?.giftCards?.pageInfo,
paginationState,
params,
settings,
updateListSettings,
numberOfColumns
};
return (
<GiftCardsListContext.Provider value={providerValues}>
{children}
</GiftCardsListContext.Provider>
);
};

View file

@ -0,0 +1,37 @@
import { UseListSettings } from "@saleor/hooks/useListSettings";
import { useContext } from "react";
import { GiftCardListColummns } from "../../../types";
import {
GiftCardListDataProps,
GiftCardsListContext
} from "../GiftCardListProvider";
export type UseGiftCardListProps = GiftCardListDataProps &
UseListSettings<GiftCardListColummns>;
const useGiftCardList = (): UseGiftCardListProps => {
const {
giftCards,
loading,
pageInfo,
paginationState,
params,
settings,
updateListSettings,
numberOfColumns
} = useContext(GiftCardsListContext);
return {
giftCards,
loading,
pageInfo,
paginationState,
params,
settings,
updateListSettings,
numberOfColumns
};
};
export default useGiftCardList;

View file

@ -0,0 +1,32 @@
import { UseBulkActionsProps } from "@saleor/hooks/useBulkActions";
import { useContext } from "react";
import {
GiftCardsListConsumerProps,
GiftCardsListContext
} from "../GiftCardListProvider";
export type UseGiftCardListBulkActionsProps = UseBulkActionsProps &
Pick<GiftCardsListConsumerProps, "selectedItemsCount">;
const useGiftCardListBulkActions = (): UseGiftCardListBulkActionsProps => {
const {
isSelected,
listElements,
reset,
toggleAll,
toggle,
selectedItemsCount
} = useContext(GiftCardsListContext);
return {
isSelected,
listElements,
reset,
toggleAll,
toggle,
selectedItemsCount
};
};
export default useGiftCardListBulkActions;

View file

@ -0,0 +1 @@
export * from "./GiftCardListProvider";

View file

@ -59,7 +59,7 @@ export const GiftCardsListProvider: React.FC<GiftCardsListProviderProps> = ({
GiftCardListColummns
>(ListViews.GIFT_CARD_LIST);
const paginationState = createPaginationState(settings.rowNumber, params);
const paginationState = createPaginationState(settings?.rowNumber, params);
const queryVariables = React.useMemo<GiftCardListVariables>(
() => ({

View file

@ -5,6 +5,7 @@ import gql from "graphql-tag";
import { giftCardDataFragment } from "../GiftCardUpdate/queries";
import { GiftCardList, GiftCardListVariables } from "./types/GiftCardList";
import { GiftCardProductsCount } from "./types/GiftCardProductsCount";
export const giftCardList = gql`
${fragmentUserBase}
@ -26,8 +27,22 @@ export const giftCardList = gql`
}
}
`;
export const useGiftCardListQuery = makeQuery<
GiftCardList,
GiftCardListVariables
>(giftCardList);
export const giftCardProductsCount = gql`
query GiftCardProductsCount {
giftCardProductTypes: productTypes(filter: { kind: GIFT_CARD }) {
totalCount
}
giftCardProducts: products(filter: { giftCard: true }) {
totalCount
}
}
`;
export const useGiftCardProductsCountQuery = makeQuery<
GiftCardProductsCount,
never
>(giftCardProductsCount);

View file

@ -34,3 +34,12 @@ export const useTableStyles = makeStyles(
}),
{ name: "GiftCardsListTable" }
);
export const useHeaderStyles = makeStyles(
theme => ({
alertLink: {
fontSize: theme.typography.body2.fontSize
}
}),
{ name: "GiftCardsListHeader" }
);

View file

@ -1,4 +1,4 @@
import { Dialog, Pagination } from "@saleor/types";
import { Dialog, Pagination, SingleAction } from "@saleor/types";
export type GiftCardListColummns =
| "giftCardCode"
@ -8,8 +8,12 @@ export type GiftCardListColummns =
| "product";
export enum GiftCardListActionParamsEnum {
CREATE = "gift-card-create"
CREATE = "gift-card-create",
DELETE = "gift-card-delete"
}
export type GiftCardListUrlQueryParams = Pagination &
Dialog<GiftCardListActionParamsEnum>;
Dialog<GiftCardListActionParamsEnum> &
SingleAction;
export const GIFT_CARD_LIST_QUERY = "GiftCardList";

View file

@ -0,0 +1,29 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardErrorCode } from "./../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: BulkDeleteGiftCard
// ====================================================
export interface BulkDeleteGiftCard_giftCardBulkDelete_errors {
__typename: "GiftCardError";
code: GiftCardErrorCode;
field: string | null;
}
export interface BulkDeleteGiftCard_giftCardBulkDelete {
__typename: "GiftCardBulkDelete";
errors: BulkDeleteGiftCard_giftCardBulkDelete_errors[];
}
export interface BulkDeleteGiftCard {
giftCardBulkDelete: BulkDeleteGiftCard_giftCardBulkDelete | null;
}
export interface BulkDeleteGiftCardVariables {
ids: (string | null)[];
}

Some files were not shown because too many files have changed in this diff Show more