feat: add stickers delete api
This commit is contained in:
@@ -54,6 +54,26 @@ export default function RewardGiftsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const remove = async (reward: GiftableReward) => {
|
||||
if (
|
||||
!window.confirm(
|
||||
`Delete “${reward.name}”? It disappears from the gallery of everyone who has it. Stickers already sent in chats stay put.`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setSending(reward.key);
|
||||
try {
|
||||
await rewardGifts.remove(reward.key);
|
||||
setCatalog((prev) => prev?.filter((c) => c.key !== reward.key) ?? null);
|
||||
toast.success("Deleted");
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : "Could not delete");
|
||||
} finally {
|
||||
setSending(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<header>
|
||||
@@ -101,14 +121,29 @@ export default function RewardGiftsPage() {
|
||||
<p className="line-clamp-2 text-sm font-medium text-foreground">
|
||||
{r.name}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
disabled={sending !== null}
|
||||
onClick={() => send(r)}
|
||||
className="mt-auto w-full rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{sending === r.key ? "Sending…" : "Send to everyone"}
|
||||
</button>
|
||||
<div className="mt-auto w-full space-y-1.5">
|
||||
<button
|
||||
type="button"
|
||||
disabled={sending !== null}
|
||||
onClick={() => send(r)}
|
||||
className="w-full rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{sending === r.key ? "Sending…" : "Send to everyone"}
|
||||
</button>
|
||||
{/* Seeded rewards have no delete: they would return on the
|
||||
next seed run, having been taken from the galleries of
|
||||
everyone who earned them. */}
|
||||
{r.key.startsWith("custom_") && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={sending !== null}
|
||||
onClick={() => remove(r)}
|
||||
className="w-full rounded-md border border-border px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted disabled:opacity-50"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -138,12 +173,23 @@ function CustomGift({
|
||||
const upload = async (original: File) => {
|
||||
setUploading(true);
|
||||
try {
|
||||
// Same compression as the banner: artwork renders at ~120px in the
|
||||
// gallery and small again in a chat bubble, so a full-size original is
|
||||
// wasted bytes on every devotee's device.
|
||||
const file = await compressImage(original, { maxDimension: 512 }).catch(
|
||||
() => original,
|
||||
);
|
||||
// WebP, NOT the helper's default JPEG: JPEG has no alpha channel, and
|
||||
// compress-image paints a white backdrop behind it so transparency
|
||||
// doesn't turn black. A sticker has to keep its cut-out, so it encodes
|
||||
// to a format that has alpha — the white fill is skipped for anything
|
||||
// that isn't JPEG.
|
||||
//
|
||||
// 1024px, not the gallery tile's 120px: the reward detail renders the
|
||||
// artwork at 224 CSS px (rewards-gallery.tsx:184), which is 672 real
|
||||
// pixels on a 3× screen — so anything smaller is upscaled and visibly
|
||||
// soft exactly where the artwork is largest. Quality is raised too:
|
||||
// stickers are flat illustration with hard edges, where WebP's default
|
||||
// ringing shows far more than it does on a photograph.
|
||||
const file = await compressImage(original, {
|
||||
maxDimension: 1024,
|
||||
quality: 0.95,
|
||||
mimeType: "image/webp",
|
||||
}).catch(() => original);
|
||||
const { upload_url, image_url } = await rewardGifts.presignImage(
|
||||
file.type,
|
||||
);
|
||||
@@ -217,7 +263,9 @@ function CustomGift({
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
// No JPEG: it cannot carry transparency, so it can only produce a
|
||||
// sticker with a hard rectangular edge.
|
||||
accept="image/png,image/webp"
|
||||
disabled={uploading}
|
||||
onChange={(e) => {
|
||||
const f = e.target.files?.[0];
|
||||
|
||||
@@ -459,6 +459,12 @@ export const rewardGifts = {
|
||||
{ method: "POST", body: JSON.stringify({ content_type: contentType }) },
|
||||
).then((r) => r.data),
|
||||
|
||||
/** Delete a custom sticker. Cascades out of every gallery and vault. */
|
||||
remove: (key: string) =>
|
||||
call<void>(`/admin/reward-gifts/${encodeURIComponent(key)}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
|
||||
/** Add a sticker to the catalog. It reaches nobody until it is sent. */
|
||||
create: (input: { name: string; meaning: string; image_url: string }) =>
|
||||
call<GiftableReward>("/admin/reward-gifts/catalog", {
|
||||
|
||||
Reference in New Issue
Block a user