AppOfTheDayWidget.tsx raw
1 import { ChevronRightIcon } from "lucide-react";
2 import { Link } from "react-router";
3 import {
4 appStoreApps,
5 getAppStoreUrl,
6 } from "src/components/connections/SuggestedAppData";
7 import {
8 Card,
9 CardContent,
10 CardDescription,
11 CardHeader,
12 CardTitle,
13 } from "src/components/ui/card";
14
15 export function AppOfTheDayWidget() {
16 function seededRandom(seed: number) {
17 const x = Math.sin(seed) * 10000;
18 return x - Math.floor(x);
19 }
20
21 // filter out apps which already have a widget
22 const excludedAppIds = ["alby-go", "zapplanner"];
23 const apps = appStoreApps.filter((a) => !excludedAppIds.includes(a.id));
24
25 // eslint-disable-next-line react-hooks/purity
26 const daysSinceEpoch = Math.floor(Date.now() / (1000 * 60 * 60 * 24));
27 const todayIndex = Math.floor(seededRandom(daysSinceEpoch) * apps.length);
28 const app = apps[todayIndex];
29
30 return (
31 <Card>
32 <CardHeader className="px-6 pb-0">
33 <CardTitle className="text-base font-semibold">
34 App of the Day
35 </CardTitle>
36 </CardHeader>
37 <CardContent className="px-6 pt-0">
38 <Link
39 to={getAppStoreUrl(app)}
40 className="group flex items-center gap-4 rounded-md"
41 >
42 <img
43 src={app.logo}
44 alt={`${app.title} logo`}
45 className="size-15 rounded-lg object-cover"
46 />
47 <div className="min-w-0 flex-1">
48 <p className="truncate text-base font-semibold text-foreground">
49 {app.title}
50 </p>
51 <CardDescription className="line-clamp-2 text-sm leading-5 text-muted-foreground">
52 {app.description}
53 </CardDescription>
54 </div>
55 <ChevronRightIcon className="size-4 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5" />
56 </Link>
57 </CardContent>
58 </Card>
59 );
60 }
61