/** * ShippingStatusCard — optional shipping logistics for a `ships` deal. * * Self-reported (a claim, never proof): the seller marks **shipped**, the buyer * marks **received**, either can flag an **issue**. `p`-tagged to the * counterparty so the push service can notify while the app is closed. No * address is ever stored — only a coarse status and an optional tracking ref. */ import { useState } from 'react'; import { Package, PackageCheck, AlertTriangle, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useListingTrade } from '@/hooks/useTradeAttestations'; import { useShipmentStatus, useSetShipmentStatus } from '@/hooks/useShipmentStatus'; import { useToast } from '@/hooks/useToast'; import type { MarketplaceItem } from '@/lib/types'; import { useTranslation } from '@/contexts/language'; export function ShippingStatusCard({ item }: { item: MarketplaceItem }) { const { t } = useTranslation(); const { user } = useCurrentUser(); const { toast } = useToast(); const listingRef = { pubkey: item.pubkey, identifier: item.identifier }; const { data: trade } = useListingTrade(listingRef, user?.pubkey); const { data: statuses = [] } = useShipmentStatus(listingRef); const { setStatus, isPending } = useSetShipmentStatus(); const [tracking, setTracking] = useState(''); if (!user) return null; const shipping = item.fulfilment === 'ships' || item.fulfilment === 'both'; if (!shipping) return null; const isSeller = user.pubkey === item.pubkey; // Seller → the buyer they named on their own attestation; buyer → the seller. const counterparty = isSeller ? trade?.attestations .find((ev) => ev.pubkey === user.pubkey) ?.tags.find(([t]) => t === 'p')?.[1] : item.pubkey; const shipped = statuses.some((s) => s.status === 'shipped'); const received = statuses.some((s) => s.status === 'received'); const issue = statuses.some((s) => s.status === 'issue'); const mark = async (status: 'shipped' | 'received' | 'issue') => { if (!counterparty) { toast({ title: t('marketUi.shipping.noCounterparty'), description: t('marketUi.shipping.noCounterpartyBody'), variant: 'destructive', }); return; } try { await setStatus({ listing: listingRef, counterparty, status, ref: tracking }); toast({ title: status === 'shipped' ? t('marketUi.shipping.markedShipped') : status === 'received' ? t('marketUi.shipping.markedReceived') : t('marketUi.shipping.issueReported'), description: status === 'received' ? t('marketUi.shipping.receivedBody') : t('marketUi.shipping.notifiedBody'), }); } catch (e) { toast({ title: t('marketUi.shipping.updateFailed'), description: e instanceof Error ? e.message : t('marketUi.shipping.somethingWrong'), variant: 'destructive', }); } }; return (

{t('marketUi.shipping.title')} {shipped && · {t('marketUi.shipping.shipped')}} {received && · {t('marketUi.shipping.received')}} {issue && · {t('marketUi.shipping.issue')}}

setTracking(e.target.value)} placeholder={t('marketUi.shipping.trackingPlaceholder')} className="h-9 border-white/15 bg-white/5 text-sm text-white" />
{isSeller && ( )} {!isSeller && ( )}

{t('marketUi.shipping.noteBefore')} {t('marketUi.shipping.notProof')}{t('marketUi.shipping.noteAfter')}

); }