Merge branch 'develop' into feature/MED-129

This commit is contained in:
Danel Kungla
2025-09-24 15:00:27 +03:00
622 changed files with 19603 additions and 10824 deletions

View File

@@ -1,7 +1,7 @@
import Medusa from "@medusajs/js-sdk";
import Medusa from '@medusajs/js-sdk';
// Defaults to standard port for Medusa server
let MEDUSA_BACKEND_URL = "http://localhost:9000";
let MEDUSA_BACKEND_URL = 'http://localhost:9000';
if (process.env.MEDUSA_BACKEND_URL) {
MEDUSA_BACKEND_URL = process.env.MEDUSA_BACKEND_URL;
@@ -9,7 +9,7 @@ if (process.env.MEDUSA_BACKEND_URL) {
export const SDK_CONFIG = {
baseUrl: MEDUSA_BACKEND_URL,
debug: process.env.NODE_ENV === "development",
debug: process.env.NODE_ENV === 'development',
publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
};

View File

@@ -1,9 +1,9 @@
import React from "react"
import { CreditCard } from "@medusajs/icons"
import React from 'react';
import Ideal from "@modules/common/icons/ideal"
import Bancontact from "@modules/common/icons/bancontact"
import PayPal from "@modules/common/icons/paypal"
import { CreditCard } from '@medusajs/icons';
import Bancontact from '@modules/common/icons/bancontact';
import Ideal from '@modules/common/icons/ideal';
import PayPal from '@modules/common/icons/paypal';
/* Map of payment provider_id to their title and icon. Add in any payment providers you want to use. */
export const paymentInfoMap: Record<
@@ -11,58 +11,58 @@ export const paymentInfoMap: Record<
{ title: string; icon: React.JSX.Element }
> = {
pp_stripe_stripe: {
title: "Credit card",
title: 'Credit card',
icon: <CreditCard />,
},
"pp_stripe-ideal_stripe": {
title: "iDeal",
'pp_stripe-ideal_stripe': {
title: 'iDeal',
icon: <Ideal />,
},
"pp_stripe-bancontact_stripe": {
title: "Bancontact",
'pp_stripe-bancontact_stripe': {
title: 'Bancontact',
icon: <Bancontact />,
},
pp_paypal_paypal: {
title: "PayPal",
title: 'PayPal',
icon: <PayPal />,
},
pp_system_default: {
title: "Manual Payment",
title: 'Manual Payment',
icon: <CreditCard />,
},
// Add more payment providers here
}
};
// This only checks if it is native stripe for card payments, it ignores the other stripe-based providers
export const isStripe = (providerId?: string) => {
return providerId?.startsWith("pp_stripe_")
}
return providerId?.startsWith('pp_stripe_');
};
export const isPaypal = (providerId?: string) => {
return providerId?.startsWith("pp_paypal")
}
return providerId?.startsWith('pp_paypal');
};
export const isManual = (providerId?: string) => {
return providerId?.startsWith("pp_system_default")
}
return providerId?.startsWith('pp_system_default');
};
// Add currencies that don't need to be divided by 100
export const noDivisionCurrencies = [
"krw",
"jpy",
"vnd",
"clp",
"pyg",
"xaf",
"xof",
"bif",
"djf",
"gnf",
"kmf",
"mga",
"rwf",
"xpf",
"htg",
"vuv",
"xag",
"xdr",
"xau",
]
'krw',
'jpy',
'vnd',
'clp',
'pyg',
'xaf',
'xof',
'bif',
'djf',
'gnf',
'kmf',
'mga',
'rwf',
'xpf',
'htg',
'vuv',
'xag',
'xdr',
'xau',
];

View File

@@ -1,16 +1,16 @@
"use client"
'use client';
import React, { createContext, useContext } from "react"
import React, { createContext, useContext } from 'react';
interface ModalContext {
close: () => void
close: () => void;
}
const ModalContext = createContext<ModalContext | null>(null)
const ModalContext = createContext<ModalContext | null>(null);
interface ModalProviderProps {
children?: React.ReactNode
close: () => void
children?: React.ReactNode;
close: () => void;
}
export const ModalProvider = ({ children, close }: ModalProviderProps) => {
@@ -22,13 +22,13 @@ export const ModalProvider = ({ children, close }: ModalProviderProps) => {
>
{children}
</ModalContext.Provider>
)
}
);
};
export const useModal = () => {
const context = useContext(ModalContext)
const context = useContext(ModalContext);
if (context === null) {
throw new Error("useModal must be used within a ModalProvider")
throw new Error('useModal must be used within a ModalProvider');
}
return context
}
return context;
};

View File

