fix(marketplace): localize trade state and pending status

Trade-record state (revoked/verifying) and the listing pending status were
printed raw. New marketUi.trades.stateRevoked/stateVerifying and
marketUi.statusPending (en/es). Also reclassify the request tag as cosmetic
(never read in the client) in the audit.
This commit is contained in:
SkateSpots 2026-10-10 10:09:41 +07:00
parent 7a60099bab
commit 057ef0a8f3
5 changed files with 18 additions and 6 deletions

View file

@ -97,10 +97,12 @@ visibility, terminology, translation).
- **[reported] `useTradeReputation` skips `onlyVerified`** (`:216-222`), unlike - **[reported] `useTradeReputation` skips `onlyVerified`** (`:216-222`), unlike
`useTradeIntegrity`/`useShipmentStatus`, so a hostile relay can inject forged `useTradeIntegrity`/`useShipmentStatus`, so a hostile relay can inject forged
reputation events. reputation events.
- **[reported] Request back-reference is wrong.** `publishTrade` writes the - **[reported, cosmetic] Request back-reference is wrong.** `publishTrade` writes
listing `a` coord as the `request` tag (`useTradeAttestations.ts:141`), but the listing `a` coord as the `request` tag (`useTradeAttestations.ts:141`) where
NIP.md documents `31872:<requestor>:<d>`; both call sites pass the coord NIP.md documents `31872:<requestor>:<d>`. Confirmed not consumed anywhere in the
(`TradesPage.tsx:210`, `ConversationPage.tsx:193`). client (a repo-wide scan finds no `request` tag reader), so it is a
spec-conformance issue only, not a functional one; deferred rather than risk a
writer/reader mismatch.
- **[reported] Receipts are one global blob, keyed by listing coord, not - **[reported] Receipts are one global blob, keyed by listing coord, not
account-scoped** (`receipts.ts:64-71`), and `ListingOffersPanel` never reads account-scoped** (`receipts.ts:64-71`), and `ListingOffersPanel` never reads
them back (`:74,335`), so the Pay button can reappear and a prior trade's them back (`:74,335`), so the Pay button can reappear and a prior trade's
@ -227,6 +229,9 @@ Second batch (payment, attestation, escrow):
`ShippingStatusCard` now show an explicit "could not reach the relays" `ShippingStatusCard` now show an explicit "could not reach the relays"
notice (`marketUi.loadFailed`) instead of an empty / "nothing waiting" / notice (`marketUi.loadFailed`) instead of an empty / "nothing waiting" /
"no trades" state when their query errors. "no trades" state when their query errors.
15. **Localized trade status:** the trade-record `state` and the listing
`pending` status are no longer printed raw (`marketUi.trades.stateRevoked` /
`stateVerifying`, `marketUi.statusPending`).
## 7. Recommended order ## 7. Recommended order

View file

