diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 53ba611d7..37e6ee9e5 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -7063,36 +7063,63 @@ "context": "site settings section name", "string": "Site Settings" }, - "src_dot_siteSettings_dot_components_dot_SiteSettingsDetails_dot_1008586926": { + "src_dot_siteSettings_dot_components_dot_SiteCheckoutSettingsCard_dot_reservedStock": { + "context": "title", + "string": "Reserved stock" + }, + "src_dot_siteSettings_dot_components_dot_SiteCheckoutSettingsCard_dot_reservedStockDescription": { + "context": "description", + "string": "Set up time amount that stock in checkout is reserved for the customer. You can set separate values for authenticated and anonymous customers." + }, + "src_dot_siteSettings_dot_components_dot_SiteCheckoutSettingsCard_dot_stockReservationForAnonymousUser": { + "context": "input label", + "string": "Stock reservation for anonymous user (in minutes)" + }, + "src_dot_siteSettings_dot_components_dot_SiteCheckoutSettingsCard_dot_stockReservationForAuthenticatedUser": { + "context": "input label", + "string": "Stock reservation for authenticated user (in minutes)" + }, + "src_dot_siteSettings_dot_components_dot_SiteCheckoutSettingsCard_dot_stockWillNotBeReserved": { + "context": "input helper text", + "string": "Leaving this setting empty will mean that stock won’t be reserved" + }, + "src_dot_siteSettings_dot_components_dot_SiteDetailsSettingsCard_dot_1008586926": { "string": "Name of your store is shown on tab in web browser" }, - "src_dot_siteSettings_dot_components_dot_SiteSettingsDetails_dot_1170194728": { + "src_dot_siteSettings_dot_components_dot_SiteDetailsSettingsCard_dot_1170194728": { "string": "Store domain" }, - "src_dot_siteSettings_dot_components_dot_SiteSettingsDetails_dot_2286355060": { + "src_dot_siteSettings_dot_components_dot_SiteDetailsSettingsCard_dot_2286355060": { "string": "Name of your store" }, - "src_dot_siteSettings_dot_components_dot_SiteSettingsDetails_dot_3868874271": { + "src_dot_siteSettings_dot_components_dot_SiteDetailsSettingsCard_dot_3868874271": { "string": "Store description" }, - "src_dot_siteSettings_dot_components_dot_SiteSettingsDetails_dot_529433178": { + "src_dot_siteSettings_dot_components_dot_SiteDetailsSettingsCard_dot_529433178": { "string": "Store description is shown on taskbar after your store name" }, - "src_dot_siteSettings_dot_components_dot_SiteSettingsPage_dot_1004240342": { - "string": "This adress will be used to generate invoices and calculate shipping rates." - }, "src_dot_siteSettings_dot_components_dot_SiteSettingsPage_dot_229184360": { "context": "section header", "string": "Store Information" }, - "src_dot_siteSettings_dot_components_dot_SiteSettingsPage_dot_2768400497": { - "context": "section header", + "src_dot_siteSettings_dot_components_dot_SiteSettingsPage_dot_sectionCheckoutDescription": { + "context": "section description", + "string": "You can set basic checkout rules that will be applied globally to all your channels" + }, + "src_dot_siteSettings_dot_components_dot_SiteSettingsPage_dot_sectionCheckoutTitle": { + "context": "section title", + "string": "Checkout Configuration" + }, + "src_dot_siteSettings_dot_components_dot_SiteSettingsPage_dot_sectionCompanyDescription": { + "context": "section description", + "string": "This address will be used to generate invoices and calculate shipping rates. Email address you provide here will be used as a contact address for your customers." + }, + "src_dot_siteSettings_dot_components_dot_SiteSettingsPage_dot_sectionCompanyTitle": { + "context": "section title", "string": "Company Information" }, - "src_dot_siteSettings_dot_components_dot_SiteSettingsPage_dot_3657173399": { - "string": "Email adress you provide here will be used as a contact adress for your customers." - }, - "src_dot_siteSettings_dot_components_dot_SiteSettingsPage_dot_3799756739": { + "src_dot_siteSettings_dot_components_dot_SiteSettingsPage_dot_sectionDetailsDescription": { + "context": "section description", "string": "These are general information about your store. They define what is the URL of your store and what is shown in browsers taskbar." }, "src_dot_somethingWentWrong": { diff --git a/schema.graphql b/schema.graphql index 5cdf4b443..37efc9971 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1016,6 +1016,7 @@ type Checkout implements Node & ObjectWithMetadata { email: String! isShippingRequired: Boolean! quantity: Int! + stockReservationExpires: DateTime lines: [CheckoutLine] shippingPrice: TaxedMoney shippingMethod: ShippingMethod @deprecated(reason: "This field will be removed in Saleor 4.0. Use `deliveryMethod` instead.") @@ -6357,6 +6358,8 @@ type Shop { defaultWeightUnit: WeightUnitsEnum translation(languageCode: LanguageCodeEnum!): ShopTranslation automaticFulfillmentDigitalProducts: Boolean + reserveStockDurationAnonymousUser: Int + reserveStockDurationAuthenticatedUser: Int defaultDigitalMaxDownloads: Int defaultDigitalUrlValidDays: Int companyAddress: Address @@ -6416,6 +6419,8 @@ input ShopSettingsInput { defaultMailSenderName: String defaultMailSenderAddress: String customerSetPasswordUrl: String + reserveStockDurationAnonymousUser: Int + reserveStockDurationAuthenticatedUser: Int } type ShopSettingsTranslate { @@ -6899,6 +6904,7 @@ type Voucher implements Node & ObjectWithMetadata { categories(before: String, after: String, first: Int, last: Int): CategoryCountableConnection collections(before: String, after: String, first: Int, last: Int): CollectionCountableConnection products(before: String, after: String, first: Int, last: Int): ProductCountableConnection + variants(before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection countries: [CountryDisplay] translation(languageCode: LanguageCodeEnum!): VoucherTranslation discountValue: Float @@ -6990,6 +6996,7 @@ input VoucherInput { endDate: DateTime discountValueType: DiscountValueTypeEnum products: [ID] + variants: [ID] collections: [ID] categories: [ID] minCheckoutItemsQuantity: Int diff --git a/src/components/PageSectionHeader/PageSectionHeader.tsx b/src/components/PageSectionHeader/PageSectionHeader.tsx new file mode 100644 index 000000000..f7889e68c --- /dev/null +++ b/src/components/PageSectionHeader/PageSectionHeader.tsx @@ -0,0 +1,23 @@ +import { Typography } from "@material-ui/core"; +import VerticalSpacer from "@saleor/apps/components/VerticalSpacer"; +import React from "react"; + +interface PageSectionHeaderProps { + title?: string; + description?: string; +} + +const PageSectionHeader: React.FC = props => { + const { title, description } = props; + + return ( +
+ {title && {title}} + {title && description && } + {description && {description}} +
+ ); +}; + +PageSectionHeader.displayName = "PageSectionHeader"; +export default PageSectionHeader; diff --git a/src/components/PageSectionHeader/index.ts b/src/components/PageSectionHeader/index.ts new file mode 100644 index 000000000..79508a5cf --- /dev/null +++ b/src/components/PageSectionHeader/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PageSectionHeader"; +export * from "./PageSectionHeader"; diff --git a/src/fragments/shop.ts b/src/fragments/shop.ts index f4da82880..155aace7a 100644 --- a/src/fragments/shop.ts +++ b/src/fragments/shop.ts @@ -41,5 +41,7 @@ export const shopFragment = gql` host } name + reserveStockDurationAnonymousUser + reserveStockDurationAuthenticatedUser } `; diff --git a/src/fragments/types/ShopFragment.ts b/src/fragments/types/ShopFragment.ts index 417116a0b..d27d401c8 100644 --- a/src/fragments/types/ShopFragment.ts +++ b/src/fragments/types/ShopFragment.ts @@ -50,4 +50,6 @@ export interface ShopFragment { description: string | null; domain: ShopFragment_domain; name: string; + reserveStockDurationAnonymousUser: number | null; + reserveStockDurationAuthenticatedUser: number | null; } diff --git a/src/siteSettings/components/SiteCheckoutSettingsCard/SiteCheckoutSettingsCard.tsx b/src/siteSettings/components/SiteCheckoutSettingsCard/SiteCheckoutSettingsCard.tsx new file mode 100644 index 000000000..1843f8dd9 --- /dev/null +++ b/src/siteSettings/components/SiteCheckoutSettingsCard/SiteCheckoutSettingsCard.tsx @@ -0,0 +1,91 @@ +import { Card, CardContent, TextField, Typography } from "@material-ui/core"; +import CardTitle from "@saleor/components/CardTitle"; +import FormSpacer from "@saleor/components/FormSpacer"; +import { ShopErrorFragment } from "@saleor/fragments/types/ShopErrorFragment"; +import { getFormErrors } from "@saleor/utils/errors"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { SiteSettingsPageFormData } from "../SiteSettingsPage"; +import { messages } from "./messages"; + +interface SiteCheckoutSettingsCardProps { + data: SiteSettingsPageFormData; + errors: ShopErrorFragment[]; + disabled: boolean; + onChange: (event: React.ChangeEvent) => void; +} + +const SiteCheckoutSettingsCard: React.FC = ({ + data, + disabled, + errors, + onChange +}) => { + const intl = useIntl(); + + const formErrors = getFormErrors( + [ + "reserveStockDurationAuthenticatedUser", + "reserveStockDurationAnonymousUser" + ], + errors + ); + + return ( + + + + + + + + + + + + + ); +}; +SiteCheckoutSettingsCard.displayName = "SiteCheckoutSettingsCard"; +export default SiteCheckoutSettingsCard; diff --git a/src/siteSettings/components/SiteCheckoutSettingsCard/index.ts b/src/siteSettings/components/SiteCheckoutSettingsCard/index.ts new file mode 100644 index 000000000..835b48f3c --- /dev/null +++ b/src/siteSettings/components/SiteCheckoutSettingsCard/index.ts @@ -0,0 +1,2 @@ +export { default } from "./SiteCheckoutSettingsCard"; +export * from "./SiteCheckoutSettingsCard"; diff --git a/src/siteSettings/components/SiteCheckoutSettingsCard/messages.ts b/src/siteSettings/components/SiteCheckoutSettingsCard/messages.ts new file mode 100644 index 000000000..6e8f6f62e --- /dev/null +++ b/src/siteSettings/components/SiteCheckoutSettingsCard/messages.ts @@ -0,0 +1,26 @@ +import { defineMessages } from "react-intl"; + +export const messages = defineMessages({ + reservedStock: { + defaultMessage: "Reserved stock", + description: "title" + }, + reservedStockDescription: { + defaultMessage: + "Set up time amount that stock in checkout is reserved for the customer. You can set separate values for authenticated and anonymous customers.", + description: "description" + }, + stockReservationForAuthenticatedUser: { + defaultMessage: "Stock reservation for authenticated user (in minutes)", + description: "input label" + }, + stockReservationForAnonymousUser: { + defaultMessage: "Stock reservation for anonymous user (in minutes)", + description: "input label" + }, + stockWillNotBeReserved: { + defaultMessage: + "Leaving this setting empty will mean that stock won’t be reserved", + description: "input helper text" + } +}); diff --git a/src/siteSettings/components/SiteSettingsDetails/SiteSettingsDetails.tsx b/src/siteSettings/components/SiteDetailsSettingsCard/SiteDetailsSettingsCard.tsx similarity index 93% rename from src/siteSettings/components/SiteSettingsDetails/SiteSettingsDetails.tsx rename to src/siteSettings/components/SiteDetailsSettingsCard/SiteDetailsSettingsCard.tsx index 9794dfae6..db99679ea 100644 --- a/src/siteSettings/components/SiteSettingsDetails/SiteSettingsDetails.tsx +++ b/src/siteSettings/components/SiteDetailsSettingsCard/SiteDetailsSettingsCard.tsx @@ -11,14 +11,14 @@ import { useIntl } from "react-intl"; import { SiteSettingsPageFormData } from "../SiteSettingsPage"; -interface SiteSettingsDetailsProps { +interface SiteDetailsSettingsCardProps { data: SiteSettingsPageFormData; errors: ShopErrorFragment[]; disabled: boolean; onChange: (event: React.ChangeEvent) => void; } -const SiteSettingsDetails: React.FC = ({ +const SiteDetailsSettingsCard: React.FC = ({ data, disabled, errors, @@ -104,5 +104,5 @@ const SiteSettingsDetails: React.FC = ({ ); }; -SiteSettingsDetails.displayName = "SiteSettingsDetails"; -export default SiteSettingsDetails; +SiteDetailsSettingsCard.displayName = "SiteDetailsSettingsCard"; +export default SiteDetailsSettingsCard; diff --git a/src/siteSettings/components/SiteDetailsSettingsCard/index.ts b/src/siteSettings/components/SiteDetailsSettingsCard/index.ts new file mode 100644 index 000000000..89d0c86e3 --- /dev/null +++ b/src/siteSettings/components/SiteDetailsSettingsCard/index.ts @@ -0,0 +1,2 @@ +export { default } from "./SiteDetailsSettingsCard"; +export * from "./SiteDetailsSettingsCard"; diff --git a/src/siteSettings/components/SiteSettingsDetails/index.ts b/src/siteSettings/components/SiteSettingsDetails/index.ts deleted file mode 100644 index ae552a229..000000000 --- a/src/siteSettings/components/SiteSettingsDetails/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./SiteSettingsDetails"; -export * from "./SiteSettingsDetails"; diff --git a/src/siteSettings/components/SiteSettingsPage/SiteSettingsPage.tsx b/src/siteSettings/components/SiteSettingsPage/SiteSettingsPage.tsx index b4dbdece5..d8c2bdf37 100644 --- a/src/siteSettings/components/SiteSettingsPage/SiteSettingsPage.tsx +++ b/src/siteSettings/components/SiteSettingsPage/SiteSettingsPage.tsx @@ -1,4 +1,3 @@ -import { Typography } from "@material-ui/core"; import CompanyAddressInput from "@saleor/components/CompanyAddressInput"; import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; import Container from "@saleor/components/Container"; @@ -6,6 +5,7 @@ import Form from "@saleor/components/Form"; import Grid from "@saleor/components/Grid"; import Hr from "@saleor/components/Hr"; import PageHeader from "@saleor/components/PageHeader"; +import PageSectionHeader from "@saleor/components/PageSectionHeader"; import Savebar from "@saleor/components/Savebar"; import { ShopErrorFragment } from "@saleor/fragments/types/ShopErrorFragment"; import useAddressValidation from "@saleor/hooks/useAddressValidation"; @@ -17,10 +17,12 @@ import { makeStyles } from "@saleor/macaw-ui"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import { mapCountriesToChoices } from "@saleor/utils/maps"; import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; +import { useIntl } from "react-intl"; import { SiteSettings_shop } from "../../types/SiteSettings"; -import SiteSettingsDetails from "../SiteSettingsDetails/SiteSettingsDetails"; +import SiteCheckoutSettingsCard from "../SiteCheckoutSettingsCard"; +import SiteSettingsDetailsCard from "../SiteDetailsSettingsCard"; +import { messages } from "./messages"; export interface SiteSettingsPageAddressFormData { city: string; @@ -38,6 +40,8 @@ export interface SiteSettingsPageFormData description: string; domain: string; name: string; + reserveStockDurationAnonymousUser: number; + reserveStockDurationAuthenticatedUser: number; } export interface SiteSettingsPageProps { @@ -111,7 +115,10 @@ const SiteSettingsPage: React.FC = props => { ...initialFormAddress, description: shop?.description || "", domain: shop?.domain.host || "", - name: shop?.name || "" + name: shop?.name || "", + reserveStockDurationAnonymousUser: shop?.reserveStockDurationAnonymousUser, + reserveStockDurationAuthenticatedUser: + shop?.reserveStockDurationAuthenticatedUser }; return ( @@ -143,33 +150,38 @@ const SiteSettingsPage: React.FC = props => { underline={true} /> -
- - {intl.formatMessage(sectionNames.siteSettings)} - - - - -
- +
-
- - - - - - - -
+ + +
+ = () => { name: data.name }, shopSettingsInput: { - description: data.description + description: data.description, + reserveStockDurationAnonymousUser: + data.reserveStockDurationAnonymousUser || null, + reserveStockDurationAuthenticatedUser: + data.reserveStockDurationAuthenticatedUser || null }, isCloudInstance: IS_CLOUD_INSTANCE } diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 2136be5e6..55117c259 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -249334,10 +249334,13 @@ exports[`Storyshots Views / Site settings / Page default 1`] = ` >
Site Settings
+
@@ -249493,14 +249496,150 @@ exports[`Storyshots Views / Site settings / Page default 1`] = ` />
+ Checkout Configuration +
+
+
+ You can set basic checkout rules that will be applied globally to all your channels +
+
+
+
+ + Reserved stock + +
+
+
+
+
+
+ Set up time amount that stock in checkout is reserved for the customer. You can set separate values for authenticated and anonymous customers. +
+
+
+ +
+ + +
+

+ Leaving this setting empty will mean that stock won’t be reserved +

+
+
+
+ +
+ + +
+

+ Leaving this setting empty will mean that stock won’t be reserved +

+
+
+
+
+
+
Company Information
+
- This adress will be used to generate invoices and calculate shipping rates.Email adress you provide here will be used as a contact adress for your customers. + This address will be used to generate invoices and calculate shipping rates. Email address you provide here will be used as a contact address for your customers.
Site Settings
+
@@ -250060,14 +250202,150 @@ exports[`Storyshots Views / Site settings / Page form errors 1`] = ` />
+ Checkout Configuration +
+
+
+ You can set basic checkout rules that will be applied globally to all your channels +
+
+
+
+ + Reserved stock + +
+
+
+
+
+
+ Set up time amount that stock in checkout is reserved for the customer. You can set separate values for authenticated and anonymous customers. +
+
+
+ +
+ + +
+

+ Leaving this setting empty will mean that stock won’t be reserved +

+
+
+
+ +
+ + +
+

+ Leaving this setting empty will mean that stock won’t be reserved +

+
+
+
+
+
+
Company Information
+
- This adress will be used to generate invoices and calculate shipping rates.Email adress you provide here will be used as a contact adress for your customers. + This address will be used to generate invoices and calculate shipping rates. Email address you provide here will be used as a contact address for your customers.
Site Settings
+
@@ -250625,14 +250906,152 @@ exports[`Storyshots Views / Site settings / Page loading 1`] = ` />
+ Checkout Configuration +
+
+
+ You can set basic checkout rules that will be applied globally to all your channels +
+
+
+
+ + Reserved stock + +
+
+
+
+
+
+ Set up time amount that stock in checkout is reserved for the customer. You can set separate values for authenticated and anonymous customers. +
+
+
+ +
+ + +
+

+ Leaving this setting empty will mean that stock won’t be reserved +

+
+
+
+ +
+ + +
+

+ Leaving this setting empty will mean that stock won’t be reserved +

+
+
+
+
+
+
Company Information
+
- This adress will be used to generate invoices and calculate shipping rates.Email adress you provide here will be used as a contact adress for your customers. + This address will be used to generate invoices and calculate shipping rates. Email address you provide here will be used as a contact address for your customers.