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;