Fix demo banner: add EN/DE translations, fix navbar overlap
Banner is now in document flow instead of fixed position, so it no longer covers the navigation. Language follows the app's i18n setting.
This commit is contained in:
@@ -90,7 +90,6 @@ export default function App() {
|
||||
<TranslationProvider>
|
||||
<ToastContainer />
|
||||
{demoMode && isAuthenticated && <DemoBanner />}
|
||||
<div style={demoMode && isAuthenticated ? { paddingTop: 36 } : undefined}>
|
||||
<Routes>
|
||||
<Route path="/" element={<RootRedirect />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
@@ -137,7 +136,6 @@ export default function App() {
|
||||
/>
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</TranslationProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,32 +1,65 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Info, X, Github, Shield, Key, Users, Database, ChevronDown, ChevronUp } from 'lucide-react'
|
||||
import { Info, Github, Shield, Key, Users, Database, ChevronDown, ChevronUp } from 'lucide-react'
|
||||
import { useTranslation } from '../../i18n'
|
||||
|
||||
const texts = {
|
||||
de: {
|
||||
title: 'Demo-Modus',
|
||||
resetInfo: 'Aenderungen werden stuendlich zurueckgesetzt',
|
||||
nextReset: 'naechster Reset in ~{min} Min.',
|
||||
moreInfo: 'Mehr Info',
|
||||
lessInfo: 'Weniger',
|
||||
description: 'Du nutzt die NOMAD Demo. Du kannst Reisen ansehen, bearbeiten und eigene erstellen — alles wird jede Stunde automatisch zurueckgesetzt.',
|
||||
fullVersionTitle: 'Diese Funktionen sind in der Vollversion verfuegbar:',
|
||||
features: [
|
||||
'API-Schluessel verwalten (Google Maps, Wetter)',
|
||||
'Benutzer & Rechte verwalten',
|
||||
'Automatische Backups & Wiederherstellung',
|
||||
'Registrierung & Sicherheitseinstellungen',
|
||||
],
|
||||
selfHost: 'NOMAD ist Open Source — ',
|
||||
selfHostLink: 'selbst hosten',
|
||||
},
|
||||
en: {
|
||||
title: 'Demo Mode',
|
||||
resetInfo: 'Changes are reset every hour',
|
||||
nextReset: 'next reset in ~{min} min.',
|
||||
moreInfo: 'More info',
|
||||
lessInfo: 'Less',
|
||||
description: 'You are using the NOMAD demo. You can view, edit and create trips — everything is automatically reset every hour.',
|
||||
fullVersionTitle: 'These features are available in the full version:',
|
||||
features: [
|
||||
'API key management (Google Maps, Weather)',
|
||||
'User & permission management',
|
||||
'Automatic backups & restore',
|
||||
'Registration & security settings',
|
||||
],
|
||||
selfHost: 'NOMAD is open source — ',
|
||||
selfHostLink: 'self-host it',
|
||||
},
|
||||
}
|
||||
|
||||
const featureIcons = [Key, Users, Database, Shield]
|
||||
|
||||
export default function DemoBanner() {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const [minutesLeft, setMinutesLeft] = useState(null)
|
||||
const { language } = useTranslation()
|
||||
const t = texts[language] || texts.en
|
||||
|
||||
useEffect(() => {
|
||||
const update = () => {
|
||||
const now = new Date()
|
||||
setMinutesLeft(59 - now.getMinutes())
|
||||
}
|
||||
const update = () => setMinutesLeft(59 - new Date().getMinutes())
|
||||
update()
|
||||
const interval = setInterval(update, 30000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const adminFeatures = [
|
||||
{ icon: Key, text: 'API-Schluessel verwalten (Google Maps, Wetter)' },
|
||||
{ icon: Users, text: 'Benutzer & Rechte verwalten' },
|
||||
{ icon: Database, text: 'Automatische Backups & Wiederherstellung' },
|
||||
{ icon: Shield, text: 'Registrierung & Sicherheitseinstellungen' },
|
||||
]
|
||||
const bannerHeight = expanded ? undefined : 36
|
||||
|
||||
return (
|
||||
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, zIndex: 300 }}>
|
||||
<div style={{ background: 'linear-gradient(135deg, #f59e0b, #d97706)', zIndex: 300 }}>
|
||||
{/* Main banner bar */}
|
||||
<div style={{
|
||||
background: 'linear-gradient(135deg, #f59e0b, #d97706)',
|
||||
color: '#451a03',
|
||||
padding: '8px 16px',
|
||||
display: 'flex',
|
||||
@@ -37,16 +70,15 @@ export default function DemoBanner() {
|
||||
fontWeight: 600,
|
||||
fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif",
|
||||
minHeight: 36,
|
||||
boxShadow: '0 2px 8px rgba(217, 119, 6, 0.3)',
|
||||
}}>
|
||||
<Info size={15} style={{ flexShrink: 0 }} />
|
||||
<span>
|
||||
Demo-Modus
|
||||
{t.title}
|
||||
<span style={{ fontWeight: 400, margin: '0 6px' }}>·</span>
|
||||
Aenderungen werden stuendlich zurueckgesetzt
|
||||
{t.resetInfo}
|
||||
{minutesLeft !== null && (
|
||||
<span style={{ fontWeight: 400, opacity: 0.8, marginLeft: 4 }}>
|
||||
(naechster Reset in ~{minutesLeft} Min.)
|
||||
({t.nextReset.replace('{min}', minutesLeft)})
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
@@ -69,7 +101,7 @@ export default function DemoBanner() {
|
||||
}}
|
||||
>
|
||||
{expanded ? <ChevronUp size={13} /> : <ChevronDown size={13} />}
|
||||
{expanded ? 'Weniger' : 'Mehr Info'}
|
||||
{expanded ? t.lessInfo : t.moreInfo}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -80,25 +112,26 @@ export default function DemoBanner() {
|
||||
borderBottom: '1px solid #fbbf24',
|
||||
padding: '16px 24px',
|
||||
fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif",
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.08)',
|
||||
}}>
|
||||
<div style={{ maxWidth: 640, margin: '0 auto' }}>
|
||||
<p style={{ fontSize: 13, color: '#92400e', margin: '0 0 12px', lineHeight: 1.6 }}>
|
||||
Du nutzt die NOMAD Demo. Du kannst Reisen ansehen, bearbeiten und eigene erstellen —
|
||||
alles wird <strong>jede Stunde automatisch zurueckgesetzt</strong>.
|
||||
{t.description}
|
||||
</p>
|
||||
|
||||
<p style={{ fontSize: 12, fontWeight: 700, color: '#78350f', margin: '0 0 8px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
||||
Diese Funktionen sind in der Vollversion verfuegbar:
|
||||
{t.fullVersionTitle}
|
||||
</p>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: 6 }}>
|
||||
{adminFeatures.map(({ icon: Icon, text }) => (
|
||||
<div key={text} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5, color: '#92400e' }}>
|
||||
<Icon size={14} style={{ flexShrink: 0, opacity: 0.7 }} />
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
))}
|
||||
{t.features.map((text, i) => {
|
||||
const Icon = featureIcons[i]
|
||||
return (
|
||||
<div key={text} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5, color: '#92400e' }}>
|
||||
<Icon size={14} style={{ flexShrink: 0, opacity: 0.7 }} />
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
@@ -112,14 +145,14 @@ export default function DemoBanner() {
|
||||
color: '#92400e',
|
||||
}}>
|
||||
<Github size={14} />
|
||||
<span>NOMAD ist Open Source — </span>
|
||||
<span>{t.selfHost}</span>
|
||||
<a
|
||||
href="https://github.com/mauriceboe/NOMAD"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: '#78350f', fontWeight: 700, textDecoration: 'underline' }}
|
||||
>
|
||||
selbst hosten →
|
||||
{t.selfHostLink} →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user