@decocms/apps-magento 8.1.0-next.0 → 8.1.0-next.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.
Files changed (47) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +3 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/magentoClient.d.ts +62 -0
  6. package/dist/magentoClient.d.ts.map +1 -0
  7. package/dist/magentoClient.js +96 -0
  8. package/dist/magentoClient.js.map +1 -0
  9. package/package.json +19 -27
  10. package/src/index.ts +2 -29
  11. package/src/README.md +0 -65
  12. package/src/__tests__/cart.test.ts +0 -139
  13. package/src/__tests__/client.test.ts +0 -252
  14. package/src/__tests__/features.test.ts +0 -51
  15. package/src/__tests__/graphql.test.ts +0 -183
  16. package/src/__tests__/magentoClient.test.ts +0 -166
  17. package/src/__tests__/newsletter-subscribe.test.ts +0 -86
  18. package/src/__tests__/product-stockAlert.test.ts +0 -80
  19. package/src/__tests__/stringifySearchCriteria.test.ts +0 -56
  20. package/src/__tests__/transform.test.ts +0 -346
  21. package/src/__tests__/user-loader.test.ts +0 -134
  22. package/src/__tests__/wishlist-actions.test.ts +0 -140
  23. package/src/__tests__/wishlist-loader.test.ts +0 -97
  24. package/src/actions/newsletter/subscribe.ts +0 -40
  25. package/src/actions/product/stockAlert.ts +0 -67
  26. package/src/actions/wishlist/addItem.ts +0 -57
  27. package/src/actions/wishlist/removeItem.ts +0 -59
  28. package/src/client.ts +0 -252
  29. package/src/loaders/cart.ts +0 -64
  30. package/src/loaders/features.ts +0 -16
  31. package/src/loaders/user.ts +0 -60
  32. package/src/loaders/wishlist.ts +0 -29
  33. package/src/middleware.ts +0 -19
  34. package/src/types.ts +0 -51
  35. package/src/utils/__tests__/instrumentation.test.ts +0 -59
  36. package/src/utils/cacheTimeControl.ts +0 -69
  37. package/src/utils/client/types.ts +0 -270
  38. package/src/utils/constants.ts +0 -126
  39. package/src/utils/fetchCache.ts +0 -55
  40. package/src/utils/graphql-types.ts +0 -99
  41. package/src/utils/graphql.ts +0 -155
  42. package/src/utils/instrumentedFetch.ts +0 -57
  43. package/src/utils/operationRouter.ts +0 -43
  44. package/src/utils/stringifySearchCriteria.ts +0 -62
  45. package/src/utils/transform.ts +0 -283
  46. package/src/utils/user.ts +0 -16
  47. package/tsconfig.json +0 -7
