Files
cozsweet-frontend-nextjs/src/app/private-room/components/private-album-card.tsx
T

144 lines
4.4 KiB
TypeScript

import type { CSSProperties } from "react";
import Image from "next/image";
import { ImageIcon, LockKeyhole } from "lucide-react";
import { CharacterAvatar } from "@/app/_components";
import type { PrivateAlbum } from "@/data/schemas/private-room";
import { isPrivateAlbumLocked } from "@/lib/private-room/private_album";
import styles from "../private-room-screen.module.css";
export interface PrivateAlbumCardProps {
album: PrivateAlbum;
displayName: string;
avatarUrl: string;
index: number;
isUnlocking: boolean;
onOpenGallery: () => void;
onUnlock: () => void;
}
export function PrivateAlbumCard({
album,
displayName,
avatarUrl,
index,
isUnlocking,
onOpenGallery,
onUnlock,
}: PrivateAlbumCardProps) {
const isLocked = isPrivateAlbumLocked(album);
const firstImage = album.images[0];
const firstImageUrl = firstImage?.url || null;
const photoCount = album.imageCount || album.images.length;
return (
<article
className={styles.postCard}
style={{ "--reveal-index": index } as CSSProperties}
>
<header className={styles.postHeader}>
<div className={styles.postAuthor}>
<CharacterAvatar
src={avatarUrl}
alt=""
size={38}
imageSize={38}
className={styles.postAvatar}
/>
<div>
<p className={styles.authorName}>{displayName}</p>
<p className={styles.authorSubline}>
{album.title || "Private album"}
</p>
</div>
</div>
<time className={styles.postTime}>
{formatAlbumTime(album.publishedAt)}
</time>
</header>
{isLocked ? (
<button
type="button"
data-analytics-key="private_album.unlock"
data-analytics-label="Unlock private album"
className={styles.lockedPreview}
disabled={isUnlocking}
onClick={onUnlock}
aria-label={`Unlock ${photoCount} private room photos from ${displayName}`}
>
{firstImageUrl ? (
<Image
src={firstImageUrl}
alt=""
fill
sizes="(max-width: 540px) calc(100vw - 36px), 484px"
className={styles.lockedCoverImage}
/>
) : null}
<span className={styles.lockedPreviewScrim} aria-hidden="true" />
<div className={styles.lockedPreviewContent}>
<div className={styles.previewIcon}>
<LockKeyhole size={22} aria-hidden="true" />
</div>
<div className={styles.previewText}>
<span>{album.previewText || "Unlock to view"}</span>
<strong>{album.unlockCost} credits</strong>
<small>
<ImageIcon size={13} aria-hidden="true" />
{photoCount} {photoCount === 1 ? "photo" : "photos"}
</small>
</div>
</div>
</button>
) : firstImageUrl ? (
<button
type="button"
data-analytics-key="private_album.open_gallery"
data-analytics-label="Open private album gallery"
className={styles.mediaCover}
onClick={onOpenGallery}
aria-label={`Open ${photoCount} private album photos`}
>
<Image
src={firstImageUrl}
alt={`${album.title || displayName} private album`}
width={720}
height={900}
className={styles.momentCoverImage}
sizes="(max-width: 540px) calc(100vw - 36px), 484px"
/>
{photoCount > 1 ? (
<span className={styles.mediaCountBadge}>
<ImageIcon size={13} aria-hidden="true" />
{photoCount} photos
</span>
) : null}
</button>
) : (
<div
className={styles.emptyMediaCover}
role="img"
aria-label="No private album photo available"
>
<ImageIcon size={32} aria-hidden="true" />
<span>No photo available</span>
</div>
)}
{album.content ? <p className={styles.postText}>{album.content}</p> : null}
</article>
);
}
function formatAlbumTime(value: string | null): string {
if (!value) return "";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return "";
return date.toLocaleDateString(undefined, {
month: "short",
day: "numeric",
});
}