SkateSpots/src/components/BetaDisclaimerDialog.tsx
SkateSpots 0b796440f1 feat(i18n): Spanish batch 2 — nav/chrome, events/shops/tours, crews/DMs, settings/auth, wallet/stash/newsletter/stats
~3,470 new keys (5,770 total, EN=ES), ~115 more files wired.
- nav + chromeUi: header, More menu, burger menu, footer, map toolbar widgets
  (sort/trust/weather), spot-type label helper.
- placesUi: events, shops, tours, food. socialUi: crews, DMs, notifications,
  collectives, crew chat/coordinator. settingsUi: settings, auth/signup,
  onboarding, profile edit, legal. walletUi: wallet/Cashu, stash, mints,
  app shop, newsletter, community stats, leaderboards, profile.
- Fixed 738 usages where an agent dropped the socialUi. prefix (caught by check:i18n).
2026-10-02 10:11:06 +07:00

153 lines
5.2 KiB
TypeScript

import { useEffect, useState } from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import { AlertTriangle, Code2, FlaskConical, ShieldOff, Wallet, ArrowRight } from 'lucide-react';
import { useTranslation } from '@/contexts/language';
/**
* Bump this version to re-show the disclaimer to everyone
* (e.g. after terms change or a major release).
*/
const DISCLAIMER_VERSION = 1;
const STORAGE_KEY = `skatespots_disclaimer_accepted_v${DISCLAIMER_VERSION}`;
const REPO_URL =
'https://github.com/search?q=SkateSpots';
export function BetaDisclaimerDialog() {
const [accepted, setAccepted] = useLocalStorage<boolean>(STORAGE_KEY, false);
const [open, setOpen] = useState(false);
const { t } = useTranslation();
// Open on first launch (after mount so SSR/hydration is clean)
useEffect(() => {
if (!accepted) {
const t = setTimeout(() => setOpen(true), 400);
return () => clearTimeout(t);
}
}, [accepted]);
const handleAccept = () => {
setAccepted(true);
setOpen(false);
};
return (
<Dialog
open={open}
// Prevent closing by clicking outside / Esc — must acknowledge
onOpenChange={(next) => {
if (!next) return;
setOpen(next);
}}
>
<DialogContent
className="bg-black border-2 border-[#ff0040]/50 mx-4 max-w-[calc(100vw-2rem)] sm:max-w-md p-0 gap-0 overflow-hidden [&>button]:hidden"
onInteractOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
>
{/* Accent gradient strip */}
<div
className="h-1 w-full"
style={{
background:
'linear-gradient(90deg, #ff0040, #ff6600, #ccff00, #ff6600, #ff0040)',
}}
/>
<div className="px-6 pt-6 pb-5">
<DialogHeader className="space-y-3">
{/* Icon badge */}
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-2xl border border-[#ff0040]/40 bg-[#ff0040]/10 shadow-[0_0_25px_rgba(255,0,64,0.25)]">
<AlertTriangle className="h-7 w-7 text-[#ff0040]" />
</div>
<DialogTitle className="text-center text-xl font-black uppercase tracking-wide text-white">
{t('settingsUi.betaDisclaimer.title')}
</DialogTitle>
<DialogDescription className="text-center text-sm text-gray-400">
{t('settingsUi.betaDisclaimer.subtitle')}
</DialogDescription>
</DialogHeader>
{/* Bullets */}
<div className="mt-5 space-y-3">
<DisclaimerRow
icon={<Code2 className="h-4 w-4 text-[#ccff00]" />}
title={t('settingsUi.betaDisclaimer.ossTitle')}
body={t('settingsUi.betaDisclaimer.ossBody')}
/>
<DisclaimerRow
icon={<FlaskConical className="h-4 w-4 text-[#ff6600]" />}
title={t('settingsUi.betaDisclaimer.betaTitle')}
body={t('settingsUi.betaDisclaimer.betaBody')}
/>
<DisclaimerRow
icon={<ShieldOff className="h-4 w-4 text-[#ff0040]" />}
title={t('settingsUi.betaDisclaimer.moneyTitle')}
body={t('settingsUi.betaDisclaimer.moneyBody')}
/>
</div>
{/* Fine print */}
<p className="mt-4 flex items-start gap-2 rounded-lg border border-gray-800 bg-gray-900/50 px-3 py-2.5 text-[11px] leading-relaxed text-gray-500">
<Wallet className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-gray-600" />
<span>
{t('settingsUi.betaDisclaimer.finePrint')}{' '}
<a
href={REPO_URL}
target="_blank"
rel="noopener noreferrer"
className="font-medium text-[#ccff00] underline decoration-dotted underline-offset-2 hover:text-[#ccff00]/80"
>
{t('settingsUi.betaDisclaimer.viewCode')}
</a>
.
</span>
</p>
</div>
<DialogFooter className="px-6 pb-6 pt-0 sm:flex-col">
<Button
onClick={handleAccept}
className="w-full bg-[#ff0040] hover:bg-[#ff0040]/90 text-white font-black uppercase tracking-wide h-12 shadow-[0_0_20px_rgba(255,0,64,0.3)] transition-all hover:shadow-[0_0_28px_rgba(255,0,64,0.45)]"
>
{t('settingsUi.betaDisclaimer.accept')}
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
function DisclaimerRow({
icon,
title,
body,
}: {
icon: React.ReactNode;
title: string;
body: string;
}) {
return (
<div className="flex items-start gap-3">
<div className="mt-0.5 flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg border border-gray-800 bg-gray-900/60">
{icon}
</div>
<div className="min-w-0">
<p className="text-sm font-bold text-white">{title}</p>
<p className="text-xs leading-relaxed text-gray-400">{body}</p>
</div>
</div>
);
}