From 7603fc42c82c570a977bc559727b0972ce30b239 Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Thu, 4 Jul 2024 13:16:15 +0200 Subject: [PATCH 01/14] fix: sorting function --- src/lib/util/sort-products-by-price.ts | 44 +++++++++++++++++++ .../store/templates/paginated-products.tsx | 25 +++++------ 2 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 src/lib/util/sort-products-by-price.ts diff --git a/src/lib/util/sort-products-by-price.ts b/src/lib/util/sort-products-by-price.ts new file mode 100644 index 000000000..29a67bf4c --- /dev/null +++ b/src/lib/util/sort-products-by-price.ts @@ -0,0 +1,44 @@ +import { HttpTypes } from "@medusajs/types" + +interface PricedVariant extends HttpTypes.StoreProductVariant { + calculated_price: { + calculated_amount: number + } +} + +interface PricedProduct extends HttpTypes.StoreProduct { + variants: PricedVariant[] + _minPrice: number +} + +/** + * Helper function to sort products by price until the store API supports sorting by price + * @param products + * @param order + * @returns products sorted by price + */ +export function sortProductsByPrice( + products: HttpTypes.StoreProduct[], + order: "asc" | "desc" +): PricedProduct[] { + const pricedProducts = products as PricedProduct[] + + // Precompute the minimum price for each product + pricedProducts.forEach((product) => { + if (product.variants && product.variants.length > 0) { + product._minPrice = Math.min( + ...product.variants.map( + (variant) => variant?.calculated_price?.calculated_amount + ) + ) + } else { + product._minPrice = Infinity + } + }) + + // Sort products based on the precomputed minimum prices + return pricedProducts.sort((a, b) => { + const diff = a._minPrice - b._minPrice + return order === "asc" ? diff : -diff + }) +} diff --git a/src/modules/store/templates/paginated-products.tsx b/src/modules/store/templates/paginated-products.tsx index 83e9ed98e..43a5ee708 100644 --- a/src/modules/store/templates/paginated-products.tsx +++ b/src/modules/store/templates/paginated-products.tsx @@ -1,5 +1,6 @@ import { getProductsList } from "@lib/data/products" import { getRegion } from "@lib/data/regions" +import { sortProductsByPrice } from "@lib/util/sort-products-by-price" import ProductPreview from "@modules/products/components/product-preview" import { Pagination } from "@modules/store/components/pagination" import { SortOptions } from "@modules/store/components/refinement-list/sort-products" @@ -45,19 +46,8 @@ export default async function PaginatedProducts({ queryParams["id"] = productsIds } - if (sortBy) { - // TODO: Currently sorting by price doesn't work, fix it - switch (sortBy) { - case "price_asc": - queryParams["order"] = "price" - break - case "price_desc": - queryParams["order"] = "-price" - break - case "created_at": - queryParams["order"] = "created_at" - break - } + if (sortBy === "created_at") { + queryParams["order"] = "created_at" } const region = await getRegion(countryCode) @@ -66,7 +56,7 @@ export default async function PaginatedProducts({ return null } - const { + let { response: { products, count }, } = await getProductsList({ pageParam: page, @@ -74,6 +64,13 @@ export default async function PaginatedProducts({ countryCode, }) + if (sortBy && ["price_asc", "price_desc"].includes(sortBy)) { + products = sortProductsByPrice( + products, + sortBy === "price_asc" ? "asc" : "desc" + ) + } + const totalPages = Math.ceil(count / PRODUCT_LIMIT) return ( From 9bdfeb862a279232c1d2b61d23b1edc44396e065 Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Thu, 4 Jul 2024 13:25:03 +0200 Subject: [PATCH 02/14] fix: sort options margin --- .../components/filter-radio-group/index.tsx | 9 ++--- yarn.lock | 34 +++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/modules/common/components/filter-radio-group/index.tsx b/src/modules/common/components/filter-radio-group/index.tsx index 727759384..a275b5115 100644 --- a/src/modules/common/components/filter-radio-group/index.tsx +++ b/src/modules/common/components/filter-radio-group/index.tsx @@ -28,17 +28,14 @@ const FilterRadioGroup = ({
{i.value === value && } - handleChange( - e as unknown as ChangeEvent, - i.value - ) + onClick={(e: ChangeEvent) => + handleChange(e, i.value) } className="hidden peer" id={i.value} diff --git a/yarn.lock b/yarn.lock index fe791bec4..a0ca4f960 100644 --- a/yarn.lock +++ b/yarn.lock @@ -538,37 +538,37 @@ resolved "https://registry.yarnpkg.com/@medusajs/client-types/-/client-types-0.2.12-preview-20240505115807.tgz#5090e140d4ade84668eedc81222ab35366088831" integrity sha512-f7T/7WUAGOFfsNsjdCcVoWLlAqryaQB+hF0v/Dzst2hv+PdJGqHfHwTNdeK3ZYhUwzSQ8YdnCYsj9OEJR9SPiQ== -"@medusajs/icons@1.2.2-preview-20240703120635": - version "1.2.2-preview-20240703120635" - resolved "https://registry.yarnpkg.com/@medusajs/icons/-/icons-1.2.2-preview-20240703120635.tgz#0af56888a4c8d847eb3c6407976dc68234f1c245" - integrity sha512-TloJclvm9Zg/PDZYJQIQ80+hsBJqNTQe+g5qdve/uuyMdjUyKICrHJ5s9vgBjWqaolgSpnaoFKcDW8IMHsXmjg== +"@medusajs/icons@1.2.2-preview-20240704090504": + version "1.2.2-preview-20240704090504" + resolved "https://registry.yarnpkg.com/@medusajs/icons/-/icons-1.2.2-preview-20240704090504.tgz#7919b28f370b4b88f0b37d681b598a20d4ae79b4" + integrity sha512-iqwPVbIUUuUy9F2OUuHI7tpXbBz7Go9OD40NP4CzN0/zoWzplxxiUKShya2lmKcTY1DO93nz4naUb8uBm/r/Zg== "@medusajs/js-sdk@preview": - version "0.0.2-preview-20240703120635" - resolved "https://registry.yarnpkg.com/@medusajs/js-sdk/-/js-sdk-0.0.2-preview-20240703120635.tgz#d3728ee36cd40a8f618fd83daa4d10d70f25793b" - integrity sha512-FyhRKwf51Rm1oc5exUNoU/egLd2zrmY5q5LN5fCz/sMJQd7+6jiAMlGnEZ8FbsMfqRh9XIfISj7bjVQiDfxtBw== + version "0.0.2-preview-20240704090504" + resolved "https://registry.yarnpkg.com/@medusajs/js-sdk/-/js-sdk-0.0.2-preview-20240704090504.tgz#30b3ad7fbed4dbdd34efb7c62e3f0158ffa659f7" + integrity sha512-P2S7soZLCxkTLrbjeKhZO6KH/cvN9LSyLWOg5/HhaF6+pF/l95gC+4fY0uIhCc3PGa/IJ0bwXxFHhJaw/8xJfQ== dependencies: qs "^6.12.1" "@medusajs/types@preview": - version "1.12.0-preview-20240703120635" - resolved "https://registry.yarnpkg.com/@medusajs/types/-/types-1.12.0-preview-20240703120635.tgz#1cf315e20ede52399ec74fe29eee8173978a4896" - integrity sha512-Sy+K2mtbNAjjGdALdZK9Cm1J8askcTN2VPL1A+kichfTnDZkGNsJ+ngf8V83e03QtNw3QjqKs1YxwfRK/9OYXg== + version "1.12.0-preview-20240704090504" + resolved "https://registry.yarnpkg.com/@medusajs/types/-/types-1.12.0-preview-20240704090504.tgz#18876ef1fd177fbae2de4711219c21dd6e0d3685" + integrity sha512-OGbhOf8urxXzqNz+Sxl4/4LvD8+pW6kb4S7pGySJpcJOQwg+E7kHSfIbEdkkv9EPI2JDSCrvny/y0dBpG0eA2g== "@medusajs/ui-preset@preview": - version "1.1.4-preview-20240703120635" - resolved "https://registry.yarnpkg.com/@medusajs/ui-preset/-/ui-preset-1.1.4-preview-20240703120635.tgz#c5d0ab18cfdbff7911e86638c9b112aebe77815d" - integrity sha512-o0JD/ZEkHvSk2VyK6mYR/8oK+HoNMuqyo9OIqs4kT0WrKe5yQWjGgsKJlWR35BTLNR6XkIWUHQnDn5sFIg9hvg== + version "1.1.4-preview-20240704090504" + resolved "https://registry.yarnpkg.com/@medusajs/ui-preset/-/ui-preset-1.1.4-preview-20240704090504.tgz#c246b4fc4b5daba053a6347a54117eeaa11c06a2" + integrity sha512-yZzDMHYk3zDsubzzUFL0LvlJtkliYa526WOT1Be2hnVSk5czZowvBpddu2O6X6/PON7F/ddjeorUmd0bzjlBOA== dependencies: "@tailwindcss/forms" "^0.5.3" tailwindcss-animate "^1.0.6" "@medusajs/ui@preview": - version "3.0.1-preview-20240703120635" - resolved "https://registry.yarnpkg.com/@medusajs/ui/-/ui-3.0.1-preview-20240703120635.tgz#6752df071f13e0e69a194864c5d2e874d1a10f4a" - integrity sha512-TAFKgvjLvC+pmjcFESenTyq4sQrUTer5cLxTng6r9zxMpn/mHg4b3wbCPr1bGG1o1bPMp7TydrDDLonMP1ZxIQ== + version "3.0.1-preview-20240704090504" + resolved "https://registry.yarnpkg.com/@medusajs/ui/-/ui-3.0.1-preview-20240704090504.tgz#7950a19b9575951e1fe86d45630c88decd244dea" + integrity sha512-ppGxvAXsr4qe4jsLGNxIqwGOBwPBO2JUkrBRlmiwyOVKbaGGyvEnqdVAiuwtYhqM8CtTcWHWVWb1N38vbCM4UQ== dependencies: - "@medusajs/icons" "1.2.2-preview-20240703120635" + "@medusajs/icons" "1.2.2-preview-20240704090504" "@radix-ui/react-accordion" "1.2.0" "@radix-ui/react-alert-dialog" "1.1.1" "@radix-ui/react-avatar" "1.1.0" From 5eb42f2b4f09292f8a58b1935fa6684da94c769b Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Thu, 4 Jul 2024 15:08:07 +0200 Subject: [PATCH 03/14] fix: checkout shipping address behaviour --- src/lib/data/payment.ts | 5 +- .../checkout/components/addresses/index.tsx | 8 +-- .../components/shipping-address/index.tsx | 53 ++++++++++--------- .../templates/checkout-form/index.tsx | 24 +++++---- 4 files changed, 48 insertions(+), 42 deletions(-) diff --git a/src/lib/data/payment.ts b/src/lib/data/payment.ts index 291ababcd..f4ec6a6f3 100644 --- a/src/lib/data/payment.ts +++ b/src/lib/data/payment.ts @@ -1,8 +1,7 @@ import { sdk } from "@lib/config" -import { cache } from "react" // Shipping actions -export const listCartPaymentMethods = cache(async function (regionId: string) { +export const listCartPaymentMethods = async function (regionId: string) { return sdk.store.payment .listPaymentProviders( { region_id: regionId }, @@ -12,4 +11,4 @@ export const listCartPaymentMethods = cache(async function (regionId: string) { .catch(() => { return null }) -}) +} diff --git a/src/modules/checkout/components/addresses/index.tsx b/src/modules/checkout/components/addresses/index.tsx index 464e2857d..e506b7abd 100644 --- a/src/modules/checkout/components/addresses/index.tsx +++ b/src/modules/checkout/components/addresses/index.tsx @@ -29,7 +29,7 @@ const Addresses = ({ const isOpen = searchParams.get("step") === "address" - const { state: sameAsSBilling, toggle: toggleSameAsBilling } = useToggleState( + const { state: sameAsBilling, toggle: toggleSameAsBilling } = useToggleState( cart?.shipping_address && cart?.billing_address ? compareAddresses(cart?.shipping_address, cart?.billing_address) : true @@ -68,12 +68,12 @@ const Addresses = ({
- {!sameAsSBilling && ( + {!sameAsBilling && (
- {sameAsSBilling ? ( + {sameAsBilling ? ( Billing- and delivery address are the same. diff --git a/src/modules/checkout/components/shipping-address/index.tsx b/src/modules/checkout/components/shipping-address/index.tsx index 1b9d48d21..7a35e4be3 100644 --- a/src/modules/checkout/components/shipping-address/index.tsx +++ b/src/modules/checkout/components/shipping-address/index.tsx @@ -1,4 +1,11 @@ -import React, { useState, useEffect, useMemo, useCallback } from "react" +import React, { + useState, + useEffect, + useMemo, + useCallback, + Dispatch, + SetStateAction, +} from "react" import Checkbox from "@modules/common/components/checkbox" import Input from "@modules/common/components/input" import AddressSelect from "../address-select" @@ -18,7 +25,7 @@ const ShippingAddress = ({ checked: boolean onChange: () => void }) => { - const [formData, setFormData] = useState({}) + const [formData, setFormData] = useState>({}) const countriesInRegion = useMemo( () => cart?.region?.countries?.map((c) => c.iso_2), @@ -34,9 +41,13 @@ const ShippingAddress = ({ [customer?.addresses, countriesInRegion] ) - const setFormAddress = useCallback( - (address?: HttpTypes.StoreCartAddress, email?: string) => { - setFormData({ + const setFormAddress = ( + address?: HttpTypes.StoreCartAddress, + email?: string + ) => { + address && + setFormData((prevState: Record) => ({ + ...prevState, "shipping_address.first_name": address?.first_name || "", "shipping_address.last_name": address?.last_name || "", "shipping_address.address_1": address?.address_1 || "", @@ -45,32 +56,26 @@ const ShippingAddress = ({ "shipping_address.city": address?.city || "", "shipping_address.country_code": address?.country_code || "", "shipping_address.province": address?.province || "", - email: email || "", "shipping_address.phone": address?.phone || "", - }) - }, - [] - ) + })) - useEffect(() => { - setFormAddress(cart?.shipping_address, cart?.email) - }, [setFormAddress, cart?.shipping_address, cart?.email]) + email && + setFormData((prevState: Record) => ({ + ...prevState, + email: email, + })) + } useEffect(() => { - if (!formData.email && customer?.email) { - return setFormData({ - ...formData, - email: customer.email, - }) + // Ensure cart is not null and has a shipping_address before setting form data + if (cart && cart.shipping_address) { + setFormAddress(cart?.shipping_address, cart?.email) } - if (!formData.email && cart?.email) { - return setFormData({ - ...formData, - email: cart.email, - }) + if (cart && !cart.email && customer?.email) { + setFormAddress(undefined, customer.email) } - }, [formData, customer?.email, cart?.email]) + }, [cart]) // Add cart as a dependency const handleChange = ( e: React.ChangeEvent< diff --git a/src/modules/checkout/templates/checkout-form/index.tsx b/src/modules/checkout/templates/checkout-form/index.tsx index 73dadf166..6abde6ad0 100644 --- a/src/modules/checkout/templates/checkout-form/index.tsx +++ b/src/modules/checkout/templates/checkout-form/index.tsx @@ -1,15 +1,13 @@ "use client" -import { retrieveCart } from "@lib/data/cart" -import { getCustomer } from "@lib/data/customer" import { listCartShippingMethods } from "@lib/data/fulfillment" import { listCartPaymentMethods } from "@lib/data/payment" -import { HttpTypes, StoreCart, StoreCustomer } from "@medusajs/types" +import { HttpTypes } from "@medusajs/types" import Addresses from "@modules/checkout/components/addresses" import Payment from "@modules/checkout/components/payment" import Review from "@modules/checkout/components/review" import Shipping from "@modules/checkout/components/shipping" -import { useState } from "react" +import { useEffect, useState } from "react" export default function CheckoutForm({ cart, @@ -25,13 +23,17 @@ export default function CheckoutForm({ return null } - listCartShippingMethods(cart.id).then((methods: any) => - setAvailableShippingMethods(methods) - ) - - listCartPaymentMethods(cart.region?.id ?? "").then((payments: any) => - setPaymentMethods(payments) - ) + useEffect(() => { + listCartShippingMethods(cart.id).then((methods: any) => + setAvailableShippingMethods(methods) + ) + }, []) + + useEffect(() => { + listCartPaymentMethods(cart.region?.id ?? "").then((payments: any) => + setPaymentMethods(payments) + ) + }, []) return (
From 14338016d7474ae2a320868a3765506cc3fdf6ee Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Thu, 4 Jul 2024 16:19:46 +0200 Subject: [PATCH 04/14] fix: shipping method selection --- src/lib/data/payment.ts | 5 ++-- .../components/shipping-address/index.tsx | 15 ++++-------- .../checkout/components/shipping/index.tsx | 8 ++++++- .../templates/checkout-form/index.tsx | 23 +++++-------------- 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/src/lib/data/payment.ts b/src/lib/data/payment.ts index f4ec6a6f3..291ababcd 100644 --- a/src/lib/data/payment.ts +++ b/src/lib/data/payment.ts @@ -1,7 +1,8 @@ import { sdk } from "@lib/config" +import { cache } from "react" // Shipping actions -export const listCartPaymentMethods = async function (regionId: string) { +export const listCartPaymentMethods = cache(async function (regionId: string) { return sdk.store.payment .listPaymentProviders( { region_id: regionId }, @@ -11,4 +12,4 @@ export const listCartPaymentMethods = async function (regionId: string) { .catch(() => { return null }) -} +}) diff --git a/src/modules/checkout/components/shipping-address/index.tsx b/src/modules/checkout/components/shipping-address/index.tsx index 7a35e4be3..59b16ea4e 100644 --- a/src/modules/checkout/components/shipping-address/index.tsx +++ b/src/modules/checkout/components/shipping-address/index.tsx @@ -1,18 +1,11 @@ -import React, { - useState, - useEffect, - useMemo, - useCallback, - Dispatch, - SetStateAction, -} from "react" +import { HttpTypes } from "@medusajs/types" +import { Container } from "@medusajs/ui" import Checkbox from "@modules/common/components/checkbox" import Input from "@modules/common/components/input" +import { mapKeys } from "lodash" +import React, { useEffect, useMemo, useState } from "react" import AddressSelect from "../address-select" import CountrySelect from "../country-select" -import { Container } from "@medusajs/ui" -import { HttpTypes } from "@medusajs/types" -import { mapKeys } from "lodash" const ShippingAddress = ({ customer, diff --git a/src/modules/checkout/components/shipping/index.tsx b/src/modules/checkout/components/shipping/index.tsx index 2d0b73178..cb0561bef 100644 --- a/src/modules/checkout/components/shipping/index.tsx +++ b/src/modules/checkout/components/shipping/index.tsx @@ -32,7 +32,8 @@ const Shipping: React.FC = ({ const isOpen = searchParams.get("step") === "delivery" const selectedShippingMethod = availableShippingMethods?.find( - (method) => method.id === cart.shipping_methods?.[0]?.shipping_option_id + // To do: remove the previously selected shipping method instead of using the last one + (method) => method.id === cart.shipping_methods?.at(-1)?.shipping_option_id ) const handleEdit = () => { @@ -52,6 +53,7 @@ const Shipping: React.FC = ({ .finally(() => { setIsLoading(false) }) + console.log("setShippingMethod", cart.shipping_methods?.[0]) } useEffect(() => { @@ -127,6 +129,10 @@ const Shipping: React.FC = ({
+ gc.id).toString()} + /> + { - listCartShippingMethods(cart.id).then((methods: any) => - setAvailableShippingMethods(methods) - ) - }, []) + const shippingMethods = await listCartShippingMethods(cart.id) + const paymentMethods = await listCartPaymentMethods(cart.region?.id ?? "") - useEffect(() => { - listCartPaymentMethods(cart.region?.id ?? "").then((payments: any) => - setPaymentMethods(payments) - ) - }, []) + if (!shippingMethods || !paymentMethods) { + return null + } return (
From 99580e68706191b11adddfd09e280a62769ab420 Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Thu, 4 Jul 2024 16:27:34 +0200 Subject: [PATCH 05/14] fix: province required --- src/modules/checkout/components/billing_address/index.tsx | 1 + src/modules/checkout/components/shipping-address/index.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/modules/checkout/components/billing_address/index.tsx b/src/modules/checkout/components/billing_address/index.tsx index 2df1b93c0..5c81c5b06 100644 --- a/src/modules/checkout/components/billing_address/index.tsx +++ b/src/modules/checkout/components/billing_address/index.tsx @@ -102,6 +102,7 @@ const BillingAddress = ({ cart }: { cart: HttpTypes.StoreCart | null }) => { autoComplete="address-level1" value={formData["billing_address.province"]} onChange={handleChange} + required data-testid="billing-province-input" />
From 87ef903c9a2a0f2be9ca24c5bc0cf0d43a541e2e Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Thu, 4 Jul 2024 16:45:03 +0200 Subject: [PATCH 06/14] fix: sort radio group typing --- .../common/components/filter-radio-group/index.tsx | 7 ++----- .../refinement-list/sort-products/index.tsx | 13 ++++++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/modules/common/components/filter-radio-group/index.tsx b/src/modules/common/components/filter-radio-group/index.tsx index a275b5115..a76863f4e 100644 --- a/src/modules/common/components/filter-radio-group/index.tsx +++ b/src/modules/common/components/filter-radio-group/index.tsx @@ -1,6 +1,6 @@ import { EllipseMiniSolid } from "@medusajs/icons" import { Label, RadioGroup, Text, clx } from "@medusajs/ui" -import { ChangeEvent } from "react" +import { ChangeEvent, MouseEventHandler } from "react" type FilterRadioGroupProps = { title: string @@ -23,7 +23,7 @@ const FilterRadioGroup = ({ return (
{title} - + {items?.map((i) => (
} ) => - handleChange(e, i.value) - } className="hidden peer" id={i.value} value={i.value} diff --git a/src/modules/store/components/refinement-list/sort-products/index.tsx b/src/modules/store/components/refinement-list/sort-products/index.tsx index d43fa52ce..ead43ca9d 100644 --- a/src/modules/store/components/refinement-list/sort-products/index.tsx +++ b/src/modules/store/components/refinement-list/sort-products/index.tsx @@ -9,7 +9,7 @@ export type SortOptions = "price_asc" | "price_desc" | "created_at" type SortProductsProps = { sortBy: SortOptions setQueryParams: (name: string, value: SortOptions) => void - 'data-testid'?: string + "data-testid"?: string } const sortOptions = [ @@ -27,10 +27,13 @@ const sortOptions = [ }, ] -const SortProducts = ({ 'data-testid': dataTestId, sortBy, setQueryParams }: SortProductsProps) => { - const handleChange = (e: ChangeEvent) => { - const newSortBy = e.target.value as SortOptions - setQueryParams("sortBy", newSortBy) +const SortProducts = ({ + "data-testid": dataTestId, + sortBy, + setQueryParams, +}: SortProductsProps) => { + const handleChange = (value: SortOptions) => { + setQueryParams("sortBy", value) } return ( From f5491281db9ed26ee91d3a4f751d0d50185d84b5 Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Thu, 4 Jul 2024 16:48:10 +0200 Subject: [PATCH 07/14] cleanup --- .../store/components/refinement-list/sort-products/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/store/components/refinement-list/sort-products/index.tsx b/src/modules/store/components/refinement-list/sort-products/index.tsx index ead43ca9d..384896aa1 100644 --- a/src/modules/store/components/refinement-list/sort-products/index.tsx +++ b/src/modules/store/components/refinement-list/sort-products/index.tsx @@ -1,7 +1,5 @@ "use client" -import { ChangeEvent } from "react" - import FilterRadioGroup from "@modules/common/components/filter-radio-group" export type SortOptions = "price_asc" | "price_desc" | "created_at" From 09118b855ff42271003d119dc988034e49f9fad6 Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Thu, 4 Jul 2024 23:01:13 +0200 Subject: [PATCH 08/14] fix: sort 100 products --- src/lib/data/products.ts | 48 ++++++++++++++++ src/lib/util/sort-products-by-price.ts | 44 -------------- src/lib/util/sort-products.ts | 57 +++++++++++++++++++ .../components/filter-radio-group/index.tsx | 1 - .../store/templates/paginated-products.tsx | 17 ++---- 5 files changed, 110 insertions(+), 57 deletions(-) delete mode 100644 src/lib/util/sort-products-by-price.ts create mode 100644 src/lib/util/sort-products.ts diff --git a/src/lib/data/products.ts b/src/lib/data/products.ts index 10358a37b..d0a01846d 100644 --- a/src/lib/data/products.ts +++ b/src/lib/data/products.ts @@ -2,6 +2,8 @@ import { sdk } from "@lib/config" import { HttpTypes } from "@medusajs/types" import { cache } from "react" import { getRegion } from "./regions" +import { SortOptions } from "@modules/store/components/refinement-list/sort-products" +import { sortProducts } from "@lib/util/sort-products" export const getProductsById = cache(async function ({ ids, @@ -85,3 +87,49 @@ export const getProductsList = cache(async function ({ } }) }) + +export const getProductsListWithSort = cache(async function ({ + page = 0, + queryParams, + sortBy = "created_at", + countryCode, +}: { + 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 +}> { + const limit = queryParams?.limit || 12 + + const { + response: { products, count }, + } = await getProductsList({ + pageParam: 0, + queryParams: { + ...queryParams, + limit: 100, + }, + countryCode, + }) + + const sortedProducts = sortProducts(products, sortBy) + + const pageParam = (page - 1) * limit + + const nextPage = count > pageParam + limit ? pageParam + limit : null + + const paginatedProducts = sortedProducts.slice(pageParam, pageParam + limit) + + return { + response: { + products: paginatedProducts, + count, + }, + nextPage, + queryParams, + } +}) diff --git a/src/lib/util/sort-products-by-price.ts b/src/lib/util/sort-products-by-price.ts deleted file mode 100644 index 29a67bf4c..000000000 --- a/src/lib/util/sort-products-by-price.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { HttpTypes } from "@medusajs/types" - -interface PricedVariant extends HttpTypes.StoreProductVariant { - calculated_price: { - calculated_amount: number - } -} - -interface PricedProduct extends HttpTypes.StoreProduct { - variants: PricedVariant[] - _minPrice: number -} - -/** - * Helper function to sort products by price until the store API supports sorting by price - * @param products - * @param order - * @returns products sorted by price - */ -export function sortProductsByPrice( - products: HttpTypes.StoreProduct[], - order: "asc" | "desc" -): PricedProduct[] { - const pricedProducts = products as PricedProduct[] - - // Precompute the minimum price for each product - pricedProducts.forEach((product) => { - if (product.variants && product.variants.length > 0) { - product._minPrice = Math.min( - ...product.variants.map( - (variant) => variant?.calculated_price?.calculated_amount - ) - ) - } else { - product._minPrice = Infinity - } - }) - - // Sort products based on the precomputed minimum prices - return pricedProducts.sort((a, b) => { - const diff = a._minPrice - b._minPrice - return order === "asc" ? diff : -diff - }) -} diff --git a/src/lib/util/sort-products.ts b/src/lib/util/sort-products.ts new file mode 100644 index 000000000..2b996cccc --- /dev/null +++ b/src/lib/util/sort-products.ts @@ -0,0 +1,57 @@ +import { HttpTypes } from "@medusajs/types" +import { SortOptions } from "@modules/store/components/refinement-list/sort-products" + +interface PricedVariant extends HttpTypes.StoreProductVariant { + calculated_price: { + calculated_amount: number + } +} + +interface PricedProduct extends HttpTypes.StoreProduct { + variants: PricedVariant[] + _minPrice: number +} + +/** + * Helper function to sort products by price until the store API supports sorting by price + * @param products + * @param sortBy + * @returns products sorted by price + */ +export function sortProducts( + products: HttpTypes.StoreProduct[], + sortBy: SortOptions +): PricedProduct[] { + let sortedProducts = products as PricedProduct[] + + 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 + ) + ) + } else { + 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 + }) + } + + if (sortBy === "created_at") { + sortedProducts.sort((a, b) => { + return ( + new Date(b.created_at!).getTime() - new Date(a.created_at!).getTime() + ) + }) + } + + return sortedProducts +} diff --git a/src/modules/common/components/filter-radio-group/index.tsx b/src/modules/common/components/filter-radio-group/index.tsx index a76863f4e..dc70edeb4 100644 --- a/src/modules/common/components/filter-radio-group/index.tsx +++ b/src/modules/common/components/filter-radio-group/index.tsx @@ -1,6 +1,5 @@ import { EllipseMiniSolid } from "@medusajs/icons" import { Label, RadioGroup, Text, clx } from "@medusajs/ui" -import { ChangeEvent, MouseEventHandler } from "react" type FilterRadioGroupProps = { title: string diff --git a/src/modules/store/templates/paginated-products.tsx b/src/modules/store/templates/paginated-products.tsx index 43a5ee708..7d3abd748 100644 --- a/src/modules/store/templates/paginated-products.tsx +++ b/src/modules/store/templates/paginated-products.tsx @@ -1,6 +1,5 @@ -import { getProductsList } from "@lib/data/products" +import { getProductsListWithSort } from "@lib/data/products" import { getRegion } from "@lib/data/regions" -import { sortProductsByPrice } from "@lib/util/sort-products-by-price" import ProductPreview from "@modules/products/components/product-preview" import { Pagination } from "@modules/store/components/pagination" import { SortOptions } from "@modules/store/components/refinement-list/sort-products" @@ -31,7 +30,7 @@ export default async function PaginatedProducts({ countryCode: string }) { const queryParams: PaginatedProductsParams = { - limit: PRODUCT_LIMIT, + limit: 100, } if (collectionId) { @@ -58,19 +57,13 @@ export default async function PaginatedProducts({ let { response: { products, count }, - } = await getProductsList({ - pageParam: page, + } = await getProductsListWithSort({ + page, queryParams, + sortBy, countryCode, }) - if (sortBy && ["price_asc", "price_desc"].includes(sortBy)) { - products = sortProductsByPrice( - products, - sortBy === "price_asc" ? "asc" : "desc" - ) - } - const totalPages = Math.ceil(count / PRODUCT_LIMIT) return ( From 2272cc099088bfaac4fd91c24403d8a665d991f9 Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Thu, 4 Jul 2024 23:07:19 +0200 Subject: [PATCH 09/14] chore: annotations, cleanup --- src/lib/data/products.ts | 4 ++++ src/modules/checkout/components/shipping/index.tsx | 4 ---- src/modules/store/templates/paginated-products.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/data/products.ts b/src/lib/data/products.ts index d0a01846d..b3fcda8d0 100644 --- a/src/lib/data/products.ts +++ b/src/lib/data/products.ts @@ -88,6 +88,10 @@ export const getProductsList = cache(async function ({ }) }) +/** + * This will fetch 100 products to the Next.js cache and sort them based on the sortBy parameter. + * It will then return the paginated products based on the page and limit parameters. + */ export const getProductsListWithSort = cache(async function ({ page = 0, queryParams, diff --git a/src/modules/checkout/components/shipping/index.tsx b/src/modules/checkout/components/shipping/index.tsx index cb0561bef..2c9847467 100644 --- a/src/modules/checkout/components/shipping/index.tsx +++ b/src/modules/checkout/components/shipping/index.tsx @@ -129,10 +129,6 @@ const Shipping: React.FC = ({
- gc.id).toString()} - /> - Date: Fri, 5 Jul 2024 12:17:12 +0200 Subject: [PATCH 10/14] fix: cleanup --- src/modules/checkout/components/shipping/index.tsx | 1 - src/modules/products/components/product-preview/price.tsx | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/checkout/components/shipping/index.tsx b/src/modules/checkout/components/shipping/index.tsx index 2c9847467..74b36b204 100644 --- a/src/modules/checkout/components/shipping/index.tsx +++ b/src/modules/checkout/components/shipping/index.tsx @@ -53,7 +53,6 @@ const Shipping: React.FC = ({ .finally(() => { setIsLoading(false) }) - console.log("setShippingMethod", cart.shipping_methods?.[0]) } useEffect(() => { diff --git a/src/modules/products/components/product-preview/price.tsx b/src/modules/products/components/product-preview/price.tsx index fbf549fe1..f8fa5866f 100644 --- a/src/modules/products/components/product-preview/price.tsx +++ b/src/modules/products/components/product-preview/price.tsx @@ -2,6 +2,10 @@ import { Text, clx } from "@medusajs/ui" import { VariantPrice } from "types/global" export default async function PreviewPrice({ price }: { price: VariantPrice }) { + if (!price) { + return null + } + return ( <> {price.price_type === "sale" && ( From 6547d6b3c2d8d99ceff13d9fc7f519ad2e70048f Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Fri, 5 Jul 2024 15:08:12 +0200 Subject: [PATCH 11/14] fix: update e2e test runner --- .github/scripts/medusa-config.js | 149 ++++++++++++------------------- .github/workflows/test-e2e.yaml | 4 +- 2 files changed, 60 insertions(+), 93 deletions(-) diff --git a/.github/scripts/medusa-config.js b/.github/scripts/medusa-config.js index 57a0b296f..e4203c05d 100644 --- a/.github/scripts/medusa-config.js +++ b/.github/scripts/medusa-config.js @@ -1,111 +1,76 @@ -const dotenv = require("dotenv") +const { defineConfig, loadEnv } = require("@medusajs/utils") -let ENV_FILE_NAME = "" -switch (process.env.NODE_ENV) { - case "production": - ENV_FILE_NAME = ".env.production" - break - case "staging": - ENV_FILE_NAME = ".env.staging" - break - case "test": - ENV_FILE_NAME = ".env.test" - break - case "development": - default: - ENV_FILE_NAME = ".env" - break -} - -try { - dotenv.config({ path: process.cwd() + "/" + ENV_FILE_NAME }) -} catch (e) {} +loadEnv(process.env.NODE_ENV || "development", process.cwd()) // CORS when consuming Medusa from admin -const ADMIN_CORS = - process.env.ADMIN_CORS || "http://localhost:7000,http://localhost:7001" +// Medusa's docs are added for a better learning experience. Feel free to remove. +const ADMIN_CORS = `${ + process.env.ADMIN_CORS?.length + ? `${process.env.ADMIN_CORS},` + : "http://localhost:7000,http://localhost:7001," +}https://docs.medusajs.com,https://medusa-docs-v2-git-docs-v2-medusajs.vercel.app,https://medusa-resources-git-docs-v2-medusajs.vercel.app` // CORS to avoid issues when consuming Medusa from a client -const STORE_CORS = process.env.STORE_CORS || "http://localhost:8000" +// Medusa's docs are added for a better learning experience. Feel free to remove. +const STORE_CORS = `${ + process.env.STORE_CORS?.length + ? `${process.env.STORE_CORS},` + : "http://localhost:8000," +}https://docs.medusajs.com,https://medusa-docs-v2-git-docs-v2-medusajs.vercel.app,https://medusa-resources-git-docs-v2-medusajs.vercel.app` const DATABASE_URL = process.env.DATABASE_URL || "postgres://medusa:password@localhost/medusa" const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379" -const plugins = [ - `medusa-fulfillment-manual`, - `medusa-payment-manual`, - { - resolve: `@medusajs/file-local`, - options: { - upload_dir: "uploads", - }, - }, - { - resolve: "@medusajs/admin", - /** @type {import('@medusajs/admin').PluginOptions} */ - options: { - autoRebuild: true, - develop: { - open: process.env.OPEN_BROWSER !== "false", +export default defineConfig({ + plugins: [ + `medusa-fulfillment-manual`, + `medusa-payment-manual`, + { + resolve: `@medusajs/file-local`, + options: { + upload_dir: "uploads", }, }, - }, - { - resolve: `medusa-plugin-meilisearch`, - options: { - config: { - host: process.env.MEILISEARCH_HOST, - apiKey: process.env.MEILISEARCH_API_KEY, - }, - settings: { - products: { - indexSettings: { - searchableAttributes: ["title", "description", "variant_sku"], - displayedAttributes: [ - "id", - "title", - "description", - "variant_sku", - "thumbnail", - "handle", - ], + { + resolve: `medusa-plugin-meilisearch`, + options: { + config: { + host: process.env.MEILISEARCH_HOST, + apiKey: process.env.MEILISEARCH_API_KEY, + }, + settings: { + products: { + indexSettings: { + searchableAttributes: ["title", "description", "variant_sku"], + displayedAttributes: [ + "id", + "title", + "description", + "variant_sku", + "thumbnail", + "handle", + ], + }, + primaryKey: "id", }, - primaryKey: "id", }, }, }, + ], + admin: { + backendUrl: "http://localhost:9000", }, -] - -const modules = { - /*eventBus: { - resolve: "@medusajs/event-bus-redis", - options: { - redisUrl: REDIS_URL - } + projectConfig: { + databaseUrl: DATABASE_URL, + http: { + storeCors: STORE_CORS, + adminCors: ADMIN_CORS, + authCors: process.env.AUTH_CORS || ADMIN_CORS, + jwtSecret: process.env.JWT_SECRET || "supersecret", + cookieSecret: process.env.COOKIE_SECRET || "supersecret", + }, + redisUrl: REDIS_URL, }, - cacheService: { - resolve: "@medusajs/cache-redis", - options: { - redisUrl: REDIS_URL - } - },*/ -} - -const projectConfig = { - jwtSecret: process.env.JWT_SECRET, - cookieSecret: process.env.COOKIE_SECRET, - store_cors: STORE_CORS, - database_url: DATABASE_URL, - admin_cors: ADMIN_CORS, - // Uncomment the following lines to enable REDIS - redis_url: REDIS_URL, -} - -module.exports = { - projectConfig, - plugins, - modules, -} +}) diff --git a/.github/workflows/test-e2e.yaml b/.github/workflows/test-e2e.yaml index 789445114..65d423505 100644 --- a/.github/workflows/test-e2e.yaml +++ b/.github/workflows/test-e2e.yaml @@ -102,8 +102,10 @@ jobs: working-directory: ../ # https://docs.medusajs.com/cli/reference#options run: | - medusa new backend \ + medusa new cli-test \ -y \ + --v2 \ + --branch feat/v2 \ --skip-db \ --skip-migrations \ --skip-env \ From ee4c6845824a942f7902a99f9021db9dedff2d8d Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Fri, 5 Jul 2024 15:13:19 +0200 Subject: [PATCH 12/14] fix: e2e cli version --- .github/workflows/test-e2e.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-e2e.yaml b/.github/workflows/test-e2e.yaml index 65d423505..8ece72c61 100644 --- a/.github/workflows/test-e2e.yaml +++ b/.github/workflows/test-e2e.yaml @@ -97,12 +97,12 @@ jobs: psql -h localhost -U postgres -d test -c "CREATE DATABASE ${{ env.TEST_POSTGRES_DATABASE }} OWNER ${{ env.TEST_POSTGRES_USER }};" - name: Install Medusa CLI - run: npm install @medusajs/medusa-cli -g + run: npm install @medusajs/medusa-cli@preview -g - name: Setup medusa backend server working-directory: ../ # https://docs.medusajs.com/cli/reference#options run: | - medusa new cli-test \ + medusa new backend \ -y \ --v2 \ --branch feat/v2 \ From 27685a988d21b3911bf548bbdff8a519c3797ce1 Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Fri, 5 Jul 2024 15:19:55 +0200 Subject: [PATCH 13/14] fix: e2e build --- .github/workflows/test-e2e.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/test-e2e.yaml b/.github/workflows/test-e2e.yaml index 8ece72c61..803403348 100644 --- a/.github/workflows/test-e2e.yaml +++ b/.github/workflows/test-e2e.yaml @@ -115,10 +115,6 @@ jobs: --db-host ${{ env.TEST_POSTGRES_HOST }} \ --db-port ${{ env.TEST_POSTGREST_PORT }} - - name: Build the backend - working-directory: ../backend - run: yarn build:admin - - name: Setup search in the backend working-directory: ../backend run: yarn add medusa-plugin-meilisearch @@ -132,7 +128,7 @@ jobs: - name: Run backend server working-directory: ../backend - run: medusa develop & + run: medusa develop - name: Install packages run: yarn install -y From 3f5dc23f75e52270bdf450a832ff4b5edef5aa2b Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Fri, 5 Jul 2024 15:26:23 +0200 Subject: [PATCH 14/14] fix: e2e node version --- .github/workflows/test-e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-e2e.yaml b/.github/workflows/test-e2e.yaml index 803403348..2f1f8363b 100644 --- a/.github/workflows/test-e2e.yaml +++ b/.github/workflows/test-e2e.yaml @@ -88,7 +88,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: "18" + node-version: "20" - name: Initialize PostgreSQL run: |