Skip to content

Commit 74cc784

Browse files
guitavanoclaude
andauthored
fix(vtex): use MatchContext signature in birthday and userSegment matchers (#1586)
Both matchers were using the wrong function signature (AppContext-based) instead of the MatchContext interface expected by the deco matcher system. Extract shared cookie parsing into parseAuthCookie utility to avoid duplicated logic and unnecessary API calls for unauthenticated users. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5fde123 commit 74cc784

4 files changed

Lines changed: 171 additions & 21 deletions

File tree

vtex/manifest.gen.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ import * as $$$47 from "./loaders/wishlist.ts";
9797
import * as $$$48 from "./loaders/workflow/product.ts";
9898
import * as $$$49 from "./loaders/workflow/products.ts";
9999
import * as $$$$$$$0 from "./matchers/birthday.ts";
100+
import * as $$$$$$$1 from "./matchers/userSegment.ts";
100101
import * as $$$$$$0 from "./sections/Analytics/Vtex.tsx";
101102
import * as $$$$$$$$$$0 from "./workflows/events.ts";
102103
import * as $$$$$$$$$$1 from "./workflows/product/index.ts";
@@ -162,6 +163,7 @@ const manifest = {
162163
},
163164
"matchers": {
164165
"vtex/matchers/birthday.ts": $$$$$$$0,
166+
"vtex/matchers/userSegment.ts": $$$$$$$1,
165167
},
166168
"actions": {
167169
"vtex/actions/address/create.ts": $$$$$$$$$0,

vtex/matchers/birthday.ts

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
import { AppContext } from "../mod.ts";
2-
import { parseCookie } from "../utils/vtexId.ts";
3-
4-
interface Profile {
5-
birthDate: string | null;
6-
}
1+
import { type MatchContext } from "@deco/deco/blocks";
2+
import { parseAuthCookie } from "../utils/vtexId.ts";
73

84
/**
95
* @title {{{match}}}
@@ -24,24 +20,17 @@ export interface Props {
2420
*/
2521
const MatchBirthday = async (
2622
props: Props,
27-
req: Request,
28-
ctx: AppContext,
23+
{ request, invoke }: MatchContext,
2924
): Promise<boolean> => {
3025
const { match = "day" } = props;
31-
const { io } = ctx;
32-
const { cookie, payload } = parseCookie(req.headers, ctx.account);
3326

34-
if (!payload?.sub || !payload?.userId) {
35-
return false;
36-
}
27+
const payload = parseAuthCookie(request.headers);
28+
if (!payload?.sub || !payload?.userId) return false;
3729

3830
try {
39-
const query = "query getUserProfile { profile { birthDate }}";
40-
41-
const { profile } = await io.query<{ profile: Profile }, null>(
42-
{ query },
43-
{ headers: { cookie } },
44-
);
31+
// deno-lint-ignore no-explicit-any
32+
const profile = await (invoke as any).vtex.loaders.profile
33+
.getCurrentProfile({}) as { birthDate?: string } | null;
4534

4635
if (!profile?.birthDate) {
4736
return false;
@@ -69,10 +58,8 @@ const MatchBirthday = async (
6958
birthMonth,
7059
birthDay,
7160
);
72-
// Get the Sunday that starts the birthday week
7361
const weekStart = new Date(birthdayThisYear);
7462
weekStart.setDate(weekStart.getDate() - weekStart.getDay());
75-
// Saturday that ends the birthday week
7663
const weekEnd = new Date(weekStart);
7764
weekEnd.setDate(weekEnd.getDate() + 6);
7865
weekEnd.setHours(23, 59, 59, 999);

vtex/matchers/userSegment.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { type MatchContext } from "@deco/deco/blocks";
2+
import { parseAuthCookie } from "../utils/vtexId.ts";
3+
4+
/** @title Anonymous without cart */
5+
interface AnonymousWithoutCart {
6+
/**
7+
* @hide true
8+
*/
9+
segment: "anonymous-without-cart";
10+
}
11+
12+
/** @title Anonymous with cart */
13+
interface AnonymousWithCart {
14+
/**
15+
* @hide true
16+
*/
17+
segment: "anonymous-with-cart";
18+
}
19+
20+
/** @title Logged in */
21+
interface LoggedIn {
22+
/**
23+
* @hide true
24+
*/
25+
segment: "logged-in";
26+
}
27+
28+
/** @title Logged in without orders */
29+
interface LoggedInWithoutOrders {
30+
/**
31+
* @hide true
32+
*/
33+
segment: "logged-in-without-orders";
34+
}
35+
36+
/** @title Logged in with orders */
37+
interface LoggedInWithOrders {
38+
/**
39+
* @hide true
40+
*/
41+
segment: "logged-in-with-orders";
42+
}
43+
44+
/** @title Logged in with recent orders */
45+
interface LoggedInWithRecentOrders {
46+
/**
47+
* @hide true
48+
*/
49+
segment: "logged-in-with-recent-orders";
50+
/**
51+
* @title Months
52+
* @description Number of months to consider an order as "recent"
53+
* @default 3
54+
*/
55+
months?: number;
56+
}
57+
58+
export type Props =
59+
| AnonymousWithoutCart
60+
| AnonymousWithCart
61+
| LoggedIn
62+
| LoggedInWithoutOrders
63+
| LoggedInWithOrders
64+
| LoggedInWithRecentOrders;
65+
66+
/**
67+
* @title User Segment
68+
* @description Segment users by authentication status, cart state, and order history
69+
* @icon user-check
70+
*/
71+
const MatchUserSegment = async (
72+
props: Props,
73+
{ request, invoke }: MatchContext,
74+
): Promise<boolean> => {
75+
const { segment } = props;
76+
// deno-lint-ignore no-explicit-any
77+
const vtex = (invoke as any).vtex;
78+
79+
const payload = parseAuthCookie(request.headers);
80+
const isLoggedIn = Boolean(payload?.sub && payload?.userId);
81+
82+
try {
83+
if (segment === "anonymous-without-cart") {
84+
if (isLoggedIn) return false;
85+
const cart = await vtex.loaders.cart();
86+
return (cart?.items?.length ?? 0) === 0;
87+
}
88+
89+
if (segment === "anonymous-with-cart") {
90+
if (isLoggedIn) return false;
91+
const cart = await vtex.loaders.cart();
92+
return (cart?.items?.length ?? 0) > 0;
93+
}
94+
95+
// All remaining segments require login
96+
if (!isLoggedIn || !payload?.sub) return false;
97+
98+
if (segment === "logged-in") {
99+
return true;
100+
}
101+
102+
const email = payload.sub;
103+
104+
if (segment === "logged-in-without-orders") {
105+
const orders = await vtex.loaders.orders.list({
106+
clientEmail: email,
107+
per_page: "1",
108+
page: "1",
109+
});
110+
return orders?.paging?.total === 0;
111+
}
112+
113+
if (segment === "logged-in-with-orders") {
114+
const orders = await vtex.loaders.orders.list({
115+
clientEmail: email,
116+
per_page: "1",
117+
page: "1",
118+
});
119+
return orders?.paging?.total > 0;
120+
}
121+
122+
if (segment === "logged-in-with-recent-orders") {
123+
const { months = 3 } = props;
124+
const orders = await vtex.loaders.orders.list({
125+
clientEmail: email,
126+
per_page: "15",
127+
page: "1",
128+
});
129+
130+
if (!orders?.paging?.total) return false;
131+
132+
const now = new Date();
133+
const cutoff = new Date(now);
134+
cutoff.setMonth(cutoff.getMonth() - months);
135+
136+
return orders.list.some((order: { creationDate: string }) => {
137+
const created = new Date(order.creationDate);
138+
return created >= cutoff;
139+
});
140+
}
141+
142+
return false;
143+
} catch {
144+
return false;
145+
}
146+
};
147+
148+
export default MatchUserSegment;

vtex/utils/vtexId.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ interface CookiePayload {
1313
userId: string;
1414
}
1515

16+
export const parseAuthCookie = (headers: Headers) => {
17+
const cookies = getCookies(headers);
18+
const token = cookies[VTEX_ID_CLIENT_COOKIE] ||
19+
Object.entries(cookies).find(([k]) =>
20+
k.startsWith(`${VTEX_ID_CLIENT_COOKIE}_`)
21+
)?.[1];
22+
23+
if (!token) return null;
24+
25+
const decoded = decode(token);
26+
return decoded?.[1] as CookiePayload | undefined ?? null;
27+
};
28+
1629
export const parseCookie = (headers: Headers, account: string) => {
1730
const cookies = getCookies(headers);
1831
const cookie = cookies[VTEX_ID_CLIENT_COOKIE] ||

0 commit comments

Comments
 (0)