+ {/* More options menu — owners manage their listing (mark sold /
+ relist / delete), non-owners can report, admins can take down. */}
+ {user && (
+
+ {/* Owner: mark sold / relist + delete */}
+ {isOwnListingCheck && (
+ <>
+ {isSold ? (
+ {
+ e.stopPropagation();
+ handleRelist();
+ }}
+ disabled={isUpdatingStatus}
+ >
+
+ Relist as active
+
+ ) : (
+ {
+ e.stopPropagation();
+ handleMarkSold();
+ }}
+ disabled={isUpdatingStatus}
+ >
+
+ Mark as sold
+
+ )}
+
+ {
+ e.stopPropagation();
+ setShowRemoveDialog(true);
+ }}
+ className="text-red-500 focus:text-red-500"
+ >
+
+ Delete listing
+
+ >
+ )}
{!isOwnListingCheck && (
{
@@ -205,7 +298,7 @@ export function GearSwapItemCard({ item, trustScore, salesCount, isFollowing }:
Report
)}
- {isAdmin && (
+ {isAdmin && !isOwnListingCheck && (
{
e.stopPropagation();
@@ -354,11 +447,12 @@ export function GearSwapItemCard({ item, trustScore, salesCount, isFollowing }:
- Remove this listing? (admin)
+ {ownerDeleting ? `Delete "${item.title}"?` : 'Remove this listing? (admin)'}
- As a SkateSpots admin you're taking down "{item.title}". It will be hidden across the
- app for everyone and a deletion request will be sent to relays.
+ {ownerDeleting
+ ? `This asks relays to remove your listing "${item.title}". It can't be undone from here — you'd have to create a new one.`
+ : `As a SkateSpots admin you're taking down "${item.title}". It will be hidden across the app for everyone and a deletion request will be sent to relays.`}
@@ -366,11 +460,11 @@ export function GearSwapItemCard({ item, trustScore, salesCount, isFollowing }:
Cancel
- {isRemoving ? 'Removing…' : 'Remove Listing'}
+ {isRemoving ? (ownerDeleting ? 'Deleting…' : 'Removing…') : (ownerDeleting ? 'Delete Listing' : 'Remove Listing')}
diff --git a/src/components/MarketplaceItemCard.tsx b/src/components/MarketplaceItemCard.tsx
index 02a366a..067c5f7 100644
--- a/src/components/MarketplaceItemCard.tsx
+++ b/src/components/MarketplaceItemCard.tsx
@@ -31,6 +31,8 @@ import { useNostrPublish } from '@/hooks/useNostrPublish';
import { useListingReports } from '@/hooks/useListingReports';
import { useSellerInfo } from '@/hooks/useSellerInfo';
import { useTrustScore } from '@/hooks/useTrustScore';
+import { useMarkListingSold } from '@/hooks/useMarkListingSold';
+import { DeleteEntityDialog } from '@/components/DeleteEntityDialog';
import { genUserName } from '@/lib/genUserName';
import { TrustBadge } from '@/components/TrustBadge';
import {
@@ -46,6 +48,9 @@ import {
Package,
AlertTriangle,
CheckCircle,
+ RotateCcw,
+ Trash2,
+ Loader2,
} from 'lucide-react';
import { formatDistanceToNow } from 'date-fns';
import type { MarketplaceItem } from '@/lib/types';
@@ -61,6 +66,7 @@ export function MarketplaceItemCard({ item, compact = false }: MarketplaceItemCa
const { toast } = useToast();
const navigate = useNavigate();
const { mutateAsync: publishEvent } = useNostrPublish();
+ const { markSold, relist, isPending: isUpdatingStatus } = useMarkListingSold();
const author = useAuthor(item.pubkey);
const metadata = author.data?.metadata;
const displayName = metadata?.name ?? genUserName(item.pubkey);
@@ -81,6 +87,7 @@ export function MarketplaceItemCard({ item, compact = false }: MarketplaceItemCa
const { totalBalance: cashuBalance, isInitialized: hasCashu, coco } = useCashu();
const [showPaymentDialog, setShowPaymentDialog] = useState(false);
const [showReportDialog, setShowReportDialog] = useState(false);
+ const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [reportReason, setReportReason] = useState('');
const [reportDetails, setReportDetails] = useState('');
const [isReporting, setIsReporting] = useState(false);
@@ -188,6 +195,33 @@ export function MarketplaceItemCard({ item, compact = false }: MarketplaceItemCa
}
const isOwnListing = user?.pubkey === item.pubkey;
+ const isSold = item.status === 'sold';
+
+ const handleMarkSold = async () => {
+ try {
+ await markSold(item.event);
+ toast({ title: 'Marked as sold', description: 'Your listing is now shown as sold.' });
+ } catch (error) {
+ toast({
+ title: 'Could not update',
+ description: error instanceof Error ? error.message : 'Something went wrong',
+ variant: 'destructive',
+ });
+ }
+ };
+
+ const handleRelist = async () => {
+ try {
+ await relist(item.event);
+ toast({ title: 'Relisted', description: 'Your listing is active again.' });
+ } catch (error) {
+ toast({
+ title: 'Could not update',
+ description: error instanceof Error ? error.message : 'Something went wrong',
+ variant: 'destructive',
+ });
+ }
+ };
return (
)}
+
+ {/* Owner management dropdown — mark sold / relist / delete */}
+ {user && isOwnListing && (
+
+
+
+
+
+ {isSold ? (
+
+
+ Relist as active
+
+ ) : (
+
+
+ Mark as sold
+
+ )}
+
+ setShowDeleteDialog(true)}
+ className="text-red-400 focus:text-red-400"
+ >
+
+ Delete listing
+
+
+
+ )}
@@ -486,6 +562,16 @@ export function MarketplaceItemCard({ item, compact = false }: MarketplaceItemCa
+ {/* Delete confirmation (controlled — trigger lives in the owner dropdown) */}
+