@@ -1,9 +1,12 @@
"use server";
'use server';
import { revalidateTag } from 'next/cache';
import { redirect } from 'next/navigation';
import { sdk } from '@lib/config';
import medusaError from '@lib/util/medusa-error';
import { HttpTypes } from '@medusajs/types';
import medusaError from "@lib/util/medusa-error";
import { HttpTypes } from "@medusajs/types";
import { revalidateTag } from "next/cache";
import { redirect } from "next/navigation";
import {
getAuthHeaders,
getCacheOptions,
@@ -11,10 +14,9 @@ import {
getCartId,
removeCartId,
setCartId,
} from "./cookies";
import { getRegion } from "./regions";
import { sdk } from "@lib/config";
import { retrieveOrder } from "./orders";
} from './cookies';
import { retrieveOrder } from './orders';
import { getRegion } from './regions';
/**
* Retrieves a cart by its ID. If no ID is provided, it will use the cart ID from the cookies.
@@ -33,15 +35,15 @@ export async function retrieveCart(cartId?: string) {
};
const next = {
...(await getCacheOptions("carts")),
...(await getCacheOptions('carts')),
};
return await sdk.client
.fetch<HttpTypes.StoreCartResponse>(`/store/carts/${id}`, {
method: "GET",
method: 'GET',
query: {
fields:
"*items, *region, *items.product, *items.variant, *items.thumbnail, *items.metadata, +items.total, *promotions, +shipping_methods.name",
'*items, *region, *items.product, *items.variant, *items.thumbnail, *items.metadata, +items.total, *promotions, +shipping_methods.name',
},
headers,
next,
@@ -68,19 +70,19 @@ export async function getOrSetCart(countryCode: string) {
const cartResp = await sdk.store.cart.create(
{ region_id: region.id },
{},
headers
headers,
);
cart = cartResp.cart;
await setCartId(cart.id);
const cartCacheTag = await getCacheTag("carts");
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
}
if (cart && cart?.region_id !== region.id) {
await sdk.store.cart.update(cart.id, { region_id: region.id }, {}, headers);
const cartCacheTag = await getCacheTag("carts");
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
}
@@ -92,13 +94,13 @@ export async function updateCart(
{ onSuccess, onError }: { onSuccess: () => void; onError: () => void } = {
onSuccess: () => {},
onError: () => {},
}
},
) {
const cartId = id || (await getCartId());
if (!cartId) {
throw new Error(
"No existing cart found, please create one before updating"
'No existing cart found, please create one before updating',
);
}
@@ -109,10 +111,10 @@ export async function updateCart(
return sdk.store.cart
.update(cartId, data, {}, headers)
.then(async ({ cart }) => {
const cartCacheTag = await getCacheTag("carts");
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
const fulfillmentCacheTag = await getCacheTag("fulfillment");
const fulfillmentCacheTag = await getCacheTag('fulfillment');
revalidateTag(fulfillmentCacheTag);
onSuccess();
@@ -134,13 +136,13 @@ export async function addToCart({
countryCode: string;
}) {
if (!variantId) {
throw new Error("Missing variant ID when adding to cart");
throw new Error('Missing variant ID when adding to cart');
}
const cart = await getOrSetCart(countryCode);
if (!cart) {
throw new Error("Error retrieving or creating cart");
throw new Error('Error retrieving or creating cart');
}
const headers = {
@@ -155,20 +157,20 @@ export async function addToCart({
quantity,
},
{},
headers
headers,
)
.then(async () => {
const cartCacheTag = await getCacheTag("carts");
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
const fulfillmentCacheTag = await getCacheTag("fulfillment");
const fulfillmentCacheTag = await getCacheTag('fulfillment');
revalidateTag(fulfillmentCacheTag);
})
.catch(medusaError);
const newCart = await getOrSetCart(countryCode);
const addedItem = newCart.items?.filter(
(item) => !cart.items?.some((oldCartItem) => oldCartItem.id === item.id)
(item) => !cart.items?.some((oldCartItem) => oldCartItem.id === item.id),
)?.[0];
return { newCart, addedItem };
@@ -184,13 +186,13 @@ export async function updateLineItem({
metadata?: Record<string, any>;
}) {
if (!lineId) {
throw new Error("Missing lineItem ID when updating line item");
throw new Error('Missing lineItem ID when updating line item');
}
const cartId = await getCartId();
if (!cartId) {
throw new Error("Missing cart ID when updating line item");
throw new Error('Missing cart ID when updating line item');
}
const headers = {
@@ -200,10 +202,10 @@ export async function updateLineItem({
await sdk.store.cart
.updateLineItem(cartId, lineId, { quantity, metadata }, {}, headers)
.then(async () => {
const cartCacheTag = await getCacheTag("carts");
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
const fulfillmentCacheTag = await getCacheTag("fulfillment");
const fulfillmentCacheTag = await getCacheTag('fulfillment');
revalidateTag(fulfillmentCacheTag);
})
.catch(medusaError);
@@ -211,13 +213,13 @@ export async function updateLineItem({
export async function deleteLineItem(lineId: string) {
if (!lineId) {
throw new Error("Missing lineItem ID when deleting line item");
throw new Error('Missing lineItem ID when deleting line item');
}
const cartId = await getCartId();
if (!cartId) {
throw new Error("Missing cart ID when deleting line item");
throw new Error('Missing cart ID when deleting line item');
}
const headers = {
@@ -227,10 +229,10 @@ export async function deleteLineItem(lineId: string) {
await sdk.store.cart
.deleteLineItem(cartId, lineId, headers)
.then(async () => {
const cartCacheTag = await getCacheTag("carts");
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
const fulfillmentCacheTag = await getCacheTag("fulfillment");
const fulfillmentCacheTag = await getCacheTag('fulfillment');
revalidateTag(fulfillmentCacheTag);
})
.catch(medusaError);
@@ -250,7 +252,7 @@ export async function setShippingMethod({
return sdk.store.cart
.addShippingMethod(cartId, { option_id: shippingMethodId }, {}, headers)
.then(async () => {
const cartCacheTag = await getCacheTag("carts");
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
})
.catch(medusaError);
@@ -258,7 +260,7 @@ export async function setShippingMethod({
export async function initiatePaymentSession(
cart: HttpTypes.StoreCart,
data: HttpTypes.StoreInitializePaymentSession
data: HttpTypes.StoreInitializePaymentSession,
) {
const headers = {
...(await getAuthHeaders()),
@@ -267,7 +269,7 @@ export async function initiatePaymentSession(
return sdk.store.payment
.initiatePaymentSession(cart, data, {}, headers)
.then(async (resp) => {
const cartCacheTag = await getCacheTag("carts");
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
return resp;
})
@@ -279,12 +281,12 @@ export async function applyPromotions(
{ onSuccess, onError }: { onSuccess: () => void; onError: () => void } = {
onSuccess: () => {},
onError: () => {},
}
},
) {
const cartId = await getCartId();
if (!cartId) {
throw new Error("No existing cart found");
throw new Error('No existing cart found');
}
const headers = {
@@ -294,10 +296,10 @@ export async function applyPromotions(
return sdk.store.cart
.update(cartId, { promo_codes: codes }, {}, headers)
.then(async () => {
const cartCacheTag = await getCacheTag("carts");
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
const fulfillmentCacheTag = await getCacheTag("fulfillment");
const fulfillmentCacheTag = await getCacheTag('fulfillment');
revalidateTag(fulfillmentCacheTag);
onSuccess();
@@ -333,7 +335,7 @@ export async function removeDiscount(code: string) {
export async function removeGiftCard(
codeToRemove: string,
giftCards: any[]
giftCards: any[],
// giftCards: GiftCard[]
) {
// const cartId = getCartId()
@@ -353,9 +355,9 @@ export async function removeGiftCard(
export async function submitPromotionForm(
currentState: unknown,
formData: FormData
formData: FormData,
) {
const code = formData.get("code") as string;
const code = formData.get('code') as string;
try {
await applyPromotions([code]);
} catch (e: any) {
@@ -367,44 +369,44 @@ export async function submitPromotionForm(
export async function setAddresses(currentState: unknown, formData: FormData) {
try {
if (!formData) {
throw new Error("No form data found when setting addresses");
throw new Error('No form data found when setting addresses');
}
const cartId = getCartId();
if (!cartId) {
throw new Error("No existing cart found when setting addresses");
throw new Error('No existing cart found when setting addresses');
}
const data = {
shipping_address: {
first_name: formData.get("shipping_address.first_name"),
last_name: formData.get("shipping_address.last_name"),
address_1: formData.get("shipping_address.address_1"),
address_2: "",
company: formData.get("shipping_address.company"),
postal_code: formData.get("shipping_address.postal_code"),
city: formData.get("shipping_address.city"),
country_code: formData.get("shipping_address.country_code"),
province: formData.get("shipping_address.province"),
phone: formData.get("shipping_address.phone"),
first_name: formData.get('shipping_address.first_name'),
last_name: formData.get('shipping_address.last_name'),
address_1: formData.get('shipping_address.address_1'),
address_2: '',
company: formData.get('shipping_address.company'),
postal_code: formData.get('shipping_address.postal_code'),
city: formData.get('shipping_address.city'),
country_code: formData.get('shipping_address.country_code'),
province: formData.get('shipping_address.province'),
phone: formData.get('shipping_address.phone'),
},
email: formData.get("email"),
email: formData.get('email'),
} as any;
const sameAsBilling = formData.get("same_as_billing");
if (sameAsBilling === "on") data.billing_address = data.shipping_address;
const sameAsBilling = formData.get('same_as_billing');
if (sameAsBilling === 'on') data.billing_address = data.shipping_address;
if (sameAsBilling !== "on")
if (sameAsBilling !== 'on')
data.billing_address = {
first_name: formData.get("billing_address.first_name"),
last_name: formData.get("billing_address.last_name"),
address_1: formData.get("billing_address.address_1"),
address_2: "",
company: formData.get("billing_address.company"),
postal_code: formData.get("billing_address.postal_code"),
city: formData.get("billing_address.city"),
country_code: formData.get("billing_address.country_code"),
province: formData.get("billing_address.province"),
phone: formData.get("billing_address.phone"),
first_name: formData.get('billing_address.first_name'),
last_name: formData.get('billing_address.last_name'),
address_1: formData.get('billing_address.address_1'),
address_2: '',
company: formData.get('billing_address.company'),
postal_code: formData.get('billing_address.postal_code'),
city: formData.get('billing_address.city'),
country_code: formData.get('billing_address.country_code'),
province: formData.get('billing_address.province'),
phone: formData.get('billing_address.phone'),
};
await updateCart(data);
} catch (e: any) {
@@ -412,7 +414,7 @@ export async function setAddresses(currentState: unknown, formData: FormData) {
}
redirect(
`/${formData.get("shipping_address.country_code")}/checkout?step=delivery`
`/${formData.get('shipping_address.country_code')}/checkout?step=delivery`,
);
}
@@ -423,12 +425,12 @@ export async function setAddresses(currentState: unknown, formData: FormData) {
*/
export async function placeOrder(
cartId?: string,
options: { revalidateCacheTags: boolean } = { revalidateCacheTags: true }
options: { revalidateCacheTags: boolean } = { revalidateCacheTags: true },
) {
const id = cartId || (await getCartId());
if (!id) {
throw new Error("No existing cart found when placing an order");
throw new Error('No existing cart found when placing an order');
}
const headers = {
@@ -439,22 +441,22 @@ export async function placeOrder(
.complete(id, {}, headers)
.then(async (cartRes) => {
if (options?.revalidateCacheTags) {
const cartCacheTag = await getCacheTag("carts");
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
}
return cartRes;
})
.catch(medusaError);
if (cartRes?.type === "order") {
if (cartRes?.type === 'order') {
if (options?.revalidateCacheTags) {
const orderCacheTag = await getCacheTag("orders");
const orderCacheTag = await getCacheTag('orders');
revalidateTag(orderCacheTag);
}
removeCartId();
} else {
throw new Error("Cart is not an order");
throw new Error('Cart is not an order');
}
return retrieveOrder(cartRes.order.id);
@@ -475,14 +477,14 @@ export async function updateRegion(countryCode: string, currentPath: string) {
if (cartId) {
await updateCart({ region_id: region.id });
const cartCacheTag = await getCacheTag("carts");
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
}
const regionCacheTag = await getCacheTag("regions");
const regionCacheTag = await getCacheTag('regions');
revalidateTag(regionCacheTag);
const productsCacheTag = await getCacheTag("products");
const productsCacheTag = await getCacheTag('products');
revalidateTag(productsCacheTag);
redirect(`/${countryCode}${currentPath}`);
@@ -494,15 +496,15 @@ export async function listCartOptions() {
...(await getAuthHeaders()),
};
const next = {
...(await getCacheOptions("shippingOptions")),
...(await getCacheOptions('shippingOptions')),
};
return await sdk.client.fetch<{
shipping_options: HttpTypes.StoreCartShippingOption[];
}>("/store/shipping-options", {
}>('/store/shipping-options', {
query: { cart_id: cartId },
next,
headers,
cache: "force-cache",
cache: 'force-cache',
});
}

View File

@@ -1,33 +1,34 @@
import { sdk } from "@lib/config";
import { HttpTypes } from "@medusajs/types";
import { getCacheOptions } from "./cookies";
import { sdk } from '@lib/config';
import { HttpTypes } from '@medusajs/types';
import { getCacheOptions } from './cookies';
export const listCategories = async (query?: Record<string, any>) => {
const next = {
...(await getCacheOptions("categories")),
...(await getCacheOptions('categories')),
};
const limit = query?.limit || 100;
return sdk.client
.fetch<{ product_categories: HttpTypes.StoreProductCategory[] }>(
"/store/product-categories",
'/store/product-categories',
{
query: {
fields:
"*category_children, *products, *parent_category, *parent_category.parent_category",
'*category_children, *products, *parent_category, *parent_category.parent_category',
limit,
...query,
},
next,
}
},
)
.then(({ product_categories }) => product_categories);
};
export const getCategoryByHandle = async (categoryHandle: string[]) => {
const { product_categories } = await getProductCategories({
handle: `${categoryHandle.join("/")}`,
handle: `${categoryHandle.join('/')}`,
limit: 1,
});
return product_categories[0];
@@ -36,14 +37,14 @@ export const getCategoryByHandle = async (categoryHandle: string[]) => {
export const getProductCategories = async ({
handle,
limit,
fields = "*category_children, *products",
fields = '*category_children, *products',
}: {
handle?: string;
limit?: number;
fields?: string;
} = {}) => {
const next = {
...(await getCacheOptions("categories")),
...(await getCacheOptions('categories')),
};
return sdk.client.fetch<HttpTypes.StoreProductCategoryListResponse>(
@@ -55,6 +56,6 @@ export const getProductCategories = async ({
limit,
},
next,
}
},
);
};

View File

@@ -1,12 +1,13 @@
"use server";
'use server';
import { sdk, SDK_CONFIG } from "@lib/config";
import { HttpTypes } from "@medusajs/types";
import { getCacheOptions } from "./cookies";
import { SDK_CONFIG, sdk } from '@lib/config';
import { HttpTypes } from '@medusajs/types';
import { getCacheOptions } from './cookies';
export const retrieveCollection = async (id: string) => {
const next = {
...(await getCacheOptions("collections")),
...(await getCacheOptions('collections')),
};
return sdk.client
@@ -14,46 +15,46 @@ export const retrieveCollection = async (id: string) => {
`/store/collections/${id}`,
{
next,
cache: "force-cache",
}
cache: 'force-cache',
},
)
.then(({ collection }) => collection);
};
export const listCollections = async (
queryParams: Record<string, string> = {}
queryParams: Record<string, string> = {},
): Promise<{ collections: HttpTypes.StoreCollection[]; count: number }> => {
const next = {
...(await getCacheOptions("collections")),
...(await getCacheOptions('collections')),
};
queryParams.limit = queryParams.limit || "100";
queryParams.offset = queryParams.offset || "0";
console.log("SDK_CONFIG: ", SDK_CONFIG.baseUrl);
queryParams.limit = queryParams.limit || '100';
queryParams.offset = queryParams.offset || '0';
console.log('SDK_CONFIG: ', SDK_CONFIG.baseUrl);
return sdk.client
.fetch<{ collections: HttpTypes.StoreCollection[]; count: number }>(
"/store/collections",
'/store/collections',
{
query: queryParams,
next,
cache: "force-cache",
}
cache: 'force-cache',
},
)
.then(({ collections }) => ({ collections, count: collections.length }));
};
export const getCollectionByHandle = async (
handle: string
handle: string,
): Promise<HttpTypes.StoreCollection> => {
const next = {
...(await getCacheOptions("collections")),
...(await getCacheOptions('collections')),
};
return sdk.client
.fetch<HttpTypes.StoreCollectionListResponse>(`/store/collections`, {
query: { handle, fields: "*products" },
query: { handle, fields: '*products' },
next,
cache: "force-cache",
cache: 'force-cache',
})
.then(({ collections }) => collections[0]);
};

View File

@@ -1,124 +1,124 @@
import "server-only"
import 'server-only';
import { cookies as nextCookies } from "next/headers"
import { cookies as nextCookies } from 'next/headers';
const CookieName = {
MEDUSA_CUSTOMER_ID: "_medusa_customer_id",
MEDUSA_JWT: "_medusa_jwt",
MEDUSA_CART_ID: "_medusa_cart_id",
MEDUSA_CACHE_ID: "_medusa_cache_id",
}
MEDUSA_CUSTOMER_ID: '_medusa_customer_id',
MEDUSA_JWT: '_medusa_jwt',
MEDUSA_CART_ID: '_medusa_cart_id',
MEDUSA_CACHE_ID: '_medusa_cache_id',
};
export const getAuthHeaders = async (): Promise<
{ authorization: string } | {}
> => {
try {
const cookies = await nextCookies()
const token = cookies.get(CookieName.MEDUSA_JWT)?.value
const cookies = await nextCookies();
const token = cookies.get(CookieName.MEDUSA_JWT)?.value;
if (!token) {
return {}
return {};
}
return { authorization: `Bearer ${token}` }
return { authorization: `Bearer ${token}` };
} catch {
return {}
return {};
}
}
};
export const getMedusaCustomerId = async (): Promise<
{ customerId: string | null }
> => {
export const getMedusaCustomerId = async (): Promise<{
customerId: string | null;
}> => {
try {
const cookies = await nextCookies()
const customerId = cookies.get(CookieName.MEDUSA_CUSTOMER_ID)?.value
const cookies = await nextCookies();
const customerId = cookies.get(CookieName.MEDUSA_CUSTOMER_ID)?.value;
if (!customerId) {
return { customerId: null }
return { customerId: null };
}
return { customerId }
return { customerId };
} catch {
return { customerId: null }
return { customerId: null };
}
}
};
export const getCacheTag = async (tag: string): Promise<string> => {
try {
const cookies = await nextCookies()
const cacheId = cookies.get(CookieName.MEDUSA_CACHE_ID)?.value
const cookies = await nextCookies();
const cacheId = cookies.get(CookieName.MEDUSA_CACHE_ID)?.value;
if (!cacheId) {
return ""
return '';
}
return `${tag}-${cacheId}`
return `${tag}-${cacheId}`;
} catch (error) {
return ""
return '';
}
}
};
export const getCacheOptions = async (
tag: string
tag: string,
): Promise<{ tags: string[] } | {}> => {
if (typeof window !== "undefined") {
return {}
if (typeof window !== 'undefined') {
return {};
}
const cacheTag = await getCacheTag(tag)
const cacheTag = await getCacheTag(tag);
if (!cacheTag) {
return {}
return {};
}
return { tags: [`${cacheTag}`] }
}
return { tags: [`${cacheTag}`] };
};
const getCookieSharedOptions = () => ({
maxAge: 60 * 60 * 24 * 7,
httpOnly: false,
secure: process.env.NODE_ENV === "production",
secure: process.env.NODE_ENV === 'production',
});
const getCookieResetOptions = () => ({
maxAge: -1,
});
export const setAuthToken = async (token: string) => {
const cookies = await nextCookies()
const cookies = await nextCookies();
cookies.set(CookieName.MEDUSA_JWT, token, {
...getCookieSharedOptions(),
})
}
});
};
export const setMedusaCustomerId = async (customerId: string) => {
const cookies = await nextCookies()
const cookies = await nextCookies();
cookies.set(CookieName.MEDUSA_CUSTOMER_ID, customerId, {
...getCookieSharedOptions(),
})
}
});
};
export const removeAuthToken = async () => {
const cookies = await nextCookies()
cookies.set(CookieName.MEDUSA_JWT, "", {
const cookies = await nextCookies();
cookies.set(CookieName.MEDUSA_JWT, '', {
...getCookieResetOptions(),
})
}
});
};
export const getCartId = async () => {
const cookies = await nextCookies()
return cookies.get(CookieName.MEDUSA_CART_ID)?.value
}
const cookies = await nextCookies();
return cookies.get(CookieName.MEDUSA_CART_ID)?.value;
};
export const setCartId = async (cartId: string) => {
const cookies = await nextCookies()
const cookies = await nextCookies();
cookies.set(CookieName.MEDUSA_CART_ID, cartId, {
...getCookieSharedOptions(),
})
}
});
};
export const removeCartId = async () => {
const cookies = await nextCookies()
cookies.set(CookieName.MEDUSA_CART_ID, "", {
const cookies = await nextCookies();
cookies.set(CookieName.MEDUSA_CART_ID, '', {
...getCookieResetOptions(),
})
}
});
};

