- Reviews: confirming a trade now lets the reviewer set a 1-10 skateboard-deck rating, stored as ["rating","n"] on the kind-31871 attestation (NIP.md updated). The Reputation tab shows each review's decks plus an average. - New DeckRating component (self-drawn deck glyph) and TradeConfirmRow (rating picker + confirm), used for both seller-confirm and buyer-confirm in My Trades. - New 'Selling' tab: your marketplace listings (active/sold) with one-tap Mark sold.
63 lines
2.1 KiB
TypeScript
63 lines
2.1 KiB
TypeScript
import { cn } from '@/lib/utils';
|
||
|
||
/** A little skateboard-deck glyph for the rating. */
|
||
function Deck({ className }: { className?: string }) {
|
||
return (
|
||
<svg viewBox="0 0 24 10" className={className} fill="currentColor" aria-hidden>
|
||
<rect x="1" y="2.2" width="22" height="3.6" rx="1.8" />
|
||
<circle cx="7" cy="7.6" r="1.4" />
|
||
<circle cx="17" cy="7.6" r="1.4" />
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
interface DeckRatingProps {
|
||
/** Current rating, 0 = none, 1–10 = decks. */
|
||
value: number;
|
||
/** Omit for a read-only display. */
|
||
onChange?: (value: number) => void;
|
||
/** `sm` for inline rows, `md` for dialogs. */
|
||
size?: 'sm' | 'md';
|
||
/** When true, only render the filled decks (for display). */
|
||
compact?: boolean;
|
||
className?: string;
|
||
}
|
||
|
||
/**
|
||
* A 1–10 skateboard-deck rating. 1 deck = bad, 10 decks = best-in-class.
|
||
* Interactive by default; read-only when no `onChange` is given.
|
||
*/
|
||
export function DeckRating({ value, onChange, size = 'md', compact = false, className }: DeckRatingProps) {
|
||
const readOnly = !onChange;
|
||
const count = readOnly && compact ? Math.max(value, 0) : 10;
|
||
const deckCls = size === 'sm' ? 'h-3 w-6' : 'h-3.5 w-7';
|
||
|
||
return (
|
||
<span className={cn('inline-flex items-center gap-0.5', className)} title={`${value}/10 decks`}>
|
||
{Array.from({ length: count }).map((_, i) => {
|
||
const n = i + 1;
|
||
const filled = n <= value;
|
||
if (readOnly) {
|
||
return <Deck key={n} className={cn(deckCls, 'text-[#ccff00]')} />;
|
||
}
|
||
return (
|
||
<button
|
||
key={n}
|
||
type="button"
|
||
onClick={() => onChange?.(n === value ? 0 : n)}
|
||
aria-label={`${n} of 10`}
|
||
aria-pressed={filled}
|
||
className={cn(deckCls, 'transition-transform hover:scale-110 active:scale-95', filled ? 'text-[#ccff00]' : 'text-gray-600')}
|
||
>
|
||
<Deck className="h-full w-full" />
|
||
</button>
|
||
);
|
||
})}
|
||
{(value > 0 || !readOnly) && (
|
||
<span className="ml-1 text-[11px] font-bold text-gray-400 tabular-nums">
|
||
{value > 0 ? `${value}/10` : ''}
|
||
</span>
|
||
)}
|
||
</span>
|
||
);
|
||
}
|