Add Gift Cards #1 (#1291)

* 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

* Fixes after review

* 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

* 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

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

* Fix gift card list table header styles

* Add enable / disable section to gift card update

* Refactor

* Refactor

* Refactor

* Add metadata to gift card update

* Update messages ids

* Refactor

* Refactor

* Refactor

* Refactor

* Update types after rebase

* Fix types

* Fixes after qa

* Fix tests
This commit is contained in:
mmarkusik 2021-08-16 15:44:00 +02:00 committed by GitHub
parent 6b24fa0dc3
commit dae95cb410
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
142 changed files with 11118 additions and 670 deletions

View file

@ -1459,6 +1459,9 @@
"context": "dialog title",
"string": "Delete categories"
},
"src_dot_channel": {
"string": "Channel"
},
"src_dot_channels": {
"context": "channels section name",
"string": "Channels"
@ -1681,8 +1684,8 @@
"context": "tab name",
"string": "All Collections"
},
"src_dot_collections_dot_components_dot_CollectionListPage_dot_4057224233": {
"string": "Search Collection"
"src_dot_collections_dot_components_dot_CollectionListPage_dot_2685595924": {
"string": "Search Collections"
},
"src_dot_collections_dot_components_dot_CollectionListPage_dot_686910896": {
"context": "button",
@ -1709,6 +1712,18 @@
"context": "collection availability",
"string": "Availability"
},
"src_dot_collections_dot_components_dot_CollectionList_dot_published": {
"context": "collection publication date",
"string": "Published on {date}"
},
"src_dot_collections_dot_components_dot_CollectionList_dot_unpublished": {
"context": "collection publication date",
"string": "Unpublished"
},
"src_dot_collections_dot_components_dot_CollectionList_dot_willBePublished": {
"context": "collection publication date",
"string": "Becomes published on {date}"
},
"src_dot_collections_dot_components_dot_CollectionProducts_dot_1657559629": {
"string": "No products found"
},
@ -3385,6 +3400,218 @@
"src_dot_generalInformations": {
"string": "General Information"
},
"src_dot_giftCards": {
"context": "gift cards section name",
"string": "Gift Cards"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_amountLabel": {
"context": "GiftCardCreateDialog amount label",
"string": "Enter amount"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_copiedToClipboardTitle": {
"context": "GiftCardCreateDialog copied to clipboard title",
"string": "Copied to clipboard"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_copyCodeLabel": {
"context": "GiftCardCreateDialog copy code label",
"string": "Copy code"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_createdGiftCardLabel": {
"context": "GiftCardCreateDialog created gift card label",
"string": "This is the code of a created gift card:"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_createdSuccessAlertTitle": {
"context": "GiftCardCreateDialog createdSuccessAlertTitle",
"string": "Successfully created gift card"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_customerLabel": {
"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"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_noteLabel": {
"context": "GiftCardCreateDialog note label",
"string": "Note"
},
"src_dot_giftCards_dot_GiftCardCreateDialog_dot_noteSubtitle": {
"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_title": {
"context": "GiftCardCreateDialog title",
"string": "Issue gift card"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateBalanceDialog_dot_changeButtonLabel": {
"context": "GiftCardUpdateDetailsCard set balance dialog change button label",
"string": "Change"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateBalanceDialog_dot_subtitle": {
"context": "GiftCardUpdateDetailsCard set balance dialog subtitle",
"string": "What would you like to set cards balance to. When you change the balance both values will be changed"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateBalanceDialog_dot_title": {
"context": "GiftCardUpdateDetailsCard set balance button label",
"string": "Set balance"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateBalanceDialog_dot_updatedSuccessAlertTitle": {
"context": "GiftCardUpdateDetailsCard update success alert title",
"string": "Successfully updated card balance"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateDetailsCard_dot_cardBalanceLabel": {
"context": "GiftCardUpdateDetailsCard card balance label",
"string": "Card Balance"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateDetailsCard_dot_setBalanceButtonLabel": {
"context": "GiftCardUpdateDetailsCard set balance button label",
"string": "set balance"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateDetailsCard_dot_title": {
"context": "GiftCardUpdateDetailsCard title",
"string": "Details"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateInfoCard_dot_boughtByLabel": {
"context": "GiftCardUpdateInfoCard bought by label",
"string": "Bought by"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateInfoCard_dot_creationLabel": {
"context": "GiftCardUpdateInfoCard creation label",
"string": "Creation"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateInfoCard_dot_issuedByAppLabel": {
"context": "GiftCardUpdateInfoCard issued by app label",
"string": "Issued by app"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateInfoCard_dot_issuedByLabel": {
"context": "GiftCardUpdateInfoCard issued by label",
"string": "Issued by"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateInfoCard_dot_orderNumberLabel": {
"context": "GiftCardUpdateInfoCard order number label",
"string": "Order number"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateInfoCard_dot_productLabel": {
"context": "GiftCardUpdateInfoCard product label",
"string": "Product bought to get gift card"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateInfoCard_dot_title": {
"context": "GiftCardUpdateInfoCard title",
"string": "Card information"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdateInfoCard_dot_usedByLabel": {
"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_successfullyDisabledTitle": {
"context": "GiftCardEnableDisableSection disable success",
"string": "Successfully disabled gift card"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_GiftCardUpdatePageHeader_dot_successfullyEnabledTitle": {
"context": "GiftCardEnableDisableSection enable success",
"string": "Successfully enabled gift card"
},
"src_dot_giftCards_dot_GiftCardUpdate_dot_title": {
"context": "GiftCardUpdateDetailsCard title",
"string": "Details"
},
"src_dot_giftCards_dot_GiftCardsList_dot_bulkIssue": {
"context": "GiftCardsListHeader menu item settings",
"string": "Bulk Issue"
},
"src_dot_giftCards_dot_GiftCardsList_dot_codeEndingWithLabel": {
"context": "GiftCardsListTable code ending with label",
"string": "Code ending with {displayCode}"
},
"src_dot_giftCards_dot_GiftCardsList_dot_exportCodes": {
"context": "GiftCardsListHeader menu item settings",
"string": "Export card codes"
},
"src_dot_giftCards_dot_GiftCardsList_dot_giftCardDisabledLabel": {
"context": "GiftCardsListTable disabled label",
"string": "Disabled"
},
"src_dot_giftCards_dot_GiftCardsList_dot_giftCardsTableColumnBalanceTitle": {
"context": "GiftCardsListTable column title balance",
"string": "Balance"
},
"src_dot_giftCards_dot_GiftCardsList_dot_giftCardsTableColumnCustomerTitle": {
"context": "GiftCardsListTable column title customer",
"string": "Used by"
},
"src_dot_giftCards_dot_GiftCardsList_dot_giftCardsTableColumnGiftCardTitle": {
"context": "GiftCardsListTable column title gift card",
"string": "Gift Card"
},
"src_dot_giftCards_dot_GiftCardsList_dot_giftCardsTableColumnProductTitle": {
"context": "GiftCardsListTable column title product",
"string": "Product"
},
"src_dot_giftCards_dot_GiftCardsList_dot_giftCardsTableColumnTagTitle": {
"context": "GiftCardsListTable column title tag",
"string": "Tag"
},
"src_dot_giftCards_dot_GiftCardsList_dot_issueButtonLabel": {
"context": "GiftCardsListHeader issue button label",
"string": "Issue card"
},
"src_dot_giftCards_dot_GiftCardsList_dot_noGiftCardsFound": {
"context": "GiftCardsListTable no cards found title",
"string": "No gift cards found"
},
"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_GiftCardExpirySelect_dot_expiryDateLabel": {
"context": "GiftCarUpdateDetailsExpirySection expiry date label",
"string": "Expiration date"
},
"src_dot_giftCards_dot_components_dot_GiftCardExpirySelect_dot_expiryPeriodLabel": {
"context": "GiftCarUpdateDetailsExpirySection expiry period label",
"string": "Expiry period"
},
"src_dot_giftCards_dot_components_dot_GiftCardExpirySelect_dot_neverExpireLabel": {
"context": "GiftCarUpdateDetailsExpirySection never expire label",
"string": "Never expire"
},
"src_dot_giftCards_dot_components_dot_GiftCardTagInput_dot_label": {
"context": "GiftCardTagInput tag label",
"string": "Card Tag"
},
"src_dot_giftCards_dot_components_dot_GiftCardTagInput_dot_placeholder": {
"context": "GiftCardTagInput tag placeholder",
"string": "Tag"
},
"src_dot_giftCards_dot_components_dot_TimePeriodField_dot_dayLabel": {
"context": "TimePeriodTextWithSelectField day label",
"string": "days after usage"
},
"src_dot_giftCards_dot_components_dot_TimePeriodField_dot_monthLabel": {
"context": "TimePeriodTextWithSelectField month label",
"string": "months after usage"
},
"src_dot_giftCards_dot_components_dot_TimePeriodField_dot_yearLabel": {
"context": "TimePeriodTextWithSelectField year label",
"string": "years after usage"
},
"src_dot_home": {
"context": "home section name",
"string": "Home"
@ -5231,18 +5458,6 @@
"context": "products section name",
"string": "Products"
},
"src_dot_products_dot_components_dot_ProductAvailabilityStatusLabel_dot_published": {
"context": "product publication date",
"string": "Published on {date}"
},
"src_dot_products_dot_components_dot_ProductAvailabilityStatusLabel_dot_unpublished": {
"context": "product publication date",
"string": "Unpublished"
},
"src_dot_products_dot_components_dot_ProductAvailabilityStatusLabel_dot_willBePublished": {
"context": "product publication date",
"string": "Becomes published on {date}"
},
"src_dot_products_dot_components_dot_ProductCategoryAndCollectionsForm_dot_1755013298": {
"string": "Category"
},
@ -5466,6 +5681,18 @@
"context": "product",
"string": "Name"
},
"src_dot_products_dot_components_dot_ProductList_dot_published": {
"context": "product publication date",
"string": "Published on {date}"
},
"src_dot_products_dot_components_dot_ProductList_dot_unpublished": {
"context": "product publication date",
"string": "Unpublished"
},
"src_dot_products_dot_components_dot_ProductList_dot_willBePublished": {
"context": "product publication date",
"string": "Becomes published on {date}"
},
"src_dot_products_dot_components_dot_ProductMediaNavigation_dot_allMedia": {
"context": "section header",
"string": "All Media"

View file

@ -2152,18 +2152,31 @@ type GatewayConfigLine {
scalar GenericScalar
type GiftCard implements Node {
code: String
user: User
created: DateTime!
startDate: Date!
endDate: Date
lastUsedOn: DateTime
type GiftCard implements Node & ObjectWithMetadata {
code: String!
isActive: Boolean!
expiryDate: Date
expiryType: GiftCardExpiryTypeEnum!
tag: String
created: DateTime!
lastUsedOn: DateTime
initialBalance: Money
currentBalance: Money
id: ID!
displayCode: String
privateMetadata: [MetadataItem]!
metadata: [MetadataItem]!
displayCode: String!
createdBy: User
usedBy: User
createdByEmail: String
usedByEmail: String
app: App
expiryPeriod: TimePeriod
product: Product
events: [GiftCardEvent!]!
user: User @deprecated(reason: "Will be removed in Saleor 4.0. Use created_by field instead")
endDate: DateTime @deprecated(reason: "Will be removed in Saleor 4.0. Use expiry_date field instead.")
startDate: DateTime @deprecated(reason: "Will be removed in Saleor 4.0.")
}
type GiftCardActivate {
@ -2190,11 +2203,14 @@ type GiftCardCreate {
}
input GiftCardCreateInput {
tag: String
startDate: Date
endDate: Date
balance: PositiveDecimal
balance: PriceInput!
userEmail: String
expirySettings: GiftCardExpirySettingsInput!
code: String
note: String
}
type GiftCardDeactivate {
@ -2203,6 +2219,12 @@ type GiftCardDeactivate {
errors: [GiftCardError!]!
}
type GiftCardDelete {
giftCardErrors: [GiftCardError!]! @deprecated(reason: "Use errors field instead. This field will be removed in Saleor 4.0.")
errors: [GiftCardError!]!
giftCard: GiftCard
}
type GiftCardError {
field: String
message: String
@ -2218,6 +2240,66 @@ enum GiftCardErrorCode {
UNIQUE
}
type GiftCardEvent implements Node {
id: ID!
date: DateTime
type: GiftCardEventsEnum
user: User
app: App
message: String
email: String
orderId: ID
orderNumber: String
tag: String
oldTag: String
balance: GiftCardEventBalance
expiry: GiftCardEventExpiry
}
type GiftCardEventBalance {
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
UPDATED
ACTIVATED
DEACTIVATED
BALANCE_RESET
EXPIRY_SETTINGS_UPDATED
SENT_TO_CUSTOMER
RESENT
}
input GiftCardExpirySettingsInput {
expiryType: GiftCardExpiryTypeEnum!
expiryDate: Date
expiryPeriod: TimePeriodInputType
}
enum GiftCardExpiryTypeEnum {
NEVER_EXPIRE
EXPIRY_PERIOD
EXPIRY_DATE
}
input GiftCardFilterInput {
tag: String
}
type GiftCardUpdate {
giftCardErrors: [GiftCardError!]! @deprecated(reason: "Use errors field instead. This field will be removed in Saleor 4.0.")
errors: [GiftCardError!]!
@ -2225,10 +2307,11 @@ type GiftCardUpdate {
}
input GiftCardUpdateInput {
tag: String
startDate: Date
endDate: Date
balance: PositiveDecimal
userEmail: String
balanceAmount: PositiveDecimal
expirySettings: GiftCardExpirySettingsInput
}
type Group implements Node {
@ -2868,6 +2951,7 @@ type Mutation {
invoiceSendNotification(id: ID!): InvoiceSendNotification
giftCardActivate(id: ID!): GiftCardActivate
giftCardCreate(input: GiftCardCreateInput!): GiftCardCreate
giftCardDelete(id: ID!): GiftCardDelete
giftCardDeactivate(id: ID!): GiftCardDeactivate
giftCardUpdate(id: ID!, input: GiftCardUpdateInput!): GiftCardUpdate
pluginUpdate(channelId: ID, id: ID!, input: PluginUpdateInput!): PluginUpdate
@ -4102,6 +4186,11 @@ enum PostalCodeRuleInclusionTypeEnum {
EXCLUDE
}
input PriceInput {
currency: String!
amount: PositiveDecimal!
}
input PriceRangeInput {
gte: Float
lte: Float
@ -4787,7 +4876,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(before: String, after: String, first: Int, last: Int): GiftCardCountableConnection
giftCards(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
@ -5258,6 +5347,7 @@ type Shop {
availablePaymentGateways(currency: String, channel: String): [PaymentGateway!]!
availableExternalAuthentications: [ExternalAuthentication!]!
availableShippingMethods(channel: String!, address: AddressInput): [ShippingMethod]
channelCurrencies: [String!]!
countries(languageCode: LanguageCodeEnum): [CountryDisplay!]!
defaultCountry: CountryDisplay
defaultMailSenderName: String
@ -5525,6 +5615,22 @@ type TaxedMoneyRange {
stop: TaxedMoney
}
type TimePeriod {
amount: Int!
type: TimePeriodTypeEnum!
}
input TimePeriodInputType {
amount: Int!
type: TimePeriodTypeEnum!
}
enum TimePeriodTypeEnum {
DAY
MONTH
YEAR
}
type Transaction implements Node {
id: ID!
created: DateTime!

View file

@ -0,0 +1,23 @@
import { makeStyles } from "@saleor/macaw-ui";
import React from "react";
export interface VerticalSpacerProps {
spacing?: number;
}
const useStyles = makeStyles(
theme => ({
container: ({ spacing }: VerticalSpacerProps) => ({
height: theme.spacing(spacing)
})
}),
{ name: "VerticalSpacer" }
);
const VerticalSpacer: React.FC<VerticalSpacerProps> = ({ spacing = 1 }) => {
const classes = useStyles({ spacing });
return <div className={classes.container} />;
};
export default VerticalSpacer;

View file

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

View file

@ -2,7 +2,7 @@ import { DEMO_MODE } from "@saleor/config";
import { User } from "@saleor/fragments/types/User";
import { SetLocalStorage } from "@saleor/hooks/useLocalStorage";
import { commonMessages } from "@saleor/intl";
import { getMutationStatus } from "@saleor/misc";
import { getFullName, getMutationStatus } from "@saleor/misc";
import errorTracker from "@saleor/services/errorTracking";
import { useEffect, useRef, useState } from "react";
import { useMutation } from "react-apollo";
@ -88,11 +88,11 @@ export function useExternalAuthProvider({
useEffect(() => {
if (authPlugin && userContext) {
const { id, email, firstName, lastName } = userContext;
const { id, email } = userContext;
errorTracker.setUserData({
email,
id,
username: `${firstName} ${lastName}`
username: getFullName(userContext)
});
if (!userContext.isStaff) {

View file

@ -2,7 +2,7 @@ import { DEMO_MODE } from "@saleor/config";
import { User } from "@saleor/fragments/types/User";
import { SetLocalStorage } from "@saleor/hooks/useLocalStorage";
import { commonMessages } from "@saleor/intl";
import { getMutationStatus } from "@saleor/misc";
import { getFullName, getMutationStatus } from "@saleor/misc";
import errorTracker from "@saleor/services/errorTracking";
import {
isSupported as isCredentialsManagementAPISupported,
@ -66,11 +66,11 @@ export function useSaleorAuthProvider({
useEffect(() => {
if (!authPlugin && userContext) {
const { id, email, firstName, lastName } = userContext;
const { id, email } = userContext;
errorTracker.setUserData({
email,
id,
username: `${firstName} ${lastName}`
username: getFullName(userContext)
});
if (!userContext.isStaff) {

View file

@ -56,11 +56,11 @@ export function createFilterStructure(
...createOptionsField(
CollectionFilterKeys.channel,
intl.formatMessage(commonMessages.channel),
[opts.channel.value],
[opts.channel?.value],
false,
opts.channel.choices
opts.channel?.choices
),
active: opts.channel.active
active: opts.channel?.active
}
];
}

View file

@ -65,4 +65,5 @@ export interface CollectionListVariables {
before?: string | null;
filter?: CollectionFilterInput | null;
sort?: CollectionSortingInput | null;
channel?: string | null;
}

View file

@ -115,7 +115,6 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
}
});
const filterOpts = getFilterOpts(params, channelOpts);
const tabs = getFilterTabs();

View file

@ -2,6 +2,7 @@
exports[`Filtering URL params should not be empty if active filters are present 1`] = `
Object {
"channel": undefined,
"status": "PUBLISHED",
}
`;

View file

@ -4,14 +4,14 @@ import React from "react";
import { ConfirmButtonTransitionState } from "../ConfirmButton";
import DialogButtons from "./DialogButtons";
import { ActionDialogVariant } from "./types";
import { ActionDialogVariant, Size } from "./types";
interface ActionDialogProps extends DialogProps {
children?: React.ReactNode;
confirmButtonLabel?: string;
confirmButtonState: ConfirmButtonTransitionState;
disabled?: boolean;
maxWidth?: "xs" | "sm" | "md" | "lg" | "xl" | false;
maxWidth?: Size | false;
title: string;
variant?: ActionDialogVariant;
onConfirm();

View file

@ -1 +1,3 @@
export type ActionDialogVariant = "default" | "delete" | "info";
export type Size = "xs" | "sm" | "md" | "lg" | "xl";

View file

@ -11,6 +11,7 @@ import {
useSavebar,
useTheme
} from "@saleor/macaw-ui";
import { isDarkTheme } from "@saleor/misc";
import { staffMemberDetailsUrl } from "@saleor/staff/urls";
import classNames from "classnames";
import React from "react";
@ -154,8 +155,7 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
});
};
const isDark = themeType === "dark";
const toggleTheme = () => setTheme(isDark ? "light" : "dark");
const toggleTheme = () => setTheme(isDarkTheme(themeType) ? "light" : "dark");
return (
<>
@ -203,7 +203,7 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
/>
)}
<UserChip
isDarkThemeEnabled={isDark}
isDarkThemeEnabled={isDarkTheme(themeType)}
user={user}
onLogout={logout}
onProfileClick={() =>

View file

@ -12,6 +12,7 @@ import {
} from "@saleor/configuration";
import { MenuItem } from "@saleor/configuration/ConfigurationPage";
import { User } from "@saleor/fragments/types/User";
import { giftCardsListUrl } from "@saleor/giftCards/urls";
import { commonMessages, sectionNames } from "@saleor/intl";
import { SidebarMenuItem } from "@saleor/macaw-ui";
import { IntlShape } from "react-intl";
@ -62,6 +63,12 @@ function createMenuStructure(intl: IntlShape, user: User): SidebarMenuItem[] {
label: intl.formatMessage(sectionNames.collections),
id: "collections",
url: collectionListUrl()
},
{
ariaLabel: "giftCards",
label: intl.formatMessage(sectionNames.giftCards),
id: "giftCards",
url: giftCardsListUrl()
}
],
iconSrc: catalogIcon,

View file

@ -0,0 +1,11 @@
import { IconButton, IconButtonProps } from "@material-ui/core";
import TrashIcon from "@saleor/icons/Trash";
import React from "react";
const DeleteIconButton: React.FC<IconButtonProps> = ({ onClick }) => (
<IconButton color="primary" onClick={onClick}>
<TrashIcon />
</IconButton>
);
export default DeleteIconButton;

View file

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

View file

@ -1,35 +1,43 @@
import { makeStyles, Typography, TypographyProps } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import React from "react";
import { LocaleConsumer } from "../Locale";
export interface IMoney {
amount: number;
currency: string;
}
export interface MoneyProps {
export interface MoneyProps extends TypographyProps {
money: IMoney | null;
}
export const formatMoney = (money: IMoney, locale: string) => {
try {
const formattedMoney = money.amount.toLocaleString(locale, {
currency: money.currency,
style: "currency"
});
return formattedMoney;
} catch (error) {
return `${money.amount} ${money.currency}`;
}
};
const useStyles = makeStyles(
() => ({
container: {
display: "flex",
alignItems: "baseline"
}
}),
{ name: "Money" }
);
export const Money: React.FC<MoneyProps> = ({ money }) =>
money ? (
<LocaleConsumer>
{({ locale }) => formatMoney(money, locale)}
</LocaleConsumer>
) : (
<>-</>
export const Money: React.FC<MoneyProps> = ({ money, ...rest }) => {
const classes = useStyles({});
if (!money) {
return null;
}
return (
<div className={classes.container}>
<Typography variant="caption" {...rest}>
{money.currency}
</Typography>
<HorizontalSpacer spacing={0.5} />
<Typography {...rest}>{money.amount.toFixed(2)}</Typography>
</div>
);
};
Money.displayName = "Money";
export default Money;

View file

@ -15,3 +15,15 @@ export function subtractMoney(init: IMoney, ...args: IMoney[]): IMoney {
currency: init.currency
};
}
export const formatMoney = (money: IMoney, locale: string) => {
try {
const formattedMoney = money.amount.toLocaleString(locale, {
currency: money.currency,
style: "currency"
});
return formattedMoney;
} catch (error) {
return `${money.amount} ${money.currency}`;
}
};

View file

@ -30,9 +30,10 @@ const useStyles = makeStyles(
marginTop: theme.spacing(2),
padding: 0
},
fontWeight: 700,
alignSelf: "flex-start",
flex: 1,
fontSize: 24
fontSize: 48
}
}),
{ name: "PageHeader" }

View file

@ -0,0 +1,39 @@
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import StatusChip from "@saleor/components/StatusChip";
import { StatusType } from "@saleor/components/StatusChip/types";
import { makeStyles } from "@saleor/macaw-ui";
import React from "react";
export interface PageTitleWithStatusChipProps {
title: string;
statusLabel: string;
statusType: StatusType;
}
const useStyles = makeStyles(
() => ({
container: {
alignItems: "center",
display: "flex"
}
}),
{ name: "OrderDetailsPageTitleWithStatusChip" }
);
const PageTitleWithStatusChip: React.FC<PageTitleWithStatusChipProps> = ({
title,
statusLabel,
statusType
}) => {
const classes = useStyles({});
return (
<div className={classes.container}>
{title}
<HorizontalSpacer spacing={2} />
<StatusChip label={statusLabel} status={statusType} />
</div>
);
};
export default PageTitleWithStatusChip;

View file

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

View file

@ -13,6 +13,7 @@ const shopInfo = gql`
country
code
}
channelCurrencies
defaultCountry {
code
country

View file

@ -42,6 +42,7 @@ export interface ShopInfo_shop_permissions {
export interface ShopInfo_shop {
__typename: "Shop";
countries: ShopInfo_shop_countries[];
channelCurrencies: string[];
defaultCountry: ShopInfo_shop_defaultCountry | null;
defaultWeightUnit: WeightUnitsEnum | null;
displayGrossPrices: boolean;

View file

@ -33,14 +33,17 @@ interface SkeletonProps {
className?: string;
primary?: boolean;
style?: React.CSSProperties;
children?: React.ReactNode;
}
const Skeleton: React.FC<SkeletonProps> = props => {
const { className, primary, style } = props;
const { className, primary, style, children } = props;
const classes = useStyles(props);
return (
return children ? (
(children as React.ReactElement)
) : (
<span
data-test-id="skeleton"
className={classNames(classes.skeleton, className, {

View file

@ -7,7 +7,11 @@ import { StatusType } from "./types";
storiesOf("Generics / Status Chip", module)
.addDecorator(Decorator)
.add("neutral", () => <StatusChip label="label" type={StatusType.NEUTRAL} />)
.add("error", () => <StatusChip label="label" type={StatusType.ERROR} />)
.add("success", () => <StatusChip label="label" type={StatusType.SUCCESS} />)
.add("alert", () => <StatusChip label="label" type={StatusType.ALERT} />);
.add("neutral", () => (
<StatusChip label="label" status={StatusType.NEUTRAL} />
))
.add("error", () => <StatusChip label="label" status={StatusType.ERROR} />)
.add("success", () => (
<StatusChip label="label" status={StatusType.SUCCESS} />
))
.add("alert", () => <StatusChip label="label" status={StatusType.ALERT} />);

View file

@ -3,14 +3,16 @@ import { makeStyles } from "@saleor/macaw-ui";
import classNames from "classnames";
import React from "react";
import { Size } from "../ActionDialog/types";
import { StatusType } from "./types";
export interface StatusChipProps {
type?: StatusType;
status?: StatusType;
size?: Size;
label?: string;
}
const StatusChipStyles = {
export const statusChipStyles = {
alert: {
background: "#FFF4E4"
},
@ -34,28 +36,38 @@ const StatusChipStyles = {
},
successLabel: {
color: "#5DC292"
},
lg: {
padding: "4px 16px"
},
lgLabel: {
fontSize: "1.5rem"
},
md: {
padding: "4px 16px"
},
mdLabel: {
fontSize: 16
}
};
const useStyles = makeStyles(
theme => ({
label: {
fontSize: theme.typography.body1.fontSize,
fontWeight: theme.typography.fontWeightBold,
textTransform: "uppercase"
},
root: {
borderRadius: 22,
display: "inline-block",
padding: "8px 24px"
display: "inline-block"
},
...StatusChipStyles
...statusChipStyles
}),
{ name: "StatusChip" }
);
const StatusChip: React.FC<StatusChipProps> = props => {
const { type = StatusType.NEUTRAL, label } = props;
const { status = StatusType.NEUTRAL, size = "lg", label } = props;
const classes = useStyles(props);
if (!label) {
@ -63,9 +75,13 @@ const StatusChip: React.FC<StatusChipProps> = props => {
}
return (
<div className={classNames(classes.root, classes[type])}>
<div className={classNames(classes.root, classes[status], classes[size])}>
<Typography
className={classNames(classes.label, classes[`${type}Label`])}
className={classNames(
classes.label,
classes[`${status}Label`],
classes[`${size}Label`]
)}
>
{label}
</Typography>

View file

@ -3,6 +3,7 @@ import { fade } from "@material-ui/core/styles/colorManipulator";
import ArrowLeft from "@material-ui/icons/ArrowLeft";
import ArrowRight from "@material-ui/icons/ArrowRight";
import { makeStyles, useTheme } from "@saleor/macaw-ui";
import { isDarkTheme } from "@saleor/misc";
import classNames from "classnames";
import React from "react";
@ -76,7 +77,7 @@ export const TablePaginationActions: React.FC<TablePaginationActionsProps> = pro
const { direction, themeType } = useTheme();
const isDark = themeType === "dark";
const isDark = isDarkTheme(themeType);
return (
<div className={classNames(classes.root, className)} {...other}>

View file

@ -0,0 +1,125 @@
import { TextField } from "@material-ui/core";
import SingleSelectField, {
Choices
} from "@saleor/components/SingleSelectField";
import { ChangeEvent, FormChange } from "@saleor/hooks/useForm";
import classNames from "classnames";
import React from "react";
import { useStyles } from "./styles";
interface CommonFieldProps {
name: string;
type?: string;
className?: string;
label?: string;
}
export interface TextWithSelectFieldProps {
change: FormChange;
choices: Choices;
helperText?: string;
isError?: boolean;
textFieldProps: CommonFieldProps & {
value?: string | number;
minValue?: number;
};
selectFieldProps: CommonFieldProps & { value: string };
containerClassName?: string;
}
const TextWithSelectField: React.FC<TextWithSelectFieldProps> = ({
change,
choices,
containerClassName,
textFieldProps,
selectFieldProps,
helperText,
isError
}) => {
const classes = useStyles();
const {
name: textFieldName,
value: textFieldValue,
label: textFieldLabel,
type: textFieldType,
minValue: textFieldMinValue
} = textFieldProps;
const {
name: selectFieldName,
value: selectFieldValue,
className: selectFieldClassName
} = selectFieldProps;
const handleSelectChange = (event: ChangeEvent) => {
// in case one of the fields in the form is empty
// we need to save the other part of the field as well
const otherTarget = {
value: textFieldValue,
name: textFieldName
};
change(event);
change({ target: otherTarget });
};
const handleTextChange = (event: ChangeEvent) => {
const { value } = event.target;
const otherTarget = {
value: selectFieldValue,
name: selectFieldName
};
// handle parsing in case of text field of type number
const parsedValue =
textFieldType === "number" && typeof value === "string"
? parseInt(value, 10)
: value;
change({
...event,
target: { ...event.target, value: parsedValue, name: event.target.name }
});
change({ target: otherTarget });
};
return (
<div className={containerClassName || classes.container}>
<TextField
error={isError}
helperText={helperText}
type="number"
className={classes.innerContainer}
name={textFieldName}
label={textFieldLabel}
inputProps={{
min: textFieldMinValue
}}
InputProps={{
className: classNames(classes.textField, {
[classes.textFieldCentered]: !textFieldLabel
}),
endAdornment: (
<SingleSelectField
name={selectFieldName}
onChange={handleSelectChange}
value={selectFieldValue}
className={classNames(
classes.autocompleteField,
selectFieldClassName
)}
choices={choices}
/>
)
}}
onChange={handleTextChange}
value={textFieldValue}
/>
</div>
);
};
export default TextWithSelectField;

View file

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

View file

@ -0,0 +1,36 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useStyles = makeStyles(
() => ({
container: {
width: 400
},
innerContainer: {
width: "100%"
},
textField: {
width: "100%",
paddingRight: 0,
"& input": {
maxWidth: "100%"
}
},
textFieldCentered: {
"& input": {
paddingTop: 16,
paddingBottom: 16
}
},
autocompleteField: {
height: 52,
border: "none",
"& *": {
border: "none"
},
"& *:focus": {
background: "none"
}
}
}),
{ name: "TextWithSelectField" }
);

View file

@ -22,6 +22,7 @@ export const PAGINATE_BY = 20;
export const VALUES_PAGINATE_BY = 10;
export type ProductListColumns = "productType" | "availability" | "price";
export interface AppListViewSettings {
[ListViews.APPS_LIST]: ListSettings;
[ListViews.ATTRIBUTE_VALUE_LIST]: ListSettings;
@ -42,7 +43,9 @@ export interface AppListViewSettings {
[ListViews.WAREHOUSE_LIST]: ListSettings;
[ListViews.WEBHOOK_LIST]: ListSettings;
[ListViews.TRANSLATION_ATTRIBUTE_VALUE_LIST]: ListSettings;
[ListViews.GIFT_CARD_LIST]: ListSettings;
}
export const defaultListSettings: AppListViewSettings = {
[ListViews.APPS_LIST]: {
rowNumber: 10
@ -101,10 +104,14 @@ export const defaultListSettings: AppListViewSettings = {
},
[ListViews.TRANSLATION_ATTRIBUTE_VALUE_LIST]: {
rowNumber: 10
},
[ListViews.GIFT_CARD_LIST]: {
rowNumber: PAGINATE_BY
}
};
export const APP_VERSION = packageInfo.version;
export const DEMO_MODE = process.env.DEMO_MODE === "true";
export const GTM_ID = process.env.GTM_ID;

View file

@ -13,4 +13,4 @@ Object {
}
`;
exports[`Filtering URL params should not be empty if active filters are present 2`] = `"channel=default-channel&startedFrom=2019-12-09&startedTo=2019-12-38&status%5B0%5D=ACTIVE&status%5B1%5D=EXPIRED&type=FIXED"`;
exports[`Filtering URL params should not be empty if active filters are present 2`] = `"channel=default-channel&startedFrom=2019-12-09&startedTo=2019-12-38&status%5B%5D=ACTIVE&status%5B%5D=EXPIRED&type=FIXED"`;

View file

@ -18,4 +18,4 @@ Object {
}
`;
exports[`Filtering URL params should not be empty if active filters are present 2`] = `"channel=default-channel&startedFrom=2019-12-09&startedTo=2019-12-38&timesUsedFrom=1&timesUsedTo=6&status%5B0%5D=ACTIVE&status%5B1%5D=EXPIRED&type%5B0%5D=FIXED&type%5B1%5D=SHIPPING"`;
exports[`Filtering URL params should not be empty if active filters are present 2`] = `"channel=default-channel&startedFrom=2019-12-09&startedTo=2019-12-38&timesUsedFrom=1&timesUsedTo=6&status%5B%5D=ACTIVE&status%5B%5D=EXPIRED&type%5B%5D=FIXED&type%5B%5D=SHIPPING"`;

View file

@ -16,3 +16,11 @@ export const fragmentUser = gql`
}
}
`;
export const fragmentUserBase = gql`
fragment UserBase on User {
id
firstName
lastName
}
`;

View file

@ -215,3 +215,10 @@ export const uploadErrorFragment = gql`
field
}
`;
export const giftCardErrorFragment = gql`
fragment GiftCardError on GiftCardError {
code
field
}
`;

View file

@ -0,0 +1,8 @@
import gql from "graphql-tag";
export const fragmentTimePeriod = gql`
fragment TimePeriod on TimePeriod {
amount
type
}
`;

View file

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

View file

@ -20,7 +20,7 @@ export interface MetadataFragment_privateMetadata {
}
export interface MetadataFragment {
__typename: "App" | "Warehouse" | "ShippingZone" | "ShippingMethod" | "Product" | "ProductType" | "Attribute" | "Category" | "ProductVariant" | "DigitalContent" | "Collection" | "Page" | "PageType" | "Sale" | "Voucher" | "MenuItem" | "Menu" | "User" | "Checkout" | "Order" | "Fulfillment" | "Invoice";
__typename: "App" | "Warehouse" | "ShippingZone" | "ShippingMethod" | "Product" | "ProductType" | "Attribute" | "Category" | "ProductVariant" | "DigitalContent" | "Collection" | "Page" | "PageType" | "Sale" | "Voucher" | "MenuItem" | "Menu" | "User" | "Checkout" | "GiftCard" | "Order" | "Fulfillment" | "Invoice";
metadata: (MetadataFragment_metadata | null)[];
privateMetadata: (MetadataFragment_privateMetadata | null)[];
}

View file

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

View file

@ -0,0 +1,15 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL fragment: UserBase
// ====================================================
export interface UserBase {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}

View file

@ -0,0 +1,114 @@
import { Dialog, DialogTitle } from "@material-ui/core";
import { IMessage } from "@saleor/components/messages";
import useNotifier from "@saleor/hooks/useNotifier";
import { GiftCardCreateInput } from "@saleor/types/globalTypes";
import commonErrorMessages from "@saleor/utils/errors/common";
import React, { useState } from "react";
import { useIntl } from "react-intl";
import GiftCardCreateDialogCodeContent from "./GiftCardCreateDialogCodeContent";
import GiftCardCreateDialogForm, {
GiftCardCreateFormData
} from "./GiftCardCreateDialogForm";
import { giftCardCreateDialogMessages as messages } from "./messages";
import { useGiftCardCreateMutation } from "./mutations";
import { GiftCardCreate } from "./types/GiftCardCreate";
import { getGiftCardExpirySettingsInputData } from "./utils";
interface GiftCardCreateDialogProps {
onClose: () => void;
open: boolean;
}
const GiftCardCreateDialog: React.FC<GiftCardCreateDialogProps> = ({
onClose,
open
}) => {
const intl = useIntl();
const notify = useNotifier();
const [cardCode, setCardCode] = useState(null);
const onCompleted = (data: GiftCardCreate) => {
const errors = data?.giftCardCreate?.errors;
const notifierData: IMessage = !!errors?.length
? {
status: "error",
text: intl.formatMessage(commonErrorMessages.unknownError)
}
: {
status: "success",
text: intl.formatMessage(messages.createdSuccessAlertTitle)
};
notify(notifierData);
if (!errors?.length) {
setCardCode(data?.giftCardCreate?.giftCard?.code);
}
};
const getParsedSubmitInputData = (
formData: GiftCardCreateFormData
): GiftCardCreateInput => {
const {
balanceAmount,
balanceCurrency,
note,
tag,
selectedCustomer
} = formData;
return {
note: note || null,
tag: tag || null,
userEmail: selectedCustomer.email || null,
balance: {
amount: balanceAmount,
currency: balanceCurrency
},
expirySettings: getGiftCardExpirySettingsInputData(formData)
};
};
const [createGiftCard, createGiftCardOpts] = useGiftCardCreateMutation({
onCompleted
});
const handleSubmit = (data: GiftCardCreateFormData) => {
createGiftCard({
variables: {
input: getParsedSubmitInputData(data)
}
});
};
const handleClose = () => {
onClose();
// 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);
};
return (
<Dialog open={open} maxWidth="sm">
<DialogTitle>{intl.formatMessage(messages.title)}</DialogTitle>
{cardCode ? (
<GiftCardCreateDialogCodeContent
cardCode={cardCode}
onClose={handleClose}
/>
) : (
<GiftCardCreateDialogForm
opts={createGiftCardOpts}
onClose={handleClose}
apiErrors={createGiftCardOpts?.data?.giftCardCreate?.errors}
onSubmit={handleSubmit}
/>
)}
</Dialog>
);
};
export default GiftCardCreateDialog;

View file

@ -0,0 +1,58 @@
import { Button, DialogContent, Typography } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import VerticalSpacer from "@saleor/apps/components/VerticalSpacer";
import useClipboard from "@saleor/hooks/useClipboard";
import useNotifier from "@saleor/hooks/useNotifier";
import { buttonMessages } from "@saleor/intl";
import React from "react";
import { useIntl } from "react-intl";
import { giftCardCreateDialogMessages as messages } from "./messages";
import { useGiftCardCreateDialogCodeContentStyles as useStyles } from "./styles";
interface GiftCardCreateDialogCodeContentProps {
cardCode: string;
onClose: () => void;
}
const GiftCardCreateDialogCodeContent: React.FC<GiftCardCreateDialogCodeContentProps> = ({
cardCode,
onClose
}) => {
const classes = useStyles({});
const intl = useIntl();
const notify = useNotifier();
const [, copy] = useClipboard();
const onCopyCode = () => {
copy(cardCode);
notify({
status: "success",
text: intl.formatMessage(messages.copiedToClipboardTitle)
});
};
return (
<DialogContent>
<Typography>
{intl.formatMessage(messages.createdGiftCardLabel)}
</Typography>
<VerticalSpacer />
<Typography variant="h6" color="textSecondary">
{cardCode}
</Typography>
<VerticalSpacer spacing={2} />
<div className={classes.buttonsContainer}>
<Button onClick={onCopyCode}>
{intl.formatMessage(messages.copyCodeLabel)}
</Button>
<HorizontalSpacer spacing={2} />
<Button color="primary" variant="contained" onClick={onClose}>
{intl.formatMessage(buttonMessages.ok)}
</Button>
</div>
</DialogContent>
);
};
export default GiftCardCreateDialogCodeContent;

View file

@ -0,0 +1,172 @@
import { DialogContent, Divider, TextField } 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 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 useShop from "@saleor/hooks/useShop";
import { commonMessages } from "@saleor/intl";
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
import Label from "@saleor/orders/components/OrderHistory/Label";
import {
GiftCardExpiryTypeEnum,
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 { getGiftCardErrorMessage } from "../GiftCardUpdate/messages";
import GiftCardCustomerSelectField from "./GiftCardCustomerSelectField";
import { giftCardCreateDialogMessages as messages } from "./messages";
import { useGiftCardCreateDialogFormStyles as useStyles } from "./styles";
import { GiftCardCommonFormData, GiftCardCreateFormCustomer } from "./types";
export interface GiftCardCreateFormData extends GiftCardCommonFormData {
note: string;
selectedCustomer?: GiftCardCreateFormCustomer;
}
const initialCustomer = { email: "", name: "" };
export const initialData: GiftCardCreateFormData = {
tag: "",
balanceAmount: 1,
balanceCurrency: null,
note: "",
expiryDate: "",
expiryType: GiftCardExpiryTypeEnum.EXPIRY_PERIOD,
expiryPeriodType: TimePeriodTypeEnum.YEAR,
expiryPeriodAmount: 1
};
interface GiftCardCreateDialogFormProps {
opts: { status: ConfirmButtonTransitionState };
apiErrors: GiftCardError[];
onSubmit: (data: GiftCardCreateFormData) => void;
onClose: () => void;
}
const GiftCardCreateDialogForm: React.FC<GiftCardCreateDialogFormProps> = ({
onSubmit,
opts,
onClose,
apiErrors
}) => {
const intl = useIntl();
const classes = useStyles({});
const shop = useShop();
// TEMP
const initialCurrency = shop?.channelCurrencies?.[0];
const [selectedCustomer, setSelectedCustomer] = useState<
GiftCardCreateFormCustomer
>(initialCustomer);
const handleSubmit = (data: GiftCardCreateFormData) =>
onSubmit({ ...data, selectedCustomer });
const { submit, change, data } = useForm(
{ ...initialData, balanceCurrency: initialCurrency },
handleSubmit
);
const formErrors = getFormErrors(
[
"tag",
"expiryDate",
"expiryPeriod",
"customer",
"currency",
"amount",
"balance"
],
apiErrors
);
const {
tag,
expiryPeriodAmount,
expiryPeriodType,
expiryType,
balanceAmount,
balanceCurrency
} = data;
return (
<>
<DialogContent>
<TextWithSelectField
isError={!!formErrors?.balance}
helperText={getGiftCardErrorMessage(formErrors?.balance, intl)}
change={change}
choices={mapSingleValueNodeToChoice(shop?.channelCurrencies)}
containerClassName={classes.balanceContainer}
textFieldProps={{
type: "number",
label: intl.formatMessage(messages.amountLabel),
name: "balanceAmount",
value: balanceAmount,
minValue: 0
}}
selectFieldProps={{
name: "balanceCurrency",
value: balanceCurrency || initialCurrency,
className: classes.currencySelectField
}}
/>
<CardSpacer />
<GiftCardTagInput
error={formErrors?.tag}
name="tag"
value={tag}
change={change}
/>
<CardSpacer />
<Divider />
<CardSpacer />
<GiftCardCustomerSelectField
selectedCustomer={selectedCustomer}
setSelectedCustomer={setSelectedCustomer}
/>
<VerticalSpacer />
<Label text={intl.formatMessage(messages.customerSubtitle)} />
<CardSpacer />
<Divider />
<CardSpacer />
<GiftCardExpirySelect
errors={formErrors}
change={change}
expiryType={expiryType}
expiryPeriodAmount={expiryPeriodAmount}
expiryPeriodType={expiryPeriodType}
/>
<CardSpacer />
<TextField
name="note"
onChange={change}
multiline
className={classes.noteField}
label={`${intl.formatMessage(
messages.noteLabel
)} *${intl.formatMessage(commonMessages.optionalField)}`}
/>
<VerticalSpacer />
<Label text={intl.formatMessage(messages.noteSubtitle)} />
</DialogContent>
<DialogButtons
onConfirm={submit}
confirmButtonLabel={intl.formatMessage(messages.issueButtonLabel)}
confirmButtonState={opts?.status}
onClose={onClose}
/>
</>
);
};
export default GiftCardCreateDialogForm;

View file

@ -0,0 +1,61 @@
import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import { commonMessages } from "@saleor/intl";
import { getFullName } from "@saleor/misc";
import useCustomerSearch from "@saleor/searches/useCustomerSearch";
import { mapEdgesToItems } from "@saleor/utils/maps";
import React from "react";
import { useIntl } from "react-intl";
import { giftCardCreateDialogMessages as messages } from "./messages";
import { GiftCardCreateFormCustomer } from "./types";
export interface GiftCardCustomerSelectFieldProps {
selectedCustomer: GiftCardCreateFormCustomer;
setSelectedCustomer: (customer: GiftCardCreateFormCustomer) => void;
}
const GiftCardCustomerSelectField: React.FC<GiftCardCustomerSelectFieldProps> = ({
selectedCustomer,
setSelectedCustomer
}) => {
const intl = useIntl();
const { loadMore, search, result } = useCustomerSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA
});
const customers = mapEdgesToItems(result?.data?.search);
const choices = customers?.map(({ email, firstName, lastName }) => ({
value: email,
label: getFullName({ firstName, lastName })
}));
const handleSelect = (event: React.ChangeEvent<any>) => {
const value = event.target.value;
const label = choices?.find(category => category.value === value)?.label;
setSelectedCustomer({ email: value, name: label });
};
const label = `${intl.formatMessage(
messages.customerLabel
)} *${intl.formatMessage(commonMessages.optionalField)}`;
return (
<SingleAutocompleteSelectField
name="customer"
label={label}
data-test-id="customer-field"
displayValue={selectedCustomer.name}
value={selectedCustomer.email}
choices={choices || []}
fetchChoices={search}
onChange={handleSelect}
onFetchMore={loadMore}
/>
);
};
export default GiftCardCustomerSelectField;

View file

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

View file

@ -0,0 +1,50 @@
import { defineMessages } from "react-intl";
export const giftCardCreateDialogMessages = defineMessages({
title: {
defaultMessage: "Issue gift card",
description: "GiftCardCreateDialog title"
},
amountLabel: {
defaultMessage: "Enter amount",
description: "GiftCardCreateDialog amount label"
},
issueButtonLabel: {
defaultMessage: "Issue",
description: "GiftCardCreateDialog issue button label"
},
customerLabel: {
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"
},
noteSubtitle: {
defaultMessage:
"Why was this gift card issued. This note will not be shown to the customer. Note will be stored in gift card history",
description: "GiftCardCreateDialog note subtitle"
},
createdGiftCardLabel: {
defaultMessage: "This is the code of a created gift card:",
description: "GiftCardCreateDialog created gift card label"
},
copyCodeLabel: {
defaultMessage: "Copy code",
description: "GiftCardCreateDialog copy code label"
},
copiedToClipboardTitle: {
defaultMessage: "Copied to clipboard",
description: "GiftCardCreateDialog copied to clipboard title"
},
createdSuccessAlertTitle: {
defaultMessage: "Successfully created gift card",
description: "GiftCardCreateDialog createdSuccessAlertTitle"
}
});

View file

@ -0,0 +1,26 @@
import makeMutation from "@saleor/hooks/makeMutation";
import gql from "graphql-tag";
import {
GiftCardCreate,
GiftCardCreateVariables
} from "./types/GiftCardCreate";
const giftCardCreate = gql`
mutation GiftCardCreate($input: GiftCardCreateInput!) {
giftCardCreate(input: $input) {
giftCard {
code
}
errors {
code
field
}
}
}
`;
export const useGiftCardCreateMutation = makeMutation<
GiftCardCreate,
GiftCardCreateVariables
>(giftCardCreate);

View file

@ -0,0 +1,25 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useGiftCardCreateDialogCodeContentStyles = makeStyles(
() => ({
buttonsContainer: {
display: "flex",
justifyContent: "flex-end",
minWidth: 450
}
}),
{ name: "GiftCardCreateDialogCodeContent" }
);
export const useGiftCardCreateDialogFormStyles = makeStyles(
() => ({
noteField: {
width: "100%"
},
currencySelectField: {
width: 100
},
balanceContainer: { width: "100%" }
}),
{ name: "GiftCardCreateDialogForm" }
);

View file

@ -0,0 +1,32 @@
import { GiftCardError } from "@saleor/fragments/types/GiftCardError";
import { FormChange } from "@saleor/hooks/useForm";
import {
GiftCardExpiryTypeEnum,
TimePeriodTypeEnum
} from "@saleor/types/globalTypes";
export interface GiftCardCreateFormCustomer {
name: string;
email: string;
}
export interface GiftCardCommonFormData {
tag: string;
balanceAmount: number;
balanceCurrency: string;
expiryDate: string;
expiryType: GiftCardExpiryTypeEnum;
expiryPeriodType: TimePeriodTypeEnum;
expiryPeriodAmount: number;
}
export type GiftCardCreateFormErrors = Record<
"tag" | "expiryDate" | "expiryPeriod" | "customer" | "currency" | "amount",
GiftCardError
>;
export interface GiftCardCreateFormCommonProps {
change: FormChange;
errors: GiftCardCreateFormErrors;
data: GiftCardCommonFormData;
}

View file

@ -0,0 +1,35 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardCreateInput, GiftCardErrorCode } from "./../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: GiftCardCreate
// ====================================================
export interface GiftCardCreate_giftCardCreate_giftCard {
__typename: "GiftCard";
code: string;
}
export interface GiftCardCreate_giftCardCreate_errors {
__typename: "GiftCardError";
code: GiftCardErrorCode;
field: string | null;
}
export interface GiftCardCreate_giftCardCreate {
__typename: "GiftCardCreate";
giftCard: GiftCardCreate_giftCardCreate_giftCard | null;
errors: GiftCardCreate_giftCardCreate_errors[];
}
export interface GiftCardCreate {
giftCardCreate: GiftCardCreate_giftCardCreate | null;
}
export interface GiftCardCreateVariables {
input: GiftCardCreateInput;
}

View file

@ -0,0 +1,41 @@
import {
GiftCardExpirySettingsInput,
GiftCardExpiryTypeEnum
} from "@saleor/types/globalTypes";
import { GiftCardCommonFormData } from "./types";
export const getGiftCardExpirySettingsInputData = ({
expiryType,
expiryDate,
expiryPeriodAmount,
expiryPeriodType
}: Pick<
GiftCardCommonFormData,
"expiryDate" | "expiryPeriodAmount" | "expiryPeriodType" | "expiryType"
>): GiftCardExpirySettingsInput => {
switch (expiryType) {
case GiftCardExpiryTypeEnum.EXPIRY_DATE: {
return {
expiryType,
expiryDate
};
}
case GiftCardExpiryTypeEnum.EXPIRY_PERIOD: {
return {
expiryType,
expiryPeriod: {
amount: expiryPeriodAmount,
type: expiryPeriodType
}
};
}
default: {
return {
expiryType
};
}
}
};

View file

@ -0,0 +1,24 @@
import React from "react";
import GiftCardUpdatePage from "./GiftCardUpdatePage";
import GiftCardDetailsProvider from "./providers/GiftCardDetailsProvider";
import GiftCardUpdateDialogsProvider from "./providers/GiftCardUpdateDialogsProvider";
import GiftCardUpdateFormProvider from "./providers/GiftCardUpdateFormProvider/GiftCardUpdateFormProvider";
import { GiftCardUpdatePageUrlQueryParams } from "./types";
interface GiftCardUpdateProps {
params: GiftCardUpdatePageUrlQueryParams;
id: string;
}
const GiftCardUpdate: React.FC<GiftCardUpdateProps> = ({ id, params }) => (
<GiftCardDetailsProvider id={id}>
<GiftCardUpdateFormProvider>
<GiftCardUpdateDialogsProvider id={id} params={params}>
<GiftCardUpdatePage />
</GiftCardUpdateDialogsProvider>
</GiftCardUpdateFormProvider>
</GiftCardDetailsProvider>
);
export default GiftCardUpdate;

View file

@ -0,0 +1,137 @@
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 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 React from "react";
import { useIntl } from "react-intl";
import { giftCardsListTableMessages as tableMessages } from "../../GiftCardsList/messages";
import { getGiftCardErrorMessage } from "../messages";
import { useGiftCardUpdateMutation } from "../mutations";
import useGiftCardDetails from "../providers/GiftCardDetailsProvider/hooks/useGiftCardDetails";
import { GiftCardUpdate } from "../types/GiftCardUpdate";
import { giftCardUpdateBalanceDialogMessages as messages } from "./messages";
import { useUpdateBalanceDialogStyles as useStyles } from "./styles";
export interface GiftCardBalanceUpdateFormData {
balanceAmount: number;
}
const GiftCardUpdateBalanceDialog: React.FC<DialogActionHandlers> = ({
open,
onClose
}) => {
const intl = useIntl();
const classes = useStyles({});
const notify = useNotifier();
const {
giftCard: {
id,
currentBalance: { amount, currency }
}
} = useGiftCardDetails();
const initialFormData: GiftCardBalanceUpdateFormData = {
balanceAmount: amount
};
const onCompleted = (data: GiftCardUpdate) => {
const errors = data?.giftCardUpdate?.errors;
const notifierData: IMessage = !!errors?.length
? {
status: "error",
text: intl.formatMessage(commonErrorMessages.unknownError)
}
: {
status: "success",
text: intl.formatMessage(messages.updatedSuccessAlertTitle)
};
notify(notifierData);
if (!errors.length) {
onClose();
}
};
const [
updateGiftCardBalance,
updateGiftCardBalanceOpts
] = useGiftCardUpdateMutation({
onCompleted
});
const handleSubmit = async ({
balanceAmount
}: GiftCardBalanceUpdateFormData) => {
const result = await updateGiftCardBalance({
variables: {
id,
input: {
balanceAmount
}
}
});
return result?.data?.giftCardUpdate?.errors;
};
const { loading, status, data } = updateGiftCardBalanceOpts;
const formErrors = getFormErrors(
["initialBalanceAmount"],
data?.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>
);
};
export default GiftCardUpdateBalanceDialog;

View file

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

View file

@ -0,0 +1,22 @@
import { defineMessages } from "react-intl";
export const giftCardUpdateBalanceDialogMessages = defineMessages({
title: {
defaultMessage: "Set balance",
description: "GiftCardUpdateDetailsCard set balance button label"
},
subtitle: {
defaultMessage:
"What would you like to set cards balance to. When you change the balance both values will be changed",
description: "GiftCardUpdateDetailsCard set balance dialog subtitle"
},
updatedSuccessAlertTitle: {
defaultMessage: "Successfully updated card balance",
description: "GiftCardUpdateDetailsCard update success alert title"
},
changeButtonLabel: {
defaultMessage: "Change",
description:
"GiftCardUpdateDetailsCard set balance dialog change button label"
}
});

View file

@ -0,0 +1,17 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useUpdateBalanceDialogStyles = makeStyles(
theme => ({
inputContainer: {
width: "100%"
},
currencyCodeContainer: {
height: 35,
display: "flex",
flexDirection: "column",
justifyContent: "flex-end",
marginRight: theme.spacing(1)
}
}),
{ name: "GiftCardUpdateBalanceDialog" }
);

View file

@ -0,0 +1,50 @@
import { Typography } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import CardSpacer from "@saleor/components/CardSpacer";
import Money from "@saleor/components/Money";
import classNames from "classnames";
import React from "react";
import { useIntl } from "react-intl";
import useGiftCardDetails from "../providers/GiftCardDetailsProvider/hooks/useGiftCardDetails";
import { giftCardUpdateDetailsCardMessages as messages } from "./messages";
import { useGiftCardDetailsBalanceStyles as useStyles } from "./styles";
const GiftCardUpdateDetailsBalanceSection: React.FC = () => {
const classes = useStyles({});
const intl = useIntl();
const {
giftCard: { currentBalance, initialBalance }
} = useGiftCardDetails();
const progressBarWidth = !!currentBalance.amount
? Math.floor((currentBalance.amount / initialBalance.amount) * 100)
: 0;
return (
<>
<div
className={classNames(classes.labelsContainer, classes.wideContainer)}
>
<Typography>{intl.formatMessage(messages.cardBalanceLabel)}</Typography>
<div className={classes.labelsContainer}>
<Money money={currentBalance} />
<HorizontalSpacer />
/
<HorizontalSpacer />
<Money money={initialBalance} />
</div>
</div>
<CardSpacer />
<div className={classes.balanceBar}>
<div
style={{ width: `${progressBarWidth}%` }}
className={classes.balanceBarProgress}
/>
</div>
</>
);
};
export default GiftCardUpdateDetailsBalanceSection;

View file

@ -0,0 +1,74 @@
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 React from "react";
import { useIntl } from "react-intl";
import useGiftCardDetails from "../providers/GiftCardDetailsProvider/hooks/useGiftCardDetails";
import useGiftCardUpdateDialogs from "../providers/GiftCardUpdateDialogsProvider/hooks/useGiftCardUpdateDialogs";
import useGiftCardUpdateForm from "../providers/GiftCardUpdateFormProvider/hooks/useGiftCardUpdateForm";
import GiftCardUpdateDetailsBalanceSection from "./GiftCardUpdateDetailsBalanceSection";
import { giftCardUpdateDetailsCardMessages as messages } from "./messages";
const GiftCardUpdateDetailsCard: React.FC = () => {
const intl = useIntl();
const { loading } = useGiftCardDetails();
const { openSetBalanceDialog } = useGiftCardUpdateDialogs();
const {
change,
data: { expiryType, expiryPeriodAmount, expiryPeriodType, tag, expiryDate },
formErrors
} = useGiftCardUpdateForm();
return (
<Card>
<CardTitle
title={intl.formatMessage(messages.title)}
toolbar={
<Button
data-test-id="set-balance-button"
color="primary"
onClick={openSetBalanceDialog}
>
{intl.formatMessage(messages.setBalanceButtonLabel)}
</Button>
}
/>
<CardContent>
<Skeleton>
{!loading && (
<>
<GiftCardUpdateDetailsBalanceSection />
<CardSpacer />
<Divider />
<CardSpacer />
<GiftCardTagInput
error={formErrors?.tag}
name="tag"
withTopLabel
value={tag}
change={change}
/>
<CardSpacer />
<GiftCardExpirySelect
expiryDate={expiryDate}
errors={formErrors}
change={change}
expiryType={expiryType}
expiryPeriodAmount={expiryPeriodAmount}
expiryPeriodType={expiryPeriodType}
/>
</>
)}
</Skeleton>
</CardContent>
</Card>
);
};
export default GiftCardUpdateDetailsCard;

View file

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

View file

@ -0,0 +1,16 @@
import { defineMessages } from "react-intl";
export const giftCardUpdateDetailsCardMessages = defineMessages({
title: {
defaultMessage: "Details",
description: "GiftCardUpdateDetailsCard title"
},
setBalanceButtonLabel: {
defaultMessage: "set balance",
description: "GiftCardUpdateDetailsCard set balance button label"
},
cardBalanceLabel: {
defaultMessage: "Card Balance",
description: "GiftCardUpdateDetailsCard card balance label"
}
});

View file

@ -0,0 +1,28 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useGiftCardDetailsBalanceStyles = makeStyles(
theme => ({
labelsContainer: {
display: "flex",
alignItems: "baseline"
},
wideContainer: {
justifyContent: "space-between"
},
balanceBar: {
width: "100%",
display: "flex",
alignItems: "center",
height: 36,
padding: "0 4px",
backgroundColor: theme.palette.background.default,
borderRadius: 18
},
balanceBarProgress: {
height: 28,
borderRadius: 14,
backgroundColor: theme.palette.primary.light
}
}),
{ name: "GiftCardUpdateDetailsBalanceSection" }
);

View file

@ -0,0 +1,26 @@
import { Card, CardContent } from "@material-ui/core";
import CardTitle from "@saleor/components/CardTitle";
import Skeleton from "@saleor/components/Skeleton";
import React from "react";
import { useIntl } from "react-intl";
import useGiftCardDetails from "../providers/GiftCardDetailsProvider/hooks/useGiftCardDetails";
import GiftCardUpdateInfoCardContent from "./GiftCardUpdateInfoCardContent";
import { giftCardUpdateInfoCardMessages as messages } from "./messages";
const GiftCardUpdateInfoCard: React.FC = () => {
const intl = useIntl();
const { loading } = useGiftCardDetails();
return (
<Card>
<CardTitle title={intl.formatMessage(messages.title)} />
<CardContent>
<Skeleton>{!loading && <GiftCardUpdateInfoCardContent />}</Skeleton>
</CardContent>
</Card>
);
};
export default GiftCardUpdateInfoCard;

View file

@ -0,0 +1,143 @@
import { Typography } from "@material-ui/core";
import { appUrl } from "@saleor/apps/urls";
import CardSpacer from "@saleor/components/CardSpacer";
import Link from "@saleor/components/Link";
import { customerUrl } from "@saleor/customers/urls";
import useDateLocalize from "@saleor/hooks/useDateLocalize";
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 { productUrl } from "@saleor/products/urls";
import { staffMemberDetailsUrl } from "@saleor/staff/urls";
import { GiftCardEventsEnum } from "@saleor/types/globalTypes";
import React from "react";
import { MessageDescriptor, useIntl } from "react-intl";
import useGiftCardDetails from "../providers/GiftCardDetailsProvider/hooks/useGiftCardDetails";
import { giftCardUpdateInfoCardMessages as messages } from "./messages";
const PLACEHOLDER = "-";
const GiftCardUpdateInfoCardContent: React.FC = () => {
const intl = useIntl();
const localizeDate = useDateLocalize();
const navigate = useNavigator();
const { giftCard } = useGiftCardDetails();
const {
created,
createdByEmail,
createdBy,
usedByEmail,
usedBy,
app,
product,
events
} = giftCard;
const cardIssuedEvent = events.find(
({ type }) => type === GiftCardEventsEnum.ISSUED
);
const getBuyerFieldData = (): {
label: MessageDescriptor;
name: string;
url?: string;
} => {
// createdBy can be either customer or staff hence
// we check for issued event
if (cardIssuedEvent) {
const userName = getFullName(createdBy);
return {
label: messages.issuedByLabel,
name: userName || createdByEmail,
url: staffMemberDetailsUrl(createdBy.id)
};
}
if (createdByEmail) {
return {
label: messages.boughtByLabel,
name: createdByEmail
};
}
if (app) {
return {
label: messages.issuedByAppLabel,
name: app.name,
url: appUrl(app.id)
};
}
return {
label: messages.boughtByLabel,
name: getFullName(createdBy),
url: customerUrl(createdBy?.id)
};
};
const orderData =
cardIssuedEvent && cardIssuedEvent.orderId
? getOrderNumberLinkObject({
id: cardIssuedEvent.orderId,
number: cardIssuedEvent.orderNumber
})
: null;
const {
label: buyerLabelMessage,
name: buyerName,
url: buyerUrl
} = getBuyerFieldData();
return (
<>
<Label text={intl.formatMessage(messages.creationLabel)} />
<Typography>{localizeDate(created, "DD MMMM YYYY")}</Typography>
<CardSpacer />
<Label text={intl.formatMessage(messages.orderNumberLabel)} />
{orderData ? (
<Link onClick={() => navigate(orderData.link)}>{orderData.text}</Link>
) : (
<Typography>{PLACEHOLDER}</Typography>
)}
<CardSpacer />
<Label text={intl.formatMessage(messages.productLabel)} />
{product ? (
<Link onClick={() => navigate(productUrl(product?.id))}>
{product?.name}
</Link>
) : (
<Typography>{PLACEHOLDER}</Typography>
)}
<CardSpacer />
<Label text={intl.formatMessage(buyerLabelMessage)} />
{buyerUrl ? (
<Link onClick={() => navigate(buyerUrl)}>{buyerName}</Link>
) : (
<Typography>{buyerName}</Typography>
)}
<CardSpacer />
<Label text={intl.formatMessage(messages.usedByLabel)} />
{usedBy ? (
<Link onClick={() => navigate(customerUrl(usedBy.id))}>
{getFullName(usedBy)}
</Link>
) : (
<Typography>
{getStringOrPlaceholder(usedByEmail, PLACEHOLDER)}
</Typography>
)}
</>
);
};
export default GiftCardUpdateInfoCardContent;

View file

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

View file

@ -0,0 +1,36 @@
import { defineMessages } from "react-intl";
export const giftCardUpdateInfoCardMessages = defineMessages({
title: {
defaultMessage: "Card information",
description: "GiftCardUpdateInfoCard title"
},
creationLabel: {
defaultMessage: "Creation",
description: "GiftCardUpdateInfoCard creation label"
},
orderNumberLabel: {
defaultMessage: "Order number",
description: "GiftCardUpdateInfoCard order number label"
},
productLabel: {
defaultMessage: "Product bought to get gift card",
description: "GiftCardUpdateInfoCard product label"
},
issuedByLabel: {
defaultMessage: "Issued by",
description: "GiftCardUpdateInfoCard issued by label"
},
issuedByAppLabel: {
defaultMessage: "Issued by app",
description: "GiftCardUpdateInfoCard issued by app label"
},
boughtByLabel: {
defaultMessage: "Bought by",
description: "GiftCardUpdateInfoCard bought by label"
},
usedByLabel: {
defaultMessage: "Used by",
description: "GiftCardUpdateInfoCard used by label"
}
});

View file

@ -0,0 +1,52 @@
import CardSpacer from "@saleor/components/CardSpacer";
import Container from "@saleor/components/Container";
import Grid from "@saleor/components/Grid";
import Metadata from "@saleor/components/Metadata";
import Savebar from "@saleor/components/Savebar";
import React from "react";
import GiftCardUpdateDetailsCard from "./GiftCardUpdateDetailsCard";
import GiftCardUpdateInfoCard from "./GiftCardUpdateInfoCard";
import GiftCardUpdatePageHeader from "./GiftCardUpdatePageHeader";
import useGiftCardUpdateDialogs from "./providers/GiftCardUpdateDialogsProvider/hooks/useGiftCardUpdateDialogs";
import useGiftCardUpdate from "./providers/GiftCardUpdateFormProvider/hooks/useGiftCardUpdate";
import useGiftCardUpdateForm from "./providers/GiftCardUpdateFormProvider/hooks/useGiftCardUpdateForm";
const GiftCardUpdatePage: React.FC = () => {
const { navigateBack } = useGiftCardUpdateDialogs();
const {
hasChanged,
submit,
data,
handlers: { changeMetadata }
} = useGiftCardUpdateForm();
const {
opts: { loading: loadingUpdate, status }
} = useGiftCardUpdate();
return (
<Container>
<GiftCardUpdatePageHeader />
<Grid>
<div>
<GiftCardUpdateDetailsCard />
<CardSpacer />
<Metadata data={data} onChange={changeMetadata} />
</div>
<div>
<GiftCardUpdateInfoCard />
</div>
</Grid>
<Savebar
state={status}
disabled={loadingUpdate || !hasChanged}
onCancel={navigateBack}
onSubmit={submit}
/>
</Container>
);
};
export default GiftCardUpdatePage;

View file

@ -0,0 +1,105 @@
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 { useIntl } from "react-intl";
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();
const {
giftCard: { id, isActive }
} = useGiftCardDetails();
const [showButtonGreen, setShowButtonGreen] = useState(!isActive);
useEffect(() => setShowButtonGreen(!isActive), [isActive]);
const onActivateCompleted = (data: GiftCardActivate) => {
const errors = data?.giftCardActivate?.errors;
if (!!errors?.length) {
notify({
status: "error",
text: intl.formatMessage(commonErrorMessages.unknownError)
});
setShowButtonGreen(false);
setTimeout(() => setShowButtonGreen(true), 3000);
return;
}
notify({
status: "success",
text: intl.formatMessage(messages.successfullyEnabledTitle)
});
};
const onDeactivateCompleted = (data: GiftCardDeactivate) => {
const errors = data?.giftCardDeactivate?.errors;
if (!!errors?.length) {
notify({
status: "error",
text: intl.formatMessage(commonErrorMessages.unknownError)
});
return;
}
notify({
status: "success",
text: intl.formatMessage(messages.successfullyDisabledTitle)
});
};
const [giftCardActivate, giftCardActivateOpts] = useGiftCardActivateMutation({
onCompleted: onActivateCompleted
});
const [
giftCardDeactivate,
giftCardDeactivateOpts
] = useGiftCardDeactivateMutation({
onCompleted: onDeactivateCompleted
});
const handleClick = () =>
isActive
? giftCardDeactivate({ variables: { id } })
: giftCardActivate({ variables: { id } });
const buttonLabel = isActive ? messages.disableLabel : messages.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={{
confirm: intl.formatMessage(buttonLabel),
error: intl.formatMessage(commonMessages.error)
}}
/>
);
};
export default GiftCardEnableDisableSection;

View file

@ -0,0 +1,56 @@
import PageHeader from "@saleor/components/PageHeader";
import PageTitleWithStatusChip from "@saleor/components/PageTitleWithStatusChip";
import { StatusType } from "@saleor/components/StatusChip/types";
import { sectionNames } from "@saleor/intl";
import { Backlink } from "@saleor/macaw-ui";
import React from "react";
import { useIntl } from "react-intl";
import { giftCardsListTableMessages as tableMessages } from "../../GiftCardsList/messages";
import useGiftCardDetails from "../providers/GiftCardDetailsProvider/hooks/useGiftCardDetails";
import useGiftCardUpdateDialogs from "../providers/GiftCardUpdateDialogsProvider/hooks/useGiftCardUpdateDialogs";
import GiftCardEnableDisableSection from "./GiftCardEnableDisableSection";
const GiftCardUpdatePageHeader: React.FC = () => {
const intl = useIntl();
const { giftCard } = useGiftCardDetails();
const { navigateBack } = useGiftCardUpdateDialogs();
if (!giftCard) {
return null;
}
const { displayCode, isActive } = giftCard;
const title = intl.formatMessage(tableMessages.codeEndingWithLabel, {
displayCode
});
return (
<>
<Backlink onClick={navigateBack}>
{intl.formatMessage(sectionNames.giftCards)}
</Backlink>
<PageHeader
inline
title={
isActive ? (
title
) : (
<PageTitleWithStatusChip
title={title}
statusLabel={intl.formatMessage(
tableMessages.giftCardDisabledLabel
)}
statusType={StatusType.ERROR}
/>
)
}
>
<GiftCardEnableDisableSection />
</PageHeader>
</>
);
};
export default GiftCardUpdatePageHeader;

View file

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

View file

@ -0,0 +1,20 @@
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"
},
successfullyDisabledTitle: {
defaultMessage: "Successfully disabled gift card",
description: "GiftCardEnableDisableSection disable success"
}
});

View file

@ -0,0 +1,53 @@
import { giftCardErrorFragment } from "@saleor/fragments/errors";
import makeMutation from "@saleor/hooks/makeMutation";
import gql from "graphql-tag";
import { giftCardDataFragment } from "../queries";
import {
GiftCardActivate,
GiftCardActivateVariables
} from "./types/GiftCardActivate";
import {
GiftCardDeactivate,
GiftCardDeactivateVariables
} from "./types/GiftCardDeactivate";
const giftCardActivate = gql`
${giftCardDataFragment}
${giftCardErrorFragment}
mutation GiftCardActivate($id: ID!) {
giftCardActivate(id: $id) {
errors {
...GiftCardError
}
giftCard {
...GiftCardData
}
}
}
`;
export const useGiftCardActivateMutation = makeMutation<
GiftCardActivate,
GiftCardActivateVariables
>(giftCardActivate);
const giftCardDeactivate = gql`
${giftCardDataFragment}
${giftCardErrorFragment}
mutation GiftCardDeactivate($id: ID!) {
giftCardDeactivate(id: $id) {
errors {
...GiftCardError
}
giftCard {
...GiftCardData
}
}
}
`;
export const useGiftCardDeactivateMutation = makeMutation<
GiftCardDeactivate,
GiftCardDeactivateVariables
>(giftCardDeactivate);

View file

@ -0,0 +1,27 @@
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

@ -0,0 +1,117 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardErrorCode, GiftCardExpiryTypeEnum, TimePeriodTypeEnum } from "./../../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: GiftCardActivate
// ====================================================
export interface GiftCardActivate_giftCardActivate_errors {
__typename: "GiftCardError";
code: GiftCardErrorCode;
field: string | null;
}
export interface GiftCardActivate_giftCardActivate_giftCard_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardActivate_giftCardActivate_giftCard_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardActivate_giftCardActivate_giftCard_createdBy {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardActivate_giftCardActivate_giftCard_product {
__typename: "Product";
id: string;
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;
firstName: string;
lastName: string;
}
export interface GiftCardActivate_giftCardActivate_giftCard_app {
__typename: "App";
id: string;
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;
currency: string;
}
export interface GiftCardActivate_giftCardActivate_giftCard_currentBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardActivate_giftCardActivate_giftCard {
__typename: "GiftCard";
metadata: (GiftCardActivate_giftCardActivate_giftCard_metadata | null)[];
privateMetadata: (GiftCardActivate_giftCardActivate_giftCard_privateMetadata | null)[];
displayCode: string;
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;
currentBalance: GiftCardActivate_giftCardActivate_giftCard_currentBalance | null;
id: string;
tag: string | null;
}
export interface GiftCardActivate_giftCardActivate {
__typename: "GiftCardActivate";
errors: GiftCardActivate_giftCardActivate_errors[];
giftCard: GiftCardActivate_giftCardActivate_giftCard | null;
}
export interface GiftCardActivate {
giftCardActivate: GiftCardActivate_giftCardActivate | null;
}
export interface GiftCardActivateVariables {
id: string;
}

View file

@ -0,0 +1,117 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardErrorCode, GiftCardExpiryTypeEnum, TimePeriodTypeEnum } from "./../../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: GiftCardDeactivate
// ====================================================
export interface GiftCardDeactivate_giftCardDeactivate_errors {
__typename: "GiftCardError";
code: GiftCardErrorCode;
field: string | null;
}
export interface GiftCardDeactivate_giftCardDeactivate_giftCard_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardDeactivate_giftCardDeactivate_giftCard_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardDeactivate_giftCardDeactivate_giftCard_createdBy {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardDeactivate_giftCardDeactivate_giftCard_product {
__typename: "Product";
id: string;
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;
firstName: string;
lastName: string;
}
export interface GiftCardDeactivate_giftCardDeactivate_giftCard_app {
__typename: "App";
id: string;
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;
currency: string;
}
export interface GiftCardDeactivate_giftCardDeactivate_giftCard_currentBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardDeactivate_giftCardDeactivate_giftCard {
__typename: "GiftCard";
metadata: (GiftCardDeactivate_giftCardDeactivate_giftCard_metadata | null)[];
privateMetadata: (GiftCardDeactivate_giftCardDeactivate_giftCard_privateMetadata | null)[];
displayCode: string;
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;
currentBalance: GiftCardDeactivate_giftCardDeactivate_giftCard_currentBalance | null;
id: string;
tag: string | null;
}
export interface GiftCardDeactivate_giftCardDeactivate {
__typename: "GiftCardDeactivate";
errors: GiftCardDeactivate_giftCardDeactivate_errors[];
giftCard: GiftCardDeactivate_giftCardDeactivate_giftCard | null;
}
export interface GiftCardDeactivate {
giftCardDeactivate: GiftCardDeactivate_giftCardDeactivate | null;
}
export interface GiftCardDeactivateVariables {
id: string;
}

View file

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

View file

@ -0,0 +1,34 @@
import { commonMessages } from "@saleor/intl";
import { GiftCardErrorCode } from "@saleor/types/globalTypes";
import commonErrorMessages 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",
description: "GiftCardUpdateDetailsCard title"
}
});
export function getGiftCardErrorMessage(
error: Omit<GiftCardUpdate_giftCardUpdate_errors, "__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);
}
}
return undefined;
}

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 {
GiftCardUpdate,
GiftCardUpdateVariables
} from "./types/GiftCardUpdate";
const giftCardUpdate = gql`
${giftCardDataFragment}
${giftCardErrorFragment}
mutation GiftCardUpdate($id: ID!, $input: GiftCardUpdateInput!) {
giftCardUpdate(id: $id, input: $input) {
errors {
...GiftCardError
}
giftCard {
...GiftCardData
}
}
}
`;
export const useGiftCardUpdateMutation = makeMutation<
GiftCardUpdate,
GiftCardUpdateVariables
>(giftCardUpdate);

View file

@ -0,0 +1,41 @@
import React, { createContext } from "react";
import { useGiftCardDetailsQuery } from "../../queries";
import { GiftCardDetails_giftCard } from "../../types/GiftCardDetails";
interface GiftCardDetailsProviderProps {
children: React.ReactNode;
id: string;
}
export interface GiftCardDetailsConsumerProps {
giftCard: GiftCardDetails_giftCard;
loading: boolean;
}
export const GiftCardDetailsContext = createContext<
GiftCardDetailsConsumerProps
>(null);
const GiftCardDetailsProvider: React.FC<GiftCardDetailsProviderProps> = ({
children,
id
}) => {
const { data, loading } = useGiftCardDetailsQuery({
displayLoader: true,
variables: { id }
});
const providerValues: GiftCardDetailsConsumerProps = {
giftCard: data?.giftCard,
loading
};
return (
<GiftCardDetailsContext.Provider value={providerValues}>
{children}
</GiftCardDetailsContext.Provider>
);
};
export default GiftCardDetailsProvider;

View file

@ -0,0 +1,14 @@
import { useContext } from "react";
import {
GiftCardDetailsConsumerProps,
GiftCardDetailsContext
} from "../GiftCardDetailsProvider";
const useGiftCardDetails = (): GiftCardDetailsConsumerProps => {
const giftCardDetailsConsumerProps = useContext(GiftCardDetailsContext);
return giftCardDetailsConsumerProps;
};
export default useGiftCardDetails;

View file

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

View file

@ -0,0 +1,70 @@
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 GiftCardUpdateBalanceDialog from "../../GiftCardUpdateBalanceDialog";
import {
GiftCardUpdatePageActionParamsEnum,
GiftCardUpdatePageUrlQueryParams
} from "../../types";
import useGiftCardDetails from "../GiftCardDetailsProvider/hooks/useGiftCardDetails";
interface GiftCardUpdateDialogsProviderProps {
children: React.ReactNode;
params: GiftCardUpdatePageUrlQueryParams;
id: string;
}
export interface GiftCardUpdateDialogsConsumerProps {
navigateBack: () => void;
openSetBalanceDialog: () => void;
closeDialog: () => void;
}
export const GiftCardUpdateDialogsContext = createContext<
GiftCardUpdateDialogsConsumerProps
>(null);
const GiftCardUpdateDialogsProvider: React.FC<GiftCardUpdateDialogsProviderProps> = ({
children,
params,
id
}) => {
const navigate = useNavigator();
const { loading: loadingGiftCard } = useGiftCardDetails();
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 navigateBack = () => navigate(giftCardsListPath);
const providerValues: GiftCardUpdateDialogsConsumerProps = {
openSetBalanceDialog,
closeDialog,
navigateBack
};
return (
<GiftCardUpdateDialogsContext.Provider value={providerValues}>
{children}
{!loadingGiftCard && (
<GiftCardUpdateBalanceDialog
onClose={closeDialog}
open={isSetBalanceDialogOpen}
/>
)}
</GiftCardUpdateDialogsContext.Provider>
);
};
export default GiftCardUpdateDialogsProvider;

View file

@ -0,0 +1,14 @@
import { useContext } from "react";
import {
GiftCardUpdateDialogsConsumerProps,
GiftCardUpdateDialogsContext
} from "../GiftCardUpdateDialogsProvider";
const useGiftCardUpdateDialogs = (): GiftCardUpdateDialogsConsumerProps => {
const giftCardUpdateDialogsProps = useContext(GiftCardUpdateDialogsContext);
return giftCardUpdateDialogsProps;
};
export default useGiftCardUpdateDialogs;

View file

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

View file

@ -0,0 +1,163 @@
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";
import { mapMetadataItemToInput } from "@saleor/utils/maps";
import getMetadata from "@saleor/utils/metadata/getMetadata";
import {
useMetadataUpdate,
usePrivateMetadataUpdate
} from "@saleor/utils/metadata/updateMetadata";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import React, { createContext } from "react";
import { useIntl } from "react-intl";
import { initialData as emptyFormData } from "../../../GiftCardCreateDialog/GiftCardCreateDialogForm";
import { useGiftCardUpdateMutation } from "../../mutations";
import { GiftCardUpdate } from "../../types/GiftCardUpdate";
import useGiftCardDetails from "../GiftCardDetailsProvider/hooks/useGiftCardDetails";
interface GiftCardUpdateFormProviderProps {
children: React.ReactNode;
}
export type GiftCardUpdateFormData = MetadataFormData &
Omit<GiftCardCommonFormData, "balanceAmount" | "balanceCurrency">;
export interface GiftCardUpdateFormConsumerData
extends GiftCardUpdateFormErrors {
opts: MutationResultWithOpts<GiftCardUpdate>;
}
export interface GiftCardUpdateFormErrors {
formErrors: Record<"tag" | "expiryDate" | "expiryPeriod", GiftCardError>;
handlers: { changeMetadata: FormChange };
}
export type GiftCardUpdateFormConsumerProps = UseFormResult<
GiftCardUpdateFormData
> &
GiftCardUpdateFormConsumerData;
export const GiftCardUpdateFormContext = createContext<
GiftCardUpdateFormConsumerProps
>(null);
const GiftCardUpdateFormProvider: React.FC<GiftCardUpdateFormProviderProps> = ({
children
}) => {
const notify = useNotifier();
const intl = useIntl();
const [updateMetadata] = useMetadataUpdate({});
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
const { loading: loadingGiftCard, giftCard } = useGiftCardDetails();
const getInitialData = (): GiftCardUpdateFormData => {
if (loadingGiftCard || !giftCard) {
return { ...emptyFormData, metadata: [], privateMetadata: [] };
}
const {
tag,
expiryDate,
expiryType,
expiryPeriod,
privateMetadata,
metadata
} = giftCard;
return {
tag,
expiryDate,
expiryType,
expiryPeriodType: expiryPeriod?.type || TimePeriodTypeEnum.YEAR,
expiryPeriodAmount: expiryPeriod?.amount || 1,
privateMetadata: privateMetadata?.map(mapMetadataItemToInput),
metadata: metadata?.map(mapMetadataItemToInput)
};
};
const onSubmit = (data: GiftCardUpdate) => {
const errors = data.giftCardUpdate.errors;
notify(getDefaultNotifierSuccessErrorData(errors, intl));
};
const [updateGiftCard, updateGiftCardOpts] = useGiftCardUpdateMutation({
onCompleted: onSubmit
});
const submit = async (formData: GiftCardUpdateFormData) => {
const result = await updateGiftCard({
variables: {
id: giftCard?.id,
input: {
tag: formData.tag,
expirySettings: getGiftCardExpirySettingsInputData(formData)
}
}
});
return result?.data?.giftCardUpdate?.errors;
};
const formProps = useForm<GiftCardUpdateFormData>(getInitialData());
const { data, change, setChanged, hasChanged } = formProps;
const {
isMetadataModified,
isPrivateMetadataModified,
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
const changeMetadata = makeMetadataChangeHandler(change);
const submitData: GiftCardUpdateFormData = {
...data,
...getMetadata(data, isMetadataModified, isPrivateMetadataModified)
};
const handleSubmit = createMetadataUpdateHandler(
giftCard,
submit,
variables => updateMetadata({ variables }),
variables => updatePrivateMetadata({ variables })
);
const formSubmit = () =>
handleFormSubmit(submitData, handleSubmit, setChanged);
const formErrors = getFormErrors(
["tag", "expiryDate", "expiryPeriod"],
updateGiftCardOpts?.data?.giftCardUpdate?.errors
);
const providerValues = {
...formProps,
opts: updateGiftCardOpts,
hasChanged,
formErrors,
submit: formSubmit,
handlers: {
changeMetadata
}
};
return (
<GiftCardUpdateFormContext.Provider value={providerValues}>
{children}
</GiftCardUpdateFormContext.Provider>
);
};
export default GiftCardUpdateFormProvider;

View file

@ -0,0 +1,14 @@
import { useContext } from "react";
import {
GiftCardUpdateFormConsumerData,
GiftCardUpdateFormContext
} from "../GiftCardUpdateFormProvider";
const useGiftCardUpdate = (): Pick<GiftCardUpdateFormConsumerData, "opts"> => {
const { opts } = useContext(GiftCardUpdateFormContext);
return { opts };
};
export default useGiftCardUpdate;

View file

@ -0,0 +1,20 @@
import { UseFormResult } from "@saleor/hooks/useForm";
import omit from "lodash/omit";
import { useContext } from "react";
import {
GiftCardUpdateFormContext,
GiftCardUpdateFormData,
GiftCardUpdateFormErrors
} from "../GiftCardUpdateFormProvider";
type UseGiftCardUpdateFormProps = UseFormResult<GiftCardUpdateFormData> &
GiftCardUpdateFormErrors;
const useGiftCardUpdate = (): UseGiftCardUpdateFormProps => {
const giftCardUpdateFormProviderProps = useContext(GiftCardUpdateFormContext);
return omit(giftCardUpdateFormProviderProps, ["opts"]);
};
export default useGiftCardUpdate;

View file

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

View file

@ -0,0 +1,116 @@
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";
import {
GiftCardDetails,
GiftCardDetailsVariables
} from "./types/GiftCardDetails";
export const giftCardDataFragment = gql`
${fragmentMoney}
${metadataFragment}
${fragmentUserBase}
${fragmentTimePeriod}
fragment GiftCardData on GiftCard {
...MetadataFragment
displayCode
createdBy {
...UserBase
}
product {
id
name
}
user {
...UserBase
}
usedBy {
...UserBase
}
usedByEmail
createdByEmail
app {
id
name
}
created
expiryDate
expiryType
expiryPeriod {
...TimePeriod
}
lastUsedOn
isActive
initialBalance {
...Money
}
currentBalance {
...Money
}
id
tag
}
`;
export const giftCardDetails = gql`
${giftCardDataFragment}
query GiftCardDetails($id: ID!) {
giftCard(id: $id) {
...GiftCardData
events {
expiry {
expiryType
expiryPeriod {
...TimePeriod
}
expiryDate
oldExpiryType
oldExpiryPeriod {
...TimePeriod
}
oldExpiryDate
}
id
date
type
user {
...UserBase
}
app {
id
name
}
message
email
orderId
orderNumber
tag
oldTag
balance {
initialBalance {
...Money
}
currentBalance {
...Money
}
oldInitialBalance {
...Money
}
oldCurrentBalance {
...Money
}
}
}
}
}
`;
export const useGiftCardDetailsQuery = makeQuery<
GiftCardDetails,
GiftCardDetailsVariables
>(giftCardDetails);

View file

@ -0,0 +1,9 @@
import { Dialog } from "@saleor/types";
export enum GiftCardUpdatePageActionParamsEnum {
SET_BALANCE = "set-balance"
}
export type GiftCardUpdatePageUrlQueryParams = Dialog<
GiftCardUpdatePageActionParamsEnum
>;

View file

@ -0,0 +1,97 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardExpiryTypeEnum, TimePeriodTypeEnum } from "./../../../types/globalTypes";
// ====================================================
// GraphQL fragment: GiftCardData
// ====================================================
export interface GiftCardData_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardData_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardData_createdBy {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardData_product {
__typename: "Product";
id: string;
name: string;
}
export interface GiftCardData_user {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardData_usedBy {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardData_app {
__typename: "App";
id: string;
name: string | null;
}
export interface GiftCardData_expiryPeriod {
__typename: "TimePeriod";
amount: number;
type: TimePeriodTypeEnum;
}
export interface GiftCardData_initialBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardData_currentBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardData {
__typename: "GiftCard";
metadata: (GiftCardData_metadata | null)[];
privateMetadata: (GiftCardData_privateMetadata | null)[];
displayCode: string;
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;
currentBalance: GiftCardData_currentBalance | null;
id: string;
tag: string | null;
}

View file

@ -0,0 +1,190 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardExpiryTypeEnum, TimePeriodTypeEnum, GiftCardEventsEnum } from "./../../../types/globalTypes";
// ====================================================
// GraphQL query operation: GiftCardDetails
// ====================================================
export interface GiftCardDetails_giftCard_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardDetails_giftCard_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardDetails_giftCard_createdBy {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardDetails_giftCard_product {
__typename: "Product";
id: string;
name: string;
}
export interface GiftCardDetails_giftCard_user {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardDetails_giftCard_usedBy {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardDetails_giftCard_app {
__typename: "App";
id: string;
name: string | null;
}
export interface GiftCardDetails_giftCard_expiryPeriod {
__typename: "TimePeriod";
amount: number;
type: TimePeriodTypeEnum;
}
export interface GiftCardDetails_giftCard_initialBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardDetails_giftCard_currentBalance {
__typename: "Money";
amount: number;
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;
firstName: string;
lastName: string;
}
export interface GiftCardDetails_giftCard_events_app {
__typename: "App";
id: string;
name: string | null;
}
export interface GiftCardDetails_giftCard_events_balance_initialBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardDetails_giftCard_events_balance_currentBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardDetails_giftCard_events_balance_oldInitialBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardDetails_giftCard_events_balance_oldCurrentBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardDetails_giftCard_events_balance {
__typename: "GiftCardEventBalance";
initialBalance: GiftCardDetails_giftCard_events_balance_initialBalance;
currentBalance: GiftCardDetails_giftCard_events_balance_currentBalance;
oldInitialBalance: GiftCardDetails_giftCard_events_balance_oldInitialBalance | null;
oldCurrentBalance: GiftCardDetails_giftCard_events_balance_oldCurrentBalance | null;
}
export interface GiftCardDetails_giftCard_events {
__typename: "GiftCardEvent";
expiry: GiftCardDetails_giftCard_events_expiry | null;
id: string;
date: any | null;
type: GiftCardEventsEnum | null;
user: GiftCardDetails_giftCard_events_user | null;
app: GiftCardDetails_giftCard_events_app | null;
message: string | null;
email: string | null;
orderId: string | null;
orderNumber: string | null;
tag: string | null;
oldTag: string | null;
balance: GiftCardDetails_giftCard_events_balance | null;
}
export interface GiftCardDetails_giftCard {
__typename: "GiftCard";
metadata: (GiftCardDetails_giftCard_metadata | null)[];
privateMetadata: (GiftCardDetails_giftCard_privateMetadata | null)[];
displayCode: string;
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;
currentBalance: GiftCardDetails_giftCard_currentBalance | null;
id: string;
tag: string | null;
events: GiftCardDetails_giftCard_events[];
}
export interface GiftCardDetails {
giftCard: GiftCardDetails_giftCard | null;
}
export interface GiftCardDetailsVariables {
id: string;
}

View file

@ -0,0 +1,118 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { GiftCardUpdateInput, GiftCardErrorCode, GiftCardExpiryTypeEnum, TimePeriodTypeEnum } from "./../../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: GiftCardUpdate
// ====================================================
export interface GiftCardUpdate_giftCardUpdate_errors {
__typename: "GiftCardError";
code: GiftCardErrorCode;
field: string | null;
}
export interface GiftCardUpdate_giftCardUpdate_giftCard_metadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardUpdate_giftCardUpdate_giftCard_privateMetadata {
__typename: "MetadataItem";
key: string;
value: string;
}
export interface GiftCardUpdate_giftCardUpdate_giftCard_createdBy {
__typename: "User";
id: string;
firstName: string;
lastName: string;
}
export interface GiftCardUpdate_giftCardUpdate_giftCard_product {
__typename: "Product";
id: string;
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;
firstName: string;
lastName: string;
}
export interface GiftCardUpdate_giftCardUpdate_giftCard_app {
__typename: "App";
id: string;
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;
currency: string;
}
export interface GiftCardUpdate_giftCardUpdate_giftCard_currentBalance {
__typename: "Money";
amount: number;
currency: string;
}
export interface GiftCardUpdate_giftCardUpdate_giftCard {
__typename: "GiftCard";
metadata: (GiftCardUpdate_giftCardUpdate_giftCard_metadata | null)[];
privateMetadata: (GiftCardUpdate_giftCardUpdate_giftCard_privateMetadata | null)[];
displayCode: string;
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;
currentBalance: GiftCardUpdate_giftCardUpdate_giftCard_currentBalance | null;
id: string;
tag: string | null;
}
export interface GiftCardUpdate_giftCardUpdate {
__typename: "GiftCardUpdate";
errors: GiftCardUpdate_giftCardUpdate_errors[];
giftCard: GiftCardUpdate_giftCardUpdate_giftCard | null;
}
export interface GiftCardUpdate {
giftCardUpdate: GiftCardUpdate_giftCardUpdate | null;
}
export interface GiftCardUpdateVariables {
id: string;
input: GiftCardUpdateInput;
}

View file

@ -0,0 +1,46 @@
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";
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}
/>
</>
);
};
export default GiftCardsList;

View file

@ -0,0 +1,49 @@
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}>
{intl.formatMessage(messages.issueButtonLabel)}
</Button>
</PageHeader>
);
};
export default GiftCardsListHeader;

View file

@ -0,0 +1,147 @@
import {
Card,
TableBody,
TableCell,
TableRow,
Typography
} from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import Checkbox from "@saleor/components/Checkbox";
import DeleteIconButton from "@saleor/components/DeleteIconButton";
import Link from "@saleor/components/Link";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Skeleton from "@saleor/components/Skeleton";
import StatusChip from "@saleor/components/StatusChip";
import { StatusType } from "@saleor/components/StatusChip/types";
import { customerUrl } from "@saleor/customers/urls";
import { giftCardUrl } from "@saleor/giftCards/urls";
import useNavigator from "@saleor/hooks/useNavigator";
import { renderCollection } from "@saleor/misc";
import { productUrl } from "@saleor/products/urls";
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 { useTableStyles as useStyles } from "../styles";
import GiftCardsListTableFooter from "./GiftCardsListTableFooter";
import GiftCardsListTableHeader from "./GiftCardsListTableHeader";
const PLACEHOLDER = "-";
const GiftCardsListTable: React.FC = () => {
const intl = useIntl();
const classes = useStyles({});
const navigate = useNavigator();
const { giftCards, numberOfColumns, loading } = useGiftCardList();
const { toggle, isSelected } = useGiftCardListBulkActions();
const redirectToGiftCardUpdate = (id: string) => () =>
navigate(giftCardUrl(id));
return (
<Card>
<ResponsiveTable>
<GiftCardsListTableHeader />
<GiftCardsListTableFooter />
<TableBody>
{renderCollection(
giftCards,
({
id,
displayCode,
usedBy,
usedByEmail,
tag,
isActive,
product,
currentBalance
}) => (
<TableRow
onClick={redirectToGiftCardUpdate(id)}
className={classes.row}
key={id}
>
<TableCell padding="checkbox">
<Checkbox
disableClickPropagation
checked={isSelected(id)}
onChange={() => toggle(id)}
/>
</TableCell>
<TableCell className={classes.colCardCode}>
<div className={classes.cardCodeContainer}>
<Typography>
{intl.formatMessage(messages.codeEndingWithLabel, {
displayCode
})}
</Typography>
{!isActive && (
<>
<HorizontalSpacer spacing={2} />
<StatusChip
size="md"
status={StatusType.ERROR}
label={intl.formatMessage(
messages.giftCardDisabledLabel
)}
/>
</>
)}
</div>
</TableCell>
<TableCell>
<Typography>{tag || PLACEHOLDER}</Typography>
</TableCell>
<TableCell>
{product ? (
<Link onClick={() => navigate(productUrl(product?.id))}>
{product?.name}
</Link>
) : (
PLACEHOLDER
)}
</TableCell>
<TableCell>
{usedBy ? (
<Link onClick={() => navigate(customerUrl(usedBy?.id))}>
{`${usedBy?.firstName} ${usedBy?.lastName}`}
</Link>
) : (
<Typography noWrap>{usedByEmail || PLACEHOLDER}</Typography>
)}
</TableCell>
<TableCell align="right" className={classes.colBalance}>
<div className={classes.moneyContainer}>
<Typography variant="caption">
{currentBalance.currency}
</Typography>
<HorizontalSpacer spacing={0.5} />
<Typography>{currentBalance.amount}</Typography>
</div>
</TableCell>
<TableCell className={classes.colDelete}>
<DeleteIconButton />
</TableCell>
</TableRow>
),
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<Skeleton>
{!loading && (
<FormattedMessage {...messages.noGiftCardsFound} />
)}
</Skeleton>
</TableCell>
</TableRow>
)
)}
</TableBody>
</ResponsiveTable>
</Card>
);
};
export default GiftCardsListTable;

View file

@ -0,0 +1,43 @@
import { TableFooter, TableRow } from "@material-ui/core";
import TablePagination from "@saleor/components/TablePagination";
import usePaginator from "@saleor/hooks/usePaginator";
import React from "react";
import useGiftCardList from "../providers/hooks/useGiftCardList";
const GiftCardsListTableFooter: React.FC = () => {
const paginate = usePaginator();
const {
settings,
updateListSettings,
pageInfo: apiPageInfo,
paginationState,
params,
numberOfColumns
} = useGiftCardList();
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
apiPageInfo,
paginationState,
params
);
return (
<TableFooter>
<TableRow>
<TablePagination
settings={settings}
colSpan={numberOfColumns}
hasNextPage={pageInfo ? pageInfo.hasNextPage : false}
onNextPage={loadNextPage}
onUpdateListSettings={updateListSettings}
onPreviousPage={loadPreviousPage}
hasPreviousPage={pageInfo ? pageInfo.hasPreviousPage : false}
/>
</TableRow>
</TableFooter>
);
};
export default GiftCardsListTableFooter;

View file

@ -0,0 +1,83 @@
import { TableCell } from "@material-ui/core";
import TableCellHeader, {
TableCellHeaderProps
} from "@saleor/components/TableCellHeader";
import TableHead from "@saleor/components/TableHead";
import Label, {
LabelSizes
} from "@saleor/orders/components/OrderHistory/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";
interface HeaderItem {
title?: MessageDescriptor;
options?: TableCellHeaderProps;
}
const GiftCardsListTableHeader: React.FC = () => {
const intl = useIntl();
const classes = useStyles({});
const { giftCards, numberOfColumns, loading } = useGiftCardList();
const { toggleAll, listElements } = useGiftCardListBulkActions();
const headerItems: HeaderItem[] = [
{
title: messages.giftCardsTableColumnGiftCardTitle,
options: {
className: classes.colCardCode,
textAlign: "left"
}
},
{
title: messages.giftCardsTableColumnTagTitle
},
{
title: messages.giftCardsTableColumnProductTitle
},
{
title: messages.giftCardsTableColumnCustomerTitle
},
{
title: messages.giftCardsTableColumnBalanceTitle,
options: {
className: classes.colBalance,
textAlign: "right"
}
}
];
return (
<>
<colgroup>
<col />
<col className={classes.colCardCode} />
<col className={classes.colBase} />
<col className={classes.colBase} />
<col className={classes.colBase} />
<col className={classes.colBalance} />
<col className={classes.colDelete} />
</colgroup>
<TableHead
disabled={loading}
colSpan={numberOfColumns}
selected={listElements.length}
items={giftCards}
toggleAll={toggleAll}
>
{headerItems.map(({ title, options }) => (
<TableCellHeader {...options}>
<Label text={intl.formatMessage(title)} size={LabelSizes.md} />
</TableCellHeader>
))}
<TableCell className={classes.colDelete} />
</TableHead>
</>
);
};
export default GiftCardsListTableHeader;

View file

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

View file

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

View file

@ -0,0 +1,55 @@
import { defineMessages } from "react-intl";
export const giftCardsListHeaderMenuItemsMessages = defineMessages({
settings: {
defaultMessage: "Settings",
description: "GiftCardsListHeader menu item settings"
},
bulkIssue: {
defaultMessage: "Bulk Issue",
description: "GiftCardsListHeader menu item settings"
},
exportCodes: {
defaultMessage: "Export card codes",
description: "GiftCardsListHeader menu item settings"
},
issueButtonLabel: {
defaultMessage: "Issue card",
description: "GiftCardsListHeader issue button label"
}
});
export const giftCardsListTableMessages = defineMessages({
giftCardsTableColumnGiftCardTitle: {
defaultMessage: "Gift Card",
description: "GiftCardsListTable column title gift card"
},
giftCardsTableColumnTagTitle: {
defaultMessage: "Tag",
description: "GiftCardsListTable column title tag"
},
giftCardsTableColumnProductTitle: {
defaultMessage: "Product",
description: "GiftCardsListTable column title product"
},
giftCardsTableColumnCustomerTitle: {
defaultMessage: "Used by",
description: "GiftCardsListTable column title customer"
},
giftCardsTableColumnBalanceTitle: {
defaultMessage: "Balance",
description: "GiftCardsListTable column title balance"
},
codeEndingWithLabel: {
defaultMessage: "Code ending with {displayCode}",
description: "GiftCardsListTable code ending with label"
},
giftCardDisabledLabel: {
defaultMessage: "Disabled",
description: "GiftCardsListTable disabled label"
},
noGiftCardsFound: {
defaultMessage: "No gift cards found",
description: "GiftCardsListTable no cards found title"
}
});

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