View File

@@ -1,9 +1,11 @@
"use server"
'use server';
import { revalidateTag } from 'next/cache';
import { sdk } from '@lib/config';
import medusaError from '@lib/util/medusa-error';
import { HttpTypes } from '@medusajs/types';
import { sdk } from "@lib/config"
import medusaError from "@lib/util/medusa-error"
import { HttpTypes } from "@medusajs/types"
import { revalidateTag } from "next/cache"
import {
getAuthHeaders,
getCacheOptions,
@@ -12,268 +14,275 @@ import {
removeAuthToken,
removeCartId,
setAuthToken,
} from "./cookies"
} from './cookies';
export const retrieveCustomer =
async (): Promise<HttpTypes.StoreCustomer | null> => {
const authHeaders = await getAuthHeaders()
const authHeaders = await getAuthHeaders();
if (!authHeaders) return null
if (!authHeaders) return null;
const headers = {
...authHeaders,
}
};
const next = {
...(await getCacheOptions("customers")),
}
...(await getCacheOptions('customers')),
};
return await sdk.client
.fetch<{ customer: HttpTypes.StoreCustomer }>(`/store/customers/me`, {
method: "GET",
method: 'GET',
query: {
fields: "*orders",
fields: '*orders',
},
headers,
next,
cache: "force-cache",
cache: 'force-cache',
})
.then(({ customer }) => customer)
.catch(() => null)
}
.catch(() => null);
};
export const updateCustomer = async (body: HttpTypes.StoreUpdateCustomer) => {
const headers = {
...(await getAuthHeaders()),
}
};
const updateRes = await sdk.store.customer
.update(body, {}, headers)
.then(({ customer }) => customer)
.catch(medusaError)
.catch(medusaError);
const cacheTag = await getCacheTag("customers")
revalidateTag(cacheTag)
const cacheTag = await getCacheTag('customers');
revalidateTag(cacheTag);
return updateRes
}
return updateRes;
};
export async function signup(_currentState: unknown, formData: FormData) {
const password = formData.get("password") as string
const password = formData.get('password') as string;
const customerForm = {
email: formData.get("email") as string,
first_name: formData.get("first_name") as string,
last_name: formData.get("last_name") as string,
phone: formData.get("phone") as string,
}
email: formData.get('email') as string,
first_name: formData.get('first_name') as string,
last_name: formData.get('last_name') as string,
phone: formData.get('phone') as string,
};
try {
const token = await sdk.auth.register("customer", "emailpass", {
const token = await sdk.auth.register('customer', 'emailpass', {
email: customerForm.email,
password: password,
})
});
await setAuthToken(token as string)
await setAuthToken(token as string);
const headers = {
...(await getAuthHeaders()),
}
};
const { customer: createdCustomer } = await sdk.store.customer.create(
customerForm,
{},
headers
)
headers,
);
const loginToken = await sdk.auth.login("customer", "emailpass", {
const loginToken = await sdk.auth.login('customer', 'emailpass', {
email: customerForm.email,
password,
})
});
await setAuthToken(loginToken as string)
await setAuthToken(loginToken as string);
const customerCacheTag = await getCacheTag("customers")
revalidateTag(customerCacheTag)
const customerCacheTag = await getCacheTag('customers');
revalidateTag(customerCacheTag);
await transferCart()
await transferCart();
return createdCustomer
return createdCustomer;
} catch (error: any) {
return error.toString()
return error.toString();
}
}
export async function login(_currentState: unknown, formData: FormData) {
const email = formData.get("email") as string
const password = formData.get("password") as string
const email = formData.get('email') as string;
const password = formData.get('password') as string;
try {
await sdk.auth
.login("customer", "emailpass", { email, password })
.login('customer', 'emailpass', { email, password })
.then(async (token) => {
await setAuthToken(token as string)
const customerCacheTag = await getCacheTag("customers")
revalidateTag(customerCacheTag)
})
await setAuthToken(token as string);
const customerCacheTag = await getCacheTag('customers');
revalidateTag(customerCacheTag);
});
} catch (error: any) {
return error.toString()
return error.toString();
}
try {
await transferCart()
await transferCart();
} catch (error: any) {
return error.toString()
return error.toString();
}
}
export async function medusaLogout(countryCode = 'ee', canRevalidateTags = true) {
await sdk.auth.logout()
export async function medusaLogout(
countryCode = 'ee',
canRevalidateTags = true,
) {
await sdk.auth.logout();
await removeAuthToken()
await removeAuthToken();
if (canRevalidateTags) {
const customerCacheTag = await getCacheTag("customers")
revalidateTag(customerCacheTag)
const customerCacheTag = await getCacheTag('customers');
revalidateTag(customerCacheTag);
}
await removeCartId()
await removeCartId();
if (canRevalidateTags) {
const cartCacheTag = await getCacheTag("carts")
revalidateTag(cartCacheTag)
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
}
}
export async function transferCart() {
const cartId = await getCartId()
const cartId = await getCartId();
if (!cartId) {
return
return;
}
const headers = await getAuthHeaders()
const headers = await getAuthHeaders();
await sdk.store.cart.transferCart(cartId, {}, headers)
await sdk.store.cart.transferCart(cartId, {}, headers);
const cartCacheTag = await getCacheTag("carts")
revalidateTag(cartCacheTag)
const cartCacheTag = await getCacheTag('carts');
revalidateTag(cartCacheTag);
}
export const addCustomerAddress = async (
currentState: Record<string, unknown>,
formData: FormData
formData: FormData,
): Promise<any> => {
const isDefaultBilling = (currentState.isDefaultBilling as boolean) || false
const isDefaultShipping = (currentState.isDefaultShipping as boolean) || false
const isDefaultBilling = (currentState.isDefaultBilling as boolean) || false;
const isDefaultShipping =
(currentState.isDefaultShipping as boolean) || false;
const address = {
first_name: formData.get("first_name") as string,
last_name: formData.get("last_name") as string,
company: formData.get("company") as string,
address_1: formData.get("address_1") as string,
address_2: formData.get("address_2") as string,
city: formData.get("city") as string,
postal_code: formData.get("postal_code") as string,
province: formData.get("province") as string,
country_code: formData.get("country_code") as string,
phone: formData.get("phone") as string,
first_name: formData.get('first_name') as string,
last_name: formData.get('last_name') as string,
company: formData.get('company') as string,
address_1: formData.get('address_1') as string,
address_2: formData.get('address_2') as string,
city: formData.get('city') as string,
postal_code: formData.get('postal_code') as string,
province: formData.get('province') as string,
country_code: formData.get('country_code') as string,
phone: formData.get('phone') as string,
is_default_billing: isDefaultBilling,
is_default_shipping: isDefaultShipping,
}
};
const headers = {
...(await getAuthHeaders()),
}
};
return sdk.store.customer
.createAddress(address, {}, headers)
.then(async ({ customer }) => {
const customerCacheTag = await getCacheTag("customers")
revalidateTag(customerCacheTag)
return { success: true, error: null }
const customerCacheTag = await getCacheTag('customers');
revalidateTag(customerCacheTag);
return { success: true, error: null };
})
.catch((err) => {
return { success: false, error: err.toString() }
})
}
return { success: false, error: err.toString() };
});
};
export const deleteCustomerAddress = async (
addressId: string
addressId: string,
): Promise<void> => {
const headers = {
...(await getAuthHeaders()),
}
};
await sdk.store.customer
.deleteAddress(addressId, headers)
.then(async () => {
const customerCacheTag = await getCacheTag("customers")
revalidateTag(customerCacheTag)
return { success: true, error: null }
const customerCacheTag = await getCacheTag('customers');
revalidateTag(customerCacheTag);
return { success: true, error: null };
})
.catch((err) => {
return { success: false, error: err.toString() }
})
}
return { success: false, error: err.toString() };
});
};
export const updateCustomerAddress = async (
currentState: Record<string, unknown>,
formData: FormData
formData: FormData,
): Promise<any> => {
const addressId =
(currentState.addressId as string) || (formData.get("addressId") as string)
(currentState.addressId as string) || (formData.get('addressId') as string);
if (!addressId) {
return { success: false, error: "Address ID is required" }
return { success: false, error: 'Address ID is required' };
}
const address = {
first_name: formData.get("first_name") as string,
last_name: formData.get("last_name") as string,
company: formData.get("company") as string,
address_1: formData.get("address_1") as string,
address_2: formData.get("address_2") as string,
city: formData.get("city") as string,
postal_code: formData.get("postal_code") as string,
province: formData.get("province") as string,
country_code: formData.get("country_code") as string,
} as HttpTypes.StoreUpdateCustomerAddress
first_name: formData.get('first_name') as string,
last_name: formData.get('last_name') as string,
company: formData.get('company') as string,
address_1: formData.get('address_1') as string,
address_2: formData.get('address_2') as string,
city: formData.get('city') as string,
postal_code: formData.get('postal_code') as string,
province: formData.get('province') as string,
country_code: formData.get('country_code') as string,
} as HttpTypes.StoreUpdateCustomerAddress;
const phone = formData.get("phone") as string
const phone = formData.get('phone') as string;
if (phone) {
address.phone = phone
address.phone = phone;
}
const headers = {
...(await getAuthHeaders()),
}
};
return sdk.store.customer
.updateAddress(addressId, address, {}, headers)
.then(async () => {
const customerCacheTag = await getCacheTag("customers")
revalidateTag(customerCacheTag)
return { success: true, error: null }
const customerCacheTag = await getCacheTag('customers');
revalidateTag(customerCacheTag);
return { success: true, error: null };
})
.catch((err) => {
return { success: false, error: err.toString() }
})
}
return { success: false, error: err.toString() };
});
};
async function medusaLogin(email: string, password: string) {
const token = await sdk.auth.login("customer", "emailpass", { email, password });
const token = await sdk.auth.login('customer', 'emailpass', {
email,
password,
});
await setAuthToken(token as string);
try {
await transferCart();
} catch (e) {
console.error("Failed to transfer cart", e);
console.error('Failed to transfer cart', e);
}
const customer = await retrieveCustomer();
if (!customer) {
throw new Error("Customer not found for active session");
throw new Error('Customer not found for active session');
}
return customer.id;
@@ -290,29 +299,41 @@ async function medusaRegister({
name: string | undefined;
lastName: string | undefined;
}) {
console.info(`Creating new Medusa account for Keycloak user with email=${email}`);
const registerToken = await sdk.auth.register("customer", "emailpass", { email, password });
console.info(
`Creating new Medusa account for Keycloak user with email=${email}`,
);
const registerToken = await sdk.auth.register('customer', 'emailpass', {
email,
password,
});
await setAuthToken(registerToken);
console.info(`Creating new Medusa customer profile for Keycloak user with email=${email} and name=${name} and lastName=${lastName}`);
console.info(
`Creating new Medusa customer profile for Keycloak user with email=${email} and name=${name} and lastName=${lastName}`,
);
await sdk.store.customer.create(
{ email, first_name: name, last_name: lastName },
{},
{
...(await getAuthHeaders()),
});
},
);
}
export async function medusaLoginOrRegister(credentials: {
email: string
supabaseUserId?: string
name?: string,
lastName?: string,
} & ({ isDevPasswordLogin: true; password: string } | { isDevPasswordLogin?: false; password?: undefined })) {
export async function medusaLoginOrRegister(
credentials: {
email: string;
supabaseUserId?: string;
name?: string;
lastName?: string;
} & (
| { isDevPasswordLogin: true; password: string }
| { isDevPasswordLogin?: false; password?: undefined }
),
) {
const { email, supabaseUserId, name, lastName } = credentials;
const password = await (async () => {
if (credentials.isDevPasswordLogin) {
return credentials.password;
@@ -324,13 +345,19 @@ export async function medusaLoginOrRegister(credentials: {
try {
return await medusaLogin(email, password);
} catch (loginError) {
console.error("Failed to login customer, attempting to register", loginError);
console.error(
'Failed to login customer, attempting to register',
loginError,
);
try {
await medusaRegister({ email, password, name, lastName });
return await medusaLogin(email, password);
} catch (registerError) {
console.error("Failed to create Medusa account for user with email=${email}", registerError);
console.error(
`Failed to create Medusa account for user with email=${email}`,
registerError,
);
throw medusaError(registerError);
}
}
@@ -340,7 +367,10 @@ export async function medusaLoginOrRegister(credentials: {
* Generate a deterministic password based on user identifier
* This ensures the same user always gets the same password for Medusa
*/
async function generateDeterministicPassword(email: string, userId?: string): Promise<string> {
async function generateDeterministicPassword(
email: string,
userId?: string,
): Promise<string> {
// Use the user ID or email as the base for deterministic generation
const baseString = userId || email;
const secret = process.env.MEDUSA_PASSWORD_SECRET!;
@@ -356,13 +386,15 @@ async function generateDeterministicPassword(email: string, userId?: string): Pr
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
['sign'],
);
// Generate HMAC
const signature = await crypto.subtle.sign('HMAC', key, messageData);
// Convert to base64 and make it a valid password
const hashArray = Array.from(new Uint8Array(signature));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
// Take first 24 characters and add some complexity
const basePassword = hashHex.substring(0, 24);
// Add some required complexity for Medusa (uppercase, lowercase, numbers, symbols)

View File

@@ -1,70 +1,71 @@
"use server"
'use server';
import { sdk } from "@lib/config"
import { HttpTypes } from "@medusajs/types"
import { getAuthHeaders, getCacheOptions } from "./cookies"
import { sdk } from '@lib/config';
import { HttpTypes } from '@medusajs/types';
import { getAuthHeaders, getCacheOptions } from './cookies';
export const listCartShippingMethods = async (cartId: string) => {
const headers = {
...(await getAuthHeaders()),
}
};
const next = {
...(await getCacheOptions("fulfillment")),
}
...(await getCacheOptions('fulfillment')),
};
return sdk.client
.fetch<HttpTypes.StoreShippingOptionListResponse>(
`/store/shipping-options`,
{
method: "GET",
method: 'GET',
query: {
cart_id: cartId,
fields:
"+service_zone.fulfllment_set.type,*service_zone.fulfillment_set.location.address",
'+service_zone.fulfllment_set.type,*service_zone.fulfillment_set.location.address',
},
headers,
next,
cache: "force-cache",
}
cache: 'force-cache',
},
)
.then(({ shipping_options }) => shipping_options)
.catch(() => {
return null
})
}
return null;
});
};
export const calculatePriceForShippingOption = async (
optionId: string,
cartId: string,
data?: Record<string, unknown>
data?: Record<string, unknown>,
) => {
const headers = {
...(await getAuthHeaders()),
}
};
const next = {
...(await getCacheOptions("fulfillment")),
}
...(await getCacheOptions('fulfillment')),
};
const body = { cart_id: cartId, data }
const body = { cart_id: cartId, data };
if (data) {
body.data = data
body.data = data;
}
return sdk.client
.fetch<{ shipping_option: HttpTypes.StoreCartShippingOption }>(
`/store/shipping-options/${optionId}/calculate`,
{
method: "POST",
method: 'POST',
body,
headers,
next,
}
},
)
.then(({ shipping_option }) => shipping_option)
.catch((e) => {
return null
})
}
return null;
});
};

