~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).
153 lines
5.2 KiB
TypeScript
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>
|
|
);
|
|
}
|