LatestUsedAppsWidget.tsx raw
1 import dayjs from "dayjs";
2 import { ChevronRightIcon } from "lucide-react";
3 import { Link } from "react-router";
4 import AppAvatar from "src/components/AppAvatar";
5 import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount";
6 import {
7 Card,
8 CardContent,
9 CardHeader,
10 CardTitle,
11 } from "src/components/ui/card";
12 import { LinkButton } from "src/components/ui/custom/link-button";
13 import { useApps } from "src/hooks/useApps";
14 import { useTransactions } from "src/hooks/useTransactions";
15 import { cn, getAppDisplayName } from "src/lib/utils";
16 import { App } from "src/types";
17
18 export function LatestUsedAppsWidget() {
19 const { data: appsData } = useApps(
20 3,
21 undefined,
22 undefined,
23 "last_settled_transaction"
24 );
25 const apps = appsData?.apps;
26 const usedApps = apps?.filter((x) => x.lastSettledTransactionAt);
27
28 if (!usedApps?.length) {
29 return null;
30 }
31
32 return (
33 <Card>
34 <CardHeader>
35 <CardTitle className="flex items-center justify-between">
36 <div>Recently Used Apps</div>
37 <LinkButton to="/apps?tab=connected-apps" variant="ghost" size="sm">
38 See All
39 </LinkButton>
40 </CardTitle>
41 </CardHeader>
42 <CardContent className="grid grid-cols-1 gap-4">
43 {usedApps
44 .sort(
45 (a, b) =>
46 new Date(b.lastSettledTransactionAt ?? 0).getTime() -
47 new Date(a.lastSettledTransactionAt ?? 0).getTime()
48 )
49 .map((app) => (
50 <RecentlyUsedAppRow key={app.id} app={app} />
51 ))}
52 </CardContent>
53 </Card>
54 );
55 }
56
57 function RecentlyUsedAppRow({ app }: { app: App }) {
58 const { data: transactionsData } = useTransactions(app.id, false, 1, 1);
59 const latestTransaction = transactionsData?.transactions[0];
60
61 return (
62 <Link to={`/apps/${app.id}`} className="group">
63 <div className="flex items-center w-full gap-4">
64 <AppAvatar app={app} className="w-12 h-12 rounded-lg" />
65 <p className="text-sm font-medium flex-1 truncate">
66 {getAppDisplayName(app.name)}
67 </p>
68 <div className="flex flex-col items-end">
69 {latestTransaction && (
70 <span
71 className={cn(
72 "text-sm font-medium",
73 latestTransaction.type === "incoming" &&
74 "text-green-600 dark:text-emerald-500"
75 )}
76 >
77 {latestTransaction.type === "outgoing" ? "-" : "+"}
78 <FormattedBitcoinAmount
79 amountMsat={latestTransaction.amountMsat}
80 />
81 </span>
82 )}
83 <span className="text-xs text-muted-foreground">
84 {app.lastSettledTransactionAt
85 ? dayjs(app.lastSettledTransactionAt).fromNow()
86 : "never"}
87 </span>
88 </div>
89 <ChevronRightIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5" />
90 </div>
91 </Link>
92 );
93 }
94