Skip to content

Commit

Permalink
Merge pull request #21 from deco-cx/feat/vnda-cart
Browse files Browse the repository at this point in the history
feat: vnda httpclient
  • Loading branch information
tlgimenes authored Sep 3, 2023
2 parents e1478e3 + d18cbae commit 0565383
Show file tree
Hide file tree
Showing 21 changed files with 336 additions and 450 deletions.
11 changes: 5 additions & 6 deletions utils/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const createHttpClient = <T>({

return (
params: Record<string, string | number | string[] | number[]>,
init: RequestInit,
init?: RequestInit,
) => {
const mapped = new Map(Object.entries(params));

Expand Down Expand Up @@ -125,20 +125,19 @@ export const createHttpClient = <T>({
arrayed.forEach((item) => url.searchParams.append(key, `${item}`));
});

const shouldStringifyBody = init.body != null &&
const isJSON = init?.body != null &&
typeof init.body !== "string" &&
!(init.body instanceof ReadableStream) &&
!(init.body instanceof FormData) &&
!(init.body instanceof URLSearchParams) &&
!(init.body instanceof Blob) &&
!(init.body instanceof ArrayBuffer);

const headers = new Headers(init.headers);
const headers = new Headers(init?.headers);
defaultHeaders?.forEach((value, key) => headers.set(key, value));
isJSON && headers.set("content-type", "application/json");

const body = shouldStringifyBody
? JSON.stringify(init.body)
: undefined;
const body = isJSON ? JSON.stringify(init.body) : undefined;

return fetcher(url, {
...init,
Expand Down
41 changes: 15 additions & 26 deletions vnda/actions/cart/addItem.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { setCookie } from "std/http/mod.ts";
import { HttpError } from "../../../utils/http.ts";
import cartLoader from "../../loaders/cart.ts";
import { AppContext } from "../../mod.ts";
import { getCartCookie } from "../../utils/cart.ts";
import type { Cart } from "../../utils/client/types.ts";

export interface Props {
Expand All @@ -13,36 +15,23 @@ const action = async (
req: Request,
ctx: AppContext,
): Promise<Cart> => {
const { client } = ctx;
const { api } = ctx;
const { itemId, quantity, attributes } = props;
const reqCookies = req.headers.get("cookie") ?? "";
const cartId = getCartCookie(req.headers);

const { orderForm, cookies } = await client.carrinho.adicionar({
cookie: reqCookies,
sku: itemId,
quantity,
attributes,
});

// in case the cart was created, set the cookie to the browser
for (const cookie of cookies) {
setCookie(ctx.response.headers, {
...cookie,
domain: new URL(req.url).hostname,
});
if (!cartId) {
throw new HttpError(400, "Missing cart cookie");
}

const allCookies = [
reqCookies,
...cookies.map(({ name, value }) => `${name}=${value}`),
].join("; ");

const relatedItems = await client.carrinho.relatedItems(allCookies);
await api["POST /api/v2/carts/:cartId/items"]({ cartId }, {
body: {
sku: itemId,
quantity,
extra: attributes,
},
});

return {
orderForm,
relatedItems,
};
return cartLoader({}, req, ctx);
};

export default action;
25 changes: 0 additions & 25 deletions vnda/actions/cart/setShippingAddress.ts

This file was deleted.

32 changes: 32 additions & 0 deletions vnda/actions/cart/updateCart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { HttpError } from "../../../utils/http.ts";
import cartLoader from "../../loaders/cart.ts";
import { AppContext } from "../../mod.ts";
import { getCartCookie } from "../../utils/cart.ts";
import type { Cart } from "../../utils/client/types.ts";

export interface Props {
agent?: string;
zip?: string;
client_id?: number;
coupon_code?: string;
rebate_token?: string;
}

const action = async (
props: Props,
req: Request,
ctx: AppContext,
): Promise<Cart> => {
const { api } = ctx;
const cartId = getCartCookie(req.headers);

if (!cartId) {
throw new HttpError(400, "Missing cart cookie");
}

await api["PATCH /api/v2/carts/:cartId"]({ cartId }, { body: props });

return cartLoader({}, req, ctx);
};

export default action;
24 changes: 0 additions & 24 deletions vnda/actions/cart/updateCoupon.ts

This file was deleted.

22 changes: 15 additions & 7 deletions vnda/actions/cart/updateItem.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { HttpError } from "../../../utils/http.ts";
import cartLoader from "../../loaders/cart.ts";
import { AppContext } from "../../mod.ts";
import cart from "../../loaders/cart.ts";
import { getCartCookie } from "../../utils/cart.ts";
import type { Cart } from "../../utils/client/types.ts";

export interface Props {
Expand All @@ -12,17 +14,23 @@ const action = async (
req: Request,
ctx: AppContext,
): Promise<Cart> => {
const { client } = ctx;
const { itemId: item_id, quantity } = props;
const cookie = req.headers.get("cookie") ?? "";
const { api } = ctx;
const { itemId, quantity } = props;
const cartId = getCartCookie(req.headers);

if (!cartId) {
throw new HttpError(400, "Missing cart cookie");
}

if (quantity > 0) {
await client.carrinho.atualizar({ item_id, quantity }, cookie);
await api["PATCH /api/v2/carts/:cartId/items/:itemId"]({ cartId, itemId }, {
body: { quantity },
});
} else {
await client.carrinho.remover(item_id, cookie);
await api["DELETE /api/v2/carts/:cartId/items/:itemId"]({ cartId, itemId });
}

return cart({}, req, ctx);
return cartLoader({}, req, ctx);
};

export default action;
7 changes: 5 additions & 2 deletions vnda/hooks/context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IS_BROWSER } from "$fresh/runtime.ts";
import { signal } from "@preact/signals";
import { Runtime } from "../runtime.ts";
import { invoke } from "../runtime.ts";
import { Cart } from "../utils/client/types.ts";

interface Context {
Expand Down Expand Up @@ -46,7 +46,10 @@ const enqueue = (
return queue;
};

const load = (signal: AbortSignal) => Runtime.vnda.loaders.cart({}, { signal });
const load = (signal: AbortSignal) =>
invoke({
cart: invoke.vnda.loaders.cart(),
}, { signal });

if (IS_BROWSER) {
enqueue(load);
Expand Down
46 changes: 25 additions & 21 deletions vnda/hooks/useCart.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { AnalyticsItem } from "../../commerce/types.ts";
import { Runtime } from "../runtime.ts";
import { Cart, Item } from "../utils/client/types.ts";
import type { InvocationFuncFor } from "deco/clients/withManifest.ts";
import type { AnalyticsItem } from "../../commerce/types.ts";
import type { Manifest } from "../manifest.gen.ts";
import { invoke } from "../runtime.ts";
import type { Cart, Item } from "../utils/client/types.ts";
import { state as storeState } from "./context.ts";

const { cart, loading } = storeState;
Expand All @@ -21,28 +23,30 @@ export const itemToAnalyticsItem = (
quantity: item.quantity,
});

const wrap =
<T>(action: (p: T, init?: RequestInit | undefined) => Promise<Cart>) =>
(p: T) =>
storeState.enqueue(async (signal) => ({
cart: await action(p, { signal }),
}));
type PropsOf<T> = T extends (props: infer P, r: any, ctx: any) => any ? P
: T extends (props: infer P, r: any) => any ? P
: T extends (props: infer P) => any ? P
: never;

type Actions =
| "vnda/actions/cart/addItem.ts"
| "vnda/actions/cart/updateCart.ts"
| "vnda/actions/cart/updateItem.ts";

const action =
(key: Actions) => (props: PropsOf<InvocationFuncFor<Manifest, typeof key>>) =>
storeState.enqueue((signal) =>
invoke({ cart: { key, props } }, { signal }) satisfies Promise<
{ cart: Cart }
>
);

const state = {
cart,
loading,
addItem: wrap(
Runtime.vnda.actions.cart.addItem,
),
updateItem: wrap(
Runtime.vnda.actions.cart.updateItem,
),
setShippingAddress: wrap(
Runtime.vnda.actions.cart.setShippingAddress,
),
updateCoupon: wrap(
Runtime.vnda.actions.cart.updateCoupon,
),
update: action("vnda/actions/cart/updateCart.ts"),
addItem: action("vnda/actions/cart/addItem.ts"),
updateItem: action("vnda/actions/cart/updateItem.ts"),
};

export const useCart = () => state;
17 changes: 10 additions & 7 deletions vnda/loaders/cart.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AppContext } from "../mod.ts";
import { getCartCookie, setCartCookie } from "../utils/cart.ts";
import type { Cart } from "../utils/client/types.ts";

/**
Expand All @@ -10,17 +11,19 @@ const loader = async (
req: Request,
ctx: AppContext,
): Promise<Cart> => {
const { client } = ctx;
const cookies = req.headers.get("cookie") ?? "";
const { api } = ctx;
const cartId = getCartCookie(req.headers);

const [orderForm, relatedItems] = await Promise.all([
client.carrinho.get(cookies),
client.carrinho.relatedItems(cookies),
]);
const orderForm = cartId
? await api["GET /api/v2/carts/:cartId"]({ cartId })
.then((res) => res.json())
: await api["POST /api/v2/carts"]({}).then((res) => res.json());

setCartCookie(ctx.response.headers, orderForm.id.toString());

return {
orderForm,
relatedItems,
relatedItems: [],
};
};

Expand Down
12 changes: 9 additions & 3 deletions vnda/loaders/productDetailsPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@ async function loader(
): Promise<ProductDetailsPage | null> {
const url = new URL(req.url);
const { slug } = props;
const { client } = ctx;
const { api } = ctx;

if (!slug) return null;

const variantId = url.searchParams.get("skuId") || null;
const { id } = parseSlug(slug);

const [maybeProduct, seo] = await Promise.all([
client.product.get(id),
client.seo.product(id),
api["GET /api/v2/products/:id"]({ id, include_images: true }, {
deco: { cache: "stale-while-revalidate" },
}).then((r) => r.json()).catch(() => null),
api["GET /api/v2/seo_data"]({
resource_type: "Product",
resource_id: id,
type: "category",
}, { deco: { cache: "stale-while-revalidate" } }).then((res) => res.json()),
]);

// 404: product not found
Expand Down
8 changes: 4 additions & 4 deletions vnda/loaders/productList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ const productListLoader = async (
ctx: AppContext,
): Promise<Product[] | null> => {
const url = new URL(req.url);
const { client } = ctx;
const { api } = ctx;

const search = await client.product.search({
const search = await api["GET /api/v2/products/search"]({
term: props?.term,
wildcard: props?.wildcard,
sort: props?.sort,
per_page: props?.count,
tags: props?.tags,
});
"tags[]": props?.tags,
}, { deco: { cache: "stale-while-revalidate" } }).then((res) => res.json());

return search.results.map((product) =>
toProduct(product, null, {
Expand Down
Loading

0 comments on commit 0565383

Please sign in to comment.