- First-visit language auto-detect (Spanish devices land in es; stored choice wins). - Sasquatch AI replies in the app language (skate trick names kept English). - i18n safety net: scripts/check-i18n.mjs wired into npm test — fails on any t() key missing from en/es or any en/es key drift. - ~1,680 new keys (2,297 total) across mapUi / spotUi / marketUi / labUi and ~65 files wired to t(): Map, spots (detail/create/edit/chat), marketplace, trades, offers, escrow, shipping, The Lab, /become/* forms, coaches, skaters. - Policy: only skate TRICK names stay English; user-generated content untouched.
74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { Lock } from 'lucide-react';
|
|
import { parseEscrowToken, type EscrowTokenInfo } from '@/lib/cashuEscrow';
|
|
import { cn } from '@/lib/utils';
|
|
import { useTranslation } from '@/contexts/language';
|
|
|
|
function hostOf(url: string): string {
|
|
try {
|
|
return new URL(url).host;
|
|
} catch {
|
|
return url;
|
|
}
|
|
}
|
|
|
|
function formatDate(seconds: number): string {
|
|
return new Date(seconds * 1000).toLocaleDateString(undefined, {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Read-only escrow summary for a locked Cashu token. Pure/offline (uses
|
|
* `parseEscrowToken`), so it renders on web as well as native — only the
|
|
* mint-touching create/redeem actions are native-only.
|
|
*/
|
|
export function EscrowPanel({ token, className }: { token: string; className?: string }) {
|
|
const { t } = useTranslation();
|
|
const info = useMemo<EscrowTokenInfo | null>(() => {
|
|
try {
|
|
return parseEscrowToken(token);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}, [token]);
|
|
|
|
if (!info || !info.locked) return null;
|
|
const locktime = info.policy?.locktime;
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'rounded-xl border border-[#ff6600]/30 bg-[#ff6600]/5 p-3 text-xs',
|
|
className,
|
|
)}
|
|
>
|
|
<div className="flex items-center gap-2 font-bold text-[#ff6600]">
|
|
<Lock className="h-3.5 w-3.5" />
|
|
{t('marketUi.escrowPanel.escrowed', { amount: info.amount.toLocaleString() })}
|
|
</div>
|
|
<div className="mt-1 space-y-0.5 text-gray-400">
|
|
<p>
|
|
{t('marketUi.escrowPanel.mint')}: <span className="text-gray-300">{hostOf(info.mint)}</span>
|
|
</p>
|
|
{locktime ? (
|
|
<p>
|
|
{info.refundable ? (
|
|
<>{t('marketUi.escrowPanel.refundSinceBefore')}{' '}
|
|
<span className="text-[#ccff00]">{formatDate(locktime)}</span>
|
|
</>
|
|
) : (
|
|
<>{t('marketUi.escrowPanel.redeemBefore')}{' '}
|
|
<span className="text-gray-300">{formatDate(locktime)}</span>
|
|
</>
|
|
)}
|
|
</p>
|
|
) : (
|
|
<p>{t('marketUi.escrowPanel.noExpiry')}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|