@evenicanpm/storefront-core 1.0.0-alpha → 1.0.0
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 +1 -1
- package/src/api-manager/api-manager.config.json +1 -1
- package/src/api-manager/datasources/d365/d365-address.datasource.ts +4 -4
- package/src/api-manager/datasources/d365/d365-cart.datasource.ts +100 -7
- package/src/api-manager/datasources/d365/d365-product.datasource.ts +39 -17
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +220 -49
- package/src/api-manager/datasources/d365/utils/decode-jwt.ts +2 -2
- package/src/api-manager/datasources/e4/e4-cart.datasource.ts +6 -0
- package/src/api-manager/datasources/e4/e4-product.datasource.ts +5 -0
- package/src/api-manager/datasources/e4/e4-user.datasource.ts +39 -15
- package/src/api-manager/datasources/e4/e4.datasource.ts +1 -0
- package/src/api-manager/datasources/e4/e4.translator.ts +2 -1
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +5231 -38282
- package/src/api-manager/index.ts +58 -11
- package/src/api-manager/lib/get-graphql-client.ts +7 -7
- package/src/api-manager/schemas/address.schema.ts +1 -19
- package/src/api-manager/schemas/cart.schema.ts +18 -0
- package/src/api-manager/schemas/product.schema.ts +7 -0
- package/src/api-manager/schemas/user.schema.ts +52 -1
- package/src/api-manager/services/address/queries/get-states.ts +1 -1
- package/src/api-manager/services/cart/mutations/checkout.ts +16 -1
- package/src/api-manager/services/cart/queries/get-cart-lines-inventory.ts +15 -0
- package/src/api-manager/services/create-extension-query.ts +26 -0
- package/src/api-manager/services/create-mutation.ts +5 -1
- package/src/api-manager/services/create-query.ts +0 -1
- package/src/api-manager/services/organization/queries/get-channel-configuration.ts +1 -1
- package/src/api-manager/services/product/queries/get-product-prices.ts +16 -0
- package/src/api-manager/services/product/queries/search-products.ts +1 -0
- package/src/api-manager/services/user/mutations/add-to-wishlist.ts +24 -0
- package/src/api-manager/services/user/mutations/copy-cart-to-wishlist.ts +23 -0
- package/src/api-manager/services/user/mutations/create-wishlist.ts +4 -4
- package/src/api-manager/services/user/mutations/delete-wishlist.ts +4 -3
- package/src/api-manager/services/user/mutations/remove-from-wishlist.ts +21 -0
- package/src/api-manager/services/user/mutations/update-wishlist.ts +23 -0
- package/src/api-manager/services/user/queries/get-wishlist-details.ts +11 -4
- package/src/api-manager/services/user/queries/get-wishlists.ts +4 -3
- package/src/api-manager/types/Datasource.ts +33 -5
- package/src/auth/auth-options.ts +2 -2
- package/src/auth/msal.ts +9 -9
- package/src/auth/next-auth-cookie-manager.ts +5 -3
- package/src/auth/providers/aadb2c-provider.ts +3 -4
- package/src/cms/endpoints.ts +2 -3
- package/src/components/_components/LazyImage.tsx +5 -2
- package/src/components/_components/header/components/user.tsx +1 -2
- package/src/components/_components/product-cards/product-card-1/components/quantity-buttons.tsx +17 -8
- package/src/components/_components/product-cards/product-card-1/product-card-1-wrapper.tsx +304 -0
- package/src/components/_components/product-cards/product-card-1/product-card.tsx +16 -55
- package/src/components/_components/product-cards/product-card-8/product-card.tsx +80 -60
- package/src/components/_components/product-cards/product-card-8/styles/index.ts +10 -0
- package/src/components/_components/product-cards/product-card-wishlist/components/hover-actions.tsx +37 -0
- package/src/components/_components/product-cards/product-card-wishlist/index.ts +1 -0
- package/src/components/_components/product-cards/product-card-wishlist/product-card.tsx +118 -0
- package/src/components/_components/product-cards/product-card-wishlist/styles/index.ts +51 -0
- package/src/components/_components/products-view/product-view-dialog.tsx +48 -10
- package/src/components/_components/products-view/products-grid-view.tsx +31 -17
- package/src/components/_components/products-view/products-list-view.tsx +30 -16
- package/src/components/_components/search-box/__test__/search-input-category.test.tsx +110 -0
- package/src/components/_components/search-box/__test__/search-input.test.tsx +137 -0
- package/src/components/_components/search-box/__test__/search-results.json +165 -0
- package/src/components/_components/search-box/components/search-result.tsx +66 -12
- package/src/components/_components/search-box/search-input-category.tsx +135 -32
- package/src/components/_components/search-box/styles/index.ts +2 -0
- package/src/components/_components/section-header/section-creator.tsx +1 -0
- package/src/components/_components/wishlist/add-to-wishlist-dialog.tsx +161 -0
- package/src/components/_components/wishlist/components/create-new-list.tsx +167 -0
- package/src/components/_components/wishlist/components/create-wishlist-button.tsx +40 -0
- package/src/components/_components/wishlist/components/dashboard-header.tsx +123 -0
- package/src/components/_components/wishlist/components/navigation.tsx +87 -0
- package/src/components/_components/wishlist/components/no-records.tsx +20 -0
- package/src/components/_components/wishlist/components/wishlist-modal-item.tsx +66 -0
- package/src/components/_components/wishlist/remove-from-wishlist-dialog.tsx +174 -0
- package/src/components/_components/wishlist/styles.ts +61 -0
- package/src/components/countries-input.tsx +6 -12
- package/src/lib/cart-cookie-handler-constants.ts +8 -0
- package/src/lib/cart-cookie-handler.ts +27 -5
- package/src/lib/create-graphql-client.ts +1 -2
- package/src/lib/product-list-types.ts +41 -0
- package/src/config.ts +0 -47
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as cartSchemas from "@/api-manager/schemas/cart.schema";
|
|
2
2
|
import * as productSchemas from "@/api-manager/schemas/product.schema";
|
|
3
|
+
import * as userSchemas from "@/api-manager/schemas/user.schema";
|
|
3
4
|
import { User, UserCounts } from "@/api-manager/schemas/user.schema";
|
|
4
5
|
import {
|
|
5
6
|
Session,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
SimpleProduct,
|
|
32
33
|
StateProvinceInfo,
|
|
33
34
|
ProductWarehouseInventoryAvailability,
|
|
35
|
+
ProductWarehouseInventoryInformation,
|
|
34
36
|
} from "@msdyn365-commerce/retail-proxy";
|
|
35
37
|
import {
|
|
36
38
|
CategoriesQuery,
|
|
@@ -89,14 +91,34 @@ export interface OrganizationApi {
|
|
|
89
91
|
* User Data Actions
|
|
90
92
|
*/
|
|
91
93
|
export interface UserApi {
|
|
94
|
+
updateUser(user: userSchemas.User): Promise<userSchemas.User>;
|
|
95
|
+
getUser(): Promise<userSchemas.User | null>;
|
|
96
|
+
getUserCounts(userId: string): Promise<UserCounts>;
|
|
97
|
+
// wishlists
|
|
98
|
+
getWishlists(): Promise<userSchemas.Wishlist[]>;
|
|
99
|
+
getWishlistDetails(
|
|
100
|
+
input: userSchemas.WishlistDetailInput,
|
|
101
|
+
): Promise<userSchemas.ExtendedWishlist>;
|
|
102
|
+
createWishlist(
|
|
103
|
+
input: userSchemas.WishlistCreateInput,
|
|
104
|
+
): Promise<userSchemas.Wishlist>;
|
|
105
|
+
deleteWishlist(input: userSchemas.WishlistDetailInput): Promise<void>;
|
|
106
|
+
updateWishlist(
|
|
107
|
+
// input: userSchemas.UpdateWishlistInput, // used to change wishlist name
|
|
108
|
+
input: userSchemas.WishlistLinesInput,
|
|
109
|
+
): Promise<userSchemas.Wishlist>;
|
|
110
|
+
addProductsToWishlist(
|
|
111
|
+
input: userSchemas.WishlistLinesInput,
|
|
112
|
+
): Promise<userSchemas.Wishlist>;
|
|
113
|
+
removeProductsFromWishlist(
|
|
114
|
+
input: userSchemas.WishlistLinesInput,
|
|
115
|
+
): Promise<void>;
|
|
116
|
+
copyCartToWishlist(
|
|
117
|
+
input: userSchemas.CopyCartToWishlistInput,
|
|
118
|
+
): Promise<userSchemas.Wishlist>;
|
|
92
119
|
updateUser(user: User): Promise<User>;
|
|
93
120
|
getUser(): Promise<User | null>;
|
|
94
121
|
getUserCounts(userId: string): Promise<UserCounts>;
|
|
95
|
-
// wishlists
|
|
96
|
-
getWishlists(): Promise<WishList[]>;
|
|
97
|
-
getWishlistDetails(id: string): Promise<WishList>;
|
|
98
|
-
createWishlist(wishlist: CreateWishListInput): Promise<WishList>;
|
|
99
|
-
deleteWishlist(id: string): Promise<string>;
|
|
100
122
|
}
|
|
101
123
|
|
|
102
124
|
/**
|
|
@@ -142,6 +164,9 @@ export interface CartApi {
|
|
|
142
164
|
input: cartSchemas.RetrieveCardPaymentAcceptResultInput,
|
|
143
165
|
): Promise<CardPaymentAcceptResult>;
|
|
144
166
|
checkout(input: cartSchemas.CheckoutInput): Promise<SalesOrder>;
|
|
167
|
+
getCartLinesInventory(
|
|
168
|
+
input: cartSchemas.InventorySearchCartLinesCriteriaInput,
|
|
169
|
+
): Promise<ProductWarehouseInventoryInformation>;
|
|
145
170
|
}
|
|
146
171
|
|
|
147
172
|
/**
|
|
@@ -167,6 +192,9 @@ export interface ProductApi {
|
|
|
167
192
|
getInventory(
|
|
168
193
|
input: productSchemas.InventorySearchCriteriaInput,
|
|
169
194
|
): Promise<ProductWarehouseInventoryAvailability>;
|
|
195
|
+
getProductPrices(
|
|
196
|
+
input: productSchemas.ProductPriceInput,
|
|
197
|
+
): Promise<productSchemas.ProductPrice[]>;
|
|
170
198
|
}
|
|
171
199
|
|
|
172
200
|
/**
|
package/src/auth/auth-options.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "next";
|
|
9
9
|
import { getProviders } from "./providers";
|
|
10
10
|
import { deleteCartCookie } from "@/lib/cart-cookie-handler";
|
|
11
|
-
import {
|
|
11
|
+
import { getSecret } from "@evenicanpm/common-core/keyvault";
|
|
12
12
|
|
|
13
13
|
// Split providers into separate array so we can iterate them and match the provider ID
|
|
14
14
|
export type ExtendedJwtToken = JWT & {
|
|
@@ -20,7 +20,7 @@ export type ExtendedSession = Session & { token?: JWT };
|
|
|
20
20
|
|
|
21
21
|
const createAuthOptions = (): NextAuthOptions => {
|
|
22
22
|
const providers = getProviders();
|
|
23
|
-
const secret =
|
|
23
|
+
const secret = getSecret("NEXTAUTH_SECRET");
|
|
24
24
|
return {
|
|
25
25
|
providers,
|
|
26
26
|
secret,
|
package/src/auth/msal.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { PublicClientApplication, Configuration } from "@azure/msal-browser";
|
|
2
|
-
import { getConfig } from "@config";
|
|
3
2
|
|
|
4
3
|
const createMsalConfig = (): Configuration => {
|
|
5
4
|
return {
|
|
6
5
|
auth: {
|
|
7
|
-
clientId:
|
|
8
|
-
authority:
|
|
9
|
-
redirectUri:
|
|
10
|
-
postLogoutRedirectUri:
|
|
6
|
+
clientId: process.env.NEXT_PUBLIC_AZURE_AD_B2C_CLIENTID || "",
|
|
7
|
+
authority: process.env.NEXT_PUBLIC_AZURE_AD_B2C_LOGIN_AUTHORITY || "",
|
|
8
|
+
redirectUri: process.env.NEXT_PUBLIC_AZURE_AD_B2C_REDIRECT || "/",
|
|
9
|
+
postLogoutRedirectUri:
|
|
10
|
+
process.env.NEXT_PUBLIC_AZURE_AD_B2C_POST_LOGOUT_REDIRECT || "/",
|
|
11
11
|
knownAuthorities: [
|
|
12
|
-
|
|
12
|
+
process.env.NEXT_PUBLIC_AZURE_AD_B2C_KNOWN_AUTHORITY || "",
|
|
13
13
|
],
|
|
14
14
|
navigateToLoginRequestUrl: true,
|
|
15
15
|
},
|
|
@@ -44,11 +44,11 @@ const getMsalInstance = () => {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
const getRequestConfig = (requestType?: string) => {
|
|
47
|
-
const editAuthority =
|
|
48
|
-
const resetAuthority =
|
|
47
|
+
const editAuthority = process.env.NEXT_PUBLIC_AZURE_AD_B2C_EDIT_AUTHORITY;
|
|
48
|
+
const resetAuthority = process.env.NEXT_PUBLIC_AZURE_AD_B2C_RESET_AUTHORITY;
|
|
49
49
|
|
|
50
50
|
const baseRequest = {
|
|
51
|
-
scopes: [
|
|
51
|
+
scopes: [process.env.NEXT_PUBLIC_AZURE_AD_B2C_API_PERMISSION],
|
|
52
52
|
authority: createMsalConfig().auth.authority,
|
|
53
53
|
};
|
|
54
54
|
|
|
@@ -6,6 +6,8 @@ import { ExtendedJwtToken } from "@/auth/auth-options";
|
|
|
6
6
|
const NEXT_AUTH_COOKIE_NAME = "next-auth.session-token";
|
|
7
7
|
const SECURE_COOKIE_PREFIX = "__Secure-";
|
|
8
8
|
|
|
9
|
+
import { getSecret } from "@evenicanpm/common-core/keyvault";
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Retrieves and decodes the NextAuth session cookie from the request.
|
|
11
13
|
*
|
|
@@ -28,7 +30,7 @@ const manuallyRetrieveSessionCookie = async () => {
|
|
|
28
30
|
if (cookie) {
|
|
29
31
|
return decode({
|
|
30
32
|
token: cookie.value,
|
|
31
|
-
secret:
|
|
33
|
+
secret: getSecret("NEXTAUTH_SECRET") || "",
|
|
32
34
|
});
|
|
33
35
|
}
|
|
34
36
|
//If the cookie is not found, we need to see if there is a chunked cookie
|
|
@@ -49,7 +51,7 @@ const manuallyRetrieveSessionCookie = async () => {
|
|
|
49
51
|
if (cookieValues.length) {
|
|
50
52
|
return decode({
|
|
51
53
|
token: cookieValues.join(""),
|
|
52
|
-
secret:
|
|
54
|
+
secret: getSecret("NEXTAUTH_SECRET") || "",
|
|
53
55
|
});
|
|
54
56
|
}
|
|
55
57
|
|
|
@@ -73,7 +75,7 @@ const manuallySetSessionCookie = async (token: JWT | ExtendedJwtToken) => {
|
|
|
73
75
|
// Set the cookie with the name __Secure-next-auth.session-token
|
|
74
76
|
const value = await encode({
|
|
75
77
|
token,
|
|
76
|
-
secret:
|
|
78
|
+
secret: getSecret("NEXTAUTH_SECRET") || "",
|
|
77
79
|
});
|
|
78
80
|
cookieStore.set({
|
|
79
81
|
name: `${SECURE_COOKIE_PREFIX}${NEXT_AUTH_COOKIE_NAME}`,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getConfig } from "@config";
|
|
2
1
|
import AzureADB2CProvider from "next-auth/providers/azure-ad-b2c";
|
|
3
2
|
|
|
4
3
|
// By default, NextAuth tries to use either PKCE or Client Secret-based authentication, depending on how the provider is configured.
|
|
@@ -6,9 +5,9 @@ import AzureADB2CProvider from "next-auth/providers/azure-ad-b2c";
|
|
|
6
5
|
// If clientSecret is missing, it assumes PKCE.
|
|
7
6
|
// But Azure AD B2C sometimes requires PKCE explicitly, so NextAuth needs to be told to always use PKCE.
|
|
8
7
|
const azureADB2CProvider = () => {
|
|
9
|
-
const CLIENT_ID =
|
|
10
|
-
const SCOPE = `${
|
|
11
|
-
const LOGIN_AUTHORITY =
|
|
8
|
+
const CLIENT_ID = process.env.NEXT_PUBLIC_AZURE_AD_B2C_CLIENTID || "";
|
|
9
|
+
const SCOPE = `${process.env.NEXT_PUBLIC_AZURE_AD_B2C_API_PERMISSION} offline_access openid`;
|
|
10
|
+
const LOGIN_AUTHORITY = process.env.NEXT_PUBLIC_AZURE_AD_B2C_LOGIN_AUTHORITY;
|
|
12
11
|
|
|
13
12
|
return AzureADB2CProvider({
|
|
14
13
|
clientId: CLIENT_ID,
|
package/src/cms/endpoints.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Formats } from "./interfaces/productData";
|
|
2
|
-
import { getConfig } from "@config";
|
|
3
2
|
|
|
4
3
|
const apiKeys = {
|
|
5
4
|
pages: "/cms/pages",
|
|
@@ -17,12 +16,12 @@ type ApiKeysType = typeof apiKeys;
|
|
|
17
16
|
type ApiKey = keyof ApiKeysType;
|
|
18
17
|
|
|
19
18
|
const getCmsApiUrl = (apiKey: ApiKey) => {
|
|
20
|
-
const cmsApiEndpoint =
|
|
19
|
+
const cmsApiEndpoint = process.env.NEXT_PUBLIC_E4_API_ENDPOINT;
|
|
21
20
|
return `${cmsApiEndpoint}${apiKeys[apiKey]}`;
|
|
22
21
|
};
|
|
23
22
|
|
|
24
23
|
const buildCmsMediaUrl = (mediaUrl: string) => {
|
|
25
|
-
const cmsEndpoint =
|
|
24
|
+
const cmsEndpoint = process.env.NEXT_PUBLIC_CMS_ENDPOINT;
|
|
26
25
|
|
|
27
26
|
if (mediaUrl === undefined || mediaUrl === null) {
|
|
28
27
|
return "";
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import NextImage from "next/image";
|
|
3
|
+
import NextImage, { ImageProps } from "next/image";
|
|
4
4
|
import styled from "@mui/material/styles/styled";
|
|
5
|
+
import { ComponentType } from "react";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
const LazyImage: ComponentType<ImageProps> = styled(NextImage)({
|
|
7
8
|
width: "100%",
|
|
8
9
|
height: "auto", //If we use CSS to change the size of image, we should include the styles 'width: "auto"' or 'height: "auto"' to maintain the aspect ratio.
|
|
9
10
|
});
|
|
11
|
+
|
|
12
|
+
export default LazyImage;
|
|
@@ -42,7 +42,6 @@ const MENU_ITEMS = [
|
|
|
42
42
|
interface Props {
|
|
43
43
|
mobile?: boolean;
|
|
44
44
|
}
|
|
45
|
-
|
|
46
45
|
const User = ({ mobile = false }: Props) => {
|
|
47
46
|
const { data: session, status } = useSession();
|
|
48
47
|
|
|
@@ -115,7 +114,7 @@ const User = ({ mobile = false }: Props) => {
|
|
|
115
114
|
|
|
116
115
|
const setAnchorElNull = () => setAnchorEl(null);
|
|
117
116
|
|
|
118
|
-
if (status === "loading") return
|
|
117
|
+
if (status === "loading") return <></>;
|
|
119
118
|
|
|
120
119
|
if (mobile) {
|
|
121
120
|
return (
|
package/src/components/_components/product-cards/product-card-1/components/quantity-buttons.tsx
CHANGED
|
@@ -6,51 +6,60 @@ import Remove from "@mui/icons-material/Remove";
|
|
|
6
6
|
// GLOBAL CUSTOM COMPONENTS
|
|
7
7
|
import { FlexBox } from "@/components/_components/flex-box/index";
|
|
8
8
|
import { Paragraph } from "@/components/_components/Typography";
|
|
9
|
+
import { CircularProgress } from "@mui/material";
|
|
9
10
|
|
|
10
11
|
// ==============================================================
|
|
11
12
|
interface Props {
|
|
12
13
|
quantity: number;
|
|
13
14
|
handleDecrement: () => void;
|
|
14
15
|
handleIncrement: () => void;
|
|
16
|
+
isLineUpdating: boolean;
|
|
15
17
|
}
|
|
16
18
|
// ==============================================================
|
|
17
19
|
|
|
18
20
|
export default function QuantityButtons(props: Props) {
|
|
19
|
-
const { quantity, handleDecrement, handleIncrement } =
|
|
21
|
+
const { quantity, handleDecrement, handleIncrement, isLineUpdating } =
|
|
22
|
+
props || {};
|
|
20
23
|
|
|
21
24
|
return (
|
|
22
25
|
<FlexBox
|
|
23
|
-
width="30px"
|
|
24
26
|
alignItems="center"
|
|
25
27
|
className="add-cart"
|
|
26
|
-
flexDirection="
|
|
28
|
+
flexDirection="row-reverse"
|
|
27
29
|
justifyContent={quantity ? "space-between" : "flex-start"}
|
|
30
|
+
gap={1}
|
|
28
31
|
>
|
|
29
32
|
<Button
|
|
30
33
|
color="primary"
|
|
31
34
|
variant="outlined"
|
|
32
35
|
onClick={handleIncrement}
|
|
33
36
|
sx={{ padding: "3px" }}
|
|
37
|
+
disabled={isLineUpdating}
|
|
34
38
|
>
|
|
35
39
|
<Add fontSize="small" />
|
|
36
40
|
</Button>
|
|
37
41
|
|
|
38
|
-
{quantity
|
|
42
|
+
{quantity && (
|
|
39
43
|
<Fragment>
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
|
|
44
|
+
{isLineUpdating ? (
|
|
45
|
+
<CircularProgress size={"10px"} color={"primary"} />
|
|
46
|
+
) : (
|
|
47
|
+
<Paragraph color="text.primary" fontWeight="600">
|
|
48
|
+
{quantity}
|
|
49
|
+
</Paragraph>
|
|
50
|
+
)}
|
|
43
51
|
|
|
44
52
|
<Button
|
|
45
53
|
color="primary"
|
|
46
54
|
variant="outlined"
|
|
47
55
|
onClick={handleDecrement}
|
|
48
56
|
sx={{ padding: "3px" }}
|
|
57
|
+
disabled={isLineUpdating}
|
|
49
58
|
>
|
|
50
59
|
<Remove fontSize="small" />
|
|
51
60
|
</Button>
|
|
52
61
|
</Fragment>
|
|
53
|
-
)
|
|
62
|
+
)}
|
|
54
63
|
</FlexBox>
|
|
55
64
|
);
|
|
56
65
|
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { IconButton } from "@mui/material";
|
|
4
|
+
import ProductCard1 from "./product-card";
|
|
5
|
+
import { Delete, ShoppingCart } from "@mui/icons-material";
|
|
6
|
+
import {
|
|
7
|
+
ExtendedProductListLine,
|
|
8
|
+
ExtendedWishlist,
|
|
9
|
+
WishlistLinesInput,
|
|
10
|
+
} from "@/api-manager/schemas/user.schema";
|
|
11
|
+
import { useTranslations } from "next-intl";
|
|
12
|
+
import useRemoveFromWishlist from "@/api-manager/services/user/mutations/remove-from-wishlist";
|
|
13
|
+
import HoverActions from "./components/hover-actions";
|
|
14
|
+
import { useState } from "react";
|
|
15
|
+
import useUpdateWishlist from "@/api-manager/services/user/mutations/update-wishlist";
|
|
16
|
+
import useAddToCart from "@/api-manager/services/cart/mutations/add-to-cart";
|
|
17
|
+
import { Cart } from "@/api-manager/schemas/cart.schema";
|
|
18
|
+
import { FlexBox } from "@/_components/flex-box";
|
|
19
|
+
import AddToWishlistDialog from "@/_components/wishlist/add-to-wishlist-dialog";
|
|
20
|
+
import RemoveFromWishlistDialog from "@/_components/wishlist/remove-from-wishlist-dialog";
|
|
21
|
+
import ProductViewDialog from "@/_components/products-view/product-view-dialog";
|
|
22
|
+
import { useNotification } from "@/components/Notifications/use-notification";
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
title: string;
|
|
26
|
+
slug: string;
|
|
27
|
+
price: number;
|
|
28
|
+
imgUrl: string;
|
|
29
|
+
rating?: number;
|
|
30
|
+
discount?: number;
|
|
31
|
+
id: string | number;
|
|
32
|
+
hideRating?: boolean;
|
|
33
|
+
hoverEffect?: boolean;
|
|
34
|
+
showProductSize?: boolean;
|
|
35
|
+
mode: "PLP" | "WishlistDetail";
|
|
36
|
+
isFavorite?: boolean; // when called from PLP, whether the product is in a wishlist
|
|
37
|
+
wishlist?: ExtendedWishlist; // when called from wishlist detail page
|
|
38
|
+
cart?: Cart; // for adding wishlist item to cart in wishlist detail page
|
|
39
|
+
};
|
|
40
|
+
export default function ProductCard1Wrapper({
|
|
41
|
+
id,
|
|
42
|
+
slug,
|
|
43
|
+
title,
|
|
44
|
+
price,
|
|
45
|
+
imgUrl,
|
|
46
|
+
rating = 5,
|
|
47
|
+
hideRating,
|
|
48
|
+
hoverEffect,
|
|
49
|
+
discount = 5,
|
|
50
|
+
showProductSize,
|
|
51
|
+
mode,
|
|
52
|
+
isFavorite,
|
|
53
|
+
wishlist,
|
|
54
|
+
cart,
|
|
55
|
+
}: Props) {
|
|
56
|
+
const t = useTranslations("Account.Wishlists");
|
|
57
|
+
const { setNotification } = useNotification();
|
|
58
|
+
const { mutateAsync: removeFromWishlist } = useRemoveFromWishlist();
|
|
59
|
+
const { mutateAsync: updateWishlist } = useUpdateWishlist();
|
|
60
|
+
const [favorite, setFavorite] = useState(isFavorite || false);
|
|
61
|
+
const [openAddToWishlistDialog, setOpenAddToWishlistDialog] = useState(false);
|
|
62
|
+
const [openRemoveFromWishlistDialog, setOpenRemoveFromWishlistDialog] =
|
|
63
|
+
useState(false);
|
|
64
|
+
const [openProductModal, setOpenProductModal] = useState(false);
|
|
65
|
+
const [quantity, setQuantity] = useState(
|
|
66
|
+
wishlist?.ProductListLines?.find(
|
|
67
|
+
(item: ExtendedProductListLine) => item.ProductId === id,
|
|
68
|
+
)?.Quantity || 0,
|
|
69
|
+
);
|
|
70
|
+
const [isLineUpdating, setIsLineUpdating] = useState(false);
|
|
71
|
+
const { mutateAsync: addToCart } = useAddToCart();
|
|
72
|
+
|
|
73
|
+
const handleRemoveFromWishlist = async (productId: string | number) => {
|
|
74
|
+
try {
|
|
75
|
+
const extendedWishlistLine = wishlist?.ProductListLines?.find(
|
|
76
|
+
(item: ExtendedProductListLine) => item.ProductId === productId,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (extendedWishlistLine && wishlist) {
|
|
80
|
+
const { ...wishlistLine } = extendedWishlistLine;
|
|
81
|
+
delete wishlistLine.Product;
|
|
82
|
+
|
|
83
|
+
const wishlistLinesInput: WishlistLinesInput = {
|
|
84
|
+
Id: wishlist.Id,
|
|
85
|
+
ProductListLines: [wishlistLine],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
await removeFromWishlist(wishlistLinesInput);
|
|
89
|
+
setNotification({
|
|
90
|
+
message: t("wishlistLineRemoveSuccess"),
|
|
91
|
+
severity: "success",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
setNotification({
|
|
96
|
+
message: t("wishlistLineRemoveFailed"),
|
|
97
|
+
severity: "error",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleToggleFavorite = () => {
|
|
103
|
+
if (favorite) {
|
|
104
|
+
setOpenRemoveFromWishlistDialog(true);
|
|
105
|
+
} else {
|
|
106
|
+
setOpenAddToWishlistDialog(true);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const handleToggleProductModal = () => {
|
|
111
|
+
setOpenProductModal(!openProductModal);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const handleCloseDialog = () => {
|
|
115
|
+
setOpenAddToWishlistDialog(false);
|
|
116
|
+
setOpenRemoveFromWishlistDialog(false);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const handleAddRemoveSuccess = () => {
|
|
120
|
+
setFavorite(!favorite);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const handleWishlistIncrementQuantity = async () => {
|
|
124
|
+
setIsLineUpdating(true);
|
|
125
|
+
// update wishlist product list lines
|
|
126
|
+
if (mode === "WishlistDetail" && wishlist && wishlist.ProductListLines) {
|
|
127
|
+
try {
|
|
128
|
+
const updatedProductListLines = wishlist.ProductListLines.map(
|
|
129
|
+
(line) => {
|
|
130
|
+
const { ...wishlistLine } = line;
|
|
131
|
+
delete wishlistLine.Product;
|
|
132
|
+
if (line.ProductId === Number(id)) {
|
|
133
|
+
return {
|
|
134
|
+
...wishlistLine,
|
|
135
|
+
Quantity: quantity + 1,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return wishlistLine;
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
await updateWishlist({
|
|
142
|
+
Id: wishlist.Id,
|
|
143
|
+
ProductListLines: updatedProductListLines,
|
|
144
|
+
});
|
|
145
|
+
setQuantity(quantity + 1);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
// Do nothing
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
// PLP mode
|
|
151
|
+
setQuantity(quantity + 1);
|
|
152
|
+
}
|
|
153
|
+
setIsLineUpdating(false);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const handleWishlistDecrementQuantity = async () => {
|
|
157
|
+
setIsLineUpdating(true);
|
|
158
|
+
// update wishlist product list lines
|
|
159
|
+
if (
|
|
160
|
+
mode === "WishlistDetail" &&
|
|
161
|
+
wishlist &&
|
|
162
|
+
wishlist.ProductListLines &&
|
|
163
|
+
quantity > 0
|
|
164
|
+
) {
|
|
165
|
+
try {
|
|
166
|
+
const updatedProductListLines = wishlist.ProductListLines.map(
|
|
167
|
+
(line) => {
|
|
168
|
+
const { ...wishlistLine } = line;
|
|
169
|
+
delete wishlistLine.Product;
|
|
170
|
+
if (line.ProductId === Number(id)) {
|
|
171
|
+
return {
|
|
172
|
+
...wishlistLine,
|
|
173
|
+
Quantity: quantity - 1,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return wishlistLine;
|
|
177
|
+
},
|
|
178
|
+
);
|
|
179
|
+
await updateWishlist({
|
|
180
|
+
Id: wishlist.Id,
|
|
181
|
+
ProductListLines: updatedProductListLines,
|
|
182
|
+
});
|
|
183
|
+
setQuantity(quantity - 1);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
// Do nothing
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
// PLP mode
|
|
189
|
+
setQuantity(quantity - 1);
|
|
190
|
+
}
|
|
191
|
+
setIsLineUpdating(false);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const handleAddItemToCart = async () => {
|
|
195
|
+
if (!cart) return;
|
|
196
|
+
try {
|
|
197
|
+
await addToCart({
|
|
198
|
+
cartId: cart.Id,
|
|
199
|
+
version: cart.Version,
|
|
200
|
+
cartLine: [
|
|
201
|
+
{
|
|
202
|
+
ProductId: Number(id),
|
|
203
|
+
Quantity: quantity,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
setNotification({
|
|
208
|
+
message: "Successfully added item to cart",
|
|
209
|
+
severity: "success",
|
|
210
|
+
});
|
|
211
|
+
} catch (error) {
|
|
212
|
+
setNotification({
|
|
213
|
+
message: "Failed to add item to cart",
|
|
214
|
+
severity: "error",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// render wishlist controls depending on mode
|
|
220
|
+
const wishlistControls =
|
|
221
|
+
mode === "WishlistDetail" ? (
|
|
222
|
+
// render delete button
|
|
223
|
+
<>
|
|
224
|
+
<FlexBox
|
|
225
|
+
width="25px"
|
|
226
|
+
alignItems="center"
|
|
227
|
+
flexDirection="row-reverse"
|
|
228
|
+
justifyContent="space-between"
|
|
229
|
+
>
|
|
230
|
+
<IconButton
|
|
231
|
+
sx={{ padding: "3px" }}
|
|
232
|
+
onClick={() => handleAddItemToCart()}
|
|
233
|
+
>
|
|
234
|
+
<ShoppingCart fontSize="small" color="inherit" />
|
|
235
|
+
</IconButton>
|
|
236
|
+
</FlexBox>
|
|
237
|
+
<FlexBox
|
|
238
|
+
width="25px"
|
|
239
|
+
alignItems="center"
|
|
240
|
+
flexDirection="row-reverse"
|
|
241
|
+
justifyContent="space-between"
|
|
242
|
+
>
|
|
243
|
+
<IconButton
|
|
244
|
+
sx={{ padding: "3px" }}
|
|
245
|
+
onClick={() => handleRemoveFromWishlist(id)}
|
|
246
|
+
>
|
|
247
|
+
<Delete fontSize="small" color="inherit" />
|
|
248
|
+
</IconButton>
|
|
249
|
+
</FlexBox>
|
|
250
|
+
</>
|
|
251
|
+
) : (
|
|
252
|
+
// render hover actions for PLP
|
|
253
|
+
<HoverActions
|
|
254
|
+
isFavorite={favorite}
|
|
255
|
+
toggleView={handleToggleProductModal}
|
|
256
|
+
toggleFavorite={handleToggleFavorite}
|
|
257
|
+
/>
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<>
|
|
262
|
+
<ProductCard1
|
|
263
|
+
id={id}
|
|
264
|
+
slug={slug}
|
|
265
|
+
title={title}
|
|
266
|
+
price={price}
|
|
267
|
+
imgUrl={imgUrl}
|
|
268
|
+
rating={rating}
|
|
269
|
+
hideRating={hideRating}
|
|
270
|
+
hoverEffect={hoverEffect}
|
|
271
|
+
discount={discount}
|
|
272
|
+
showProductSize={showProductSize}
|
|
273
|
+
wishlistControls={wishlistControls}
|
|
274
|
+
quantity={quantity}
|
|
275
|
+
isLineUpdating={isLineUpdating}
|
|
276
|
+
handleIncrementQuantity={handleWishlistIncrementQuantity}
|
|
277
|
+
handleDecrementQuantity={handleWishlistDecrementQuantity}
|
|
278
|
+
/>
|
|
279
|
+
{openAddToWishlistDialog && (
|
|
280
|
+
<AddToWishlistDialog
|
|
281
|
+
open={openAddToWishlistDialog}
|
|
282
|
+
handleClose={handleCloseDialog}
|
|
283
|
+
productId={Number(id)}
|
|
284
|
+
onSuccess={handleAddRemoveSuccess}
|
|
285
|
+
/>
|
|
286
|
+
)}
|
|
287
|
+
{openRemoveFromWishlistDialog && (
|
|
288
|
+
<RemoveFromWishlistDialog
|
|
289
|
+
open={openRemoveFromWishlistDialog}
|
|
290
|
+
handleClose={handleCloseDialog}
|
|
291
|
+
productId={Number(id)}
|
|
292
|
+
onSuccess={handleAddRemoveSuccess}
|
|
293
|
+
/>
|
|
294
|
+
)}
|
|
295
|
+
{openProductModal && (
|
|
296
|
+
<ProductViewDialog
|
|
297
|
+
openDialog={openProductModal || false}
|
|
298
|
+
handleCloseDialog={handleToggleProductModal}
|
|
299
|
+
product={{ title, price, id, slug, imgGroup: [imgUrl, imgUrl] }}
|
|
300
|
+
/>
|
|
301
|
+
)}
|
|
302
|
+
</>
|
|
303
|
+
);
|
|
304
|
+
}
|