- 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).
123 lines
4.4 KiB
TypeScript
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>
|
|
);
|
|
}
|