import { useState, useEffect } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { X, Radio, Pencil } from 'lucide-react'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { NEWSLETTER_ISSUES } from '@/lib/newsletterIssues'; import { useTranslation } from '@/contexts/language'; import { isAdminPubkey } from '@/lib/admins'; import { cn } from '@/lib/utils'; /** * How many days the "Latest Drop" pill stays visible after a new issue. * After this window it auto-hides until the next issue drops. */ const PILL_VISIBLE_DAYS = 7; /** * Pre-filled Shakespeare chat URL that opens ready to write the next newsletter. * When you click it on a Monday, it takes you straight into the AI with the * prompt already typed — just hit send. */ const SHAKESPEARE_PROMPT = encodeURIComponent( 'Write this week\'s SkateSpots newsletter issue. Read the git log for the past 7 days, summarize all changes in plain skater language, pick a spot of the week if any new ones were submitted, and format it following the same structure as Issue #001. Keep it short, punchy, no corporate tone.' ); const WRITE_NEWSLETTER_URL = `https://shakespeare.diy/project/skatespots?prompt=${SHAKESPEARE_PROMPT}`; // ─── Helpers ───────────────────────────────────────────────────────────────── function isMonday(): boolean { return new Date().getDay() === 1; } function daysSinceIssue(isoDate: string): number { const issued = new Date(isoDate).getTime(); const now = Date.now(); return Math.floor((now - issued) / (1000 * 60 * 60 * 24)); } function formatIssueNumber(n: number): string { return `#${String(n).padStart(3, '0')}`; } // ─── Floating "Latest Drop" pill ───────────────────────────────────────────── function LatestDropPill() { const { t } = useTranslation(); const location = useLocation(); const latest = NEWSLETTER_ISSUES[0]; const [dismissed, setDismissed] = useLocalStorage( 'newsletter-pill-dismissed-issue', 0 ); // Guard: no archive yet ⇒ nothing to promote. if (!latest) return null; const age = daysSinceIssue(latest.date); // Hide on newsletter page, map page (to avoid conflicts with map UI), if too old, or if already dismissed const isMapPage = location.pathname === '/' || location.pathname === '/map'; if ( location.pathname === '/newsletter' || isMapPage || age > PILL_VISIBLE_DAYS || dismissed === latest.number ) { return null; } return (
{t('walletUi.newsletter.dropIsOut', { number: formatIssueNumber(latest.number) })} · {age === 0 ? t('time.today') : age === 1 ? t('time.yesterday') : t('walletUi.newsletter.daysAgo', { count: age })}
); } // ─── Monday admin reminder ──────────────────────────────────────────────────── function MondayAdminReminder() { const { t } = useTranslation(); const { user } = useCurrentUser(); const location = useLocation(); const [dismissed, setDismissed] = useLocalStorage( 'newsletter-monday-dismissed', '' ); // Today's date key — so it only shows once per Monday, not every page load const todayKey = new Date().toISOString().slice(0, 10); const isAdmin = isAdminPubkey(user?.pubkey); const alreadyDismissedToday = dismissed === todayKey; if ( !isAdmin || !isMonday() || alreadyDismissedToday || NEWSLETTER_ISSUES.length === 0 || location.pathname === '/newsletter' ) { return null; } return (
); } // ─── Combined overlay export ────────────────────────────────────────────────── export function NewsletterOverlay() { const [mounted, setMounted] = useState(false); // Avoid SSR hydration mismatch — only render client-side useEffect(() => { setMounted(true); }, []); if (!mounted) return null; return ( <> ); }