@evenicanpm/storefront-core 1.0.0 → 1.0.2
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.
- package/package.json +3 -2
- package/src/api-manager/datasources/d365/d365-address.datasource.ts +1 -1
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +0 -19
- package/src/api-manager/datasources/e4/e4-cart.datasource.ts +2 -2
- package/src/api-manager/datasources/e4/e4-product.datasource.ts +19 -4
- package/src/api-manager/datasources/e4/e4.datasource.ts +1 -2
- package/src/api-manager/datasources/e4/e4.remaps.ts +24 -0
- package/src/api-manager/datasources/e4/e4.translator.ts +17 -0
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +131 -2168
- package/src/api-manager/datasources/e4/{e4-user.datasource.ts → user/e4-user.datasource.ts} +5 -7
- package/src/api-manager/datasources/e4/user/e4-user.remaps.ts +4 -0
- package/src/api-manager/datasources/e4/user/e4-user.translator.ts +10 -0
- package/src/api-manager/index.ts +21 -6
- package/src/api-manager/schemas/product.schema.ts +1 -1
- package/src/api-manager/services/address/queries/get-states.ts +1 -1
- package/src/api-manager/services/create-query.ts +10 -3
- package/src/api-manager/types/Datasource.ts +9 -9
- package/src/components/Blocks/Components/ProductCarousel/index.tsx +6 -6
- package/src/components/_components/navbar/category-based-menu/components/categories.tsx +1 -1
- package/src/components/_components/navbar/mega-menu/mega-menu.tsx +1 -1
- package/src/components/_components/navbar/nav-list/nav-item-child.tsx +2 -2
- package/src/components/_components/product-dimensions/product-dimensions.tsx +114 -0
- package/src/components/_components/product-quantity-buttons/product-quantity-buttons.tsx +88 -0
- package/src/components/_components/section-header/section-header.tsx +1 -1
- package/src/components/_components/settings/setting.tsx +1 -1
- package/src/components/_components/wishlist-modal/TODO.md +10 -0
- package/src/components/_components/wishlist-modal/add-to-wishlist-button.tsx +68 -0
- package/src/components/_components/wishlist-modal/add-to-wishlist-dialog.tsx +161 -0
- package/src/components/_components/wishlist-modal/remove-from-wishlist-dialog.tsx +174 -0
- package/src/components/_components/wishlist-modal/wishlist-modal-item.tsx +65 -0
- package/src/components/countries-input.tsx +1 -0
- package/src/hooks/use-cart.tsx +82 -0
- package/src/hooks/use-variants.tsx +85 -0
- package/src/pages/account/addresses/address-form.tsx +289 -0
- package/src/pages/account/addresses/address-item.tsx +104 -0
- package/src/pages/account/dashboard-header.tsx +123 -0
- package/src/pages/account/navigation.tsx +84 -0
- package/src/pages/account/no-records.tsx +20 -0
- package/src/pages/account/orders/icons/delivery.tsx +14 -0
- package/src/pages/account/orders/icons/package-box.tsx +13 -0
- package/src/pages/account/orders/icons/truck-filled.tsx +14 -0
- package/src/pages/account/orders/order-progress.tsx +111 -0
- package/src/pages/account/orders/order-row.tsx +61 -0
- package/src/pages/account/orders/order-summary.tsx +83 -0
- package/src/pages/account/orders/ordered-products.tsx +114 -0
- package/src/pages/account/profile/profile-button.test.tsx +59 -0
- package/src/pages/account/profile/profile-button.tsx +50 -0
- package/src/pages/account/profile/user-info.tsx +59 -0
- package/src/pages/account/styles.ts +32 -0
- package/src/pages/account/table-row.tsx +19 -0
- package/src/pages/account/wishlist/create-new-list.tsx +167 -0
- package/src/pages/account/wishlist/create-wishlist-button.tsx +40 -0
- package/src/pages/account/wishlist/wishlist-item.tsx +82 -0
- package/src/pages/blog/pagination.tsx +38 -0
- package/src/pages/cart/cart-item.tsx +312 -0
- package/src/pages/cart/checkout-form.tsx +122 -0
- package/src/pages/cart/coupon-entry.tsx +90 -0
- package/src/pages/cart/estimate-shipping.tsx +183 -0
- package/src/pages/cart/wrapper.tsx +30 -0
- package/src/pages/checkout/checkout-alt-form/address-card.tsx +73 -0
- package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +133 -0
- package/src/pages/checkout/checkout-alt-form/checkout-step.tsx +120 -0
- package/src/pages/checkout/checkout-alt-form/customer-information.tsx +94 -0
- package/src/pages/checkout/checkout-alt-form/delivery-address.tsx +170 -0
- package/src/pages/checkout/checkout-alt-form/delivery-date.tsx +114 -0
- package/src/pages/checkout/checkout-alt-form/delivery-options.tsx +186 -0
- package/src/pages/checkout/checkout-alt-form/edit-address-form.tsx +130 -0
- package/src/pages/checkout/checkout-alt-form/heading.tsx +50 -0
- package/src/pages/checkout/checkout-alt-form/index.ts +1 -0
- package/src/pages/checkout/checkout-alt-form/new-address-form.tsx +294 -0
- package/src/pages/checkout/checkout-alt-form/node.tsx +9 -0
- package/src/pages/checkout/checkout-alt-form/payment-details.tsx +344 -0
- package/src/pages/checkout/checkout-alt-form/safe-card-preview-data.tsx +36 -0
- package/src/pages/checkout/checkout-alt-form/types.ts +20 -0
- package/src/pages/checkout/checkout-alt-summary/cart-item.tsx +39 -0
- package/src/pages/checkout/checkout-alt-summary/checkout-alt-summary.tsx +40 -0
- package/src/pages/checkout/checkout-alt-summary/index.ts +1 -0
- package/src/pages/checkout/checkout-alt-summary/list-item.tsx +31 -0
- package/src/pages/confirmation/address.tsx +22 -0
- package/src/pages/confirmation/confirmation-summary.tsx +52 -0
- package/src/pages/confirmation/ordered-products.tsx +108 -0
- package/src/pages/product-details/available-shops.tsx +48 -0
- package/src/pages/product-details/bopis/find-in-store-button.tsx +63 -0
- package/src/pages/product-details/bopis/find-in-store-modal.tsx +257 -0
- package/src/pages/product-details/bopis/pickup-option-select.tsx +127 -0
- package/src/pages/product-details/bopis/search-header.tsx +69 -0
- package/src/pages/product-details/checkbox-label.tsx +20 -0
- package/src/pages/product-details/currency.ts +64 -0
- package/src/pages/product-details/frequently-bought.tsx +90 -0
- package/src/pages/product-details/frequently-product-card.tsx +60 -0
- package/src/pages/product-details/product-comment.tsx +44 -0
- package/src/pages/product-details/product-description.tsx +22 -0
- package/src/pages/product-details/product-filter-card.tsx +257 -0
- package/src/pages/product-details/product-intro/product-intro-images.tsx +87 -0
- package/src/pages/product-details/product-intro/product-intro.tsx +250 -0
- package/src/pages/product-details/product-review.tsx +131 -0
- package/src/pages/product-details/product-tabs.tsx +51 -0
- package/src/pages/product-details/related-products.tsx +42 -0
- package/src/pages/product-details/types.ts +11 -0
- package/src/pages/product-list/breadcrumbs.tsx +39 -0
- package/src/pages/product-list/checkbox-label.tsx +20 -0
- package/src/pages/product-list/facet-group.tsx +125 -0
- package/src/pages/product-list/facet.tsx +36 -0
- package/src/pages/product-list/list-filter.tsx +40 -0
- package/src/pages/product-list/pagination.tsx +80 -0
- package/src/pages/product-list/product-list-context.tsx +302 -0
- package/src/pages/product-list/product-list-state.ts +187 -0
- package/src/pages/product-list/product-rating.tsx +16 -0
- package/src/pages/product-list/quick-view-dialog-content.tsx +205 -0
- package/src/pages/product-list/quick-view-dialog.tsx +54 -0
- package/src/pages/product-list/range-filter.tsx +125 -0
- package/src/pages/product-list/scrollbar.tsx +38 -0
- package/src/pages/product-list/search-bar.tsx +162 -0
- package/src/pages/product-list/selected-facets.tsx +80 -0
- package/src/pages/product-list/side-nav.tsx +49 -0
- package/src/pages/product-list/swatch.tsx +80 -0
- package/src/pages/product-list/types.ts +18 -0
- package/src/pages/product-list/use-product-list.ts +12 -0
- package/src/pages/product-list/useCategoryFilter.tsx +26 -0
- package/src/pages/product-list/utils/facet-helpers.ts +5 -0
- package/src/pages/product-list/utils/generate-breadcrumbs.ts +30 -0
- package/src/pages/product-list/utils/getCategoryFilterFromSlug.ts +153 -0
- package/src/pages/product-list/utils/product-list-helper.ts +111 -0
- package/src/pages/product-list/utils/product-list-types.ts +41 -0
- package/src/pages/product-list/utils/search-for-category.ts +76 -0
- package/src/pages/product-list/utils/sort-options.ts +44 -0
- package/src/pages/product-list/utils/use-previous-refiners.ts +14 -0
- package/src/pages/quickorder/order-upload.tsx +150 -0
- package/src/pages/quickorder/quick-order-form.tsx +343 -0
- package/src/pages/quickorder/quick-order-row.tsx +144 -0
- package/tsconfig.json +1 -0
- /package/src/{components/_components/hooks/useOverflowDetect.ts → hooks/use-overflow-detect.ts} +0 -0
- /package/src/{components/_components/hooks/useSettings.ts → hooks/use-settings.ts} +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Fragment } from "react";
|
|
2
|
+
import { useTranslations } from "next-intl";
|
|
3
|
+
// MUI
|
|
4
|
+
import Box from "@mui/material/Box";
|
|
5
|
+
import Card from "@mui/material/Card";
|
|
6
|
+
import Avatar from "@mui/material/Avatar";
|
|
7
|
+
import styled from "@mui/material/styles/styled";
|
|
8
|
+
import { MdDone as Done } from "react-icons/md";
|
|
9
|
+
// CUSTOM ICON COMPONENTS
|
|
10
|
+
import Delivery from "./icons/delivery";
|
|
11
|
+
import PackageBox from "./icons/package-box";
|
|
12
|
+
import TruckFilled from "./icons/truck-filled";
|
|
13
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
14
|
+
import { Paragraph } from "@/components/_components/Typography";
|
|
15
|
+
import { FlexBetween, FlexBox } from "@/components/_components/flex-box/index";
|
|
16
|
+
|
|
17
|
+
// STYLED COMPONENTS
|
|
18
|
+
const StyledFlexbox = styled(FlexBetween)(({ theme }) => ({
|
|
19
|
+
flexWrap: "wrap",
|
|
20
|
+
marginTop: "2rem",
|
|
21
|
+
marginBottom: "2rem",
|
|
22
|
+
[theme.breakpoints.down("sm")]: { flexDirection: "column" },
|
|
23
|
+
"& .line": {
|
|
24
|
+
height: 4,
|
|
25
|
+
minWidth: 50,
|
|
26
|
+
flex: "1 1 0",
|
|
27
|
+
[theme.breakpoints.down("sm")]: { flex: "unset", height: 50, minWidth: 4 },
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const StyledAvatar = styled(Avatar)(({ theme }) => ({
|
|
32
|
+
top: -5,
|
|
33
|
+
right: -5,
|
|
34
|
+
width: 22,
|
|
35
|
+
height: 22,
|
|
36
|
+
position: "absolute",
|
|
37
|
+
bgcolor: theme.palette.grey[200],
|
|
38
|
+
color: theme.palette.success.main,
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
type Props = {
|
|
42
|
+
status: number;
|
|
43
|
+
};
|
|
44
|
+
export default function OrderProgress({ status }: Props) {
|
|
45
|
+
const t = useTranslations("Account.OrderDetails");
|
|
46
|
+
|
|
47
|
+
let ORDER_STATUS = "";
|
|
48
|
+
|
|
49
|
+
// Map API statuses to ORDER_STATUS_LIST values
|
|
50
|
+
if ([1, 2, 4, 5].includes(status)) ORDER_STATUS = "Packaging";
|
|
51
|
+
if ([3].includes(status)) ORDER_STATUS = "Complete";
|
|
52
|
+
|
|
53
|
+
const hideDeliveryDate = !![4, 5].includes(status);
|
|
54
|
+
|
|
55
|
+
const STEP_ICONS = [PackageBox, TruckFilled, Delivery];
|
|
56
|
+
const ORDER_STATUS_LIST = ["Packaging", "Shipping", "Delivering", "Complete"];
|
|
57
|
+
|
|
58
|
+
const statusIndex = ORDER_STATUS_LIST.indexOf(ORDER_STATUS);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Card sx={{ p: "2rem 1.5rem", mb: 4 }}>
|
|
62
|
+
<StyledFlexbox>
|
|
63
|
+
{STEP_ICONS.map((Icon, index) => (
|
|
64
|
+
<Fragment key={index}>
|
|
65
|
+
<Box position="relative">
|
|
66
|
+
<Avatar
|
|
67
|
+
alt="shipping"
|
|
68
|
+
sx={{
|
|
69
|
+
width: 64,
|
|
70
|
+
height: 64,
|
|
71
|
+
color: index <= statusIndex ? "white" : "primary.main",
|
|
72
|
+
bgcolor: index <= statusIndex ? "primary.main" : "grey.300",
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<Icon color="inherit" fontSize="large" />
|
|
76
|
+
</Avatar>
|
|
77
|
+
|
|
78
|
+
{index < statusIndex ? (
|
|
79
|
+
<StyledAvatar alt="done">
|
|
80
|
+
<Done color="inherit" size={16} />
|
|
81
|
+
</StyledAvatar>
|
|
82
|
+
) : null}
|
|
83
|
+
</Box>
|
|
84
|
+
|
|
85
|
+
{index < STEP_ICONS.length - 1 ? (
|
|
86
|
+
<Box
|
|
87
|
+
className="line"
|
|
88
|
+
bgcolor={index < statusIndex ? "primary.main" : "grey.300"}
|
|
89
|
+
/>
|
|
90
|
+
) : null}
|
|
91
|
+
</Fragment>
|
|
92
|
+
))}
|
|
93
|
+
</StyledFlexbox>
|
|
94
|
+
|
|
95
|
+
{!hideDeliveryDate && (
|
|
96
|
+
<FlexBox justifyContent={{ xs: "center", sm: "flex-end" }}>
|
|
97
|
+
<Paragraph
|
|
98
|
+
p="0.5rem 1rem"
|
|
99
|
+
textAlign="center"
|
|
100
|
+
borderRadius="300px"
|
|
101
|
+
color="primary.main"
|
|
102
|
+
bgcolor="primary.light"
|
|
103
|
+
>
|
|
104
|
+
{t(statusIndex === 3 ? "deliveredDate" : "estimatedDeliveryDate")}
|
|
105
|
+
<b> 4th October</b>
|
|
106
|
+
</Paragraph>
|
|
107
|
+
</FlexBox>
|
|
108
|
+
)}
|
|
109
|
+
</Card>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import Box from "@mui/material/Box";
|
|
3
|
+
import Chip from "@mui/material/Chip";
|
|
4
|
+
import IconButton from "@mui/material/IconButton";
|
|
5
|
+
import { MdEast as East } from "react-icons/md";
|
|
6
|
+
import { ChipProps } from "@mui/material";
|
|
7
|
+
import { format } from "date-fns";
|
|
8
|
+
// GLOBAL CUSTOM COMPONENT
|
|
9
|
+
import { H6, Paragraph } from "@/components/_components/Typography";
|
|
10
|
+
// Local CUSTOM COMPONENT
|
|
11
|
+
import TableRow from "../table-row";
|
|
12
|
+
// CUSTOM UTILS LIBRARY FUNCTION
|
|
13
|
+
import useCurrencyFormatter from "@/lib/currency-formatter";
|
|
14
|
+
import { SalesOrder } from "@/api-manager/schemas/order.schema";
|
|
15
|
+
|
|
16
|
+
// =================================================
|
|
17
|
+
type Props = { order: SalesOrder };
|
|
18
|
+
// =================================================
|
|
19
|
+
const STATUS_MAP: Record<number, { label: string; color: ChipProps["color"] }> =
|
|
20
|
+
{
|
|
21
|
+
1: { label: "Open", color: "secondary" },
|
|
22
|
+
2: { label: "Processing", color: "secondary" },
|
|
23
|
+
3: { label: "Complete", color: "success" },
|
|
24
|
+
4: { label: "On Hold", color: "primary" },
|
|
25
|
+
5: { label: "Cancelled", color: "primary" },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getStatusInfo = (status: number) => {
|
|
29
|
+
return STATUS_MAP[status] ?? { label: "Unknown", color: "default" };
|
|
30
|
+
};
|
|
31
|
+
export default function OrderRow({ order }: Props) {
|
|
32
|
+
const { formatCurrency } = useCurrencyFormatter();
|
|
33
|
+
|
|
34
|
+
const { label, color } = getStatusInfo(order.StatusValue ?? 0);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Link href={`/account/orders/${order.Id}`}>
|
|
38
|
+
<TableRow sx={{ gridTemplateColumns: "2fr 1fr 1fr 1fr 1fr" }}>
|
|
39
|
+
<H6 ellipsis>{order?.ChannelReferenceId}</H6>
|
|
40
|
+
|
|
41
|
+
<Box textAlign="center">
|
|
42
|
+
<Chip size="small" label={label} color={color} />
|
|
43
|
+
</Box>
|
|
44
|
+
|
|
45
|
+
<Paragraph textAlign={{ sm: "center", xs: "left" }}>
|
|
46
|
+
{format(new Date(order.TransactionDate ?? ""), "MMM dd, yyyy")}
|
|
47
|
+
</Paragraph>
|
|
48
|
+
|
|
49
|
+
<Paragraph textAlign="center">
|
|
50
|
+
{formatCurrency(order.TotalAmount ?? 0)}
|
|
51
|
+
</Paragraph>
|
|
52
|
+
|
|
53
|
+
<Box display={{ sm: "inline-flex", xs: "none" }} justifyContent="end">
|
|
54
|
+
<IconButton sx={{ color: "grey.500" }}>
|
|
55
|
+
<East fontSize="large" />
|
|
56
|
+
</IconButton>
|
|
57
|
+
</Box>
|
|
58
|
+
</TableRow>
|
|
59
|
+
</Link>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import Grid from "@mui/material/Grid2";
|
|
2
|
+
import Card from "@mui/material/Card";
|
|
3
|
+
import Divider from "@mui/material/Divider";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
6
|
+
import { FlexBetween } from "@/components/_components/flex-box/index";
|
|
7
|
+
import { H5, H6, Paragraph } from "@/components/_components/Typography";
|
|
8
|
+
// CUSTOM UTILS LIBRARY FUNCTION
|
|
9
|
+
import useCurrencyFormatter from "@/lib/currency-formatter";
|
|
10
|
+
import { SalesOrder } from "@/api-manager/schemas/order.schema";
|
|
11
|
+
|
|
12
|
+
// ==============================================================
|
|
13
|
+
type Props = { order: SalesOrder };
|
|
14
|
+
// ==============================================================
|
|
15
|
+
|
|
16
|
+
function ListItem({ title, value }: { title: string; value: string }) {
|
|
17
|
+
const t = useTranslations("Account.OrderDetails");
|
|
18
|
+
return (
|
|
19
|
+
<FlexBetween mb={1}>
|
|
20
|
+
<Paragraph color="grey.600">{t(title)}</Paragraph>
|
|
21
|
+
<H6>{value}</H6>
|
|
22
|
+
</FlexBetween>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function OrderSummary({ order }: Props) {
|
|
27
|
+
const t = useTranslations("Account.OrderDetails");
|
|
28
|
+
const { formatCurrency } = useCurrencyFormatter();
|
|
29
|
+
const shippingAddress = order.ShippingAddress;
|
|
30
|
+
return (
|
|
31
|
+
<Grid container spacing={3}>
|
|
32
|
+
{/* SHIPMENT ADDRESS SECTION */}
|
|
33
|
+
<Grid size={{ lg: 6, md: 6, xs: 12 }}>
|
|
34
|
+
<Card sx={{ p: 3 }}>
|
|
35
|
+
<H5 mt={0} mb={2}>
|
|
36
|
+
{t("addressHeading")}
|
|
37
|
+
</H5>
|
|
38
|
+
|
|
39
|
+
{shippingAddress && (
|
|
40
|
+
<Paragraph fontSize={14} my={0}>
|
|
41
|
+
{`${shippingAddress.Name}, ${shippingAddress.Street}, ${shippingAddress.City} ${shippingAddress.State} ${shippingAddress.ThreeLetterISORegionName} ${shippingAddress.ZipCode}`}
|
|
42
|
+
</Paragraph>
|
|
43
|
+
)}
|
|
44
|
+
</Card>
|
|
45
|
+
</Grid>
|
|
46
|
+
|
|
47
|
+
{/* TOTAL SUMMERY SECTION */}
|
|
48
|
+
<Grid size={{ lg: 6, md: 6, xs: 12 }}>
|
|
49
|
+
<Card sx={{ p: 3 }}>
|
|
50
|
+
<H5 mt={0} mb={2}>
|
|
51
|
+
{t("headingSummary")}
|
|
52
|
+
</H5>
|
|
53
|
+
|
|
54
|
+
<ListItem
|
|
55
|
+
title="subTotal"
|
|
56
|
+
value={formatCurrency(order.SubtotalAmount ?? 0)}
|
|
57
|
+
/>
|
|
58
|
+
<ListItem
|
|
59
|
+
title="shippingTotal"
|
|
60
|
+
value={formatCurrency(order.ShippingChargeAmount ?? 0)}
|
|
61
|
+
/>
|
|
62
|
+
<ListItem
|
|
63
|
+
title="taxTotal"
|
|
64
|
+
value={formatCurrency(order.TaxAmount ?? 0)}
|
|
65
|
+
/>
|
|
66
|
+
<ListItem
|
|
67
|
+
title="discountTotal"
|
|
68
|
+
value={formatCurrency(order.DiscountAmount ?? 0)}
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<Divider sx={{ mb: 1 }} />
|
|
72
|
+
|
|
73
|
+
<FlexBetween mb={2}>
|
|
74
|
+
<H6>{t("total")}</H6>
|
|
75
|
+
<H6>{formatCurrency(order.TotalAmount ?? 0)}</H6>
|
|
76
|
+
</FlexBetween>
|
|
77
|
+
|
|
78
|
+
<Paragraph>{t("paidBy")}</Paragraph>
|
|
79
|
+
</Card>
|
|
80
|
+
</Grid>
|
|
81
|
+
</Grid>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import Card from "@mui/material/Card";
|
|
2
|
+
import Button from "@mui/material/Button";
|
|
3
|
+
import Avatar from "@mui/material/Avatar";
|
|
4
|
+
import Box from "@mui/material/Box";
|
|
5
|
+
import Grid from "@mui/material/Grid2";
|
|
6
|
+
import { format } from "date-fns";
|
|
7
|
+
import Link from "next/link";
|
|
8
|
+
import { useTranslations } from "next-intl";
|
|
9
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
10
|
+
import { H6, Paragraph, Span } from "@/components/_components/Typography";
|
|
11
|
+
import { FlexBetween, FlexBox } from "@/components/_components/flex-box/index";
|
|
12
|
+
// CUSTOM UTILS LIBRARY FUNCTION
|
|
13
|
+
import useCurrencyFormatter from "@/lib/currency-formatter";
|
|
14
|
+
import { SalesLine, SalesOrder } from "@/api-manager/schemas/order.schema";
|
|
15
|
+
import { ProductDimensionType } from "@msdyn365-commerce/retail-proxy";
|
|
16
|
+
import buildDetailUrl from "@/lib/build-detail-url";
|
|
17
|
+
|
|
18
|
+
// ==============================================================
|
|
19
|
+
type Props = { order: SalesOrder };
|
|
20
|
+
// ==============================================================
|
|
21
|
+
|
|
22
|
+
export default function OrderedProducts({ order }: Props) {
|
|
23
|
+
const t = useTranslations("Account.OrderDetails");
|
|
24
|
+
const { formatCurrency } = useCurrencyFormatter();
|
|
25
|
+
|
|
26
|
+
const deliveredDate = "";
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Card sx={{ p: 0, mb: "30px" }}>
|
|
30
|
+
<FlexBetween px={3} py={2} flexWrap="wrap" bgcolor="grey.200">
|
|
31
|
+
<Item title="orderId" value={order?.ChannelReferenceId ?? ""} />
|
|
32
|
+
<Item
|
|
33
|
+
title="orderDate"
|
|
34
|
+
value={format(new Date(order?.TransactionDate ?? ""), "dd MMM, yyyy")}
|
|
35
|
+
/>
|
|
36
|
+
<Item
|
|
37
|
+
title="deliveredOn"
|
|
38
|
+
value={
|
|
39
|
+
deliveredDate
|
|
40
|
+
? format(new Date(deliveredDate), "dd MMM, yyyy")
|
|
41
|
+
: t("deliveredOnValue")
|
|
42
|
+
}
|
|
43
|
+
/>
|
|
44
|
+
</FlexBetween>
|
|
45
|
+
|
|
46
|
+
<Grid
|
|
47
|
+
container
|
|
48
|
+
spacing={2}
|
|
49
|
+
padding={"20px 15px 10px"}
|
|
50
|
+
alignItems={"center"}
|
|
51
|
+
>
|
|
52
|
+
{order?.SalesLines?.map((item: SalesLine, index: number) => (
|
|
53
|
+
<Link
|
|
54
|
+
href={buildDetailUrl(
|
|
55
|
+
item?.Product?.Name ?? "",
|
|
56
|
+
item?.Product?.RecordId ?? 0,
|
|
57
|
+
)}
|
|
58
|
+
key={index}
|
|
59
|
+
style={{ display: "contents" }}
|
|
60
|
+
>
|
|
61
|
+
<Grid size={{ xs: 12, sm: 5 }}>
|
|
62
|
+
<FlexBox gap={2.5} alignItems="center" padding={"12px 15px"}>
|
|
63
|
+
<Avatar
|
|
64
|
+
alt={item?.Product?.Name}
|
|
65
|
+
src={item?.Product?.PrimaryImageUrl}
|
|
66
|
+
sx={{ height: 64, width: 64 }}
|
|
67
|
+
/>
|
|
68
|
+
<div>
|
|
69
|
+
<H6>{item?.Product?.Name}</H6>
|
|
70
|
+
<Paragraph color="grey.600">
|
|
71
|
+
{formatCurrency(item?.Price ?? 0)} x {item?.Quantity || 1}
|
|
72
|
+
</Paragraph>
|
|
73
|
+
</div>
|
|
74
|
+
</FlexBox>
|
|
75
|
+
</Grid>
|
|
76
|
+
|
|
77
|
+
<Grid size={{ xs: 8, sm: 4 }}>
|
|
78
|
+
{item.Product?.Dimensions?.length ? (
|
|
79
|
+
item.Product.Dimensions.map((dim, dimInd) => (
|
|
80
|
+
<Paragraph key={dimInd} paddingLeft={"15px"}>
|
|
81
|
+
<Span>
|
|
82
|
+
{ProductDimensionType[dim.DimensionTypeValue || 0]}:
|
|
83
|
+
</Span>
|
|
84
|
+
<Span sx={{ ml: 1 }}>{dim.DimensionValue?.Value}</Span>
|
|
85
|
+
</Paragraph>
|
|
86
|
+
))
|
|
87
|
+
) : (
|
|
88
|
+
<Box />
|
|
89
|
+
)}
|
|
90
|
+
</Grid>
|
|
91
|
+
|
|
92
|
+
<Grid size={{ xs: 4, sm: 3 }}>
|
|
93
|
+
<Box justifySelf={"end"}>
|
|
94
|
+
<Button variant="text" color="primary">
|
|
95
|
+
{t("writeReview")}
|
|
96
|
+
</Button>
|
|
97
|
+
</Box>
|
|
98
|
+
</Grid>
|
|
99
|
+
</Link>
|
|
100
|
+
))}
|
|
101
|
+
</Grid>
|
|
102
|
+
</Card>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function Item({ title, value }: { title: string; value: string }) {
|
|
107
|
+
const t = useTranslations("Account.OrderDetails");
|
|
108
|
+
return (
|
|
109
|
+
<FlexBox gap={1} alignItems="center">
|
|
110
|
+
<Paragraph color="grey.600">{t(title)}</Paragraph>
|
|
111
|
+
<Paragraph>{value}</Paragraph>
|
|
112
|
+
</FlexBox>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
2
|
+
import { vi } from "vitest";
|
|
3
|
+
import ProfileButton from "./profile-button";
|
|
4
|
+
import { getMsalInstance, initializeMsal, getRequestConfig } from "@/auth/msal";
|
|
5
|
+
|
|
6
|
+
vi.mock("@/auth/msal", () => ({
|
|
7
|
+
initializeMsal: vi.fn(),
|
|
8
|
+
getMsalInstance: vi.fn(),
|
|
9
|
+
getRequestConfig: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock("@/components/T/index", () => ({
|
|
13
|
+
default: ({ path }: { path: string }) => <span>{path}</span>,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe("ProfileButton Component", () => {
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
vi.restoreAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("renders disabled button initially (MSAL not ready)", () => {
|
|
22
|
+
render(<ProfileButton type="edit" path="Account.Profile.btnEditProfile" />);
|
|
23
|
+
const button = screen.getByRole("button");
|
|
24
|
+
expect(button).toBeDisabled();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("initializes MSAL on mount", async () => {
|
|
28
|
+
render(<ProfileButton type="edit" path="Account.Profile.btnEditProfile" />);
|
|
29
|
+
await waitFor(() => {
|
|
30
|
+
expect(initializeMsal).toHaveBeenCalled();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("calls MSAL loginRedirect when clicked", async () => {
|
|
35
|
+
(initializeMsal as ReturnType<typeof vi.fn>).mockImplementation(() =>
|
|
36
|
+
Promise.resolve(),
|
|
37
|
+
);
|
|
38
|
+
(getRequestConfig as ReturnType<typeof vi.fn>).mockReturnValue({
|
|
39
|
+
scopes: ["user.read"],
|
|
40
|
+
});
|
|
41
|
+
(getMsalInstance as ReturnType<typeof vi.fn>).mockReturnValue({
|
|
42
|
+
loginRedirect: vi.fn(),
|
|
43
|
+
});
|
|
44
|
+
render(<ProfileButton type="edit" path="Account.Profile.btnEditProfile" />);
|
|
45
|
+
|
|
46
|
+
await waitFor(() => {
|
|
47
|
+
expect(initializeMsal).toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const button = screen.getByRole("button");
|
|
51
|
+
fireEvent.click(button);
|
|
52
|
+
|
|
53
|
+
await waitFor(() => {
|
|
54
|
+
expect(getMsalInstance().loginRedirect).toHaveBeenCalledWith({
|
|
55
|
+
scopes: ["user.read"],
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { getMsalInstance, initializeMsal, getRequestConfig } from "@/auth/msal";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { Button } from "@mui/material";
|
|
6
|
+
import T from "@/components/T/index";
|
|
7
|
+
import { RedirectRequest } from "@azure/msal-browser";
|
|
8
|
+
interface Props {
|
|
9
|
+
type: string;
|
|
10
|
+
path: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ProfileButton = ({ type, path }: Props) => {
|
|
14
|
+
const [isMsalReady, setIsMsalReady] = useState(false);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const initMsal = async () => {
|
|
18
|
+
await initializeMsal();
|
|
19
|
+
setIsMsalReady(true);
|
|
20
|
+
};
|
|
21
|
+
initMsal();
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
const handleProfile = async () => {
|
|
25
|
+
if (!isMsalReady) {
|
|
26
|
+
console.error("MSAL is not initialized yet.");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const req = getRequestConfig(type);
|
|
32
|
+
await getMsalInstance().loginRedirect(req as RedirectRequest); // "handleRedirectPromise" is in user component.
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error("Profile update through MSAL failed", error);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Button
|
|
40
|
+
onClick={() => handleProfile()}
|
|
41
|
+
disabled={!isMsalReady}
|
|
42
|
+
color="primary"
|
|
43
|
+
sx={{ bgcolor: "primary.light", px: 4 }}
|
|
44
|
+
>
|
|
45
|
+
<T path={path} />
|
|
46
|
+
</Button>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default ProfileButton;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import useMediaQuery from "@mui/material/useMediaQuery";
|
|
3
|
+
import { Theme } from "@mui/material/styles";
|
|
4
|
+
import Card from "@mui/material/Card";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
6
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
7
|
+
import FlexBox from "@/components/_components/flex-box/flex-box";
|
|
8
|
+
import { Small, Span } from "@/components/_components/Typography";
|
|
9
|
+
// API
|
|
10
|
+
import { User } from "@/api-manager/schemas/user.schema";
|
|
11
|
+
type Props = {
|
|
12
|
+
user: User;
|
|
13
|
+
};
|
|
14
|
+
export default function UserInfo({ user }: Props) {
|
|
15
|
+
const downMd = useMediaQuery((theme: Theme) => theme.breakpoints.down("sm"));
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Card
|
|
19
|
+
sx={{
|
|
20
|
+
mt: 3,
|
|
21
|
+
display: "flex",
|
|
22
|
+
flexWrap: "wrap",
|
|
23
|
+
gap: "5rem",
|
|
24
|
+
p: "0.75rem 1.5rem",
|
|
25
|
+
alignItems: "center",
|
|
26
|
+
justifyContent: "flex-start",
|
|
27
|
+
...(downMd && {
|
|
28
|
+
gap: "1rem",
|
|
29
|
+
alignItems: "start",
|
|
30
|
+
flexDirection: "column",
|
|
31
|
+
justifyContent: "flex-start",
|
|
32
|
+
}),
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
<TableRowItem
|
|
36
|
+
title="labelFirstName"
|
|
37
|
+
value={_.capitalize(user?.FirstName)}
|
|
38
|
+
/>
|
|
39
|
+
<TableRowItem
|
|
40
|
+
title="labelLastName"
|
|
41
|
+
value={_.capitalize(user?.LastName)}
|
|
42
|
+
/>
|
|
43
|
+
<TableRowItem title="labelEmail" value={user?.Email ?? ""} />
|
|
44
|
+
</Card>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function TableRowItem({ title, value = "" }: { title: string; value: string }) {
|
|
49
|
+
const t = useTranslations("Account.Profile");
|
|
50
|
+
return (
|
|
51
|
+
<FlexBox flexDirection="column" p={1}>
|
|
52
|
+
<Small color="grey.600" mb={0.5}>
|
|
53
|
+
{t(title)}
|
|
54
|
+
</Small>
|
|
55
|
+
|
|
56
|
+
<Span>{value}</Span>
|
|
57
|
+
</FlexBox>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Card from "@mui/material/Card";
|
|
2
|
+
import styled from "@mui/material/styles/styled";
|
|
3
|
+
import NavLink from "@/components/_components/nav-link/nav-link";
|
|
4
|
+
|
|
5
|
+
export const MainContainer = styled(Card)(({ theme }) => ({
|
|
6
|
+
paddingBottom: "1.5rem",
|
|
7
|
+
[theme.breakpoints.down("md")]: {
|
|
8
|
+
boxShadow: "none",
|
|
9
|
+
overflowY: "auto",
|
|
10
|
+
height: "calc(100vh - 64px)",
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
export const StyledNavLink = styled(NavLink, {
|
|
15
|
+
shouldForwardProp: (prop) => prop !== "isCurrentPath",
|
|
16
|
+
})<{ isCurrentPath: boolean }>(({ theme, isCurrentPath }) => ({
|
|
17
|
+
display: "flex",
|
|
18
|
+
alignItems: "center",
|
|
19
|
+
borderLeft: "4px solid",
|
|
20
|
+
paddingLeft: "1.5rem",
|
|
21
|
+
paddingRight: "1.5rem",
|
|
22
|
+
marginBottom: "1.25rem",
|
|
23
|
+
justifyContent: "space-between",
|
|
24
|
+
borderColor: isCurrentPath ? theme.palette.primary.main : "transparent",
|
|
25
|
+
"& .nav-icon": {
|
|
26
|
+
color: isCurrentPath ? theme.palette.primary.main : theme.palette.grey[600],
|
|
27
|
+
},
|
|
28
|
+
"&:hover": {
|
|
29
|
+
borderColor: theme.palette.primary.main,
|
|
30
|
+
"& .nav-icon": { color: theme.palette.primary.main },
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Card from "@mui/material/Card";
|
|
2
|
+
import styled from "@mui/material/styles/styled";
|
|
3
|
+
|
|
4
|
+
const TableRow = styled(Card)(({ theme }) => ({
|
|
5
|
+
gap: 16,
|
|
6
|
+
marginBlock: 16,
|
|
7
|
+
display: "grid",
|
|
8
|
+
borderRadius: 10,
|
|
9
|
+
cursor: "pointer",
|
|
10
|
+
alignItems: "center",
|
|
11
|
+
padding: ".6rem 1.2rem",
|
|
12
|
+
gridTemplateColumns: "1.5fr 2fr 1.5fr auto",
|
|
13
|
+
[theme.breakpoints.down("sm")]: {
|
|
14
|
+
gap: 8,
|
|
15
|
+
gridTemplateColumns: "repeat(2, 1fr)",
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
export default TableRow;
|