Changes

gpt-engineer-app[bot] committed Nov 27, 2025 at 11:44 UTC d91f50314428d9745d5b3caf566b181c15389a75
3 files changed +360 -1
src/App.tsx
+2
@@ -7,6 +7,7 @@ import Home from "./pages/Home";
7 import Pay from "./pages/Pay";
8 import SendMoney from "./pages/SendMoney";
9 import Recipients from "./pages/Recipients";
10 +import TransactionDetails from "./pages/TransactionDetails";
11 import Profile from "./pages/Profile";
12 import NotFound from "./pages/NotFound";
13
@@ -23,6 +24,7 @@ const App = () => (
24 <Route path="/pay" element={<Pay />} />
25 <Route path="/send" element={<SendMoney />} />
26 <Route path="/recipients" element={<Recipients />} />
27 + <Route path="/transaction/:id" element={<TransactionDetails />} />
28 <Route path="/profile" element={<Profile />} />
29 {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
30 <Route path="*" element={<NotFound />} />
src/pages/Home.tsx
+5 -1
@@ -79,7 +79,11 @@ const Home = () => {
79 ) : (
80 <div className="space-y-3">
81 {transactions.map((transaction) => (
82 - <Card key={transaction.id} className="p-5 border-0 shadow-md hover:shadow-lg transition-all cursor-pointer">
82 + <Card
83 + key={transaction.id}
84 + className="p-5 border-0 shadow-md hover:shadow-lg transition-all cursor-pointer"
85 + onClick={() => navigate(`/transaction/${transaction.id}`)}
86 + >
87 <div className="flex items-center justify-between">
88 <div className="flex-1">
89 <div className="flex items-center gap-3 mb-1">
src/pages/TransactionDetails.tsx new
+353
@@ -0,0 +1,353 @@
1 +import { useEffect, useState } from "react";
2 +import { useParams, useNavigate } from "react-router-dom";
3 +import Layout from "@/components/Layout";
4 +import { Card } from "@/components/ui/card";
5 +import { Button } from "@/components/ui/button";
6 +import { Badge } from "@/components/ui/badge";
7 +import { Separator } from "@/components/ui/separator";
8 +import {
9 + ArrowLeft,
10 + Download,
11 + CheckCircle2,
12 + Clock,
13 + AlertCircle,
14 + Mail,
15 + MessageCircle,
16 + Share2,
17 +} from "lucide-react";
18 +import { transactionService } from "@/services/transactionService";
19 +import { Transaction } from "@/types/transaction";
20 +import { formatDistanceToNow, format } from "date-fns";
21 +import { toast } from "@/hooks/use-toast";
22 +
23 +const TransactionDetails = () => {
24 + const { id } = useParams();
25 + const navigate = useNavigate();
26 + const [transaction, setTransaction] = useState<Transaction | null>(null);
27 + const [loading, setLoading] = useState(true);
28 +
29 + useEffect(() => {
30 + const loadTransaction = async () => {
31 + if (!id) {
32 + navigate("/");
33 + return;
34 + }
35 +
36 + setLoading(true);
37 + const data = await transactionService.getTransaction(id);
38 +
39 + if (!data) {
40 + toast({
41 + title: "Transaction Not Found",
42 + description: "The transaction you're looking for doesn't exist",
43 + variant: "destructive",
44 + });
45 + navigate("/");
46 + return;
47 + }
48 +
49 + setTransaction(data);
50 + setLoading(false);
51 + };
52 +
53 + loadTransaction();
54 + }, [id, navigate]);
55 +
56 + const getStatusInfo = (status: string) => {
57 + switch (status) {
58 + case "completed":
59 + return {
60 + icon: CheckCircle2,
61 + color: "text-expense-positive",
62 + bgColor: "bg-expense-positive/10",
63 + label: "Completed",
64 + };
65 + case "processing":
66 + return {
67 + icon: Clock,
68 + color: "text-accent",
69 + bgColor: "bg-accent/10",
70 + label: "Processing",
71 + };
72 + case "pending":
73 + return {
74 + icon: Clock,
75 + color: "text-yellow-500",
76 + bgColor: "bg-yellow-500/10",
77 + label: "Pending",
78 + };
79 + case "failed":
80 + return {
81 + icon: AlertCircle,
82 + color: "text-destructive",
83 + bgColor: "bg-destructive/10",
84 + label: "Failed",
85 + };
86 + default:
87 + return {
88 + icon: Clock,
89 + color: "text-muted-foreground",
90 + bgColor: "bg-muted",
91 + label: "Unknown",
92 + };
93 + }
94 + };
95 +
96 + const handleDownloadReceipt = () => {
97 + toast({
98 + title: "Receipt Downloaded",
99 + description: "Your receipt has been saved to your device",
100 + });
101 + };
102 +
103 + const handleShare = () => {
104 + toast({
105 + title: "Share Transaction",
106 + description: "Transaction details copied to clipboard",
107 + });
108 + };
109 +
110 + const handleContactSupport = () => {
111 + toast({
112 + title: "Contact Support",
113 + description: "Opening support chat...",
114 + });
115 + };
116 +
117 + if (loading) {
118 + return (
119 + <Layout title="Transaction">
120 + <div className="p-4">
121 + <Card className="p-8 border-0 shadow-md text-center">
122 + <p className="text-muted-foreground">Loading transaction...</p>
123 + </Card>
124 + </div>
125 + </Layout>
126 + );
127 + }
128 +
129 + if (!transaction) {
130 + return null;
131 + }
132 +
133 + const statusInfo = getStatusInfo(transaction.status);
134 + const StatusIcon = statusInfo.icon;
135 +
136 + return (
137 + <Layout title="Transaction Details">
138 + <div className="p-4 space-y-6">
139 + {/* Header */}
140 + <div className="flex items-center gap-3">
141 + <Button
142 + variant="ghost"
143 + size="icon"
144 + onClick={() => navigate("/")}
145 + className="h-10 w-10"
146 + >
147 + <ArrowLeft className="h-5 w-5" />
148 + </Button>
149 + <div className="flex-1">
150 + <h2 className="text-xl font-bold">Transaction Receipt</h2>
151 + <p className="text-sm text-muted-foreground font-medium">
152 + {transaction.id}
153 + </p>
154 + </div>
155 + </div>
156 +
157 + {/* Status Card */}
158 + <Card className="p-8 border-0 shadow-lg text-center">
159 + <div
160 + className={`h-20 w-20 rounded-full ${statusInfo.bgColor} mx-auto flex items-center justify-center mb-4`}
161 + >
162 + <StatusIcon className={`h-10 w-10 ${statusInfo.color}`} />
163 + </div>
164 + <Badge
165 + variant="outline"
166 + className={`mb-3 text-base px-4 py-1 ${statusInfo.color} border-2`}
167 + >
168 + {statusInfo.label}
169 + </Badge>
170 + <h3 className="text-4xl font-bold mb-2">
171 + {transaction.currency} {transaction.amount.toFixed(2)}
172 + </h3>
173 + <p className="text-muted-foreground font-medium">
174 + {formatDistanceToNow(transaction.timestamp, { addSuffix: true })}
175 + </p>
176 + </Card>
177 +
178 + {/* Timeline */}
179 + <Card className="p-6 border-0 shadow-md">
180 + <h3 className="text-lg font-bold mb-6">Transaction Timeline</h3>
181 + <div className="space-y-6">
182 + {/* Completed/Failed */}
183 + {(transaction.status === "completed" || transaction.status === "failed") && (
184 + <div className="flex gap-4">
185 + <div className="flex flex-col items-center">
186 + <div
187 + className={`h-10 w-10 rounded-full ${
188 + transaction.status === "completed"
189 + ? "bg-expense-positive"
190 + : "bg-destructive"
191 + } flex items-center justify-center`}
192 + >
193 + {transaction.status === "completed" ? (
194 + <CheckCircle2 className="h-6 w-6 text-white" />
195 + ) : (
196 + <AlertCircle className="h-6 w-6 text-white" />
197 + )}
198 + </div>
199 + {transaction.status === "completed" && (
200 + <div className="w-0.5 h-8 bg-border" />
201 + )}
202 + </div>
203 + <div className="flex-1 pb-2">
204 + <p className="font-semibold">
205 + {transaction.status === "completed"
206 + ? "Payment Completed"
207 + : "Payment Failed"}
208 + </p>
209 + <p className="text-sm text-muted-foreground font-medium">
210 + {format(transaction.timestamp, "MMM dd, yyyy 'at' hh:mm a")}
211 + </p>
212 + </div>
213 + </div>
214 + )}
215 +
216 + {/* Processing */}
217 + {transaction.status === "completed" && (
218 + <div className="flex gap-4">
219 + <div className="flex flex-col items-center">
220 + <div className="h-10 w-10 rounded-full bg-accent flex items-center justify-center">
221 + <Clock className="h-6 w-6 text-white" />
222 + </div>
223 + <div className="w-0.5 h-8 bg-border" />
224 + </div>
225 + <div className="flex-1 pb-2">
226 + <p className="font-semibold">Processing Payment</p>
227 + <p className="text-sm text-muted-foreground font-medium">
228 + Verified and processed
229 + </p>
230 + </div>
231 + </div>
232 + )}
233 +
234 + {/* Initiated */}
235 + <div className="flex gap-4">
236 + <div className="flex flex-col items-center">
237 + <div className="h-10 w-10 rounded-full bg-primary flex items-center justify-center">
238 + <CheckCircle2 className="h-6 w-6 text-primary-foreground" />
239 + </div>
240 + </div>
241 + <div className="flex-1">
242 + <p className="font-semibold">Payment Initiated</p>
243 + <p className="text-sm text-muted-foreground font-medium">
244 + Transaction created
245 + </p>
246 + </div>
247 + </div>
248 + </div>
249 + </Card>
250 +
251 + {/* Transaction Details */}
252 + <Card className="p-6 border-0 shadow-md">
253 + <h3 className="text-lg font-bold mb-4">Payment Details</h3>
254 + <div className="space-y-4">
255 + <div className="flex justify-between">
256 + <span className="text-muted-foreground font-medium">Recipient</span>
257 + <span className="font-semibold text-right">
258 + {transaction.recipientName}
259 + </span>
260 + </div>
261 + <Separator />
262 + <div className="flex justify-between">
263 + <span className="text-muted-foreground font-medium">Email</span>
264 + <span className="font-semibold text-right break-all">
265 + {transaction.recipient}
266 + </span>
267 + </div>
268 + <Separator />
269 + {transaction.description && (
270 + <>
271 + <div className="flex justify-between">
272 + <span className="text-muted-foreground font-medium">
273 + Description
274 + </span>
275 + <span className="font-semibold text-right">
276 + {transaction.description}
277 + </span>
278 + </div>
279 + <Separator />
280 + </>
281 + )}
282 + <div className="flex justify-between">
283 + <span className="text-muted-foreground font-medium">Amount</span>
284 + <span className="font-semibold">
285 + {transaction.currency} {transaction.amount.toFixed(2)}
286 + </span>
287 + </div>
288 + <Separator />
289 + <div className="flex justify-between">
290 + <span className="text-muted-foreground font-medium">Fee</span>
291 + <span className="font-semibold">
292 + {transaction.currency} {transaction.fee.toFixed(2)}
293 + </span>
294 + </div>
295 + <Separator />
296 + <div className="flex justify-between text-lg">
297 + <span className="font-bold">Total</span>
298 + <span className="font-bold">
299 + {transaction.currency}{" "}
300 + {(transaction.amount + transaction.fee).toFixed(2)}
301 + </span>
302 + </div>
303 + </div>
304 + </Card>
305 +
306 + {/* Actions */}
307 + <div className="grid grid-cols-2 gap-3">
308 + <Button variant="outline" onClick={handleDownloadReceipt}>
309 + <Download className="h-5 w-5 mr-2" />
310 + Receipt
311 + </Button>
312 + <Button variant="outline" onClick={handleShare}>
313 + <Share2 className="h-5 w-5 mr-2" />
314 + Share
315 + </Button>
316 + </div>
317 +
318 + {/* Support */}
319 + <Card className="p-6 border-0 shadow-md">
320 + <h3 className="text-lg font-bold mb-4">Need Help?</h3>
321 + <div className="space-y-3">
322 + <Button
323 + variant="outline"
324 + className="w-full justify-start"
325 + onClick={handleContactSupport}
326 + >
327 + <MessageCircle className="h-5 w-5 mr-3" />
328 + Chat with Support
329 + </Button>
330 + <Button
331 + variant="outline"
332 + className="w-full justify-start"
333 + onClick={() => {
334 + toast({
335 + title: "Email Support",
336 + description: "Opening email client...",
337 + });
338 + }}
339 + >
340 + <Mail className="h-5 w-5 mr-3" />
341 + Email Support
342 + </Button>
343 + </div>
344 + <p className="text-xs text-muted-foreground mt-4 text-center font-medium">
345 + Our support team typically responds within 24 hours
346 + </p>
347 + </Card>
348 + </div>
349 + </Layout>
350 + );
351 +};
352 +
353 +export default TransactionDetails;