SkateSpots/src/components/BackupReminder.tsx
SkateSpots b2a6108eeb refactor: split non-component exports (react-refresh) into their own modules
Removes the last react-refresh disables by splitting exports instead:
- contexts/language.ts: Language/Translations types + LanguageContext +
  useTranslation/useT (LanguageProvider stays a component-only file); 12
  importers repointed.
- lib/sortOptions.ts: calculatePopularity + calculateDistance (out of SortDropdown)
- lib/trustFilter.ts: TrustFilterLevel + filterByTrustLevel (out of TrustFilter)
- EventCountdown: deleted the unused useEventStatus hook
- NewsletterPage: dropped the NEWSLETTER_ISSUES re-export shim
- AGENTS.md updated (lint stays clean with no blanket disables)

Gate: 101 tests, 0 errors/warnings, boot smoke green.
2026-09-26 23:29:42 +07:00

164 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
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/language';
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet';
const BACKUP_CONFIRMED_KEY = 'backup-confirmed';
// Session-scoped snooze key. "Remind me later" used to set a PERMANENT
// localStorage flag, which silently turned into "never remind again" — a real
// safety hole. We now snooze only for the current browser session, so the
// reminder returns on the next visit until the user actually saves their login.
// Only `backup-confirmed` (set when they back up) stops it for good.
const BACKUP_SNOOZE_KEY = 'backup-reminder-snoozed-session';
/**
* Gently-persistent backup reminder shown after signup.
* Encourages users to save their recovery code (nsec) so they never lose access.
* Non-blocking: one tap to back up, one tap to say "already done", or snooze for
* this session. It keeps coming back each session until backup is confirmed.
* Uses a bottom sheet for a good experience on both phone and desktop.
*/
export function BackupReminder() {
const { user } = useCurrentUser();
const [confirmed, setConfirmed] = useLocalStorage(BACKUP_CONFIRMED_KEY, false);
const [showReminder, setShowReminder] = useState(false);
const { t } = useTranslation();
const navigate = useNavigate();
useEffect(() => {
if (!user) {
setShowReminder(false);
return;
}
// Show reminder if:
// 1. User is logged in
// 2. Not snoozed for THIS session (resets when they come back)
// 3. Not confirmed they backed up (the only permanent stop)
// Add a small delay so it doesn't immediately pop up on login.
const timer = setTimeout(() => {
const snoozed = sessionStorage.getItem(BACKUP_SNOOZE_KEY) === 'true';
const shouldShow = !snoozed && !confirmed;
setShowReminder(shouldShow);
}, 2000);
return () => clearTimeout(timer);
}, [user, confirmed]);
const handleDismiss = () => {
// Snooze for this session only — it returns next visit until backup is done.
try {
sessionStorage.setItem(BACKUP_SNOOZE_KEY, 'true');
} catch {
// sessionStorage can throw in strict privacy modes — safe to ignore.
}
setShowReminder(false);
};
const handleConfirmBackup = () => {
setConfirmed(true);
setShowReminder(false);
};
const handleBackupNow = () => {
// Client-side nav (HashRouter). window.location.href forced a full reload to
// a non-hash path the router couldn't match — it dropped the user on a blank
// page instead of the security settings. navigate() keeps it in-app.
setShowReminder(false);
// SettingsPage deep-links via ?section=… (NOT ?tab=…) — using the wrong
// param silently dropped the user on the settings root instead of Security.
navigate('/settings?section=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>
);
}