View File

@@ -1,11 +1,11 @@
export * from "./cart";
export * from "./categories";
export * from "./collections";
export * from "./cookies";
export * from "./customer";
export * from "./fulfillment";
export * from "./onboarding";
export * from "./orders";
export * from "./payment";
export * from "./products";
export * from "./regions";
export * from './cart';
export * from './categories';
export * from './collections';
export * from './cookies';
export * from './customer';
export * from './fulfillment';
export * from './onboarding';
export * from './orders';
export * from './payment';
export * from './products';
export * from './regions';

View File

@@ -1,9 +1,10 @@
"use server"
import { cookies as nextCookies } from "next/headers"
import { redirect } from "next/navigation"
'use server';
import { cookies as nextCookies } from 'next/headers';
import { redirect } from 'next/navigation';
export async function resetOnboardingState(orderId: string) {
const cookies = await nextCookies()
cookies.set("_medusa_onboarding", "false", { maxAge: -1 })
redirect(`http://localhost:7001/a/orders/${orderId}`)
const cookies = await nextCookies();
cookies.set('_medusa_onboarding', 'false', { maxAge: -1 });
redirect(`http://localhost:7001/a/orders/${orderId}`);
}

View File

@@ -1,111 +1,112 @@
"use server"
'use server';
import { sdk } from "@lib/config"
import medusaError from "@lib/util/medusa-error"
import { getAuthHeaders, getCacheOptions } from "./cookies"
import { HttpTypes } from "@medusajs/types"
import { sdk } from '@lib/config';
import medusaError from '@lib/util/medusa-error';
import { HttpTypes } from '@medusajs/types';
import { getAuthHeaders, getCacheOptions } from './cookies';
export const retrieveOrder = async (id: string) => {
const headers = {
...(await getAuthHeaders()),
}
};
const next = {
...(await getCacheOptions("orders")),
}
...(await getCacheOptions('orders')),
};
return sdk.client
.fetch<HttpTypes.StoreOrderResponse>(`/store/orders/${id}`, {
method: "GET",
method: 'GET',
query: {
fields:
"*payment_collections.payments,*items,*items.metadata,*items.variant,*items.product",
'*payment_collections.payments,*items,*items.metadata,*items.variant,*items.product',
},
headers,
next,
cache: "force-cache",
cache: 'force-cache',
})
.then(({ order }) => order)
.catch((err) => medusaError(err))
}
.catch((err) => medusaError(err));
};
export const listOrders = async (
limit: number = 10,
offset: number = 0,
filters?: Record<string, any>
filters?: Record<string, any>,
) => {
const headers = {
...(await getAuthHeaders()),
}
};
const next = {
...(await getCacheOptions("orders")),
}
...(await getCacheOptions('orders')),
};
return sdk.client
.fetch<HttpTypes.StoreOrderListResponse>(`/store/orders`, {
method: "GET",
method: 'GET',
query: {
limit,
offset,
order: "-created_at",
fields: "*items,+items.metadata,*items.variant,*items.product",
order: '-created_at',
fields: '*items,+items.metadata,*items.variant,*items.product',
...filters,
},
headers,
next,
})
.then(({ orders }) => orders)
.catch((err) => medusaError(err))
}
.catch((err) => medusaError(err));
};
export const createTransferRequest = async (
state: {
success: boolean
error: string | null
order: HttpTypes.StoreOrder | null
success: boolean;
error: string | null;
order: HttpTypes.StoreOrder | null;
},
formData: FormData
formData: FormData,
): Promise<{
success: boolean
error: string | null
order: HttpTypes.StoreOrder | null
success: boolean;
error: string | null;
order: HttpTypes.StoreOrder | null;
}> => {
const id = formData.get("order_id") as string
const id = formData.get('order_id') as string;
if (!id) {
return { success: false, error: "Order ID is required", order: null }
return { success: false, error: 'Order ID is required', order: null };
}
const headers = await getAuthHeaders()
const headers = await getAuthHeaders();
return await sdk.store.order
.requestTransfer(
id,
{},
{
fields: "id, email",
fields: 'id, email',
},
headers
headers,
)
.then(({ order }) => ({ success: true, error: null, order }))
.catch((err) => ({ success: false, error: err.message, order: null }))
}
.catch((err) => ({ success: false, error: err.message, order: null }));
};
export const acceptTransferRequest = async (id: string, token: string) => {
const headers = await getAuthHeaders()
const headers = await getAuthHeaders();
return await sdk.store.order
.acceptTransfer(id, { token }, {}, headers)
.then(({ order }) => ({ success: true, error: null, order }))
.catch((err) => ({ success: false, error: err.message, order: null }))
}
.catch((err) => ({ success: false, error: err.message, order: null }));
};
export const declineTransferRequest = async (id: string, token: string) => {
const headers = await getAuthHeaders()
const headers = await getAuthHeaders();
return await sdk.store.order
.declineTransfer(id, { token }, {}, headers)
.then(({ order }) => ({ success: true, error: null, order }))
.catch((err) => ({ success: false, error: err.message, order: null }))
}
.catch((err) => ({ success: false, error: err.message, order: null }));
};

