Add persistent admin analytics, backup restore workflow, and tabbed admin UI

This commit is contained in:
nmemmert
2026-04-12 13:12:03 -04:00
parent bac22950ba
commit 738cc2a412
6 changed files with 1707 additions and 109 deletions
+56
View File
@@ -13,6 +13,7 @@ const YOUTUBE_URL = 'https://www.youtube.com/@blackzebraem5558'
const AMAZON_MUSIC_URL =
'https://music.amazon.com/podcasts/202322bf-db86-4e7d-9a6b-4db7cbccbccf/verse-by-verse-with-nate'
const FACEBOOK_URL = 'https://facebook.com/versebyversewithnate'
const CONSENT_KEY = 'vbn_analytics_consent_choice'
function FacebookIcon() {
return (
@@ -176,6 +177,56 @@ function ContactForm() {
)
}
function AnalyticsConsentBanner() {
const [choice, setChoice] = useState<'unknown' | 'accepted' | 'declined'>(() => {
const saved = localStorage.getItem(CONSENT_KEY)
if (saved === 'accepted' || saved === 'declined') return saved
return 'unknown'
})
async function sendChoice(consent: boolean) {
await fetch('/api/analytics-consent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ consent }),
})
}
async function accept() {
setChoice('accepted')
localStorage.setItem(CONSENT_KEY, 'accepted')
try {
await sendChoice(true)
} catch {
// Keep local preference even if network fails.
}
}
async function decline() {
setChoice('declined')
localStorage.setItem(CONSENT_KEY, 'declined')
try {
await sendChoice(false)
} catch {
// Keep local preference even if network fails.
}
}
if (choice !== 'unknown') return null
return (
<div className="consent-banner" role="region" aria-label="Analytics consent">
<p>
We use optional analytics cookies to measure visits and location trends for site improvement.
</p>
<div className="consent-actions">
<button type="button" className="btn-primary" onClick={accept}>Accept</button>
<button type="button" className="btn-secondary" onClick={decline}>Decline</button>
</div>
</div>
)
}
function LandingPage({ content }: { content: SiteContent }) {
return (
<div className="site">
@@ -468,7 +519,12 @@ function LandingPage({ content }: { content: SiteContent }) {
])}
</nav>
<p className="footer-copy">© 2026 Nate Emmert · Made with faith.</p>
<p className="footer-privacy">
Privacy: with consent, analytics may store masked IP-based location data (country/state/county/city) and returning visitor activity.
</p>
</footer>
<AnalyticsConsentBanner />
</div>
)
}