Skip to content

Commit bde1c26

Browse files
authored
Merge pull request #3 from deco-sites/JonasJesus42/sync-main
refactor: migrate storefront off Fresh-era patterns to TanStack Start idioms
2 parents a67c562 + 502e91b commit bde1c26

70 files changed

Lines changed: 4035 additions & 2021 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bun.lock

Lines changed: 881 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/actions/shipping/simulate.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { usePlatform } from "../../apps/site";
2+
3+
export interface ShippingMethod {
4+
id: string;
5+
name: string;
6+
/** Estimated delivery in business days. */
7+
days: number;
8+
/** Price in major units of `currency`. 0 = free. */
9+
price: number;
10+
/** ISO-4217 currency code (e.g. "USD", "BRL"). */
11+
currency: string;
12+
}
13+
14+
export interface ShippingSimulation {
15+
postalCode: string;
16+
methods: ShippingMethod[];
17+
}
18+
19+
interface Props {
20+
postalCode: string;
21+
}
22+
23+
const sanitize = (s: string) => (s ?? "").replace(/\D+/g, "");
24+
25+
async function action(
26+
props: Props,
27+
_req?: Request,
28+
): Promise<ShippingSimulation> {
29+
const cep = sanitize(props.postalCode);
30+
if (cep.length < 4) throw new Error("Invalid postal code");
31+
32+
const platform = usePlatform();
33+
34+
if (platform === "vtex") {
35+
// TODO(consumer): call VTEX shipping simulator (sla simulation),
36+
// map response to ShippingMethod[].
37+
}
38+
if (platform === "shopify") {
39+
// TODO(consumer): create a draft cart with the line item(s),
40+
// run buyerIdentityUpdate with this postal code, read deliveryGroups.
41+
}
42+
if (platform === "wake") {
43+
// TODO(consumer): wire wake shipping endpoint here.
44+
}
45+
46+
// Default: deterministic mock so the demo works without a backend.
47+
// Remove this fallback once a real platform branch is implemented.
48+
const seed = Number.parseInt(cep.slice(0, 3) || "0", 10) || 0;
49+
const baseDays = 2 + (seed % 7);
50+
51+
const methods: ShippingMethod[] = [
52+
{
53+
id: "standard",
54+
name: "Standard shipping",
55+
days: baseDays + 2,
56+
price: 19.9,
57+
currency: "USD",
58+
},
59+
{
60+
id: "express",
61+
name: "Express shipping",
62+
days: Math.max(1, baseDays - 1),
63+
price: 39.9,
64+
currency: "USD",
65+
},
66+
{
67+
id: "free",
68+
name: "Free shipping",
69+
days: baseDays + 5,
70+
price: 0,
71+
currency: "USD",
72+
},
73+
];
74+
75+
return { postalCode: cep, methods };
76+
}
77+
78+
export default action;

src/actions/wishlist/submit.ts

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,56 @@
1-
import { type AppContext, usePlatform } from "../../apps/site";
2-
import { type Wishlist } from "../../components/wishlist/Provider";
1+
import { RequestContext } from "@decocms/start/sdk/requestContext";
2+
import { usePlatform } from "../../apps/site";
3+
import {
4+
EMPTY_WISHLIST,
5+
type WishlistState,
6+
} from "../../platform/wishlist";
7+
import {
8+
readWishlistCookie,
9+
serializeWishlistCookie,
10+
} from "../../loaders/_cookie";
311

412
interface Props {
513
productID: string;
614
productGroupID: string;
715
}
816