View File

@@ -1,35 +1,36 @@
"use server"
'use server';
import { sdk } from "@lib/config"
import { getAuthHeaders, getCacheOptions } from "./cookies"
import { HttpTypes } from "@medusajs/types"
import { sdk } from '@lib/config';
import { HttpTypes } from '@medusajs/types';
import { getAuthHeaders, getCacheOptions } from './cookies';
export const listCartPaymentMethods = async (regionId: string) => {
const headers = {
...(await getAuthHeaders()),
}
};
const next = {
...(await getCacheOptions("payment_providers")),
}
...(await getCacheOptions('payment_providers')),
};
return sdk.client
.fetch<HttpTypes.StorePaymentProviderListResponse>(
`/store/payment-providers`,
{
method: "GET",
method: 'GET',
query: { region_id: regionId },
headers,
next,
cache: "force-cache",
}
cache: 'force-cache',
},
)
.then(({ payment_providers }) =>
payment_providers.sort((a, b) => {
return a.id > b.id ? 1 : -1
})
return a.id > b.id ? 1 : -1;
}),
)
.catch(() => {
return null
})
}
return null;
});
};

View File

@@ -1,11 +1,12 @@
"use server"
'use server';
import { sdk } from "@lib/config"
import { sortProducts } from "@lib/util/sort-products"
import { HttpTypes } from "@medusajs/types"
import { SortOptions } from "@modules/store/components/refinement-list/sort-products"
import { getAuthHeaders, getCacheOptions } from "./cookies"
import { getRegion, retrieveRegion } from "./regions"
import { sdk } from '@lib/config';
import { sortProducts } from '@lib/util/sort-products';
import { HttpTypes } from '@medusajs/types';
import { SortOptions } from '@modules/store/components/refinement-list/sort-products';
import { getAuthHeaders, getCacheOptions } from './cookies';
import { getRegion, retrieveRegion } from './regions';
export const listProducts = async ({
pageParam = 1,
@@ -13,70 +14,71 @@ export const listProducts = async ({
countryCode,
regionId,
}: {
pageParam?: number
queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams & {
"type_id[0]"?: string;
id?: string[],
category_id?: string;
order?: 'title';
}
countryCode?: string
regionId?: string
pageParam?: number;
queryParams?: HttpTypes.FindParams &
HttpTypes.StoreProductParams & {
'type_id[0]'?: string;
id?: string[];
category_id?: string;
order?: 'title';
};
countryCode?: string;
regionId?: string;
}): Promise<{
response: { products: HttpTypes.StoreProduct[]; count: number }
nextPage: number | null
queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams
response: { products: HttpTypes.StoreProduct[]; count: number };
nextPage: number | null;
queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams;
}> => {
if (!countryCode && !regionId) {
throw new Error("Country code or region ID is required")
throw new Error('Country code or region ID is required');
}
const limit = queryParams?.limit || 12
const _pageParam = Math.max(pageParam, 1)
const offset = (_pageParam === 1) ? 0 : (_pageParam - 1) * limit;
const limit = queryParams?.limit || 12;
const _pageParam = Math.max(pageParam, 1);
const offset = _pageParam === 1 ? 0 : (_pageParam - 1) * limit;
let region: HttpTypes.StoreRegion | undefined | null
let region: HttpTypes.StoreRegion | undefined | null;
if (countryCode) {
region = await getRegion(countryCode)
region = await getRegion(countryCode);
} else {
region = await retrieveRegion(regionId!)
region = await retrieveRegion(regionId!);
}
if (!region) {
return {
response: { products: [], count: 0 },
nextPage: null,
}
};
}
const headers = {
...(await getAuthHeaders()),
}
};
const next = {
...(await getCacheOptions("products")),
}
...(await getCacheOptions('products')),
};
return sdk.client
.fetch<{ products: HttpTypes.StoreProduct[]; count: number }>(
`/store/products`,
{
method: "GET",
method: 'GET',
query: {
limit,
offset,
region_id: region?.id,
fields:
"*variants.calculated_price,+variants.inventory_quantity,+metadata,+tags,+status",
'*variants.calculated_price,+variants.inventory_quantity,+metadata,+tags,+status',
...queryParams,
},
headers,
next,
}
},
)
.then(({ products, count }) => {
const nextPage = count > offset + limit ? pageParam + 1 : null
const nextPage = count > offset + limit ? pageParam + 1 : null;
return {
response: {
@@ -85,9 +87,9 @@ export const listProducts = async ({
},
nextPage: nextPage,
queryParams,
}
})
}
};
});
};
/**
* This will fetch 100 products to the Next.js cache and sort them based on the sortBy parameter.
@@ -96,19 +98,19 @@ export const listProducts = async ({
export const listProductsWithSort = async ({
page = 0,
queryParams,
sortBy = "created_at",
sortBy = 'created_at',
countryCode,
}: {
page?: number
queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams
sortBy?: SortOptions
countryCode: string
page?: number;
queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams;
sortBy?: SortOptions;
countryCode: string;
}): Promise<{
response: { products: HttpTypes.StoreProduct[]; count: number }
nextPage: number | null
queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams
response: { products: HttpTypes.StoreProduct[]; count: number };
nextPage: number | null;
queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams;
}> => {
const limit = queryParams?.limit || 12
const limit = queryParams?.limit || 12;
const {
response: { products, count },
@@ -119,15 +121,15 @@ export const listProductsWithSort = async ({
limit: 100,
},
countryCode,
})
});
const sortedProducts = sortProducts(products, sortBy)
const sortedProducts = sortProducts(products, sortBy);
const pageParam = (page - 1) * limit
const pageParam = (page - 1) * limit;
const nextPage = count > pageParam + limit ? pageParam + limit : null
const nextPage = count > pageParam + limit ? pageParam + limit : null;
const paginatedProducts = sortedProducts.slice(pageParam, pageParam + limit)
const paginatedProducts = sortedProducts.slice(pageParam, pageParam + limit);
return {
response: {
@@ -136,24 +138,27 @@ export const listProductsWithSort = async ({
},
nextPage,
queryParams,
}
}
};
};
export const listProductTypes = async (): Promise<{ productTypes: HttpTypes.StoreProductType[]; count: number }> => {
export const listProductTypes = async (): Promise<{
productTypes: HttpTypes.StoreProductType[];
count: number;
}> => {
const next = {
...(await getCacheOptions("productTypes")),
...(await getCacheOptions('productTypes')),
};
return sdk.client
.fetch<{ product_types: HttpTypes.StoreProductType[]; count: number }>(
"/store/product-types",
'/store/product-types',
{
next,
//cache: "force-cache",
query: {
fields: "id,value,metadata",
fields: 'id,value,metadata',
},
}
},
)
.then(({ product_types, count }) => {
return { productTypes: product_types, count };

View File

@@ -1,66 +1,67 @@
"use server"
'use server';
import { sdk } from "@lib/config"
import medusaError from "@lib/util/medusa-error"
import { HttpTypes } from "@medusajs/types"
import { getCacheOptions } from "./cookies"
import { sdk } from '@lib/config';
import medusaError from '@lib/util/medusa-error';
import { HttpTypes } from '@medusajs/types';
import { getCacheOptions } from './cookies';
export const listRegions = async () => {
const next = {
...(await getCacheOptions("regions")),
}
...(await getCacheOptions('regions')),
};
return sdk.client
.fetch<{ regions: HttpTypes.StoreRegion[] }>(`/store/regions`, {
method: "GET",
method: 'GET',
next,
cache: "force-cache",
cache: 'force-cache',
})
.then(({ regions }) => regions)
.catch(medusaError)
}
.catch(medusaError);
};
export const retrieveRegion = async (id: string) => {
const next = {
...(await getCacheOptions(["regions", id].join("-"))),
}
...(await getCacheOptions(['regions', id].join('-'))),
};
return sdk.client
.fetch<{ region: HttpTypes.StoreRegion }>(`/store/regions/${id}`, {
method: "GET",
method: 'GET',
next,
cache: "force-cache",
cache: 'force-cache',
})
.then(({ region }) => region)
.catch(medusaError)
}
.catch(medusaError);
};
const regionMap = new Map<string, HttpTypes.StoreRegion>()
const regionMap = new Map<string, HttpTypes.StoreRegion>();
export const getRegion = async (countryCode: string) => {
try {
if (regionMap.has(countryCode)) {
return regionMap.get(countryCode)
return regionMap.get(countryCode);
}
const regions = await listRegions()
const regions = await listRegions();
if (!regions) {
return null
return null;
}
regions.forEach((region) => {
region.countries?.forEach((c) => {
regionMap.set(c?.iso_2 ?? "", region)
})
})
regionMap.set(c?.iso_2 ?? '', region);
});
});
const region = countryCode
? regionMap.get(countryCode)
: regionMap.get("et")
: regionMap.get('et');
return region
return region;
} catch (e: any) {
return null
return null;
}
}
};

View File

@@ -1,29 +1,29 @@
import { RefObject, useEffect, useState } from "react"
import { RefObject, useEffect, useState } from 'react';
export const useIntersection = (
element: RefObject<HTMLDivElement | null>,
rootMargin: string
rootMargin: string,
) => {
const [isVisible, setState] = useState(false)
const [isVisible, setState] = useState(false);
useEffect(() => {
if (!element.current) {
return
return;
}
const el = element.current
const el = element.current;
const observer = new IntersectionObserver(
([entry]) => {
setState(entry.isIntersecting)
setState(entry.isIntersecting);
},
{ rootMargin }
)
{ rootMargin },
);
observer.observe(el)
observer.observe(el);
return () => observer.unobserve(el)
}, [element, rootMargin])
return () => observer.unobserve(el);
}, [element, rootMargin]);
return isVisible
}
return isVisible;
};

View File

@@ -1,11 +1,11 @@
import { useState } from "react"
import { useState } from 'react';
export type StateType = [boolean, () => void, () => void, () => void] & {
state: boolean
open: () => void
close: () => void
toggle: () => void
}
state: boolean;
open: () => void;
close: () => void;
toggle: () => void;
};
/**
*
@@ -21,26 +21,26 @@ export type StateType = [boolean, () => void, () => void, () => void] & {
*/
const useToggleState = (initialState = false) => {
const [state, setState] = useState<boolean>(initialState)
const [state, setState] = useState<boolean>(initialState);
const close = () => {
setState(false)
}
setState(false);
};
const open = () => {
setState(true)
}
setState(true);
};
const toggle = () => {
setState((state) => !state)
}
setState((state) => !state);
};
const hookData = [state, open, close, toggle] as StateType
hookData.state = state
hookData.open = open
hookData.close = close
hookData.toggle = toggle
return hookData
}
const hookData = [state, open, close, toggle] as StateType;
hookData.state = state;
hookData.open = open;
hookData.close = close;
hookData.toggle = toggle;
return hookData;
};
export default useToggleState
export default useToggleState;