@@ -1,134 +0,0 @@
1
- /**
2
- * Tests for the user loader.
3
- *
4
- * Parity goals against deco-cx/apps/magento/loaders/user.ts:
5
- * - Returns null when no PHPSESSID cookie is present (anonymous).
6
- * - Calls /customer/section/load?sections=customer,carbono-customer
7
- * with the Cookie header set from PHPSESSID.
8
- * - Maps the bundle into schema.org Person { @id, email, givenName,
9
- * familyName? }.
10
- * - Derives familyName by stripping firstname from fullname (matches
11
- * prod's `fullname.replace(firstname, "").trim()`).
12
- * - Returns null when carbono-customer.data_id is missing OR when
13
- * customer slice is absent.
14
- * - Swallows fetch errors and returns null (the storefront re-renders
15
- * the logged-out UI rather than crashing).
16
- */
17
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
18
- import { configureMagento } from "../client";
19
- import user from "../loaders/user";
20
-
21
- function mockResponse(body: unknown, status = 200): Response {
22
- return new Response(JSON.stringify(body), {
23
- status,
24
- headers: { "content-type": "application/json" },
25
- });
26
- }
27
-
28
- function requestWithCookie(cookie: string): Request {
29
- const r = new Request("http://localhost/");
30
- const headers = new Headers();
31
- headers.set("cookie", cookie);
32
- Object.defineProperty(r, "headers", { value: headers, configurable: true });
33
- return r;
34
- }
35
-
36
- describe("user loader", () => {
37
- let fetchSpy: ReturnType<typeof vi.spyOn>;
38
-
39
- beforeEach(() => {
40
- configureMagento({
41
- baseUrl: "https://loja.example.com/",
42
- apiKey: "secret",
43
- storeId: 1,
44
- site: "example",
45
- });
46
- fetchSpy = vi.spyOn(globalThis, "fetch");
47
- });
48
-
49
- afterEach(() => {
50
- vi.restoreAllMocks();
51
- });
52
-
53
- it("returns null when no PHPSESSID cookie is present", async () => {
54
- const result = await user(null, new Request("http://localhost/"));
55
- expect(result).toBeNull();
56
- expect(fetchSpy).not.toHaveBeenCalled();
57
- });
58
-
59
- it("requests /customer/section/load with the Cookie header", async () => {
60
- fetchSpy.mockResolvedValue(
61
- mockResponse({
62
- customer: { data_id: 1, fullname: "Alice Doe", firstname: "Alice" },
63
- "carbono-customer": { data_id: 1, customerId: "c1", email: "a@b.com" },
64
- }),
65
- );
66
- await user(null, requestWithCookie("PHPSESSID=abc"));
67
- const [target, init] = fetchSpy.mock.calls[0] as [URL, RequestInit];
68
- expect(target.toString()).toBe(
69
- "https://loja.example.com/example/customer/section/load?sections=customer,carbono-customer",
70
- );
71
- const cookieHeader = new Headers(init.headers).get("cookie");
72
- expect(cookieHeader).toBe("PHPSESSID=abc");
73
- });
74
-
75
- it("maps a happy-path bundle into a schema.org Person", async () => {
76
- fetchSpy.mockResolvedValue(
77
- mockResponse({
78
- customer: { data_id: 1, fullname: "Alice Doe", firstname: "Alice" },
79
- "carbono-customer": { data_id: 1, customerId: "c1", email: "a@b.com" },
80
- }),
81
- );
82
- const out = await user(null, requestWithCookie("PHPSESSID=abc"));
83
- expect(out).toEqual({
84
- "@id": "c1",
85
- email: "a@b.com",
86
- givenName: "Alice",
87
- familyName: "Doe",
88
- });
89
- });
90
-
91
- it("omits familyName when fullname is just firstname (no surname)", async () => {
92
- fetchSpy.mockResolvedValue(
93
- mockResponse({
94
- customer: { data_id: 1, fullname: "Alice", firstname: "Alice" },
95
- "carbono-customer": { data_id: 1, customerId: "c1", email: "a@b.com" },
96
- }),
97
- );
98
- const out = await user(null, requestWithCookie("PHPSESSID=abc"));
99
- expect(out).toMatchObject({ givenName: "Alice" });
100
- // Magento returns fullname=firstname; prod's `replace(firstname, "").trim()`
101
- // yields "" — we include the key with an empty string. Test the
102
- // surface, not the empty-vs-missing detail.
103
- expect(out?.familyName ?? "").toBe("");
104
- });
105
-
106
- it("returns null when carbono-customer.data_id is missing", async () => {
107
- fetchSpy.mockResolvedValue(
108
- mockResponse({
109
- customer: { data_id: 1, fullname: "Alice Doe", firstname: "Alice" },
110
- "carbono-customer": { customerId: "c1", email: "a@b.com" }, // no data_id
111
- }),
112
- );
113
- expect(await user(null, requestWithCookie("PHPSESSID=abc"))).toBeNull();
114
- });
115
-
116
- it("returns null when customer slice is absent", async () => {
117
- fetchSpy.mockResolvedValue(
118
- mockResponse({
119
- "carbono-customer": { data_id: 1, customerId: "c1", email: "a@b.com" },
120
- }),
121
- );
122
- expect(await user(null, requestWithCookie("PHPSESSID=abc"))).toBeNull();
123
- });
124
-
125
- it("returns null when fetch throws", async () => {
126
- fetchSpy.mockRejectedValue(new Error("ECONNREFUSED"));
127
- expect(await user(null, requestWithCookie("PHPSESSID=abc"))).toBeNull();
128
- });
129
-
130
- it("returns null on non-2xx response", async () => {
131
- fetchSpy.mockResolvedValue(mockResponse(null, 500));
132
- expect(await user(null, requestWithCookie("PHPSESSID=abc"))).toBeNull();
133
- });
134
- });
@@ -1,140 +0,0 @@
1
- /**
2
- * Tests for the wishlist add/remove actions.
3
- *
4
- * Parity goals against deco-cx/apps/magento/actions/wishlist/{addItem,removeItem}:
5
- * - Both require PHPSESSID + form_key cookies; return null when either absent.
6
- * - addItem POSTs FormData {product, form_key} to /wishlist/index/add/.
7
- * - removeItem POSTs FormData {item, uenc:"", form_key} to /wishlist/index/remove/.
8
- * - On success ({success:true}) both delegate to the wishlist loader to
9
- * fetch the refreshed list.
10
- * - Failure / thrown / success:false → null.
11
- */
12
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
13
- import addItem from "../actions/wishlist/addItem";
14
- import removeItem from "../actions/wishlist/removeItem";
15
- import { configureMagento } from "../client";
16
-
17
- function mockResponse(body: unknown, status = 200): Response {
18
- return new Response(JSON.stringify(body), {
19
- status,
20
- headers: { "content-type": "application/json" },
21
- });
22
- }
23
-
24
- function requestWithCookies(cookieHeader: string): Request {
25
- const r = new Request("http://localhost/");
26
- const headers = new Headers();
27
- headers.set("cookie", cookieHeader);
28
- Object.defineProperty(r, "headers", { value: headers, configurable: true });
29
- return r;
30
- }
31
-
32
- const AUTHED = "PHPSESSID=abc; form_key=xyz";
33
-
34
- describe("wishlist/addItem", () => {
35
- let fetchSpy: ReturnType<typeof vi.spyOn>;
36
-
37
- beforeEach(() => {
38
- configureMagento({
39
- baseUrl: "https://loja.example.com/",
40
- apiKey: "secret",
41
- storeId: 1,
42
- site: "example",
43
- });
44
- fetchSpy = vi.spyOn(globalThis, "fetch");
45
- });
46
-
47
- afterEach(() => vi.restoreAllMocks());
48
-
49
- it("returns null when PHPSESSID is missing", async () => {
50
- expect(await addItem({ productId: "1" }, requestWithCookies("form_key=xyz"))).toBeNull();
51
- });
52
-
53
- it("returns null when form_key is missing", async () => {
54
- expect(await addItem({ productId: "1" }, requestWithCookies("PHPSESSID=abc"))).toBeNull();
55
- });
56
-
57
- it("POSTs FormData {product, form_key} to /wishlist/index/add/", async () => {
58
- fetchSpy
59
- .mockResolvedValueOnce(mockResponse({ success: true })) // POST add
60
- .mockResolvedValueOnce(
61
- mockResponse({
62
- wishlist: { counter: "1", items: [], counter_number: 1, data_id: 1 },
63
- }),
64
- ); // wishlist loader fetch
65
-
66
- await addItem({ productId: "p42" }, requestWithCookies(AUTHED));
67
-
68
- const [target, init] = fetchSpy.mock.calls[0] as [URL, RequestInit];
69
- expect(target.toString()).toBe("https://loja.example.com/example/wishlist/index/add/");
70
- expect(init.method).toBe("POST");
71
-
72
- const fd = init.body as FormData;
73
- expect(fd.get("product")).toBe("p42");
74
- expect(fd.get("form_key")).toBe("xyz");
75
- });
76
-
77
- it("delegates to the wishlist loader on success", async () => {
78
- const wl = { counter: "1", items: [], counter_number: 1, data_id: 1 };
79
- fetchSpy
80
- .mockResolvedValueOnce(mockResponse({ success: true }))
81
- .mockResolvedValueOnce(mockResponse({ wishlist: wl }));
82
-
83
- expect(await addItem({ productId: "p42" }, requestWithCookies(AUTHED))).toEqual(wl);
84
- });
85
-
86
- it("returns null when Magento responds with success:false", async () => {
87
- fetchSpy.mockResolvedValueOnce(mockResponse({ success: false }));
88
- expect(await addItem({ productId: "p42" }, requestWithCookies(AUTHED))).toBeNull();
89
- });
90
-
91
- it("returns null on a thrown fetch", async () => {
92
- fetchSpy.mockRejectedValueOnce(new Error("boom"));
93
- expect(await addItem({ productId: "p42" }, requestWithCookies(AUTHED))).toBeNull();
94
- });
95
- });
96
-
97
- describe("wishlist/removeItem", () => {
98
- let fetchSpy: ReturnType<typeof vi.spyOn>;
99
-
100
- beforeEach(() => {
101
- configureMagento({
102
- baseUrl: "https://loja.example.com/",
103
- apiKey: "secret",
104
- storeId: 1,
105
- site: "example",
106
- });
107
- fetchSpy = vi.spyOn(globalThis, "fetch");
108
- });
109
-
110
- afterEach(() => vi.restoreAllMocks());
111
-
112
- it("POSTs FormData {item, uenc:'', form_key} to /wishlist/index/remove/", async () => {
113
- fetchSpy.mockResolvedValueOnce(mockResponse({ success: true })).mockResolvedValueOnce(
114
- mockResponse({
115
- wishlist: { counter: "0", items: [], counter_number: 0, data_id: 1 },
116
- }),
117
- );
118
-
119
- await removeItem({ productId: "row-42" }, requestWithCookies(AUTHED));
120
-
121
- const [target, init] = fetchSpy.mock.calls[0] as [URL, RequestInit];
122
- expect(target.toString()).toBe("https://loja.example.com/example/wishlist/index/remove/");
123
- const fd = init.body as FormData;
124
- expect(fd.get("item")).toBe("row-42");
125
- expect(fd.get("uenc")).toBe("");
126
- expect(fd.get("form_key")).toBe("xyz");
127
- });
128
-
129
- it("returns the refreshed wishlist on success", async () => {
130
- const wl = { counter: "0", items: [], counter_number: 0, data_id: 1 };
131
- fetchSpy
132
- .mockResolvedValueOnce(mockResponse({ success: true }))
133
- .mockResolvedValueOnce(mockResponse({ wishlist: wl }));
134
- expect(await removeItem({ productId: "row-42" }, requestWithCookies(AUTHED))).toEqual(wl);
135
- });
136
-
137
- it("returns null when PHPSESSID is missing", async () => {
138
- expect(await removeItem({ productId: "x" }, requestWithCookies("form_key=xyz"))).toBeNull();
139
- });
140
- });
@@ -1,97 +0,0 @@
1
- /**
2
- * Tests for the wishlist loader.
3
- *
4
- * Parity goals against deco-cx/apps/magento/loaders/wishlist.ts:
5
- * - Returns null when no PHPSESSID (anonymous).
6
- * - Hits /customer/section/load?sections=wishlist with the Cookie header.
7
- * - Returns the wishlist payload directly when present.
8
- * - Returns null when the bundle has no wishlist slice.
9
- */
10
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
11
- import { configureMagento } from "../client";
12
- import wishlist from "../loaders/wishlist";
13
-
14
- function mockResponse(body: unknown, status = 200): Response {
15
- return new Response(JSON.stringify(body), {
16
- status,
17
- headers: { "content-type": "application/json" },
18
- });
19
- }
20
-
21
- function requestWithCookie(cookie: string): Request {
22
- const r = new Request("http://localhost/");
23
- const headers = new Headers();
24
- headers.set("cookie", cookie);
25
- Object.defineProperty(r, "headers", { value: headers, configurable: true });
26
- return r;
27
- }
28
-
29
- describe("wishlist loader", () => {
30
- let fetchSpy: ReturnType<typeof vi.spyOn>;
31
-
32
- beforeEach(() => {
33
- configureMagento({
34
- baseUrl: "https://loja.example.com/",
35
- apiKey: "secret",
36
- storeId: 1,
37
- site: "example",
38
- });
39
- fetchSpy = vi.spyOn(globalThis, "fetch");
40
- });
41
-
42
- afterEach(() => {
43
- vi.restoreAllMocks();
44
- });
45
-
46
- it("returns null when no PHPSESSID cookie is present", async () => {
47
- expect(await wishlist(null, new Request("http://localhost/"))).toBeNull();
48
- expect(fetchSpy).not.toHaveBeenCalled();
49
- });
50
-
51
- it("requests /customer/section/load?sections=wishlist", async () => {
52
- fetchSpy.mockResolvedValue(
53
- mockResponse({
54
- wishlist: { counter: "0", items: [], counter_number: 0, data_id: 1 },
55
- }),
56
- );
57
- await wishlist(null, requestWithCookie("PHPSESSID=abc"));
58
- const [target] = fetchSpy.mock.calls[0] as [URL];
59
- expect(target.toString()).toBe(
60
- "https://loja.example.com/example/customer/section/load?sections=wishlist",
61
- );
62
- });
63
-
64
- it("returns the wishlist payload on success", async () => {
65
- const wl = {
66
- counter: "1",
67
- counter_number: 1,
68
- data_id: 1,
69
- items: [
70
- {
71
- image: { template: "", src: "", width: 0, height: 0, alt: "" },
72
- product_sku: "ABC",
73
- product_id: "1",
74
- product_url: "",
75
- product_name: "n",
76
- product_price: "10",
77
- product_is_saleable_and_visible: true,
78
- product_has_required_options: false,
79
- add_to_cart_params: "",
80
- delete_item_params: "",
81
- },
82
- ],
83
- };
84
- fetchSpy.mockResolvedValue(mockResponse({ wishlist: wl }));
85
- expect(await wishlist(null, requestWithCookie("PHPSESSID=abc"))).toEqual(wl);
86
- });
87
-
88
- it("returns null when bundle lacks a wishlist slice", async () => {
89
- fetchSpy.mockResolvedValue(mockResponse({}));
90
- expect(await wishlist(null, requestWithCookie("PHPSESSID=abc"))).toBeNull();
91
- });
92
-
93
- it("returns null on non-2xx response", async () => {
94
- fetchSpy.mockResolvedValue(mockResponse(null, 500));
95
- expect(await wishlist(null, requestWithCookie("PHPSESSID=abc"))).toBeNull();
96
- });
97
- });
@@ -1,40 +0,0 @@
1
- /**
2
- * Magento newsletter subscribe — POST a customer email to the
3
- * `/V1/newsletter/subscribed` REST endpoint.
4
- *
5
- * Verbatim port of `deco-cx/apps/magento/actions/newsletter/subscribe.ts`:
6
- * the legacy version took `(props, _req, ctx)` and pulled `storeId` /
7
- * `clientAdmin` / `site` off the App() context. The TanStack/Node port
8
- * reads the same fields from `getMagentoConfig()` and uses
9
- * `magentoFetch` so auth/origin/Referer headers stay aligned with the
10
- * rest of the magento app. Endpoint shape and request body are
11
- * unchanged so the Magento backend doesn't need re-tuning.
12
- */
13
- import { getMagentoConfig, magentoFetch } from "../../client";
14
- import type { NewsletterData } from "../../types";
15
-
16
- export interface SubscribeProps {
17
- /**
18
- * @title Email
19
- */
20
- email: string;
21
- }
22
-
23
- export default async function subscribe(props: SubscribeProps): Promise<NewsletterData | null> {
24
- const { site, storeId } = getMagentoConfig();
25
- const path = `/rest/${encodeURIComponent(site)}/V1/newsletter/subscribed`;
26
-
27
- const res = await magentoFetch(path, {
28
- method: "POST",
29
- headers: { "Content-Type": "application/json" },
30
- body: JSON.stringify({
31
- email: props.email,
32
- store_id: Number(storeId),
33
- }),
34
- });
35
-
36
- if (!res.ok) return null;
37
- const result = (await res.json()) as NewsletterData | { success: false };
38
- if (!result || (result as NewsletterData).success === false) return null;
39
- return result as NewsletterData;
40
- }
@@ -1,67 +0,0 @@
1
- /**
2
- * Magento product stock-alert subscribe — fires a GraphQL mutation so
3
- * the customer gets notified when an out-of-stock SKU is replenished.
4
- *
5
- * Verbatim port of `deco-cx/apps/magento/actions/product/stockAlert.ts`.
6
- * The Fresh version called `ctx.clientGraphql.query(...)` with a third
7
- * `STALE` parameter that opted into a 1h SWR cache. The TanStack/Node
8
- * port uses `magentoFetch` against `/graphql` directly; STALE was a
9
- * cache hint only — the mutation is a write so no caching applies and
10
- * we can drop the parameter (the original passed it but mutations are
11
- * never cached server-side, so the behavior is identical).
12
- *
13
- * Response shape preserved: returns `{ data: { productStockAlert } }`
14
- * on success or `{ error: string }` on failure.
15
- */
16
- import { getMagentoConfig, magentoFetch } from "../../client";
17
- import type { ProductStockAlertResponse } from "../../types";
18
-
19
- export interface StockAlertProps {
20
- product_id: number;
21
- name: string;
22
- email: string;
23
- }
24
-
25
- const MUTATION = `mutation ProductStockAlert($product_id: Int!, $name: String!, $email: String!) {
26
- productStockAlert(
27
- product_id: $product_id
28
- name: $name
29
- email: $email
30
- ) {
31
- message
32
- status
33
- }
34
- }`;
35
-
36
- export default async function stockAlert(
37
- props: StockAlertProps,
38
- ): Promise<ProductStockAlertResponse | { error: string }> {
39
- const { product_id, name, email } = props;
40
- const { baseUrl } = getMagentoConfig();
41
-
42
- try {
43
- const res = await magentoFetch(`${baseUrl.replace(/\/$/, "")}/graphql`, {
44
- method: "POST",
45
- headers: { "Content-Type": "application/json" },
46
- body: JSON.stringify({
47
- operationName: "ProductStockAlert",
48
- variables: { product_id, name, email },
49
- query: MUTATION,
50
- }),
51
- });
52
-
53
- const json = (await res.json()) as {
54
- data?: { productStockAlert: { message: string; status: boolean } };
55
- };
56
-
57
- if (!json.data?.productStockAlert) {
58
- return { error: "productStockAlert payload missing in GraphQL response" };
59
- }
60
-
61
- return { data: { productStockAlert: json.data.productStockAlert } };
62
- } catch (error) {
63
- return {
64
- error: error instanceof Error ? error.message : "Erro desconhecido",
65
- };
66
- }
67
- }
@@ -1,57 +0,0 @@
1
- /**
2
- * Adds a product to the customer's wishlist and returns the refreshed
3
- * wishlist on success (or null on failure).
4
- *
5
- * Ported from `deco-cx/apps/magento/actions/wishlist/addItem.ts`. The
6
- * Fresh version posted multipart FormData with `product` + `form_key`
7
- * to `/wishlist/index/add/` and on success delegated to
8
- * wishlistLoader to fetch the updated state. Same flow here — but the
9
- * wishlistLoader is imported from the upstream port instead of the
10
- * legacy in-site path.
11
- */
12
- import { getCookies } from "@decocms/blocks/sdk/cookie";
13
- import { getMagentoConfig, magentoFetch } from "../../client";
14
- import wishlistLoader from "../../loaders/wishlist";
15
- import type { Wishlist } from "../../utils/client/types";
16
- import { FORM_KEY_COOKIE, SESSION_COOKIE } from "../../utils/constants";
17
- import { getUserCookie } from "../../utils/user";
18
-
19
- export interface AddWishlistItemProps {
20
- productId: string;
21
- }
22
-
23
- export default async function addItem(
24
- { productId }: AddWishlistItemProps,
25
- req: Request,
26
- ): Promise<Wishlist | null> {
27
- try {
28
- const sessionId = getUserCookie(req.headers);
29
- if (!sessionId) return null;
30
-
31
- const cookies = getCookies(req.headers);
32
- const formKey = cookies[FORM_KEY_COOKIE];
33
- if (!formKey) return null;
34
-
35
- const { site } = getMagentoConfig();
36
- const body = new FormData();
37
- body.append("product", productId);
38
- body.append("form_key", formKey);
39
-
40
- const res = await magentoFetch(`/${encodeURIComponent(site)}/wishlist/index/add/`, {
41
- method: "POST",
42
- headers: {
43
- Cookie: `${SESSION_COOKIE}=${sessionId}`,
44
- "x-requested-with": "XMLHttpRequest",
45
- },
46
- body,
47
- });
48
-
49
- if (!res.ok) return null;
50
- const { success } = (await res.json()) as { success?: boolean };
51
- if (!success) return null;
52
-
53
- return wishlistLoader(null, req);
54
- } catch {
55
- return null;
56
- }
57
- }
@@ -1,59 +0,0 @@
1
- /**
2
- * Removes a wishlist item by its Magento item id and returns the
3
- * refreshed wishlist on success (or null on failure).
4
- *
5
- * Ported from `deco-cx/apps/magento/actions/wishlist/removeItem.ts`.
6
- * The legacy code also passed an empty `uenc=""` form param — kept
7
- * here byte-for-byte because Magento's wishlist controller treats the
8
- * absence of `uenc` differently from an empty value and we don't want
9
- * a silent behavior shift.
10
- */
11
- import { getCookies } from "@decocms/blocks/sdk/cookie";
12
- import { getMagentoConfig, magentoFetch } from "../../client";
13
- import wishlistLoader from "../../loaders/wishlist";
14
- import type { Wishlist } from "../../utils/client/types";
15
- import { FORM_KEY_COOKIE, SESSION_COOKIE } from "../../utils/constants";
16
- import { getUserCookie } from "../../utils/user";
17
-
18
- export interface RemoveWishlistItemProps {
19
- /** Magento wishlist item id (NOT the product id — the wishlist row id) */
20
- productId: string;
21
- }
22
-
23
- export default async function removeItem(
24
- { productId }: RemoveWishlistItemProps,
25
- req: Request,
26
- ): Promise<Wishlist | null> {
27
- try {
28
- const sessionId = getUserCookie(req.headers);
29
- if (!sessionId) return null;
30
-
31
- const cookies = getCookies(req.headers);
32
- const formKey = cookies[FORM_KEY_COOKIE];
33
- if (!formKey) return null;
34
-
35
- const { site } = getMagentoConfig();
36
- const body = new FormData();
37
- body.append("item", productId);
38
- body.append("uenc", "");
39
- body.append("form_key", formKey);
40
-
41
- const res = await magentoFetch(`/${encodeURIComponent(site)}/wishlist/index/remove/`, {
42
- method: "POST",
43
- headers: {
44
- Cookie: `${SESSION_COOKIE}=${sessionId}`,
45
- "x-requested-with": "XMLHttpRequest",
46
- "Content-Type": "application/x-www-form-urlencoded",
47
- },
48
- body,
49
- });
50
-
51
- if (!res.ok) return null;
52
- const { success } = (await res.json()) as { success?: boolean };
53
- if (!success) return null;
54
-
55
- return wishlistLoader(null, req);
56
- } catch {
57
- return null;
58
- }
59
- }