forked from
joebasser.com/atmosphere-account
this repo has no description
1import type { ProfileRow } from "../../lib/registry.ts";
2import { PUBLIC_CATEGORIES } from "../../lib/lexicons.ts";
3import { useT } from "../../i18n/mod.ts";
4import { bskyCdnAvatarUrl } from "../../lib/avatar.ts";
5import VerifiedBadge from "../VerifiedBadge.tsx";
6
7interface Props {
8 profile: ProfileRow;
9}
10
11/**
12 * Listing-grid card. Clicking the card opens the project's profile
13 * detail page (/explore/<handle>) — visitors get the description,
14 * Atmosphere services, Web / iOS / Android links, and any custom buttons
15 * on the detail page.
16 */
17export default function ProfileCard({ profile }: Props) {
18 const t = useT();
19 const tCat = t.categories as Record<string, string>;
20 const publicCategories = profile.categories.filter((c) =>
21 (PUBLIC_CATEGORIES as readonly string[]).includes(c)
22 );
23 const appSubcategories = publicCategories.includes("app")
24 ? profile.subcategories.slice(0, 2)
25 : [];
26 const featured = profile.featured;
27
28 return (
29 <a
30 href={`/explore/${encodeURIComponent(profile.handle)}`}
31 class="glass profile-card"
32 >
33 <div class="profile-card-avatar">
34 {profile.avatarCid
35 ? (
36 <img
37 src={bskyCdnAvatarUrl(profile.did, profile.avatarCid)}
38 alt=""
39 loading="lazy"
40 decoding="async"
41 />
42 )
43 : (
44 <div class="profile-card-avatar-fallback" aria-hidden="true">
45 {profile.name.slice(0, 1).toUpperCase()}
46 </div>
47 )}
48 </div>
49 <div class="profile-card-body">
50 <div class="profile-card-title-row">
51 <h3 class="profile-card-name">{profile.name}</h3>
52 {profile.iconAccessStatus === "granted" && (
53 <VerifiedBadge
54 size={16}
55 />
56 )}
57 {featured?.badges?.includes("official") && (
58 <span class="profile-badge profile-badge--official">
59 {t.badges.official}
60 </span>
61 )}
62 {featured?.badges?.includes("verified") &&
63 !featured.badges.includes("official") && (
64 <span class="profile-badge profile-badge--verified">
65 {t.badges.verified}
66 </span>
67 )}
68 </div>
69 <p class="profile-card-handle">@{profile.handle}</p>
70 {profile.description && (
71 <p class="profile-card-description">{profile.description}</p>
72 )}
73 {(publicCategories.length > 0 || appSubcategories.length > 0) && (
74 <div class="profile-card-meta">
75 {publicCategories.length > 0 && (
76 <div class="profile-card-categories">
77 {publicCategories.map((c) => (
78 <span key={c} class="profile-card-category">
79 {tCat[c] ?? c}
80 </span>
81 ))}
82 </div>
83 )}
84 {appSubcategories.length > 0 && (
85 <div class="profile-card-subcategories">
86 {appSubcategories.map((s) => {
87 const sub = (t.subcategories as Record<string, string>)[s] ??
88 s;
89 return (
90 <span key={s} class="profile-card-sub">
91 {sub}
92 </span>
93 );
94 })}
95 </div>
96 )}
97 </div>
98 )}
99 </div>
100 </a>
101 );
102}