SuggestedApps.tsx raw
1 import React from "react";
2 import { Link, useSearchParams } from "react-router";
3 import { Badge } from "src/components/ui/badge";
4 import {
5 Card,
6 CardContent,
7 CardDescription,
8 CardTitle,
9 } from "src/components/ui/card";
10 import { cn } from "src/lib/utils";
11 import {
12 AppStoreApp,
13 appStoreApps,
14 getAppStoreUrl,
15 sortedAppStoreCategories,
16 } from "./SuggestedAppData";
17
18 function AppCard(app: AppStoreApp) {
19 return (
20 <Link to={getAppStoreUrl(app)}>
21 <Card className="h-full">
22 <CardContent>
23 <div className="flex gap-3 items-center">
24 <img
25 src={app.logo}
26 alt={`${app.title} logo`}
27 className="inline rounded-lg size-12"
28 />
29 <div className="grow">
30 <CardTitle>{app.title}</CardTitle>
31 <CardDescription>
32 {app.description}
33 {app.legacyTitles?.length
34 ? ` (Previously ${app.legacyTitles.join(", ")})`
35 : ""}
36 </CardDescription>
37 </div>
38 </div>
39 </CardContent>
40 </Card>
41 </Link>
42 );
43 }
44
45 export default function SuggestedApps() {
46 const [searchParams] = useSearchParams();
47 const [selectedCategories, setSelectedCategories] = React.useState<string[]>(
48 () => {
49 const category = searchParams.get("category");
50 return category ? [category] : [];
51 }
52 );
53
54 return (
55 <>
56 <div className="flex gap-2 flex-wrap mt-6 mb-2">
57 {sortedAppStoreCategories.map(([categoryId, category]) => (
58 <Badge
59 key={categoryId}
60 variant={
61 selectedCategories.includes(categoryId) ? "default" : "secondary"
62 }
63 className={cn(
64 "cursor-pointer",
65 selectedCategories.includes(categoryId)
66 ? ""
67 : "border-transparent font-normal select-none"
68 )}
69 onClick={() =>
70 setSelectedCategories((current) => [
71 ...current.filter((c) => c !== categoryId),
72 ...(current.includes(categoryId) ? [] : [categoryId]),
73 ])
74 }
75 >
76 {category.title}
77 </Badge>
78 ))}
79 </div>
80 <div className="flex flex-col gap-8">
81 {sortedAppStoreCategories
82 .filter(
83 ([categoryId]) =>
84 !selectedCategories.length ||
85 selectedCategories.includes(categoryId)
86 )
87 .map(([categoryId, category]) => {
88 return (
89 <div key={categoryId} className="pt-4">
90 <h3 className="font-semibold text-xl">{category.title}</h3>
91 <div className="grid md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-3 mt-4">
92 {appStoreApps
93 .filter((app) =>
94 (app.categories as string[]).includes(categoryId)
95 )
96 .map((app) => (
97 <AppCard key={app.id} {...app} />
98 ))}
99 </div>
100 </div>
101 );
102 })}
103 </div>
104 </>
105 );
106 }
107