View File

@@ -1,28 +1,28 @@
import { isEqual, pick } from "lodash"
import { isEqual, pick } from 'lodash';
export default function compareAddresses(address1: any, address2: any) {
return isEqual(
pick(address1, [
"first_name",
"last_name",
"address_1",
"company",
"postal_code",
"city",
"country_code",
"province",
"phone",
'first_name',
'last_name',
'address_1',
'company',
'postal_code',
'city',
'country_code',
'province',
'phone',
]),
pick(address2, [
"first_name",
"last_name",
"address_1",
"company",
"postal_code",
"city",
"country_code",
"province",
"phone",
])
)
'first_name',
'last_name',
'address_1',
'company',
'postal_code',
'city',
'country_code',
'province',
'phone',
]),
);
}

View File

@@ -1,3 +1,3 @@
export const getBaseURL = () => {
return process.env.NEXT_PUBLIC_BASE_URL || "https://localhost:8000"
}
return process.env.NEXT_PUBLIC_BASE_URL || 'https://localhost:8000';
};

View File

@@ -1,6 +1,6 @@
export const getPercentageDiff = (original: number, calculated: number) => {
const diff = original - calculated
const decrease = (diff / original) * 100
const diff = original - calculated;
const decrease = (diff / original) * 100;
return decrease.toFixed()
}
return decrease.toFixed();
};

