diff --git a/artifacts/ehsan-poc/src/components/OpportunityCard.tsx b/artifacts/ehsan-poc/src/components/OpportunityCard.tsx
index 4a9a0dd..1a944ab 100644
--- a/artifacts/ehsan-poc/src/components/OpportunityCard.tsx
+++ b/artifacts/ehsan-poc/src/components/OpportunityCard.tsx
@@ -4,7 +4,7 @@ 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, Check, Trash2 } from "lucide-react";
+import { Share2, ShoppingCart } from "lucide-react";
import { getNeedImage } from "../lib/needImages";
import { Riyal } from "@/components/Riyal";
@@ -110,20 +110,18 @@ export function OpportunityCard({ request }: OpportunityCardProps) {
{/* Donate / cart row */}
{inCart ? (
-
-
+
{t.cart.added}
diff --git a/artifacts/ehsan-poc/src/lib/i18n/translations.ts b/artifacts/ehsan-poc/src/lib/i18n/translations.ts
index ca69b4c..76f633a 100644
--- a/artifacts/ehsan-poc/src/lib/i18n/translations.ts
+++ b/artifacts/ehsan-poc/src/lib/i18n/translations.ts
@@ -344,6 +344,9 @@ export const en = {
empty: "Your donation cart is empty",
emptyHint: "Browse the available opportunities and add the causes you wish to support.",
browse: "Browse Opportunities",
+ amountRequired: "Please enter an amount for each item in the cart.",
+ itemsCount: "Items",
+ successMessage: "Thank you. Your donations have been completed successfully. May Allah reward you.",
},
admin: {
title: "Admin Dashboard",
@@ -742,6 +745,9 @@ export const ar = {
empty: "سلة تبرعاتك فارغة",
emptyHint: "تصفّح الفرص المتاحة وأضف القضايا التي ترغب بدعمها.",
browse: "تصفّح الفرص",
+ amountRequired: "الرجاء إدخال قيمة المبلغ لكل عنصر في السلة.",
+ itemsCount: "عدد العناصر",
+ successMessage: "شكراً لك. تمت تبرعاتك بنجاح. جزاك الله خيراً.",
},
admin: {
title: "لوحة الإدارة",
diff --git a/artifacts/ehsan-poc/src/pages/cart.tsx b/artifacts/ehsan-poc/src/pages/cart.tsx
index 7b36ba3..48371c8 100644
--- a/artifacts/ehsan-poc/src/pages/cart.tsx
+++ b/artifacts/ehsan-poc/src/pages/cart.tsx
@@ -1,28 +1,123 @@
+import { useState } from "react";
import { useLocation, Link } from "wouter";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
import { useLanguage } from "../contexts/LanguageContext";
import { useCart } from "../contexts/CartContext";
+import {
+ useDonateToRequest,
+ getListRequestsQueryKey,
+ getListPublishedRequestsQueryKey,
+} from "@workspace/api-client-react";
+import { useQueryClient } from "@tanstack/react-query";
+import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
-import { ShoppingCart, Trash2, ChevronLeft, ChevronRight } from "lucide-react";
+import { ShoppingCart, Trash2, ChevronLeft, ChevronRight, CheckCircle, Heart } 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";
+const schema = z.object({
+ donorName: z.string().min(2),
+ donorPhone: z.string().min(10),
+ donorEmail: z.string().email().optional().or(z.literal("")),
+});
+
+type FormData = z.infer;
+
export default function Cart() {
const { t, dir } = useLanguage();
const [, setLocation] = useLocation();
- const { items, removeItem, updateAmount, total } = useCart();
+ const queryClient = useQueryClient();
+ const { items, removeItem, updateAmount, total, clear } = useCart();
+ const donateMutation = useDonateToRequest();
+
+ const [step, setStep] = useState<"cart" | "payment">("cart");
+ const [amountError, setAmountError] = useState(false);
+ const [submitError, setSubmitError] = useState(false);
+ const [done, setDone] = useState(false);
+
+ const form = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: { donorName: "", donorPhone: "", donorEmail: "" },
+ });
const Chevron = dir === "rtl" ? ChevronLeft : ChevronRight;
- const proceed = () => {
+ const goToPayment = () => {
if (items.length === 0) return;
- const first = items[0];
- const suffix = first.amount > 0 ? `?amount=${first.amount}` : "";
- setLocation(`/donate/${first.id}${suffix}`);
+ if (!items.every((i) => i.amount > 0)) {
+ setAmountError(true);
+ return;
+ }
+ setAmountError(false);
+ setStep("payment");
};
+ const onSubmit = async (data: FormData) => {
+ // Re-validate amounts at submit time in case the user edited a value
+ // back to zero after entering the payment step.
+ if (!items.every((i) => i.amount > 0)) {
+ setSubmitError(false);
+ setStep("cart");
+ setAmountError(true);
+ return;
+ }
+ setSubmitError(false);
+ let failed = false;
+ try {
+ for (const item of items) {
+ await donateMutation.mutateAsync({
+ id: item.id,
+ data: {
+ donorName: data.donorName,
+ donorPhone: data.donorPhone,
+ donorEmail: data.donorEmail || null,
+ amount: item.amount,
+ },
+ });
+ removeItem(item.id);
+ }
+ } catch {
+ failed = true;
+ setSubmitError(true);
+ } finally {
+ // Always refresh request data: even on partial failure, the donations
+ // that did succeed have already changed server state.
+ queryClient.invalidateQueries({ queryKey: getListRequestsQueryKey() });
+ queryClient.invalidateQueries({ queryKey: getListPublishedRequestsQueryKey() });
+ }
+ if (!failed) {
+ clear();
+ setDone(true);
+ }
+ };
+
+ // Success screen after a completed multi-item donation
+ if (done) {
+ return (
+
+
+
+
+
{t.common.success}
+
{t.cart.successMessage}
+
+
+
+
+
+
+ );
+ }
+
return (
{/* Decorative leaf background */}
@@ -124,7 +219,10 @@ export default function Cart() {
type="number"
min={1}
value={item.amount > 0 ? item.amount : ""}
- onChange={(e) => updateAmount(item.id, Number(e.target.value))}
+ onChange={(e) => {
+ updateAmount(item.id, Number(e.target.value));
+ setAmountError(false);
+ }}
placeholder={t.cart.amountLabel}
className="ps-8"
data-testid={`input-amount-${item.id}`}
@@ -137,22 +235,110 @@ export default function Cart() {
})}
- {/* Summary panel */}
+ {/* Summary / checkout panel */}
+
+ {t.cart.itemsCount}
+
+ {items.length}
+
+
{t.cart.total}
-
+
{total.toLocaleString()}
-
+
+ {step === "cart" ? (
+ <>
+ {amountError && (
+
+ {t.cart.amountRequired}
+
+ )}
+
+ >
+ ) : (
+
+
+ )}
diff --git a/attached_assets/image_1780689567898.png b/attached_assets/image_1780689567898.png
new file mode 100644
index 0000000..7588557
Binary files /dev/null and b/attached_assets/image_1780689567898.png differ
diff --git a/attached_assets/image_1780690235820.png b/attached_assets/image_1780690235820.png
new file mode 100644
index 0000000..2fe26b4
Binary files /dev/null and b/attached_assets/image_1780690235820.png differ