Skip to content

Commit

Permalink
fix: Add debounce to cart (#1251)
Browse files Browse the repository at this point in the history
## Describe your changes

Fixes: #
  • Loading branch information
didrikmunther authored Aug 5, 2024
1 parent 6e867c5 commit 68bda6d
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,43 @@ import { RegistrationSection } from "@/shared/vars"
import { cn } from "@/utils/cx"
import { formatCurrency } from "@/utils/format_currency"
import { cx } from "class-variance-authority"
import { useRef, useState } from "react"

const useDebouncedRequest = <T,>(
defaultValue: T,
callback: (value: T) => Promise<void>,
delay = 500
): [T, (value: T) => void, boolean] => {
const [value, setValue] = useState(defaultValue)
const [loading, setLoading] = useState(false)
const currentPromise = useRef<Promise<void> | null>(null)
const abortController = useRef(new AbortController())

const onChange = async (value: T) => {
setValue(value)
abortController.current.abort()
abortController.current = new AbortController()
setLoading(true)

const currentController = abortController.current

if (currentPromise.current != null) {
await currentPromise.current
}

if (currentController.signal.aborted) return

setTimeout(async () => {
if (currentController.signal.aborted) return
currentPromise.current = callback(value)
await currentPromise.current
if (currentController.signal.aborted) return
setLoading(false)
}, delay)
}

return [value, onChange, loading]
}

export function ProductOrderingCard({
product
Expand All @@ -48,8 +85,6 @@ export function ProductOrderingCard({
const productAdjustedUnitPrice =
(productWithAdjustedPrice?.adjustedPrice ?? 0) / (order?.quantity ?? 1)

const selected = orders.find(x => x.product.id === product.id)

const packageOrder = orders.find(order =>
belongsToSection(order.product, RegistrationSection.Packages)
)
Expand All @@ -64,34 +99,40 @@ export function ProductOrderingCard({
current => current.child_product.id === product.id
)?.quantity ?? 0

async function onChange(quantity: number) {
if (isNaN(quantity)) quantity = 0
if (product.max_quantity != null && quantity > product.max_quantity)
quantity = product.max_quantity
if (quantity <= 0 || quantity == null) {
await setProductOrder(product.id, 0)
} else if (
!(
// If the product has a max of 1 and is already
// included, don't allow it to be added or incremented
(
product.max_quantity != null &&
product.max_quantity <= 1 &&
packageProductBaseQuantity > 0
const [quantity, setQuantity] = useDebouncedRequest(
order?.quantity ?? undefined,
async (quantity: number | undefined) => {
if (quantity == null || isNaN(quantity)) quantity = 0
if (product.max_quantity != null && quantity > product.max_quantity)
quantity = product.max_quantity
if (quantity <= 0 || quantity == null) {
await setProductOrder(product.id, 0)
} else if (
!(
// If the product has a max of 1 and is already
// included, don't allow it to be added or incremented
(
product.max_quantity != null &&
product.max_quantity <= 1 &&
packageProductBaseQuantity > 0
)
)
)
) {
await setProductOrder(product.id, quantity)
}
}
) {
await setProductOrder(product.id, quantity)
}
},
// Seems to work fine with 0 seconds debounce
// Anyway I don't have time to remove the "debounce" functionality
0
)

const packageName =
productPackage?.short_name || productPackage?.name || "package"

return (
<Card
className={cx("w-full border-2", {
"border-emerald-500": selected
"border-emerald-500": order
})}
>
<CardHeader className="flex flex-col justify-between">
Expand All @@ -111,10 +152,11 @@ export function ProductOrderingCard({
packageProductBaseQuantity <= 0 ? (
<Switch
onCheckedChange={value =>
onChange(value ? 1 : 0)
setQuantity(value ? 1 : 0)
}
checked={
selected != null ||
// order != null ||
(quantity != null && quantity > 0) ||
packageProductBaseQuantity > 0
}
disabled={packageProductBaseQuantity > 0}
Expand All @@ -126,13 +168,20 @@ export function ProductOrderingCard({
<Input
placeholder="Quantity"
className="w-40"
value={selected?.quantity.toString() ?? "0"}
// value={order?.quantity.toString() ?? "0"}
value={quantity?.toString() ?? ""}
min={0}
max={product.max_quantity}
type="number"
onChange={event =>
onChange(parseInt(event.target.value))
}
onChange={event => {
if (event.target.value === "") {
setQuantity(undefined)
} else {
setQuantity(
parseInt(event.target.value)
)
}
}}
/>
)}
</div>
Expand All @@ -149,7 +198,7 @@ export function ProductOrderingCard({
packageProductBaseQuantity > 0
? `Included In ${packageName}`
: packageProductBaseQuantity > 0 &&
(selected?.quantity ?? 0) <= 0
(order?.quantity ?? 0) <= 0
? "0 kr"
: `${formatCurrency(
productWithAdjustedPrice?.adjustedPrice ??
Expand All @@ -158,7 +207,7 @@ export function ProductOrderingCard({
</Badge>
</div>
<div className="flex gap-3">
{selected != null && selected.quantity > 1 && (
{order != null && order.quantity > 1 && (
<div>
<p className="pb-1 text-xs">Unit price</p>
<Badge
Expand Down
20 changes: 12 additions & 8 deletions apps/dashboard/src/forms/fr_accounting/useAccounting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useOrders } from "@/shared/hooks/api/useOrders"
import { HOST } from "@/shared/vars"
import { useMutation } from "@tanstack/react-query"
import { useParams } from "@tanstack/react-router"
import { useCallback } from "react"
import { toast } from "sonner"

export type OrderMutation = Pick<Order, "quantity"> & {
Expand Down Expand Up @@ -73,14 +74,17 @@ export function useAccountingMutation<
* @param productId The id of the product you're picking
* @param quantity How many instances of this product you want, 0 to remove it
*/
async function setProductOrder(productId: number, quantity: number) {
const newOrders = orders.filter(
order => order.product.id !== productId
) as OrderMutation[] as TVar
if (quantity > 0)
newOrders.push({ product: { id: productId }, quantity })
await mutation.mutateAsync(newOrders)
}
const setProductOrder = useCallback(
async (productId: number, quantity: number) => {
const newOrders = orders.filter(
order => order.product.id !== productId
) as OrderMutation[] as TVar
if (quantity > 0)
newOrders.push({ product: { id: productId }, quantity })
await mutation.mutateAsync(newOrders)
},
[mutation, orders]
)

return {
...mutation,
Expand Down
Loading

0 comments on commit 68bda6d

Please sign in to comment.