terry-wallet-login #15

Merged
terry merged 95 commits from terry-wallet-login into terry-staging 2026-06-05 16:32:43 +00:00
Showing only changes of commit 724bfb8f24 - Show all commits

View File

@@ -147,57 +147,112 @@ function FileCard({ post, att }: { post: Post; att: Attachment }) {
);
}
function PillDownloadIcon() {
return (
<svg
width="12"
height="10"
viewBox="0 0 12 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M9.84528 3.61663C9.68928 1.5958 8.02426 0 6.00008 0C4.26863 0 2.78232 1.14503 2.30275 2.81502C0.934727 3.29852 0 4.6181 0 6.10918C0 8.034 1.53816 9.60013 3.42862 9.60013H9.00013C10.6544 9.60013 12.0002 8.22993 12.0002 6.54555C12.0002 5.17142 11.113 3.99191 9.84528 3.61663ZM8.0174 5.98132L6.30309 7.7268C6.21952 7.81189 6.1098 7.85465 6.00008 7.85465C5.89037 7.85465 5.78065 7.81189 5.69708 7.7268L3.98277 5.98132C3.8602 5.85652 3.82334 5.66888 3.88977 5.50568C3.9562 5.34291 4.11263 5.23644 4.28577 5.23644H5.14293V3.49096C5.14293 3.00921 5.52693 2.61822 6.00008 2.61822C6.47323 2.61822 6.85724 3.00921 6.85724 3.49096V5.23644H7.71439C7.88754 5.23644 8.04397 5.34291 8.1104 5.50568C8.17683 5.66888 8.13997 5.85652 8.0174 5.98132Z"
fill="#A8A9AE"
/>
</svg>
);
}
function MediaSizeChip({ att }: { att: Attachment }) {
return (
<div className="absolute left-3 top-2.5 z-20 flex h-6 w-[72px] items-center overflow-hidden rounded-full bg-black text-white">
<span className="grid h-6 w-6 shrink-0 place-items-center bg-[#545454]">
<PillDownloadIcon />
</span>
<span className="flex h-4 w-12 items-center justify-center text-[10px] font-medium leading-4">
{formatBytes(att.sizeBytes)}
</span>
</div>
);
}
function MediaTile({
att,
showExtra,
}: {
att: Attachment;
showExtra?: number;
}) {
const src = attachmentPreview(att);
const isVideo = att.kind === "video" || att.mime.startsWith("video/");
return (
<div className="relative h-full w-full overflow-hidden bg-black">
{src ? (
<img
src={src}
alt=""
className="h-full w-full object-cover"
loading="lazy"
decoding="async"
/>
) : null}
<MediaSizeChip att={att} />
{isVideo ? (
<div className="absolute inset-0 grid place-items-center">
<span className="grid h-16 w-16 place-items-center rounded-full bg-black/55 text-white backdrop-blur-sm">
<Play className="ml-1 h-8 w-8 fill-current" />
</span>
</div>
) : null}
{showExtra ? (
<div className="absolute inset-0 grid place-items-center bg-black/55 text-[24px] font-bold text-white backdrop-blur-sm">
+{showExtra}
</div>
) : null}
</div>
);
}
function MediaGrid({ attachments }: { attachments: Attachment[] }) {
const visible = attachments.slice(0, 4);
const extra = Math.max(0, attachments.length - visible.length);
if (visible.length <= 1) {
const src = attachmentPreview(visible[0]);
return src ? (
<img
src={src}
alt=""
className="h-full w-full object-cover"
loading="lazy"
decoding="async"
/>
) : (
<div className="h-full w-full bg-black" />
if (visible.length === 0) return <div className="h-full w-full bg-black" />;
if (visible.length === 1) return <MediaTile att={visible[0]} />;
if (visible.length === 2) {
return (
<div className="grid h-full w-full grid-cols-2 gap-0">
{visible.map((att) => (
<MediaTile key={att.id} att={att} />
))}
</div>
);
}
if (visible.length === 3) {
return (
<div className="grid h-full w-full grid-cols-2 gap-0">
<MediaTile att={visible[0]} />
<div className="grid h-full grid-rows-2 gap-0">
<MediaTile att={visible[1]} />
<MediaTile att={visible[2]} />
</div>
</div>
);
}
return (
<div className="grid h-full w-full grid-cols-2 grid-rows-2 gap-0">
{visible.map((att, index) => (
<div key={att.id} className="relative overflow-hidden bg-black">
<img
src={attachmentPreview(att)}
alt=""
className="h-full w-full object-cover"
loading="lazy"
decoding="async"
/>
{extra > 0 && index === visible.length - 1 ? (
<div className="absolute inset-0 grid place-items-center bg-black/55 text-[24px] font-bold text-white backdrop-blur-sm">
+{extra}
</div>
) : null}
</div>
<MediaTile
key={att.id}
att={att}
showExtra={extra > 0 && index === visible.length - 1 ? extra : 0}
/>
))}
</div>
);
}
function MediaBadge({ att }: { att?: Attachment }) {
if (!att) return null;
return (
<div className="absolute left-3 top-3 z-20 inline-flex h-8 items-center overflow-hidden rounded-full bg-black/75 text-[12px] font-medium text-white shadow-lg backdrop-blur">
<span className="grid h-8 w-8 place-items-center rounded-full bg-[#3a3a40]">
<DownloadCloudIcon />
</span>
<span className="px-3">{formatBytes(att.sizeBytes)}</span>
</div>
);
}
function VisualCard({ post }: { post: Post }) {
const { lang } = useI18n();
const att = post.attachments[0];
@@ -213,21 +268,13 @@ function VisualCard({ post }: { post: Post }) {
}`}
>
<MediaGrid attachments={post.attachments} />
<MediaBadge att={att} />
{isVideo ? (
<div className="absolute inset-0 grid place-items-center">
<span className="grid h-14 w-14 place-items-center rounded-full bg-black/55 text-white backdrop-blur-sm">
<Play className="ml-1 h-7 w-7 fill-current" />
</span>
</div>
) : null}
</div>
{text || post.title ? (
<div className="message-stream-copyable-text whitespace-pre-wrap break-words px-4 pt-3 text-[15px] font-medium leading-6 text-white">
{autolink(text || post.title || "")}
</div>
) : null}
<Footer post={post} attachment={att} />
<Footer post={post} />
</article>
);
}