@evenicanpm/storefront-core 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/package.json +3 -2
  2. package/src/api-manager/datasources/d365/d365-address.datasource.ts +1 -1
  3. package/src/api-manager/datasources/d365/d365-user.datasource.ts +0 -19
  4. package/src/api-manager/datasources/e4/e4-cart.datasource.ts +2 -2
  5. package/src/api-manager/datasources/e4/e4-product.datasource.ts +19 -4
  6. package/src/api-manager/datasources/e4/e4.datasource.ts +1 -2
  7. package/src/api-manager/datasources/e4/e4.remaps.ts +24 -0
  8. package/src/api-manager/datasources/e4/e4.translator.ts +17 -0
  9. package/src/api-manager/datasources/e4/{e4-user.datasource.ts → user/e4-user.datasource.ts} +5 -7
  10. package/src/api-manager/datasources/e4/user/e4-user.remaps.ts +4 -0
  11. package/src/api-manager/datasources/e4/user/e4-user.translator.ts +10 -0
  12. package/src/api-manager/index.ts +21 -6
  13. package/src/api-manager/schemas/product.schema.ts +1 -1
  14. package/src/api-manager/services/address/queries/get-states.ts +1 -1
  15. package/src/api-manager/services/create-query.ts +10 -3
  16. package/src/api-manager/types/Datasource.ts +9 -9
  17. package/src/components/Blocks/Components/ProductCarousel/index.tsx +6 -6
  18. package/src/components/_components/navbar/category-based-menu/components/categories.tsx +1 -1
  19. package/src/components/_components/navbar/mega-menu/mega-menu.tsx +1 -1
  20. package/src/components/_components/navbar/nav-list/nav-item-child.tsx +2 -2
  21. package/src/components/_components/product-dimensions/product-dimensions.tsx +114 -0
  22. package/src/components/_components/product-quantity-buttons/product-quantity-buttons.tsx +88 -0
  23. package/src/components/_components/section-header/section-header.tsx +1 -1
  24. package/src/components/_components/settings/setting.tsx +1 -1
  25. package/src/components/_components/wishlist-modal/TODO.md +10 -0
  26. package/src/components/_components/wishlist-modal/add-to-wishlist-button.tsx +68 -0
  27. package/src/components/_components/wishlist-modal/add-to-wishlist-dialog.tsx +161 -0
  28. package/src/components/_components/wishlist-modal/remove-from-wishlist-dialog.tsx +174 -0
  29. package/src/components/_components/wishlist-modal/wishlist-modal-item.tsx +65 -0
  30. package/src/components/countries-input.tsx +1 -0
  31. package/src/hooks/use-cart.tsx +82 -0
  32. package/src/hooks/use-variants.tsx +85 -0
  33. package/src/pages/account/addresses/address-form.tsx +289 -0
  34. package/src/pages/account/addresses/address-item.tsx +104 -0
  35. package/src/pages/account/dashboard-header.tsx +123 -0
  36. package/src/pages/account/navigation.tsx +84 -0
  37. package/src/pages/account/no-records.tsx +20 -0
  38. package/src/pages/account/orders/icons/delivery.tsx +14 -0
  39. package/src/pages/account/orders/icons/package-box.tsx +13 -0
  40. package/src/pages/account/orders/icons/truck-filled.tsx +14 -0
  41. package/src/pages/account/orders/order-progress.tsx +111 -0
  42. package/src/pages/account/orders/order-row.tsx +61 -0
  43. package/src/pages/account/orders/order-summary.tsx +83 -0
  44. package/src/pages/account/orders/ordered-products.tsx +114 -0
  45. package/src/pages/account/profile/profile-button.test.tsx +59 -0
  46. package/src/pages/account/profile/profile-button.tsx +50 -0
  47. package/src/pages/account/profile/user-info.tsx +59 -0
  48. package/src/pages/account/styles.ts +32 -0
  49. package/src/pages/account/table-row.tsx +19 -0
  50. package/src/pages/account/wishlist/create-new-list.tsx +167 -0
  51. package/src/pages/account/wishlist/create-wishlist-button.tsx +40 -0
  52. package/src/pages/account/wishlist/wishlist-item.tsx +82 -0
  53. package/src/pages/blog/pagination.tsx +38 -0
  54. package/src/pages/cart/cart-item.tsx +312 -0
  55. package/src/pages/cart/checkout-form.tsx +122 -0
  56. package/src/pages/cart/coupon-entry.tsx +90 -0
  57. package/src/pages/cart/estimate-shipping.tsx +183 -0
  58. package/src/pages/cart/wrapper.tsx +30 -0
  59. package/src/pages/checkout/checkout-alt-form/address-card.tsx +73 -0
  60. package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +133 -0
  61. package/src/pages/checkout/checkout-alt-form/checkout-step.tsx +120 -0
  62. package/src/pages/checkout/checkout-alt-form/customer-information.tsx +94 -0
  63. package/src/pages/checkout/checkout-alt-form/delivery-address.tsx +170 -0
  64. package/src/pages/checkout/checkout-alt-form/delivery-date.tsx +114 -0
  65. package/src/pages/checkout/checkout-alt-form/delivery-options.tsx +186 -0
  66. package/src/pages/checkout/checkout-alt-form/edit-address-form.tsx +130 -0
  67. package/src/pages/checkout/checkout-alt-form/heading.tsx +50 -0
  68. package/src/pages/checkout/checkout-alt-form/index.ts +1 -0
  69. package/src/pages/checkout/checkout-alt-form/new-address-form.tsx +294 -0
  70. package/src/pages/checkout/checkout-alt-form/node.tsx +9 -0
  71. package/src/pages/checkout/checkout-alt-form/payment-details.tsx +344 -0
  72. package/src/pages/checkout/checkout-alt-form/safe-card-preview-data.tsx +36 -0
  73. package/src/pages/checkout/checkout-alt-form/types.ts +20 -0
  74. package/src/pages/checkout/checkout-alt-summary/cart-item.tsx +39 -0
  75. package/src/pages/checkout/checkout-alt-summary/checkout-alt-summary.tsx +40 -0
  76. package/src/pages/checkout/checkout-alt-summary/index.ts +1 -0
  77. package/src/pages/checkout/checkout-alt-summary/list-item.tsx +31 -0
  78. package/src/pages/confirmation/address.tsx +22 -0
  79. package/src/pages/confirmation/confirmation-summary.tsx +52 -0
  80. package/src/pages/confirmation/ordered-products.tsx +108 -0
  81. package/src/pages/product-details/available-shops.tsx +48 -0
  82. package/src/pages/product-details/bopis/find-in-store-button.tsx +63 -0
  83. package/src/pages/product-details/bopis/find-in-store-modal.tsx +257 -0
  84. package/src/pages/product-details/bopis/pickup-option-select.tsx +127 -0
  85. package/src/pages/product-details/bopis/search-header.tsx +69 -0
  86. package/src/pages/product-details/checkbox-label.tsx +20 -0
  87. package/src/pages/product-details/currency.ts +64 -0
  88. package/src/pages/product-details/frequently-bought.tsx +90 -0
  89. package/src/pages/product-details/frequently-product-card.tsx +60 -0
  90. package/src/pages/product-details/product-comment.tsx +44 -0
  91. package/src/pages/product-details/product-description.tsx +22 -0
  92. package/src/pages/product-details/product-filter-card.tsx +257 -0
  93. package/src/pages/product-details/product-intro/product-intro-images.tsx +87 -0
  94. package/src/pages/product-details/product-intro/product-intro.tsx +250 -0
  95. package/src/pages/product-details/product-review.tsx +131 -0
  96. package/src/pages/product-details/product-tabs.tsx +51 -0
  97. package/src/pages/product-details/related-products.tsx +42 -0
  98. package/src/pages/product-details/types.ts +11 -0
  99. package/src/pages/product-list/breadcrumbs.tsx +39 -0
  100. package/src/pages/product-list/checkbox-label.tsx +20 -0
  101. package/src/pages/product-list/facet-group.tsx +125 -0
  102. package/src/pages/product-list/facet.tsx +36 -0
  103. package/src/pages/product-list/list-filter.tsx +40 -0
  104. package/src/pages/product-list/pagination.tsx +80 -0
  105. package/src/pages/product-list/product-list-context.tsx +302 -0
  106. package/src/pages/product-list/product-list-state.ts +187 -0
  107. package/src/pages/product-list/product-rating.tsx +16 -0
  108. package/src/pages/product-list/quick-view-dialog-content.tsx +205 -0
  109. package/src/pages/product-list/quick-view-dialog.tsx +54 -0
  110. package/src/pages/product-list/range-filter.tsx +125 -0
  111. package/src/pages/product-list/scrollbar.tsx +38 -0
  112. package/src/pages/product-list/search-bar.tsx +162 -0
  113. package/src/pages/product-list/selected-facets.tsx +80 -0
  114. package/src/pages/product-list/side-nav.tsx +49 -0
  115. package/src/pages/product-list/swatch.tsx +80 -0
  116. package/src/pages/product-list/types.ts +18 -0
  117. package/src/pages/product-list/use-product-list.ts +12 -0
  118. package/src/pages/product-list/useCategoryFilter.tsx +26 -0
  119. package/src/pages/product-list/utils/facet-helpers.ts +5 -0
  120. package/src/pages/product-list/utils/generate-breadcrumbs.ts +30 -0
  121. package/src/pages/product-list/utils/getCategoryFilterFromSlug.ts +153 -0
  122. package/src/pages/product-list/utils/product-list-helper.ts +111 -0
  123. package/src/pages/product-list/utils/product-list-types.ts +41 -0
  124. package/src/pages/product-list/utils/search-for-category.ts +76 -0
  125. package/src/pages/product-list/utils/sort-options.ts +44 -0
  126. package/src/pages/product-list/utils/use-previous-refiners.ts +14 -0
  127. package/src/pages/quickorder/order-upload.tsx +150 -0
  128. package/src/pages/quickorder/quick-order-form.tsx +343 -0
  129. package/src/pages/quickorder/quick-order-row.tsx +144 -0
  130. package/tsconfig.json +1 -0
  131. package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +0 -10464
  132. /package/src/{components/_components/hooks/useOverflowDetect.ts → hooks/use-overflow-detect.ts} +0 -0
  133. /package/src/{components/_components/hooks/useSettings.ts → hooks/use-settings.ts} +0 -0
