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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user