Files
Ehsan/artifacts/ehsan-poc/src/pages/thank-you.tsx
T
Replit Agent 1dcfa0bfa5 Complete EHSAN POC: fix all code review findings
- API routes: explicit return types on all Express handlers (fixes TS7030),
  `beneficiaryName` now accepted and stored in /thank-you route per OpenAPI spec.

- Home page: added search bar (filters by case ID, description, name) +
  Featured Opportunities section with live cards, progress bars, and Donate buttons.

- Opportunities page: added need-type filter pill bar (all 8 types + "All Types")
  with active state highlighting; empty state respects selected filter.

- i18n: expanded translations with all previously hardcoded strings
  (trackCase, notFound, noData, currentStep, search, searchPlaceholder,
  featuredTitle, noResults, donate.caseSummary, donate.caseNotFound,
  admin.noRequests, admin.needType, admin.amount, admin.track, admin.whatsapp,
  track.caseInfo, track.rejected, track.currentStepLabel, track.submitThankYou,
  thankYou.successNote, thankYou.beneficiaryMessageLabel,
  whatsapp.donorPhone, whatsapp.beneficiaryMessage, whatsapp.noEntries,
  opportunities.noOpportunities, opportunities.verified).
  All pages now use t.* — zero hardcoded English UI strings.

- TypeScript: both frontend (tsc --noEmit) and API server build are clean.
2026-06-05 17:12:44 +00:00

183 lines
6.0 KiB
TypeScript

import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useParams, useLocation } from "wouter";
import { useLanguage } from "../contexts/LanguageContext";
import {
useGetRequest, useSubmitThankYou,
getListRequestsQueryKey, getGetRequestQueryKey,
} from "@workspace/api-client-react";
import { useQueryClient } from "@tanstack/react-query";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { CheckCircle, Heart } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import { Link } from "wouter";
const schema = z.object({
beneficiaryName: z.string().min(2),
message: z.string().min(10),
});
type FormData = z.infer<typeof schema>;
export default function ThankYou() {
const { t } = useLanguage();
const params = useParams<{ id: string }>();
const [, setLocation] = useLocation();
const queryClient = useQueryClient();
const [submitted, setSubmitted] = useState(false);
const { data: request, isLoading } = useGetRequest(params.id || "", {
query: { enabled: !!params.id, queryKey: getGetRequestQueryKey(params.id || "") },
});
const submitThankYou = useSubmitThankYou();
const form = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
beneficiaryName: "",
message: "",
},
});
// Pre-fill name once loaded
const beneficiaryName = request?.beneficiaryName || "";
const onSubmit = (data: FormData) => {
submitThankYou.mutate(
{
id: params.id || "",
data: { beneficiaryName: data.beneficiaryName, message: data.message },
},
{
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: getListRequestsQueryKey() });
queryClient.invalidateQueries({ queryKey: getGetRequestQueryKey(params.id || "") });
setSubmitted(true);
},
}
);
};
if (isLoading) {
return (
<div className="container mx-auto px-4 py-12 max-w-xl space-y-4">
<Skeleton className="h-10 w-48" />
<Skeleton className="h-64 w-full" />
</div>
);
}
if (!request) {
return (
<div className="container mx-auto px-4 py-12 text-center text-muted-foreground">
{t.common.notFound}
</div>
);
}
if (submitted) {
return (
<div className="container mx-auto px-4 py-12 max-w-xl">
<Card className="border-2 border-green-200">
<CardContent className="pt-10 pb-10 text-center">
<div className="flex justify-center gap-2 mb-4">
<Heart className="w-12 h-12 text-green-600 fill-green-100" />
<CheckCircle className="w-12 h-12 text-green-500" />
</div>
<h2 className="text-2xl font-bold text-green-700 mb-2">{t.common.success}</h2>
<p className="text-muted-foreground">{t.thankYou.title}</p>
<p className="mt-4 text-sm text-muted-foreground">{t.thankYou.successNote}</p>
<div className="mt-8 flex gap-3 justify-center">
<Button onClick={() => setLocation(`/track/${params.id}`)}>
{t.common.trackCase}
</Button>
<Button variant="outline" onClick={() => setLocation("/")}>
{t.common.home}
</Button>
</div>
</CardContent>
</Card>
</div>
);
}
return (
<div className="container mx-auto px-4 py-12 max-w-xl">
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground">{t.thankYou.title}</h1>
<p className="text-muted-foreground mt-1">
{request.caseId} {request.beneficiaryName}
</p>
</div>
<Card>
<CardHeader>
<CardTitle className="text-base text-primary">{t.thankYou.title}</CardTitle>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-5">
<FormField
control={form.control}
name="beneficiaryName"
render={({ field }) => (
<FormItem>
<FormLabel>{t.request.beneficiaryName}</FormLabel>
<FormControl>
<Input
data-testid="input-beneficiaryName"
placeholder={beneficiaryName}
{...field}
defaultValue={beneficiaryName}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="message"
render={({ field }) => (
<FormItem>
<FormLabel>{t.thankYou.message}</FormLabel>
<FormControl>
<Textarea
data-testid="input-thankYouMessage"
rows={5}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
className="w-full"
disabled={submitThankYou.isPending}
data-testid="button-submitThankYou"
>
{submitThankYou.isPending ? t.common.loading : t.thankYou.submitLabel}
</Button>
</form>
</Form>
</CardContent>
</Card>
<div className="mt-4 text-center">
<Link href={`/track/${params.id}`}>
<Button variant="ghost" size="sm">{t.common.back}</Button>
</Link>
</div>
</div>
);
}