View File

@@ -1,10 +1,11 @@
import { HttpTypes } from "@medusajs/types"
import { getPercentageDiff } from "./get-precentage-diff"
import { convertToLocale } from "./money"
import { HttpTypes } from '@medusajs/types';
import { getPercentageDiff } from './get-precentage-diff';
import { convertToLocale } from './money';
export const getPricesForVariant = (variant: any) => {
if (!variant?.calculated_price?.calculated_amount) {
return null
return null;
}
return {
@@ -22,25 +23,25 @@ export const getPricesForVariant = (variant: any) => {
price_type: variant.calculated_price.calculated_price.price_list_type,
percentage_diff: getPercentageDiff(
variant.calculated_price.original_amount,
variant.calculated_price.calculated_amount
variant.calculated_price.calculated_amount,
),
}
}
};
};
export function getProductPrice({
product,
variantId,
}: {
product: HttpTypes.StoreProduct
variantId?: string
product: HttpTypes.StoreProduct;
variantId?: string;
}) {
if (!product || !product.id) {
throw new Error("No product provided")
throw new Error('No product provided');
}
const cheapestPrice = () => {
if (!product || !product.variants?.length) {
return null
return null;
}
const cheapestVariant: any = product.variants
@@ -49,31 +50,31 @@ export function getProductPrice({
return (
a.calculated_price.calculated_amount -
b.calculated_price.calculated_amount
)
})[0]
);
})[0];
return getPricesForVariant(cheapestVariant)
}
return getPricesForVariant(cheapestVariant);
};
const variantPrice = () => {
if (!product || !variantId) {
return null
return null;
}
const variant: any = product.variants?.find(
(v) => v.id === variantId || v.sku === variantId
)
(v) => v.id === variantId || v.sku === variantId,
);
if (!variant) {
return null
return null;
}
return getPricesForVariant(variant)
}
return getPricesForVariant(variant);
};
return {
product,
cheapestPrice: cheapestPrice(),
variantPrice: variantPrice(),
}
};
}