@@ -0,0 +1,122 @@
1
+ import Link from "next/link";
2
+ // MUI
3
+ import Card from "@mui/material/Card";
4
+ import Button from "@mui/material/Button";
5
+ import Divider from "@mui/material/Divider";
6
+ import TextField from "@mui/material/TextField";
7
+ // GLOBAL CUSTOM COMPONENTS
8
+ import { Span } from "@evenicanpm/storefront-core/src/components/_components/Typography";
9
+ import {
10
+ FlexBetween,
11
+ FlexBox,
12
+ } from "@evenicanpm/storefront-core/src/components/_components/flex-box/index";
13
+ import { useFormatter, useTranslations } from "next-intl";
14
+ import { CountryRegionInfo, Coupon } from "@msdyn365-commerce/retail-proxy";
15
+ import getStates from "@evenicanpm/storefront-core/src/api-manager/services/address/queries/get-states";
16
+ import { debounce } from "lodash";
17
+ import EstimateShipping from "./estimate-shipping";
18
+ import CouponEntry from "./coupon-entry";
19
+ import React from "react";
20
+
21
+ interface CheckoutFormProps {
22
+ cartId: string;
23
+ cartLineIds: string[];
24
+ cartTotal: number;
25
+ countries: CountryRegionInfo[];
26
+ updateCartComment: (comment: string) => void;
27
+ appliedCoupons: Coupon[];
28
+ commentFromData: string;
29
+ checkoutDisabled?: boolean;
30
+ }
31
+
32
+ export default function CheckoutForm({
33
+ cartTotal,
34
+ countries,
35
+ updateCartComment,
36
+ commentFromData,
37
+ appliedCoupons,
38
+ cartId,
39
+ cartLineIds,
40
+ checkoutDisabled,
41
+ }: CheckoutFormProps) {
42
+ const { number } = useFormatter();
43
+ const t = useTranslations("Cart.Sidebar");
44
+ const textRef = React.useRef<HTMLTextAreaElement>();
45
+ const [selectedCountry, setSelectedCountry] = React.useState<
46
+ CountryRegionInfo | undefined
47
+ >(undefined);
48
+ const { data: states } = getStates.useData({
49
+ countryId: selectedCountry?.CountryRegionId,
50
+ });
51
+
52
+ const handleCommentUpdate = (e: React.ChangeEvent<HTMLInputElement>) => {
53
+ updateCartComment(e.target.value);
54
+ };
55
+
56
+ React.useEffect(() => {
57
+ if (textRef.current) {
58
+ textRef.current.value = commentFromData;
59
+ }
60
+ }, [commentFromData]);
61
+
62
+ const debounceCommentUpdate = debounce(handleCommentUpdate, 2500);
63
+
64
+ return (
65
+ <Card sx={{ padding: 3 }}>
66
+ <FlexBetween mb={2}>
67
+ <Span color="grey.600">{t("total")}:</Span>
68
+
69
+ <Span fontSize={18} fontWeight={600} lineHeight="1">
70
+ {number(cartTotal, { style: "currency", currency: "USD" })}
71
+ </Span>
72
+ </FlexBetween>
73
+
74
+ <Divider sx={{ mb: 2 }} />
75
+
76
+ <FlexBox alignItems="center" columnGap={1} mb={2}>
77
+ <Span fontWeight="600">{t("notes")}</Span>
78
+ </FlexBox>
79
+
80
+ {/* COMMENTS TEXT FIELD */}
81
+ <TextField
82
+ variant="outlined"
83
+ rows={6}
84
+ fullWidth
85
+ multiline
86
+ onChange={debounceCommentUpdate}
87
+ inputRef={textRef}
88
+ inputProps={{
89
+ ["data-testid"]: "cart-comment",
90
+ }}
91
+ />
92
+
93
+ <Divider sx={{ mb: 2 }} />
94
+
95
+ {/* APPLY VOUCHER TEXT FIELD */}
96
+ <CouponEntry appliedCoupons={appliedCoupons} cartId={cartId} />
97
+
98
+ <Divider sx={{ mb: 2 }} />
99
+ {/* COUNTRY TEXT FIELD */}
100
+ <EstimateShipping
101
+ countries={countries}
102
+ states={states || []}
103
+ setCountry={setSelectedCountry}
104
+ cartId={cartId}
105
+ cartLineIds={cartLineIds}
106
+ />
107
+
108
+ <Divider sx={{ mb: 2 }} />
109
+
110
+ <Button
111
+ fullWidth
112
+ color="primary"
113
+ href="/checkout"
114
+ variant="contained"
115
+ LinkComponent={Link}
116
+ disabled={checkoutDisabled}
117
+ >
118
+ {t("checkoutButton")}
119
+ </Button>
120
+ </Card>
121
+ );
122
+ }
@@ -0,0 +1,90 @@
1
+ import useAddDiscountCode from "@evenicanpm/storefront-core/src/api-manager/services/cart/mutations/add-discount-code";
2
+ import { Coupon } from "@msdyn365-commerce/retail-proxy";
3
+ import {
4
+ Box,
5
+ Button,
6
+ IconButton,
7
+ List,
8
+ ListItem,
9
+ ListItemText,
10
+ TextField,
11
+ } from "@mui/material";
12
+ import { FlexBox } from "@evenicanpm/storefront-core/src/components/_components/flex-box/index";
13
+ import { Span } from "@evenicanpm/storefront-core/src/components/_components/Typography";
14
+ import { useTranslations } from "next-intl";
15
+ import Close from "@mui/icons-material/Close";
16
+ import React from "react";
17
+ import useRemoveDiscountcode from "@evenicanpm/storefront-core/src/api-manager/services/cart/mutations/remove-discount-code";
18
+
19
+ interface CouponEntryProps {
20
+ appliedCoupons: Coupon[];
21
+ cartId: string;
22
+ }
23
+
24
+ export default function CouponEntry(props: CouponEntryProps) {
25
+ const { appliedCoupons, cartId } = props;
26
+ const [textValue, setTextValue] = React.useState<string>("");
27
+ const { mutate: updateCoupon } = useAddDiscountCode();
28
+ const { mutate: removeCoupon } = useRemoveDiscountcode();
29
+ const t = useTranslations("Cart.Coupons");
30
+
31
+ const handleUpdateTextValue = (e: React.ChangeEvent<HTMLInputElement>) => {
32
+ setTextValue(e.target.value);
33
+ };
34
+
35
+ const handleApplyCoupon = () => {
36
+ updateCoupon({ discountCode: textValue, cartId });
37
+ setTextValue("");
38
+ };
39
+
40
+ const handleRemoveCoupon = (coupon: string) => {
41
+ removeCoupon({ discountCode: coupon, cartId });
42
+ };
43
+ return (
44
+ <Box>
45
+ <TextField
46
+ fullWidth
47
+ size="small"
48
+ label={t("coupon")}
49
+ variant="outlined"
50
+ placeholder={t("coupon")}
51
+ value={textValue}
52
+ onChange={handleUpdateTextValue}
53
+ />
54
+ <Button
55
+ variant="outlined"
56
+ color="primary"
57
+ fullWidth
58
+ sx={{ mt: 2, mb: 4 }}
59
+ onClick={handleApplyCoupon}
60
+ >
61
+ {t("applyCouponButton")}
62
+ </Button>
63
+ {appliedCoupons.length > 0 && (
64
+ <>
65
+ <FlexBox alignItems="center" columnGap={1} mb={2}>
66
+ <Span fontWeight="600">{t("appliedCoupons")}</Span>
67
+ </FlexBox>
68
+ {appliedCoupons.map((coupon) => (
69
+ <List key={coupon.Code}>
70
+ <ListItem
71
+ secondaryAction={
72
+ <IconButton
73
+ edge="end"
74
+ aria-label="delete"
75
+ onClick={() => handleRemoveCoupon(coupon.Code ?? "")}
76
+ title={"Remove Coupon"}
77
+ >
78
+ <Close />
79
+ </IconButton>
80
+ }
81
+ >
82
+ <ListItemText primary={coupon.Code} />
83
+ </ListItem>
84
+ </List>
85
+ ))}
86
+ </>
87
+ )}
88
+ </Box>
89
+ );
90
+ }
@@ -0,0 +1,183 @@
1
+ import useGetDeliveryOptions from "@evenicanpm/storefront-core/src/api-manager/services/address/mutations/get-delivery-options";
2
+ import { Span } from "@evenicanpm/storefront-core/src/components/_components/Typography";
3
+ import CountryInput from "@evenicanpm/storefront-core/src/components/countries-input";
4
+ import useCurrencyFormatter from "@evenicanpm/storefront-core/src/lib/currency-formatter";
5
+ import {
6
+ CountryRegionInfo,
7
+ DeliveryOption,
8
+ StateProvinceInfo,
9
+ } from "@msdyn365-commerce/retail-proxy";
10
+ import {
11
+ Box,
12
+ Button,
13
+ Divider,
14
+ List,
15
+ ListItem,
16
+ ListItemText,
17
+ MenuItem,
18
+ TextField,
19
+ } from "@mui/material";
20
+ import { Address } from "@evenicanpm/storefront-core/src/api-manager/schemas/address.schema";
21
+ import { useTranslations } from "next-intl";
22
+ import React from "react";
23
+
24
+ export interface EstimateShippingProps {
25
+ countries: CountryRegionInfo[];
26
+ states: StateProvinceInfo[];
27
+ setCountry: (country: CountryRegionInfo) => void;
28
+ cartId: string;
29
+ cartLineIds: string[];
30
+ defaultCountryCode?: string;
31
+ }
32
+
33
+ export default function EstimateShipping(props: EstimateShippingProps) {
34
+ const {
35
+ countries,
36
+ states,
37
+ setCountry,
38
+ cartId,
39
+ cartLineIds,
40
+ defaultCountryCode,
41
+ } = props;
42
+ const t = useTranslations("Cart.Sidebar");
43
+ const defaultCountry = countries.find(
44
+ (c) => c.CountryRegionId === (defaultCountryCode ?? "USA"),
45
+ );
46
+ const [selectedCountry, setSelectedCountry] =
47
+ React.useState<CountryRegionInfo | null>(defaultCountry ?? null);
48
+ const [selectedState, setSelectedState] = React.useState<string | undefined>(
49
+ undefined,
50
+ );
51
+ const [zipCode, setZipCode] = React.useState<string>("");
52
+ const [deliveryOptions, setDeliveryOptions] = React.useState<
53
+ DeliveryOption[]
54
+ >([]);
55
+ const [noOptionsFound, setNoOptionsFound] = React.useState<boolean>(false);
56
+ const { formatCurrency } = useCurrencyFormatter();
57
+ const { mutateAsync: getDeliveryOptions } = useGetDeliveryOptions();
58
+
59
+ React.useEffect(() => {
60
+ if (selectedCountry) {
61
+ setCountry(selectedCountry);
62
+ }
63
+ }, [selectedCountry, setCountry]);
64
+
65
+ const handleStateChange = (event: React.ChangeEvent<HTMLInputElement>) =>
66
+ setSelectedState(event.target.value);
67
+
68
+ const handleZipCodeChange = (event: React.ChangeEvent<HTMLInputElement>) =>
69
+ setZipCode(event.target.value);
70
+
71
+ const createAddressFromInput = (): Address => ({
72
+ ZipCode: zipCode,
73
+ State: selectedState,
74
+ TwoLetterISORegionName: selectedCountry?.ISOCode,
75
+ ThreeLetterISORegionName: selectedCountry?.CountryRegionId,
76
+ });
77
+
78
+ const doDeliveryMethodLookup = async () => {
79
+ if (!zipCode || !selectedState || !selectedCountry) {
80
+ return;
81
+ }
82
+ setNoOptionsFound(false);
83
+ const data = await getDeliveryOptions({
84
+ cartId,
85
+ cartLineIds,
86
+ shippingAddress: createAddressFromInput(),
87
+ });
88
+ if (data.length) {
89
+ setDeliveryOptions(data);
90
+ } else {
91
+ setNoOptionsFound(true);
92
+ }
93
+ };
94
+
95
+ const renderOptionsSection = () => {
96
+ if (noOptionsFound) {
97
+ return <Span color="error.main">{t("noDeliveryOptionsFound")}</Span>;
98
+ } else if (deliveryOptions.length) {
99
+ return (
100
+ <Box>
101
+ <Span fontWeight={600} mb={2} display="block">
102
+ {t("deliveryOptionsLabel")}
103
+ </Span>
104
+ <Divider sx={{ mb: 2 }} />
105
+ <List>
106
+ {deliveryOptions.map((dl) => (
107
+ <ListItem
108
+ key={dl.RecordId}
109
+ secondaryAction={
110
+ <Span>{formatCurrency(dl.TotalChargeAmount || 0)}</Span>
111
+ }
112
+ >
113
+ <ListItemText primary={dl.Description} />
114
+ </ListItem>
115
+ ))}
116
+ </List>
117
+ </Box>
118
+ );
119
+ }
120
+ };
121
+ return (
122
+ <Box>
123
+ <Span fontWeight={600} mb={2} display="block">
124
+ {t("estimatesLabel")}
125
+ </Span>
126
+
127
+ {/* COUNTRY TEXT FIELD */}
128
+ <Box sx={{ mb: 2 }}>
129
+ <CountryInput
130
+ selectedCountry={selectedCountry}
131
+ setSelectedCountry={setSelectedCountry}
132
+ countries={countries}
133
+ label={t("countryLabel")}
134
+ />
135
+ </Box>
136
+
137
+ {/* STATE/CITY TEXT FIELD */}
138
+ <TextField
139
+ select
140
+ fullWidth
141
+ size="small"
142
+ label={t("stateLabel")}
143
+ value={selectedState || 0}
144
+ onChange={handleStateChange}
145
+ variant="outlined"
146
+ placeholder="Select State"
147
+ >
148
+ <MenuItem value={0}>Select a state</MenuItem>
149
+ {states &&
150
+ states.length > 0 &&
151
+ states.map(({ StateName, StateId }) => (
152
+ <MenuItem value={StateId} key={StateName}>
153
+ {StateName}
154
+ </MenuItem>
155
+ ))}
156
+ </TextField>
157
+
158
+ {/* ZIP-CODE TEXT FIELD */}
159
+ <TextField
160
+ fullWidth
161
+ size="small"
162
+ label={t("zipCodeLabel")}
163
+ placeholder="3100"
164
+ variant="outlined"
165
+ value={zipCode}
166
+ onChange={handleZipCodeChange}
167
+ sx={{ mt: 2 }}
168
+ />
169
+
170
+ <Button
171
+ variant="outlined"
172
+ color="primary"
173
+ fullWidth
174
+ sx={{ my: 2 }}
175
+ onClick={doDeliveryMethodLookup}
176
+ >
177
+ {t("calculateShippingLabel")}
178
+ </Button>
179
+
180
+ {renderOptionsSection()}
181
+ </Box>
182
+ );
183
+ }
@@ -0,0 +1,30 @@
1
+ import Card from "@mui/material/Card";
2
+ import styled from "@mui/material/styles/styled";
3
+
4
+ export const Wrapper = styled(Card)(({ theme }) => ({
5
+ display: "flex",
6
+ overflow: "hidden",
7
+ alignItems: "center",
8
+ position: "relative",
9
+ borderRadius: "10px",
10
+ marginBottom: "1.5rem",
11
+ boxShadow: theme.shadows[2],
12
+ backgroundColor: theme.palette.background.paper,
13
+
14
+ "@media only screen and (max-width: 425px)": {
15
+ flexWrap: "wrap",
16
+ img: { height: "auto", minWidth: "100%" },
17
+ },
18
+ "& .img-wrapper": {
19
+ width: 150,
20
+ height: 150,
21
+ flexShrink: 0,
22
+ position: "relative",
23
+ backgroundColor: theme.palette.grey[200],
24
+
25
+ "@media only screen and (max-width: 425px)": {
26
+ width: "100%",
27
+ height: "auto",
28
+ },
29
+ },
30
+ }));
@@ -0,0 +1,73 @@
1
+ import { ModeEditOutline, DeleteOutline } from "@mui/icons-material";
2
+ import { Card, IconButton } from "@mui/material";
3
+ import Grid from "@mui/material/Grid2";
4
+ import { Address } from "@/api-manager/schemas/address.schema";
5
+ import { FlexBox } from "@/components/_components/flex-box";
6
+ import { H6, Paragraph } from "@/components/_components/Typography";
7
+
8
+ interface Props {
9
+ address: Address;
10
+ isCartAddress: boolean;
11
+ hideControls?: boolean;
12
+ onEdit?: () => void;
13
+ onSelect?: () => void;
14
+ onDelete?: () => void;
15
+ }
16
+
17
+ export default function AddressCard({
18
+ address,
19
+ isCartAddress,
20
+ hideControls,
21
+ onEdit,
22
+ onSelect,
23
+ onDelete,
24
+ }: Props) {
25
+ return (
26
+ <Grid onClick={() => onSelect?.()}>
27
+ <Card
28
+ sx={{
29
+ padding: 2,
30
+ boxShadow: "none",
31
+ cursor: onSelect ? "pointer" : "default",
32
+ border: "1px solid",
33
+ position: "relative",
34
+ backgroundColor: "grey.100",
35
+ borderColor: isCartAddress ? "secondary.main" : "transparent",
36
+ }}
37
+ >
38
+ {!hideControls && (
39
+ <FlexBox position="absolute" top={5} right={5}>
40
+ <IconButton
41
+ size="small"
42
+ onClick={(e) => {
43
+ e.stopPropagation();
44
+ onEdit?.();
45
+ }}
46
+ >
47
+ <ModeEditOutline fontSize="inherit" />
48
+ </IconButton>
49
+ <IconButton
50
+ size="small"
51
+ color="error"
52
+ onClick={(e) => {
53
+ e.stopPropagation();
54
+ onDelete?.();
55
+ }}
56
+ disabled={isCartAddress}
57
+ >
58
+ <DeleteOutline fontSize="inherit" />
59
+ </IconButton>
60
+ </FlexBox>
61
+ )}
62
+ <H6 mb={0.5}>{address.Name}</H6>
63
+ <Paragraph color="grey.700">{address.Street}</Paragraph>
64
+ <Paragraph color="grey.700">{address.City}</Paragraph>
65
+ <Paragraph color="grey.700">
66
+ {address.State}, {address.ThreeLetterISORegionName}
67
+ </Paragraph>
68
+ <Paragraph color="grey.700">{address.ZipCode}</Paragraph>
69
+ <Paragraph color="grey.700">{address.Phone}</Paragraph>
70
+ </Card>
71
+ </Grid>
72
+ );
73
+ }
@@ -0,0 +1,133 @@
1
+ "use client";
2
+ // LOCAL CUSTOM COMPONENTS
3
+ import DeliveryAddress from "./delivery-address";
4
+
5
+ import { Cart } from "@/api-manager/schemas/cart.schema";
6
+ import CustomerInformation from "./customer-information";
7
+ import { Box, Button } from "@mui/material";
8
+ import Grid from "@mui/material/Grid2";
9
+ import DeliveryOptions from "./delivery-options";
10
+ import React from "react";
11
+ import CheckoutStep from "./checkout-step";
12
+ import PaymentDetails from "./payment-details";
13
+ import useCheckout from "@/api-manager/services/cart/mutations/checkout";
14
+ import { useQueryClient } from "@tanstack/react-query";
15
+ import { CardPaymentAcceptResult } from "@msdyn365-commerce/retail-proxy";
16
+ import { useRouter } from "next/navigation";
17
+ import { PageSlug } from "@/lib/page-slugs";
18
+ import { deleteCartCookie, setCartCookie } from "@/lib/cart-cookie-handler";
19
+ import getChannelConfiguration from "@/api-manager/services/organization/queries/get-channel-configuration";
20
+ import { useTranslations } from "next-intl";
21
+ import getCart from "@/api-manager/services/cart/queries/get-cart";
22
+ import { useNotification } from "@/components/Notifications/use-notification";
23
+ interface Props {
24
+ cart: Cart;
25
+ }
26
+
27
+ export default function CheckoutForm({ cart }: Props) {
28
+ const t = useTranslations("Checkout");
29
+ const [activeStep, setActiveStep] = React.useState<number>(1);
30
+ const { mutateAsync: placeOrder } = useCheckout();
31
+ const { setNotification } = useNotification();
32
+ const router = useRouter();
33
+ const queryClient = useQueryClient();
34
+ const { data: channelConfigurationData } = getChannelConfiguration.useData();
35
+ const isFormComplete = () => activeStep > steps.length;
36
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
37
+ const steps = [
38
+ {
39
+ title: t("Steps.customerInformation"),
40
+ component: CustomerInformation,
41
+ },
42
+ { title: t("Steps.deliveryAddress"), component: DeliveryAddress },
43
+ { title: t("Steps.deliveryOptions"), component: DeliveryOptions },
44
+ { title: t("Steps.payment"), component: PaymentDetails },
45
+ ];
46
+ const handleStepChange = (step: number) => {
47
+ setActiveStep(step);
48
+ };
49
+
50
+ const handlePlaceOrder = async () => {
51
+ setIsSubmitting(true);
52
+ const cache = queryClient.getQueryCache();
53
+ const acceptResultCache = cache.get(
54
+ JSON.stringify(["paymentAcceptResult", cart.Id]),
55
+ );
56
+ const paymentAcceptResult = acceptResultCache?.state
57
+ .data as CardPaymentAcceptResult;
58
+ if (!paymentAcceptResult) {
59
+ return;
60
+ }
61
+
62
+ if (!paymentAcceptResult.TokenizedPaymentCard) {
63
+ return;
64
+ }
65
+ try {
66
+ const data = await placeOrder({
67
+ id: cart.Id,
68
+ receiptEmail: cart.ReceiptEmail ?? "",
69
+ tokenizedPaymentCard: paymentAcceptResult.TokenizedPaymentCard,
70
+ currency: channelConfigurationData?.Currency ?? "",
71
+ amount: cart.TotalAmount || 0,
72
+ });
73
+ await cleanupCart(data.Id);
74
+ setNotification({
75
+ message: t("placeOrderSuccessMessage"),
76
+ severity: "success",
77
+ });
78
+ } catch (error) {
79
+ console.error(error);
80
+ setNotification({
81
+ message: t("placeOrderErrorMessage"),
82
+ severity: "error",
83
+ });
84
+ }
85
+ setIsSubmitting(false);
86
+ };
87
+
88
+ const cleanupCart = async (transactionId: string) => {
89
+ // Clear cached cart data
90
+ // await deleteCartCookie();
91
+ // Fetch a new cart
92
+ // const newCart = await getCart.fetchData(queryClient);
93
+ // Set new cookie
94
+ // setCartCookie(newCart.Id);
95
+ //Go to confirmation page
96
+ router.push(`${PageSlug.Confirmation}?transactionId=${transactionId}`);
97
+ };
98
+
99
+ const renderFormFooter = () => {
100
+ return (
101
+ <Grid container justifyContent="flex-end">
102
+ <Button
103
+ disabled={!isFormComplete() || isSubmitting}
104
+ variant="contained"
105
+ color="primary"
106
+ onClick={() => handlePlaceOrder()}
107
+ >
108
+ {t("placeOrderButton")}
109
+ </Button>
110
+ </Grid>
111
+ );
112
+ };
113
+ return (
114
+ <Box>
115
+ {steps.map((step, index) => {
116
+ const stepNumber = index + 1;
117
+ return (
118
+ <CheckoutStep
119
+ title={step.title}
120
+ stepNumber={stepNumber}
121
+ activeStep={activeStep}
122
+ setActiveStep={handleStepChange}
123
+ isLastStep={stepNumber === steps.length}
124
+ key={stepNumber}
125
+ cart={cart}
126
+ component={step.component}
127
+ />
128
+ );
129
+ })}
130
+ {renderFormFooter()}
131
+ </Box>
132
+ );
133
+ }