@ -1660,6 +1660,8 @@ marketUi: {
recordedSale: 'recorded a sale — confirm your side', recordedSale: 'recorded a sale — confirm your side',
confirmSale: 'Confirm sale', confirmSale: 'Confirm sale',
confirm: 'Confirm', confirm: 'Confirm',
stateRevoked: 'revoked',
stateVerifying: 'verifying',
nothingWaiting: 'Nothing waiting on you right now. 🛹', nothingWaiting: 'Nothing waiting on you right now. 🛹',
yourRecords: 'Your trade records', yourRecords: 'Your trade records',
noTrades: 'No trades yet. Close a deal and confirm it to start building reputation.', noTrades: 'No trades yet. Close a deal and confirm it to start building reputation.',
@ -1773,6 +1775,7 @@ marketUi: {
paidBody: 'Ask the seller to confirm the trade so it counts for both of you.', paidBody: 'Ask the seller to confirm the trade so it counts for both of you.',
}, },
loadFailed: 'Could not reach the relays. Check your connection and try again.', loadFailed: 'Could not reach the relays. Check your connection and try again.',
statusPending: 'pending',
offers: { offers: {
accepted: 'Accepted', accepted: 'Accepted',
declined: 'Declined', declined: 'Declined',

View file

@ -1660,6 +1660,8 @@ marketUi: {
recordedSale: 'registró una venta — confirma tu parte', recordedSale: 'registró una venta — confirma tu parte',
confirmSale: 'Confirmar venta', confirmSale: 'Confirmar venta',
confirm: 'Confirmar', confirm: 'Confirmar',
stateRevoked: 'revocada',
stateVerifying: 'verificando',
nothingWaiting: 'No hay nada pendiente por tu parte. 🛹', nothingWaiting: 'No hay nada pendiente por tu parte. 🛹',
yourRecords: 'Tus registros de tratos', yourRecords: 'Tus registros de tratos',
noTrades: 'Aún no hay tratos. Cierra un trato y confírmalo para empezar a construir reputación.', noTrades: 'Aún no hay tratos. Cierra un trato y confírmalo para empezar a construir reputación.',
@ -1773,6 +1775,7 @@ marketUi: {
paidBody: 'Pide al vendedor que confirme el trato para que cuente para ambos.', paidBody: 'Pide al vendedor que confirme el trato para que cuente para ambos.',
}, },
loadFailed: 'No se pudo contactar con los relays. Comprueba tu conexión e inténtalo de nuevo.', loadFailed: 'No se pudo contactar con los relays. Comprueba tu conexión e inténtalo de nuevo.',
statusPending: 'pendiente',
offers: { offers: {
accepted: 'Aceptada', accepted: 'Aceptada',
declined: 'Rechazada', declined: 'Rechazada',

View file

@ -200,7 +200,7 @@ export default function ListingDetailPage() {
)} )}
{item.status && item.status !== 'active' && !isSold && ( {item.status && item.status !== 'active' && !isSold && (
<Badge variant="secondary" className="bg-yellow-900/50 text-yellow-400"> <Badge variant="secondary" className="bg-yellow-900/50 text-yellow-400">
{item.status.toUpperCase()} {item.status === 'pending' ? t('marketUi.statusPending') : item.status.toUpperCase()}
</Badge> </Badge>
)} )}
</div> </div>

View file

@ -274,6 +274,7 @@ export default function TradesPage() {
: null; : null;
const role = roleTyped === 'sold' ? t('marketUi.trades.roleSold') : roleTyped === 'bought' ? t('marketUi.trades.roleBought') : t('marketUi.trades.roleTrade'); const role = roleTyped === 'sold' ? t('marketUi.trades.roleSold') : roleTyped === 'bought' ? t('marketUi.trades.roleBought') : t('marketUi.trades.roleTrade');
const state = ev.tags.find(([t]) => t === 's')?.[1] ?? 'valid'; const state = ev.tags.find(([t]) => t === 's')?.[1] ?? 'valid';
const stateLabel = state === 'revoked' ? t('marketUi.trades.stateRevoked') : state === 'verifying' ? t('marketUi.trades.stateVerifying') : state;
const counterparty = ev.tags.find(([t]) => t === 'p')?.[1]; const counterparty = ev.tags.find(([t]) => t === 'p')?.[1];
return ( return (
<Card key={ev.id} className="bg-black/80 border-white/10"> <Card key={ev.id} className="bg-black/80 border-white/10">
@ -293,7 +294,7 @@ export default function TradesPage() {
</p> </p>
<p className="text-[11px] text-gray-500"> <p className="text-[11px] text-gray-500">
{new Date(ev.created_at * 1000).toLocaleDateString(dateLocaleCode())} {new Date(ev.created_at * 1000).toLocaleDateString(dateLocaleCode())}
{state !== 'valid' && ` · ${state}`} {state !== 'valid' && ` · ${stateLabel}`}
</p> </p>
</div> </div>
{parsed && ( {parsed && (