@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.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/magentoClient.d.ts +62 -0
- package/dist/magentoClient.d.ts.map +1 -0
- package/dist/magentoClient.js +96 -0
- package/dist/magentoClient.js.map +1 -0
- package/package.json +19 -27
- package/src/index.ts +2 -29
- package/src/README.md +0 -65
- package/src/__tests__/cart.test.ts +0 -139
- package/src/__tests__/client.test.ts +0 -252
- package/src/__tests__/features.test.ts +0 -51
- package/src/__tests__/graphql.test.ts +0 -183
- package/src/__tests__/magentoClient.test.ts +0 -166
- package/src/__tests__/newsletter-subscribe.test.ts +0 -86
- package/src/__tests__/product-stockAlert.test.ts +0 -80
- package/src/__tests__/stringifySearchCriteria.test.ts +0 -56
- package/src/__tests__/transform.test.ts +0 -346
- package/src/__tests__/user-loader.test.ts +0 -134
- package/src/__tests__/wishlist-actions.test.ts +0 -140
- package/src/__tests__/wishlist-loader.test.ts +0 -97
- package/src/actions/newsletter/subscribe.ts +0 -40
- package/src/actions/product/stockAlert.ts +0 -67
- package/src/actions/wishlist/addItem.ts +0 -57
- package/src/actions/wishlist/removeItem.ts +0 -59
- package/src/client.ts +0 -252
- package/src/loaders/cart.ts +0 -64
- package/src/loaders/features.ts +0 -16
- package/src/loaders/user.ts +0 -60
- package/src/loaders/wishlist.ts +0 -29
- package/src/middleware.ts +0 -19
- package/src/types.ts +0 -51
- package/src/utils/__tests__/instrumentation.test.ts +0 -59
- package/src/utils/cacheTimeControl.ts +0 -69
- package/src/utils/client/types.ts +0 -270
- package/src/utils/constants.ts +0 -126
- package/src/utils/fetchCache.ts +0 -55
- package/src/utils/graphql-types.ts +0 -99
- package/src/utils/graphql.ts +0 -155
- package/src/utils/instrumentedFetch.ts +0 -57
- package/src/utils/operationRouter.ts +0 -43
- package/src/utils/stringifySearchCriteria.ts +0 -62
- package/src/utils/transform.ts +0 -283
- package/src/utils/user.ts +0 -16
- 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
|
-
}
|