SkateSpots/src/components/SasquatchActionCard.tsx
SkateSpots e357833557 i18n(es): batch 3 part 3 — shared label maps + leftover routing/Sasquatch strings
- labelsUi.* (221+ keys): spot categories/features/types, access, shelter, event
  types, gear categories/conditions, price/listings, tours, crews,
  member-visibility, tag categories, spot status. New src/lib/labelKeys.ts holds
  the shared value->key maps (one source; the five duplicated EVENT_TYPE_KEYS
  copies now import it). spotStatus.statusTimeAgo() uses the locale date shim.
- formatPrice() now takes an optional t so FREE/Trade/Make Offer translate.
- Route helpers: skateTime/distanceReadout/maneuver descriptors/formatAge
  translated (routeUi.*), consumers updated.
- Sasquatch: PRO_TIERS labels/badges + summarizeDraft titles/lines/ctas
  translated (sasquatchUi.*).
- Lab *_LABELS/_DESCRIPTIONS maps remain English (documented out of scope).
2026-10-02 15:31:27 +07:00

123 lines
4.4 KiB
TypeScript

/**
* SasquatchActionCard — the "review & confirm" card for a drafted action.
*
* Sasquatch proposes a draft; the user can tweak the key field(s), then Publish
* or Cancel. Nothing is signed or published until Publish is tapped.
*/
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Loader2, Check, X, Wand2 } from 'lucide-react';
import { useTranslation } from '@/contexts/language';
import {
summarizeDraft,
type SasquatchDraft,
} from '@/hooks/useSasquatchActions';
interface Props {
draft: SasquatchDraft;
onPublish: (draft: SasquatchDraft) => Promise<void>;
onCancel: () => void;
/** Set once resolved so the card locks into a done/cancelled state. */
status: 'pending' | 'publishing' | 'done' | 'cancelled';
resultLabel?: string;
}
export function SasquatchActionCard({ draft, onPublish, onCancel, status, resultLabel }: Props) {
const { t } = useTranslation();
const [edited, setEdited] = useState<SasquatchDraft>(draft);
const summary = summarizeDraft(edited, t);
if (status === 'done') {
return (
<div className="rounded-xl border border-[#ccff00]/30 bg-[#ccff00]/10 p-3 text-xs text-[#ccff00] flex items-center gap-2">
<Check className="h-4 w-4 shrink-0" /> {resultLabel ?? t('sasquatchUi.done')}
</div>
);
}
if (status === 'cancelled') {
return (
<div className="rounded-xl border border-white/10 bg-white/[0.03] p-3 text-xs text-gray-500 flex items-center gap-2">
<X className="h-4 w-4 shrink-0" /> {t('sasquatchUi.cancelledNothing')}
</div>
);
}
const busy = status === 'publishing';
return (
<div className="rounded-xl border border-red-500/30 bg-red-500/[0.06] p-3 space-y-2.5">
<div className="flex items-center gap-1.5 text-[10px] font-black uppercase tracking-wider text-red-400">
<Wand2 className="h-3 w-3" /> {t('sasquatchUi.draftReview')}
</div>
<p className="text-sm font-bold text-white">{summary.title}</p>
{/* Editable key field(s) */}
{edited.type === 'post_status' ? (
<Input
value={edited.text}
onChange={(e) => setEdited({ ...edited, text: e.target.value })}
disabled={busy}
className="h-9 bg-black/40 border-white/10 text-white text-sm"
placeholder={t('sasquatchUi.statusTextPlaceholder')}
/>
) : (
<div className="grid grid-cols-2 gap-2">
<Input
value={edited.title}
onChange={(e) => setEdited({ ...edited, title: e.target.value })}
disabled={busy}
className="col-span-2 h-9 bg-black/40 border-white/10 text-white text-sm"
placeholder={t('sasquatchUi.titlePlaceholder')}
/>
<Input
value={edited.price ?? ''}
onChange={(e) => setEdited({ ...edited, price: e.target.value ? Number(e.target.value) : undefined })}
disabled={busy}
type="number"
inputMode="numeric"
className="h-9 bg-black/40 border-white/10 text-white text-sm"
placeholder={t('sasquatchUi.priceSatsPlaceholder')}
/>
<Input
value={edited.condition ?? ''}
onChange={(e) => setEdited({ ...edited, condition: e.target.value })}
disabled={busy}
className="h-9 bg-black/40 border-white/10 text-white text-sm"
placeholder={t('sasquatchUi.conditionPlaceholder')}
/>
</div>
)}
{/* Read-only summary lines */}
<ul className="text-[11px] text-gray-400 space-y-0.5">
{summary.lines.map((l, i) => (
<li key={i} className="truncate">• {l}</li>
))}
</ul>
<div className="flex gap-2 pt-0.5">
<Button
variant="outline"
size="sm"
className="flex-1 gap-1.5 border-white/15 text-gray-300 h-8"
onClick={onCancel}
disabled={busy}
>
<X className="h-3.5 w-3.5" /> {t('sasquatchUi.cancel')}
</Button>
<Button
size="sm"
className="flex-1 gap-1.5 bg-red-500 hover:bg-red-600 font-bold h-8"
onClick={() => onPublish(edited)}
disabled={busy}
>
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Check className="h-3.5 w-3.5" />}
{summary.cta}
</Button>
</div>
</div>
);
}