feat: chat stickers
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* The sticker drawer, opened from the composer.
|
||||
*
|
||||
* The collection is whatever the server says it is — rewards earned through
|
||||
* sādhana plus stickers saved from a chat — so a sticker you were sent
|
||||
* appears here the moment you keep it, with no client-side bookkeeping.
|
||||
*
|
||||
* Empty is a real state worth explaining: stickers are earned, so someone
|
||||
* with none needs to know how to get one rather than seeing a blank sheet.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { ResponsiveDialog } from "@/components/chat/responsive-dialog";
|
||||
import { chat, type Sticker } from "@/lib/chat";
|
||||
|
||||
export function StickerPicker({
|
||||
onPick,
|
||||
onClose,
|
||||
}: {
|
||||
onPick: (sticker: Sticker) => void;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const [stickers, setStickers] = useState<Sticker[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
chat
|
||||
.stickers()
|
||||
.then((s) => {
|
||||
if (!cancelled) setStickers(s);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setStickers([]);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ResponsiveDialog
|
||||
title="Stickers"
|
||||
description="Earned through sādhana, or saved from a chat."
|
||||
onClose={onClose}
|
||||
>
|
||||
{stickers === null ? (
|
||||
<p className="p-4 text-sm text-muted-foreground">Loading…</p>
|
||||
) : stickers.length === 0 ? (
|
||||
<p className="p-4 text-sm text-muted-foreground">
|
||||
No stickers yet. Keep your sādhana streak — a sacred reward arrives
|
||||
every 7 days — or save one someone sends you.
|
||||
</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-4 gap-2 p-4 sm:grid-cols-5">
|
||||
{stickers.map((s) => (
|
||||
<button
|
||||
key={s.key}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onPick(s);
|
||||
onClose();
|
||||
}}
|
||||
title={s.name}
|
||||
aria-label={s.name}
|
||||
className="relative aspect-square rounded-lg p-1 transition-colors hover:bg-muted"
|
||||
>
|
||||
<Image
|
||||
src={s.image_url}
|
||||
alt=""
|
||||
fill
|
||||
sizes="80px"
|
||||
className="object-contain p-1"
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user