Task #19: Donation cart (سلة تبرعاتك) for EHSAN POC
Built a real donation cart matching ehsan.sa: - CartContext provider (localStorage-persisted, defensive parsing) wired into App.tsx - OpportunityCard: cart icon now adds the case with its typed amount and swaps the action row to an added state («مضاف لسلة تبرعاتك» + «إزالة») - Header cart button: live count badge + navigates to /cart - New /cart page + route: breadcrumb, item list (delete icon, category, name, editable «قيمة المبلغ» amount, image+progress%), summary panel «الإجمالي» + green «للمتابعة للدفع», decorative leaf SVG background, empty state - translations.ts: parallel AR+EN `cart` section - donate.tsx: removes the donated case from the cart on successful donation (cart reconciliation), preventing stale added-state/badge Notes/deviations: - Checkout handoff routes the first cart item into the existing single-case donate flow (the POC backend has no multi-item payment). Reconciliation keeps remaining items coherent. A true multi-item checkout backend was out of scope. - Verified with passing e2e test (add → badge → cart page → remove → empty) and clean tsc; architect review addressed (reconciliation + defensive parsing).
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 60 KiB |
@@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { LanguageProvider } from "./contexts/LanguageContext";
|
||||
import { CartProvider } from "./contexts/CartContext";
|
||||
import { AuthProvider, useAuth } from "./contexts/AuthContext";
|
||||
import { AppLayout } from "./components/layout/AppLayout";
|
||||
import NotFound from "@/pages/not-found";
|
||||
@@ -19,6 +20,7 @@ import Track from "./pages/track";
|
||||
import ThankYou from "./pages/thank-you";
|
||||
import WhatsappLog from "./pages/whatsapp-log";
|
||||
import Login from "./pages/login";
|
||||
import Cart from "./pages/cart";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@@ -39,6 +41,7 @@ function Router() {
|
||||
<Route path="/request" component={RequestSupport} />
|
||||
<Route path="/opportunities" component={Opportunities} />
|
||||
<Route path="/donate/:id" component={Donate} />
|
||||
<Route path="/cart" component={Cart} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/admin">
|
||||
<Protected component={Admin} />
|
||||
@@ -59,12 +62,14 @@ function App() {
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<LanguageProvider>
|
||||
<AuthProvider>
|
||||
<TooltipProvider>
|
||||
<WouterRouter base={import.meta.env.BASE_URL.replace(/\/$/, "")}>
|
||||
<Router />
|
||||
</WouterRouter>
|
||||
<Toaster />
|
||||
</TooltipProvider>
|
||||
<CartProvider>
|
||||
<TooltipProvider>
|
||||
<WouterRouter base={import.meta.env.BASE_URL.replace(/\/$/, "")}>
|
||||
<Router />
|
||||
</WouterRouter>
|
||||
<Toaster />
|
||||
</TooltipProvider>
|
||||
</CartProvider>
|
||||
</AuthProvider>
|
||||
</LanguageProvider>
|
||||
</QueryClientProvider>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useState } from "react";
|
||||
import { useLocation } from "wouter";
|
||||
import { useLanguage } from "../contexts/LanguageContext";
|
||||
import { useCart } from "../contexts/CartContext";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Share2, ShoppingCart } from "lucide-react";
|
||||
import { Share2, ShoppingCart, Check, Trash2 } from "lucide-react";
|
||||
import { getNeedImage } from "../lib/needImages";
|
||||
import { Riyal } from "@/components/Riyal";
|
||||
|
||||
@@ -21,8 +22,11 @@ interface OpportunityCardProps {
|
||||
export function OpportunityCard({ request }: OpportunityCardProps) {
|
||||
const { t } = useLanguage();
|
||||
const [, setLocation] = useLocation();
|
||||
const { addItem, removeItem, isInCart } = useCart();
|
||||
const [amount, setAmount] = useState("");
|
||||
|
||||
const inCart = isInCart(request.id);
|
||||
|
||||
const progress = Math.min(
|
||||
100,
|
||||
request.requestedAmount > 0
|
||||
@@ -38,6 +42,20 @@ export function OpportunityCard({ request }: OpportunityCardProps) {
|
||||
setLocation(`/donate/${request.id}${suffix}`);
|
||||
};
|
||||
|
||||
const handleAddToCart = () => {
|
||||
addItem(
|
||||
{
|
||||
id: request.id,
|
||||
caseId: request.caseId,
|
||||
description: request.description,
|
||||
needType: request.needType,
|
||||
requestedAmount: request.requestedAmount,
|
||||
collectedAmount: request.collectedAmount,
|
||||
},
|
||||
Number(amount)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-card rounded-2xl border border-card-border shadow-sm overflow-hidden flex flex-col h-full hover-elevate">
|
||||
{/* Photo + progress bar */}
|
||||
@@ -89,43 +107,64 @@ export function OpportunityCard({ request }: OpportunityCardProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Donate row */}
|
||||
<div className="flex items-stretch gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="shrink-0"
|
||||
onClick={goDonate}
|
||||
disabled={isComplete}
|
||||
aria-label={t.common.cart}
|
||||
data-testid={`button-cart-${request.id}`}
|
||||
{/* Donate / cart row */}
|
||||
{inCart ? (
|
||||
<div
|
||||
className="flex items-center justify-between gap-2 rounded-lg border border-primary/30 bg-primary/5 px-4 py-3"
|
||||
data-testid={`added-state-${request.id}`}
|
||||
>
|
||||
<ShoppingCart className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
className="shrink-0 px-5"
|
||||
onClick={goDonate}
|
||||
disabled={isComplete}
|
||||
data-testid={`button-donate-${request.id}`}
|
||||
>
|
||||
{t.opportunities.donate}
|
||||
</Button>
|
||||
<div className="relative flex-1">
|
||||
<span className="absolute start-3 top-1/2 -translate-y-1/2 text-muted-foreground text-sm pointer-events-none">
|
||||
<Riyal />
|
||||
<span className="inline-flex items-center gap-2 text-sm font-medium text-primary">
|
||||
<Check className="w-4 h-4 shrink-0" />
|
||||
{t.cart.added}
|
||||
</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
placeholder={t.opportunities.amountPlaceholder}
|
||||
className="ps-8 h-full"
|
||||
disabled={isComplete}
|
||||
data-testid={`input-cardAmount-${request.id}`}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeItem(request.id)}
|
||||
className="inline-flex items-center gap-1 text-sm font-medium text-destructive hover:underline"
|
||||
data-testid={`button-removeFromCart-${request.id}`}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
{t.cart.remove}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-stretch gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="shrink-0"
|
||||
onClick={handleAddToCart}
|
||||
disabled={isComplete}
|
||||
aria-label={t.cart.addToCart}
|
||||
data-testid={`button-cart-${request.id}`}
|
||||
>
|
||||
<ShoppingCart className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
className="shrink-0 px-5"
|
||||
onClick={goDonate}
|
||||
disabled={isComplete}
|
||||
data-testid={`button-donate-${request.id}`}
|
||||
>
|
||||
{t.opportunities.donate}
|
||||
</Button>
|
||||
<div className="relative flex-1">
|
||||
<span className="absolute start-3 top-1/2 -translate-y-1/2 text-muted-foreground text-sm pointer-events-none">
|
||||
<Riyal />
|
||||
</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
placeholder={t.opportunities.amountPlaceholder}
|
||||
className="ps-8 h-full"
|
||||
disabled={isComplete}
|
||||
data-testid={`input-cardAmount-${request.id}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useLanguage } from "../../contexts/LanguageContext";
|
||||
import { useAuth } from "../../contexts/AuthContext";
|
||||
import { useCart } from "../../contexts/CartContext";
|
||||
import { Button } from "../ui/button";
|
||||
import {
|
||||
Search,
|
||||
@@ -29,6 +30,7 @@ import ehsanLogo from "../../assets/ehsan-logo.png";
|
||||
export function Header() {
|
||||
const { language, setLanguage, t } = useLanguage();
|
||||
const { isAuthenticated, logout } = useAuth();
|
||||
const { count: cartCount } = useCart();
|
||||
const [location, setLocation] = useLocation();
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const [servicesOpen, setServicesOpen] = useState(false);
|
||||
@@ -218,11 +220,20 @@ export function Header() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-foreground"
|
||||
className="text-foreground relative"
|
||||
aria-label={t.common.cart}
|
||||
onClick={() => setLocation("/cart")}
|
||||
data-testid="button-cart"
|
||||
>
|
||||
<ShoppingCart className="w-4 h-4" />
|
||||
{cartCount > 0 && (
|
||||
<span
|
||||
className="absolute -top-1 -end-1 min-w-[1.1rem] h-[1.1rem] px-1 rounded-full bg-primary text-primary-foreground text-[0.65rem] font-bold flex items-center justify-center leading-none"
|
||||
data-testid="badge-cart-count"
|
||||
>
|
||||
{cartCount}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { createContext, useContext, useEffect, useState, ReactNode } from "react";
|
||||
|
||||
export interface CartItem {
|
||||
id: string;
|
||||
caseId: string;
|
||||
description: string;
|
||||
needType: string;
|
||||
requestedAmount: number;
|
||||
collectedAmount: number;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
interface CartContextType {
|
||||
items: CartItem[];
|
||||
addItem: (item: Omit<CartItem, "amount">, amount: number) => void;
|
||||
removeItem: (id: string) => void;
|
||||
updateAmount: (id: string, amount: number) => void;
|
||||
isInCart: (id: string) => boolean;
|
||||
clear: () => void;
|
||||
count: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
const CartContext = createContext<CartContextType | undefined>(undefined);
|
||||
|
||||
const STORAGE_KEY = "ehsan-cart";
|
||||
|
||||
export function CartProvider({ children }: { children: ReactNode }) {
|
||||
const [items, setItems] = useState<CartItem[]>(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY);
|
||||
if (!saved) return [];
|
||||
const parsed = JSON.parse(saved);
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed
|
||||
.filter((i) => i && typeof i.id === "string")
|
||||
.map((i) => ({
|
||||
id: String(i.id),
|
||||
caseId: String(i.caseId ?? ""),
|
||||
description: String(i.description ?? ""),
|
||||
needType: String(i.needType ?? ""),
|
||||
requestedAmount: Number(i.requestedAmount) || 0,
|
||||
collectedAmount: Number(i.collectedAmount) || 0,
|
||||
amount: Number(i.amount) || 0,
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
|
||||
}, [items]);
|
||||
|
||||
const addItem = (item: Omit<CartItem, "amount">, amount: number) => {
|
||||
setItems((prev) => {
|
||||
if (prev.some((i) => i.id === item.id)) return prev;
|
||||
return [...prev, { ...item, amount: amount > 0 ? amount : 0 }];
|
||||
});
|
||||
};
|
||||
|
||||
const removeItem = (id: string) => {
|
||||
setItems((prev) => prev.filter((i) => i.id !== id));
|
||||
};
|
||||
|
||||
const updateAmount = (id: string, amount: number) => {
|
||||
setItems((prev) =>
|
||||
prev.map((i) => (i.id === id ? { ...i, amount: amount > 0 ? amount : 0 } : i))
|
||||
);
|
||||
};
|
||||
|
||||
const isInCart = (id: string) => items.some((i) => i.id === id);
|
||||
|
||||
const clear = () => setItems([]);
|
||||
|
||||
const count = items.length;
|
||||
const total = items.reduce((sum, i) => sum + (i.amount || 0), 0);
|
||||
|
||||
const value: CartContextType = {
|
||||
items,
|
||||
addItem,
|
||||
removeItem,
|
||||
updateAmount,
|
||||
isInCart,
|
||||
clear,
|
||||
count,
|
||||
total,
|
||||
};
|
||||
|
||||
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
|
||||
}
|
||||
|
||||
export function useCart() {
|
||||
const context = useContext(CartContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useCart must be used within a CartProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -236,6 +236,19 @@ export const en = {
|
||||
paymentTitle: "Payment Details",
|
||||
selectAmountError: "Please select or enter a valid amount.",
|
||||
},
|
||||
cart: {
|
||||
title: "Your Donation Cart",
|
||||
breadcrumbHome: "Home",
|
||||
amountLabel: "Amount value",
|
||||
remove: "Remove",
|
||||
added: "Added to your donation cart",
|
||||
addToCart: "Add to cart",
|
||||
total: "Total",
|
||||
proceedToPayment: "Proceed to Payment",
|
||||
empty: "Your donation cart is empty",
|
||||
emptyHint: "Browse the available opportunities and add the causes you wish to support.",
|
||||
browse: "Browse Opportunities",
|
||||
},
|
||||
admin: {
|
||||
title: "Admin Dashboard",
|
||||
caseId: "Case ID",
|
||||
@@ -525,6 +538,19 @@ export const ar = {
|
||||
paymentTitle: "بيانات الدفع",
|
||||
selectAmountError: "الرجاء اختيار أو إدخال مبلغ صحيح.",
|
||||
},
|
||||
cart: {
|
||||
title: "سلة تبرعاتك",
|
||||
breadcrumbHome: "الرئيسية",
|
||||
amountLabel: "قيمة المبلغ",
|
||||
remove: "إزالة",
|
||||
added: "مضاف لسلة تبرعاتك",
|
||||
addToCart: "أضف للسلة",
|
||||
total: "الإجمالي",
|
||||
proceedToPayment: "للمتابعة للدفع",
|
||||
empty: "سلة تبرعاتك فارغة",
|
||||
emptyHint: "تصفّح الفرص المتاحة وأضف القضايا التي ترغب بدعمها.",
|
||||
browse: "تصفّح الفرص",
|
||||
},
|
||||
admin: {
|
||||
title: "لوحة الإدارة",
|
||||
caseId: "رقم الحالة",
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import { useLocation, Link } from "wouter";
|
||||
import { useLanguage } from "../contexts/LanguageContext";
|
||||
import { useCart } from "../contexts/CartContext";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ShoppingCart, Trash2, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { getNeedImage } from "../lib/needImages";
|
||||
import { Riyal } from "@/components/Riyal";
|
||||
import { Reveal } from "../components/Reveal";
|
||||
import leafPattern from "@assets/right-snapel-2_1780688632733.svg";
|
||||
|
||||
export default function Cart() {
|
||||
const { t, dir } = useLanguage();
|
||||
const [, setLocation] = useLocation();
|
||||
const { items, removeItem, updateAmount, total } = useCart();
|
||||
|
||||
const Chevron = dir === "rtl" ? ChevronLeft : ChevronRight;
|
||||
|
||||
const proceed = () => {
|
||||
if (items.length === 0) return;
|
||||
const first = items[0];
|
||||
const suffix = first.amount > 0 ? `?amount=${first.amount}` : "";
|
||||
setLocation(`/donate/${first.id}${suffix}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden">
|
||||
{/* Decorative leaf background */}
|
||||
<img
|
||||
src={leafPattern}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute top-8 start-0 w-56 opacity-60 select-none"
|
||||
/>
|
||||
|
||||
<div className="container relative mx-auto px-4 py-10 max-w-6xl">
|
||||
{/* Breadcrumb */}
|
||||
<nav className="flex items-center gap-2 text-sm text-muted-foreground mb-6" aria-label="breadcrumb">
|
||||
<Link href="/" className="hover:text-primary transition-colors" data-testid="link-breadcrumb-home">
|
||||
{t.cart.breadcrumbHome}
|
||||
</Link>
|
||||
<Chevron className="w-4 h-4" />
|
||||
<span className="text-foreground font-medium">{t.cart.title}</span>
|
||||
</nav>
|
||||
|
||||
<h1 className="text-2xl md:text-3xl font-bold text-primary mb-8" data-testid="text-cart-title">
|
||||
{t.cart.title}
|
||||
</h1>
|
||||
|
||||
{items.length === 0 ? (
|
||||
<Reveal>
|
||||
<div className="flex flex-col items-center justify-center text-center py-20 bg-card rounded-2xl border border-card-border">
|
||||
<ShoppingCart className="w-14 h-14 text-muted-foreground/40 mb-4" />
|
||||
<p className="text-lg font-bold text-foreground mb-2" data-testid="text-cart-empty">
|
||||
{t.cart.empty}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground max-w-sm mb-6">{t.cart.emptyHint}</p>
|
||||
<Button onClick={() => setLocation("/opportunities")} data-testid="button-browse-opportunities">
|
||||
{t.cart.browse}
|
||||
</Button>
|
||||
</div>
|
||||
</Reveal>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 items-start">
|
||||
{/* Items list */}
|
||||
<div className="lg:col-span-2 space-y-4">
|
||||
{items.map((item) => {
|
||||
const progress = Math.min(
|
||||
100,
|
||||
item.requestedAmount > 0
|
||||
? Math.round((item.collectedAmount / item.requestedAmount) * 100)
|
||||
: 0
|
||||
);
|
||||
return (
|
||||
<Reveal key={item.id}>
|
||||
<div
|
||||
className="bg-card rounded-2xl border border-card-border shadow-sm p-4 flex gap-4"
|
||||
data-testid={`cart-item-${item.id}`}
|
||||
>
|
||||
{/* Image + progress */}
|
||||
<div className="relative w-28 sm:w-36 shrink-0 rounded-xl overflow-hidden">
|
||||
<img
|
||||
src={getNeedImage(item.needType)}
|
||||
alt={item.description}
|
||||
className="w-full h-full object-cover min-h-[7rem]"
|
||||
/>
|
||||
<div className="absolute bottom-0 inset-x-0 h-5 bg-gray-200 flex">
|
||||
<div
|
||||
className="h-full bg-primary flex items-center justify-end px-2 text-primary-foreground text-[0.65rem] font-bold min-w-[2.5rem]"
|
||||
style={{ width: `${progress}%` }}
|
||||
>
|
||||
{progress}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0 flex flex-col">
|
||||
<div className="flex items-start justify-between gap-2 mb-2">
|
||||
<span className="inline-block text-xs font-medium text-muted-foreground border border-border rounded-md px-2.5 py-1">
|
||||
{t.needTypes[item.needType as keyof typeof t.needTypes] ||
|
||||
t.opportunities.generalProjects}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeItem(item.id)}
|
||||
className="text-muted-foreground hover:text-destructive transition-colors shrink-0"
|
||||
aria-label={t.cart.remove}
|
||||
data-testid={`button-remove-${item.id}`}
|
||||
>
|
||||
<Trash2 className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h3 className="text-sm sm:text-base font-bold text-primary leading-snug line-clamp-2 mb-3">
|
||||
{item.description}
|
||||
</h3>
|
||||
|
||||
<div className="mt-auto relative max-w-xs">
|
||||
<span className="absolute start-3 top-1/2 -translate-y-1/2 text-muted-foreground text-sm pointer-events-none">
|
||||
<Riyal />
|
||||
</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={item.amount > 0 ? item.amount : ""}
|
||||
onChange={(e) => updateAmount(item.id, Number(e.target.value))}
|
||||
placeholder={t.cart.amountLabel}
|
||||
className="ps-8"
|
||||
data-testid={`input-amount-${item.id}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Summary panel */}
|
||||
<div className="lg:sticky lg:top-24">
|
||||
<div className="bg-card rounded-2xl border border-card-border shadow-sm p-6">
|
||||
<div className="flex items-center justify-between pb-4 border-b border-border">
|
||||
<span className="text-base font-bold text-foreground">{t.cart.total}</span>
|
||||
<span className="text-xl font-bold text-primary inline-flex items-center gap-1" data-testid="text-cart-total">
|
||||
{total.toLocaleString()} <Riyal />
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full mt-5 h-12 text-base"
|
||||
onClick={proceed}
|
||||
data-testid="button-proceed-to-payment"
|
||||
>
|
||||
{t.cart.proceedToPayment}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { useParams, useLocation, useSearch, Link } from "wouter";
|
||||
import { useLanguage } from "../contexts/LanguageContext";
|
||||
import { useCart } from "../contexts/CartContext";
|
||||
import {
|
||||
useGetRequest, useDonateToRequest,
|
||||
getListRequestsQueryKey, getListPublishedRequestsQueryKey, getGetRequestQueryKey,
|
||||
@@ -35,6 +36,7 @@ export default function Donate() {
|
||||
const search = useSearch();
|
||||
const [, setLocation] = useLocation();
|
||||
const queryClient = useQueryClient();
|
||||
const { removeItem: removeFromCart } = useCart();
|
||||
|
||||
const initialAmount = (() => {
|
||||
const a = Number(new URLSearchParams(search).get("amount"));
|
||||
@@ -140,6 +142,7 @@ export default function Donate() {
|
||||
queryClient.invalidateQueries({ queryKey: getListRequestsQueryKey() });
|
||||
queryClient.invalidateQueries({ queryKey: getListPublishedRequestsQueryKey() });
|
||||
queryClient.invalidateQueries({ queryKey: getGetRequestQueryKey(params.id || "") });
|
||||
removeFromCart(params.id || "");
|
||||
setDonated(true);
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user