v0.21.1 — Pantry: delete items with inline confirm

Trash icon on each row; clicking shows "Delete? Yes / No" inline
(no dialog needed). Soft-deletes via active=false. Removed the
admin-only guard on DELETE /api/pantry/[id] since anyone logged in
should be able to manage their own pantry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Bonna Moon
2026-06-29 18:32:30 -05:00
parent 6b75e47ee9
commit dd7af9e8d4
4 changed files with 55 additions and 6 deletions

View File

@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
import { z } from "zod";
import { ItemSource } from "@prisma/client";
import { db } from "@/lib/db";
import { requireAuth, isAdmin } from "@/lib/auth";
import { requireAuth } from "@/lib/auth";
const PANTRY_SELECT = {
id: true, name: true, description: true, quantity: true, unit: true,
@@ -96,7 +96,6 @@ export async function PATCH(req: Request, { params }: { params: { id: string } }
export async function DELETE(_req: Request, { params }: { params: { id: string } }) {
try {
const session = await requireAuth();
if (!isAdmin(session.user.role)) return NextResponse.json({ error: "Forbidden" }, { status: 403 });
await db.item.update({ where: { id: params.id }, data: { active: false } });
await db.auditEvent.create({
data: { action: "pantry.delete", entityId: params.id, actorId: session.user.id },

View File

@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { AlertTriangle, ChevronDown, ChevronRight, UtensilsCrossed } from "lucide-react";
import { AlertTriangle, ChevronDown, ChevronRight, Trash2, UtensilsCrossed } from "lucide-react";
import { PantryItemDialog } from "./pantry-item-dialog";
import { PullDialog } from "./pull-dialog";
import { UseItemDialog } from "./use-item-dialog";
@@ -51,6 +51,20 @@ function ExpiryBadge({ date }: { date: Date }) {
function ItemRow({ item, locations, plants, onRefresh }: {
item: PantryItem; locations: LocationOption[]; plants: PlantOption[]; onRefresh: () => void;
}) {
const [confirming, setConfirming] = useState(false);
const [deleting, setDeleting] = useState(false);
async function handleDelete() {
setDeleting(true);
try {
const res = await fetch(`/api/pantry/${item.id}`, { method: "DELETE" });
if (res.ok) onRefresh();
} finally {
setDeleting(false);
setConfirming(false);
}
}
const priceStr = item.pricePaid != null
? `$${item.pricePaid.toFixed(2)}${item.priceUnit && item.priceUnit !== "package" ? `/${item.priceUnit}` : ""}`
: null;
@@ -83,8 +97,37 @@ function ItemRow({ item, locations, plants, onRefresh }: {
{item.notes && <p className="text-xs text-muted-foreground mt-0.5 italic">{item.notes}</p>}
</div>
<div className="flex items-center gap-1 shrink-0">
{confirming ? (
<>
<span className="text-xs text-muted-foreground mr-1">Delete?</span>
<button
onClick={handleDelete}
disabled={deleting}
className="text-xs text-destructive font-medium hover:underline disabled:opacity-50"
>
{deleting ? "…" : "Yes"}
</button>
<span className="text-muted-foreground text-xs">/</span>
<button
onClick={() => setConfirming(false)}
className="text-xs text-muted-foreground hover:underline"
>
No
</button>
</>
) : (
<>
<UseItemDialog item={item} onSuccess={onRefresh} />
<PantryItemDialog item={item} locations={locations} plants={plants} onSuccess={onRefresh} />
<button
onClick={() => setConfirming(true)}
className="h-8 w-8 flex items-center justify-center rounded-md hover:bg-destructive/10 hover:text-destructive text-muted-foreground transition-colors"
title="Delete"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
</>
)}
</div>
</div>
);

View File

@@ -8,6 +8,13 @@ export type ChangelogEntry = {
};
export const CHANGELOG: ChangelogEntry[] = [
{
version: "0.21.1",
date: "2026-06-29",
changes: [
"Pantry: trash icon on each item — click it, confirm with Yes/No. Removes the item from the inventory.",
],
},
{
version: "0.21.0",
date: "2026-06-29",

View File

@@ -1,2 +1,2 @@
// Bump on every user-visible release. Changelog entries live in `src/lib/changelog.ts`.
export const APP_VERSION = "0.21.0";
export const APP_VERSION = "0.21.1";