9-
// Phase 6 TODO: replace `any` with the real VTEX context once we re-export
10-
// AppContextVTEX from @decocms/apps/vtex.
11-
type AppContextVTEX = AppContext & { invoke: (...args: any[]) => any };
12-
1317
async function action(
1418
props: Props,
15-
_req?: Request,
16-
ctx: AppContext = {} as AppContext,
17-
): Promise<Wishlist> {
18-
const { productID, productGroupID } = props;
19+
req?: Request,
20+
): Promise<WishlistState> {
21+
if (!props?.productID) throw new Error("productID is required");
22+
23+
const request = req ?? RequestContext.current?.request;
1924
const platform = usePlatform();
2025

2126
if (platform === "vtex") {
22-
const vtex = ctx as unknown as AppContextVTEX;
23-
24-
const list: any[] = await vtex.invoke("vtex/loaders/wishlist.ts");
25-
const item = list.find((i: any) => i.sku === productID);
26-
27-
try {
28-
const response = item
29-
? await vtex.invoke(
30-
"vtex/actions/wishlist/removeItem.ts",
31-
{ id: item.id },
32-
)
33-
: await vtex.invoke(
34-
"vtex/actions/wishlist/addItem.ts",
35-
{ sku: productID, productId: productGroupID },
36-
);
27+
// TODO(consumer): real VTEX wishlist toggle, e.g.
28+
// const list = await invoke("vtex/loaders/wishlist.ts");
29+
// const item = list.find((i) => i.sku === props.productID);
30+
// const next = item
31+
// ? await invoke("vtex/actions/wishlist/removeItem.ts", { id: item.id })
32+
// : await invoke("vtex/actions/wishlist/addItem.ts", {
33+
// sku: props.productID, productId: props.productGroupID,
34+
// });
35+
// return { productIDs: next.map((i) => i.sku) };
36+
}
37+
if (platform === "wake") {
38+
// TODO(consumer): wire wake wishlist endpoint here.
39+
}
3740

38-
return {
39-
productIDs: response.map((item) => item.sku),
40-
};
41-
} catch {
42-
return {
43-
productIDs: list.map((item) => item.sku),
44-
};
41+
// Default: cookie-backed so the demo persists per-browser without a backend.
42+
const current = request ? readWishlistCookie(request) : EMPTY_WISHLIST;
43+
const next: WishlistState = current.productIDs.includes(props.productID)
44+
? {
45+
productIDs: current.productIDs.filter((id) => id !== props.productID),
4546
}
46-
}
47+
: { productIDs: [...current.productIDs, props.productID] };
4748

48-
throw new Error(`Unsupported platform: ${platform}`);
49+
RequestContext.responseHeaders.append(
50+
"Set-Cookie",
51+
serializeWishlistCookie(next),
52+
);
53+
return next;
4954
}
5055

5156
export default action;

src/components/header/SignIn.tsx

Lines changed: 26 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,34 @@
1+
import { Link } from "@tanstack/react-router";
12
import { clx } from "~/sdk/clx";
2-
import { useId } from "react";
3+
import { useUser } from "../../platform/user";
34
import Icon from "../ui/Icon";
4-
import { useScript } from "@decocms/start/sdk/useScript";
5-
const onLoad = (containerID: string) => {
6-
window.STOREFRONT.USER.subscribe((sdk) => {
7-
const container = document.getElementById(containerID) as HTMLDivElement;
8-
const nodes = container.querySelectorAll<HTMLAnchorElement>("a");
9-
const login = nodes.item(0);
10-
const account = nodes.item(1);
11-
const user = sdk.getUser();
12-
if (user?.email) {
13-
login.classList.add("hidden");
14-
account.classList.remove("hidden");
15-
} else {
16-
login.classList.remove("hidden");
17-
account.classList.add("hidden");
18-
}
19-
});
20-
};
21-
function SignIn({ variant }: {
5+
6+
interface Props {
227
variant: "mobile" | "desktop";
23-
}) {
24-
const id = useId();
25-
return (
26-
<div id={id}>
27-
<a
28-
className={clx(
29-
"btn btn-sm font-thin btn-ghost no-animation",
30-
variant === "mobile" && "btn-square",
31-
)}
32-
href="/login"
33-
aria-label="Login"
34-
>
35-
<Icon id="account_circle" />
36-
{variant === "desktop" && <span>Sign in</span>}
37-
</a>
38-
<a
39-
className={clx(
40-
"hidden",
41-
"btn btn-sm font-thin btn-ghost no-animation",
42-
variant === "mobile" && "btn-square",
43-
)}
44-
href="/account"
45-
aria-label="Account"
46-
>
8+
}
9+
10+
function SignIn({ variant }: Props) {
11+
const { isAuthenticated } = useUser();
12+
const className = clx(
13+
"btn btn-sm font-thin btn-ghost no-animation",
14+
variant === "mobile" && "btn-square",
15+
);
16+
17+
if (isAuthenticated) {
18+
return (
19+
<Link to="/account" preload="intent" className={className} aria-label="Account">
4720
<Icon id="account_circle" />
4821
{variant === "desktop" && <span>My account</span>}
49-
</a>
50-
<script
51-
type="module"
52-
suppressHydrationWarning
53-
dangerouslySetInnerHTML={{ __html: useScript(onLoad, id) }}
54-
/>
55-
</div>
22+
</Link>
23+
);
24+
}
25+
26+
return (
27+
<Link to="/login" preload="intent" className={className} aria-label="Login">
28+
<Icon id="account_circle" />
29+
{variant === "desktop" && <span>Sign in</span>}
30+
</Link>
5631
);
5732
}
33+
5834
export default SignIn;

0 commit comments

Comments
 (0)