The entire skate-spots codebase was accidentally copied into src/src/ and public/public/ instead of src/ and public/ directly. This left the real src/ directory as blank MKStack template stubs, explaining why the landing page showed the default template instead of SkateSpots. Changes made: - Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx, all pages, components, hooks, contexts, lib, i18n, test) - Copied all files from public/public/ up to public/ (images, manifest, etc.) - Deleted src/src/ and public/public/ nested directories - Removed HelloWorld.tsx template stub from pages/ - Retained the previously fixed index.css (Tailwind v3 directives) Full skate-spots feature set is now active: - All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.) - NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler - Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.) - Outfit Variable font, Amber pre-check in main.tsx - All hooks (useSkateSpots, useCoaches, useZaps, etc.) - i18n support (en/es) Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
144 lines
4.8 KiB
TypeScript
144 lines
4.8 KiB
TypeScript
import { useState, useEffect } from 'react';
|
||
import { AlertTriangle, X, Download, CheckCircle } from 'lucide-react';
|
||
import { Button } from '@/components/ui/button';
|
||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||
import { useTranslation } from '@/contexts/LanguageContext';
|
||
import {
|
||
Sheet,
|
||
SheetContent,
|
||
SheetDescription,
|
||
SheetHeader,
|
||
SheetTitle,
|
||
} from '@/components/ui/sheet';
|
||
|
||
const BACKUP_REMINDER_KEY = 'backup-reminder-dismissed';
|
||
const BACKUP_CONFIRMED_KEY = 'backup-confirmed';
|
||
|
||
/**
|
||
* Persistent backup reminder that shows after signup
|
||
* Encourages users to backup their recovery phrase or nsec
|
||
* Uses a bottom sheet for better mobile experience
|
||
*/
|
||
export function BackupReminder() {
|
||
const { user } = useCurrentUser();
|
||
const [dismissed, setDismissed] = useLocalStorage(BACKUP_REMINDER_KEY, false);
|
||
const [confirmed, setConfirmed] = useLocalStorage(BACKUP_CONFIRMED_KEY, false);
|
||
const [showReminder, setShowReminder] = useState(false);
|
||
const { t } = useTranslation();
|
||
|
||
useEffect(() => {
|
||
if (!user) {
|
||
setShowReminder(false);
|
||
return;
|
||
}
|
||
|
||
// Show reminder if:
|
||
// 1. User is logged in
|
||
// 2. Not dismissed
|
||
// 3. Not confirmed they backed up
|
||
// Add a small delay so it doesn't immediately pop up on login
|
||
const timer = setTimeout(() => {
|
||
const shouldShow = !dismissed && !confirmed;
|
||
setShowReminder(shouldShow);
|
||
}, 2000);
|
||
|
||
return () => clearTimeout(timer);
|
||
}, [user, dismissed, confirmed]);
|
||
|
||
const handleDismiss = () => {
|
||
setDismissed(true);
|
||
setShowReminder(false);
|
||
};
|
||
|
||
const handleConfirmBackup = () => {
|
||
setConfirmed(true);
|
||
setShowReminder(false);
|
||
};
|
||
|
||
const handleBackupNow = () => {
|
||
window.location.href = '/settings?tab=security';
|
||
};
|
||
|
||
if (!showReminder) return null;
|
||
|
||
return (
|
||
<Sheet open={showReminder} onOpenChange={setShowReminder}>
|
||
<SheetContent
|
||
side="bottom"
|
||
className="rounded-t-2xl border-t-4 border-amber-500 bg-gradient-to-b from-amber-950/90 to-black/95 backdrop-blur-lg"
|
||
onInteractOutside={(e) => e.preventDefault()}
|
||
>
|
||
<SheetHeader className="text-left">
|
||
<div className="flex items-start justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<div className="flex-shrink-0 w-10 h-10 sm:w-12 sm:h-12 rounded-full bg-amber-500/20 flex items-center justify-center">
|
||
<AlertTriangle className="h-5 w-5 sm:h-6 sm:w-6 text-amber-500" />
|
||
</div>
|
||
<div className="flex-1">
|
||
<SheetTitle className="text-lg sm:text-xl font-bold text-amber-400">
|
||
{t('backup.title')}
|
||
</SheetTitle>
|
||
<SheetDescription className="text-xs sm:text-sm text-gray-400">
|
||
{t('backup.subtitle')}
|
||
</SheetDescription>
|
||
</div>
|
||
</div>
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
onClick={handleDismiss}
|
||
className="ml-2 -mt-1 text-gray-500 hover:text-gray-300"
|
||
>
|
||
<X className="h-5 w-5" />
|
||
</Button>
|
||
</div>
|
||
</SheetHeader>
|
||
|
||
<div className="mt-4 sm:mt-6 space-y-4">
|
||
{/* Warning message */}
|
||
<div className="p-3 sm:p-4 rounded-xl bg-amber-500/10 border border-amber-500/30">
|
||
<p className="text-sm sm:text-base text-gray-200 leading-relaxed">
|
||
{t('backup.message')}
|
||
</p>
|
||
<p className="text-sm sm:text-base text-gray-300 mt-2">
|
||
⚠️ {t('backup.warning')}
|
||
</p>
|
||
</div>
|
||
|
||
{/* Action buttons - stacked on mobile */}
|
||
<div className="flex flex-col sm:flex-row gap-3">
|
||
<Button
|
||
onClick={handleBackupNow}
|
||
className="flex-1 h-12 sm:h-11 text-sm sm:text-base font-semibold bg-amber-500 hover:bg-amber-600 text-black"
|
||
size="lg"
|
||
>
|
||
<Download className="mr-2 h-5 w-5" />
|
||
{t('backup.backupNow')}
|
||
</Button>
|
||
|
||
<Button
|
||
onClick={handleConfirmBackup}
|
||
variant="outline"
|
||
className="flex-1 h-12 sm:h-11 text-sm sm:text-base border-amber-500/50 text-amber-400 hover:bg-amber-500/20"
|
||
size="lg"
|
||
>
|
||
<CheckCircle className="mr-2 h-5 w-5" />
|
||
{t('backup.confirmed')}
|
||
</Button>
|
||
</div>
|
||
|
||
{/* Dismiss link */}
|
||
<div className="text-center pt-1">
|
||
<button
|
||
onClick={handleDismiss}
|
||
className="text-xs text-gray-500 hover:text-gray-400 underline"
|
||
>
|
||
{t('backup.remindLater')}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</SheetContent>
|
||
</Sheet>
|
||
);
|
||
}
|