Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes: sorting and checkout #342

Merged
merged 14 commits into from
Jul 9, 2024
44 changes: 44 additions & 0 deletions src/lib/util/sort-products-by-price.ts
Original file line number Diff line number Diff line change
@@ -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
})
}
8 changes: 4 additions & 4 deletions src/modules/checkout/components/addresses/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -68,12 +68,12 @@ const Addresses = ({
<div className="pb-8">
<ShippingAddress
customer={customer}
checked={sameAsSBilling}
checked={sameAsBilling}
onChange={toggleSameAsBilling}
cart={cart}
/>

{!sameAsSBilling && (
{!sameAsBilling && (
<div>
<Heading
level="h2"
Expand Down Expand Up @@ -144,7 +144,7 @@ const Addresses = ({
Billing Address
</Text>

{sameAsSBilling ? (
{sameAsBilling ? (
<Text className="txt-medium text-ui-fg-subtle">
Billing- and delivery address are the same.
</Text>
Expand Down
1 change: 1 addition & 0 deletions src/modules/checkout/components/billing_address/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
<Input
Expand Down
53 changes: 26 additions & 27 deletions src/modules/checkout/components/shipping-address/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { useState, useEffect, useMemo, useCallback } 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,
Expand All @@ -18,7 +18,7 @@ const ShippingAddress = ({
checked: boolean
onChange: () => void
}) => {
const [formData, setFormData] = useState<any>({})
const [formData, setFormData] = useState<Record<string, any>>({})

const countriesInRegion = useMemo(
() => cart?.region?.countries?.map((c) => c.iso_2),
Expand All @@ -34,9 +34,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<string, any>) => ({
...prevState,
"shipping_address.first_name": address?.first_name || "",
"shipping_address.last_name": address?.last_name || "",
"shipping_address.address_1": address?.address_1 || "",
Expand All @@ -45,32 +49,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<string, any>) => ({
...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<
Expand Down Expand Up @@ -170,6 +168,7 @@ const ShippingAddress = ({
autoComplete="address-level1"
value={formData["shipping_address.province"]}
onChange={handleChange}
required
data-testid="shipping-province-input"
/>
</div>
Expand Down
8 changes: 7 additions & 1 deletion src/modules/checkout/components/shipping/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ const Shipping: React.FC<ShippingProps> = ({
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 = () => {
Expand All @@ -52,6 +53,7 @@ const Shipping: React.FC<ShippingProps> = ({
.finally(() => {
setIsLoading(false)
})
console.log("setShippingMethod", cart.shipping_methods?.[0])
}

useEffect(() => {
Expand Down Expand Up @@ -127,6 +129,10 @@ const Shipping: React.FC<ShippingProps> = ({
</RadioGroup>
</div>

<ErrorMessage
error={cart.shipping_methods?.map((gc) => gc.id).toString()}
/>

<ErrorMessage
error={error}
data-testid="delivery-option-error-message"
Expand Down
23 changes: 7 additions & 16 deletions src/modules/checkout/templates/checkout-form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,28 @@
"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"

export default function CheckoutForm({
export default async function CheckoutForm({
cart,
customer,
}: {
cart: HttpTypes.StoreCart | null
customer: HttpTypes.StoreCustomer | null
}) {
const [shippingMethods, setAvailableShippingMethods] = useState([])
const [paymentMethods, setPaymentMethods] = useState([])

if (!cart) {
return null
}

listCartShippingMethods(cart.id).then((methods: any) =>
setAvailableShippingMethods(methods)
)
const shippingMethods = await listCartShippingMethods(cart.id)
const paymentMethods = await listCartPaymentMethods(cart.region?.id ?? "")

listCartPaymentMethods(cart.region?.id ?? "").then((payments: any) =>
setPaymentMethods(payments)
)
if (!shippingMethods || !paymentMethods) {
return null
}

return (
<div>
Expand Down
12 changes: 3 additions & 9 deletions src/modules/common/components/filter-radio-group/index.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -23,23 +23,17 @@ const FilterRadioGroup = ({
return (
<div className="flex gap-x-3 flex-col gap-y-3">
<Text className="txt-compact-small-plus text-ui-fg-muted">{title}</Text>
<RadioGroup data-testid={dataTestId}>
<RadioGroup data-testid={dataTestId} onValueChange={handleChange}>
{items?.map((i) => (
<div
key={i.value}
className={clx("flex gap-x-2 items-center", {
"ml-[-1.75rem]": i.value === value,
"ml-[-23px]": i.value === value,
})}
>
{i.value === value && <EllipseMiniSolid />}
<RadioGroup.Item
checked={i.value === value}
onClick={(e) =>
handleChange(
e as unknown as ChangeEvent<HTMLButtonElement>,
i.value
)
}
className="hidden peer"
id={i.value}
value={i.value}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
"use client"

import { ChangeEvent } from "react"

import FilterRadioGroup from "@modules/common/components/filter-radio-group"

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 = [
Expand All @@ -27,10 +25,13 @@ const sortOptions = [
},
]

const SortProducts = ({ 'data-testid': dataTestId, sortBy, setQueryParams }: SortProductsProps) => {
const handleChange = (e: ChangeEvent<HTMLButtonElement>) => {
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 (
Expand Down
25 changes: 11 additions & 14 deletions src/modules/store/templates/paginated-products.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -66,14 +56,21 @@ export default async function PaginatedProducts({
return null
}

const {
let {
response: { products, count },
} = await getProductsList({
pageParam: page,
queryParams,
countryCode,
})

if (sortBy && ["price_asc", "price_desc"].includes(sortBy)) {
products = sortProductsByPrice(
carlos-r-l-rodrigues marked this conversation as resolved.
Show resolved Hide resolved
products,
sortBy === "price_asc" ? "asc" : "desc"
)
}

const totalPages = Math.ceil(count / PRODUCT_LIMIT)

return (
Expand Down
Loading
Loading