Add persistent admin analytics, backup restore workflow, and tabbed admin UI
This commit is contained in:
+56
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user