View File

@@ -1,10 +1,10 @@
export * from "./compare-addresses";
export * from "./env";
export * from "./get-precentage-diff";
export * from "./get-product-price";
export * from "./isEmpty";
export * from "./medusa-error";
export * from "./money";
export * from "./product";
export * from "./repeat";
export * from "./sort-products";
export * from './compare-addresses';
export * from './env';
export * from './get-precentage-diff';
export * from './get-product-price';
export * from './isEmpty';
export * from './medusa-error';
export * from './money';
export * from './product';
export * from './repeat';
export * from './sort-products';

View File

@@ -1,11 +1,11 @@
export const isObject = (input: any) => input instanceof Object
export const isArray = (input: any) => Array.isArray(input)
export const isObject = (input: any) => input instanceof Object;
export const isArray = (input: any) => Array.isArray(input);
export const isEmpty = (input: any) => {
return (
input === null ||
input === undefined ||
(isObject(input) && Object.keys(input).length === 0) ||
(isArray(input) && (input as any[]).length === 0) ||
(typeof input === "string" && input.trim().length === 0)
)
}
(typeof input === 'string' && input.trim().length === 0)
);
};

View File

@@ -2,21 +2,21 @@ export default function medusaError(error: any): never {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
const u = new URL(error.config.url, error.config.baseURL)
console.error("Resource:", u.toString())
console.error("Response data:", error.response.data)
console.error("Status code:", error.response.status)
console.error("Headers:", error.response.headers)
const u = new URL(error.config.url, error.config.baseURL);
console.error('Resource:', u.toString());
console.error('Response data:', error.response.data);
console.error('Status code:', error.response.status);
console.error('Headers:', error.response.headers);
// Extracting the error message from the response data
const message = error.response.data.message || error.response.data
const message = error.response.data.message || error.response.data;
throw new Error(message.charAt(0).toUpperCase() + message.slice(1) + ".")
throw new Error(message.charAt(0).toUpperCase() + message.slice(1) + '.');
} else if (error.request) {
// The request was made but no response was received
throw new Error("No response received: " + error.request)
throw new Error('No response received: ' + error.request);
} else {
// Something happened in setting up the request that triggered an Error
throw new Error("Error setting up the request: " + error.message)
throw new Error('Error setting up the request: ' + error.message);
}
}

View File

@@ -1,26 +1,26 @@
import { isEmpty } from "./isEmpty"
import { isEmpty } from './isEmpty';
type ConvertToLocaleParams = {
amount: number
currency_code: string
minimumFractionDigits?: number
maximumFractionDigits?: number
locale?: string
}
amount: number;
currency_code: string;
minimumFractionDigits?: number;
maximumFractionDigits?: number;
locale?: string;
};
export const convertToLocale = ({
amount,
currency_code,
minimumFractionDigits,
maximumFractionDigits,
locale = "en-US",
locale = 'en-US',
}: ConvertToLocaleParams) => {
return currency_code && !isEmpty(currency_code)
? new Intl.NumberFormat(locale, {
style: "currency",
style: 'currency',
currency: currency_code,
minimumFractionDigits,
maximumFractionDigits,
}).format(amount)
: amount.toString()
}
: amount.toString();
};

View File

@@ -1,5 +1,7 @@
import { HttpTypes } from "@medusajs/types";
import { HttpTypes } from '@medusajs/types';
export const isSimpleProduct = (product: HttpTypes.StoreProduct): boolean => {
return product.options?.length === 1 && product.options[0].values?.length === 1;
}
return (
product.options?.length === 1 && product.options[0].values?.length === 1
);
};

View File

@@ -1,5 +1,5 @@
const repeat = (times: number) => {
return Array.from(Array(times).keys())
}
return Array.from(Array(times).keys());
};
export default repeat
export default repeat;

View File

@@ -1,8 +1,8 @@
import { HttpTypes } from "@medusajs/types"
import { SortOptions } from "@modules/store/components/refinement-list/sort-products"
import { HttpTypes } from '@medusajs/types';
import { SortOptions } from '@modules/store/components/refinement-list/sort-products';
interface MinPricedProduct extends HttpTypes.StoreProduct {
_minPrice?: number
_minPrice?: number;
}
/**
@@ -13,38 +13,38 @@ interface MinPricedProduct extends HttpTypes.StoreProduct {
*/
export function sortProducts(
products: HttpTypes.StoreProduct[],
sortBy: SortOptions
sortBy: SortOptions,
): HttpTypes.StoreProduct[] {
let sortedProducts = products as MinPricedProduct[]
const sortedProducts = products as MinPricedProduct[];
if (["price_asc", "price_desc"].includes(sortBy)) {
if (['price_asc', 'price_desc'].includes(sortBy)) {
// Precompute the minimum price for each product
sortedProducts.forEach((product) => {
if (product.variants && product.variants.length > 0) {
product._minPrice = Math.min(
...product.variants.map(
(variant) => variant?.calculated_price?.calculated_amount || 0
)
)
(variant) => variant?.calculated_price?.calculated_amount || 0,
),
);
} else {
product._minPrice = Infinity
product._minPrice = Infinity;
}
})
});
// Sort products based on the precomputed minimum prices
sortedProducts.sort((a, b) => {
const diff = a._minPrice! - b._minPrice!
return sortBy === "price_asc" ? diff : -diff
})
const diff = a._minPrice! - b._minPrice!;
return sortBy === 'price_asc' ? diff : -diff;
});
}
if (sortBy === "created_at") {
if (sortBy === 'created_at') {
sortedProducts.sort((a, b) => {
return (
new Date(b.created_at!).getTime() - new Date(a.created_at!).getTime()
)
})
);
});
}
return sortedProducts
return sortedProducts;
}