Demo popup: show on every dashboard visit, add upload notice (v2.2.4)

- Popup now shows every time user visits dashboard (not session-cached)
- Only shows on dashboard, not other pages
- Added upload disabled notice with amber highlight
- Upload listed as first full-version feature
This commit is contained in:
Maurice
2026-03-19 15:17:31 +01:00
parent c582a7b6c8
commit 9b0755debc
4 changed files with 23 additions and 15 deletions
-1
View File
@@ -87,7 +87,6 @@ export default function App() {
return ( return (
<TranslationProvider> <TranslationProvider>
<ToastContainer /> <ToastContainer />
{demoMode && isAuthenticated && <DemoBanner />}
<Routes> <Routes>
<Route path="/" element={<RootRedirect />} /> <Route path="/" element={<RootRedirect />} />
<Route path="/login" element={<LoginPage />} /> <Route path="/login" element={<LoginPage />} />
+19 -13
View File
@@ -1,17 +1,18 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { Info, Github, Shield, Key, Users, Database, X } from 'lucide-react' import { Info, Github, Shield, Key, Users, Database, Upload } from 'lucide-react'
import { useTranslation } from '../../i18n' import { useTranslation } from '../../i18n'
const texts = { const texts = {
de: { de: {
title: 'Willkommen zur NOMAD Demo', title: 'Willkommen zur NOMAD Demo',
description: 'Du kannst Reisen ansehen, bearbeiten und eigene erstellen. Alle Aenderungen werden jede Stunde automatisch zurueckgesetzt.', description: 'Du kannst Reisen ansehen, bearbeiten und eigene erstellen. Alle Aenderungen werden jede Stunde automatisch zurueckgesetzt.',
uploadNote: 'Datei-Uploads (Fotos, Dokumente, Cover) sind in der Demo deaktiviert.',
fullVersionTitle: 'In der Vollversion zusaetzlich verfuegbar:', fullVersionTitle: 'In der Vollversion zusaetzlich verfuegbar:',
features: [ features: [
'Datei-Uploads (Fotos, Dokumente, Reise-Cover)',
'API-Schluessel verwalten (Google Maps, Wetter)', 'API-Schluessel verwalten (Google Maps, Wetter)',
'Benutzer & Rechte verwalten', 'Benutzer & Rechte verwalten',
'Automatische Backups & Wiederherstellung', 'Automatische Backups & Wiederherstellung',
'Registrierung & Sicherheitseinstellungen',
], ],
selfHost: 'NOMAD ist Open Source — ', selfHost: 'NOMAD ist Open Source — ',
selfHostLink: 'selbst hosten', selfHostLink: 'selbst hosten',
@@ -20,12 +21,13 @@ const texts = {
en: { en: {
title: 'Welcome to the NOMAD Demo', title: 'Welcome to the NOMAD Demo',
description: 'You can view, edit and create trips. All changes are automatically reset every hour.', description: 'You can view, edit and create trips. All changes are automatically reset every hour.',
uploadNote: 'File uploads (photos, documents, covers) are disabled in demo mode.',
fullVersionTitle: 'Additionally available in the full version:', fullVersionTitle: 'Additionally available in the full version:',
features: [ features: [
'File uploads (photos, documents, trip covers)',
'API key management (Google Maps, Weather)', 'API key management (Google Maps, Weather)',
'User & permission management', 'User & permission management',
'Automatic backups & restore', 'Automatic backups & restore',
'Registration & security settings',
], ],
selfHost: 'NOMAD is open source — ', selfHost: 'NOMAD is open source — ',
selfHostLink: 'self-host it', selfHostLink: 'self-host it',
@@ -33,20 +35,15 @@ const texts = {
}, },
} }
const featureIcons = [Key, Users, Database, Shield] const featureIcons = [Upload, Key, Users, Database]
export default function DemoBanner() { export default function DemoBanner() {
const [dismissed, setDismissed] = useState(() => sessionStorage.getItem('demo_dismissed') === 'true') const [dismissed, setDismissed] = useState(false)
const { language } = useTranslation() const { language } = useTranslation()
const t = texts[language] || texts.en const t = texts[language] || texts.en
if (dismissed) return null if (dismissed) return null
const handleClose = () => {
sessionStorage.setItem('demo_dismissed', 'true')
setDismissed(true)
}
return ( return (
<div style={{ <div style={{
position: 'fixed', inset: 0, zIndex: 9999, position: 'fixed', inset: 0, zIndex: 9999,
@@ -54,7 +51,7 @@ export default function DemoBanner() {
display: 'flex', alignItems: 'center', justifyContent: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center',
padding: 24, padding: 24,
fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif", fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif",
}} onClick={handleClose}> }} onClick={() => setDismissed(true)}>
<div style={{ <div style={{
background: 'white', borderRadius: 20, padding: '32px 28px 24px', background: 'white', borderRadius: 20, padding: '32px 28px 24px',
maxWidth: 440, width: '100%', maxWidth: 440, width: '100%',
@@ -74,10 +71,19 @@ export default function DemoBanner() {
</h2> </h2>
</div> </div>
<p style={{ fontSize: 14, color: '#6b7280', lineHeight: 1.6, margin: '0 0 20px' }}> <p style={{ fontSize: 14, color: '#6b7280', lineHeight: 1.6, margin: '0 0 12px' }}>
{t.description} {t.description}
</p> </p>
<p style={{
fontSize: 13, color: '#b45309', lineHeight: 1.5, margin: '0 0 20px',
background: '#fffbeb', border: '1px solid #fde68a', borderRadius: 10, padding: '10px 12px',
display: 'flex', alignItems: 'center', gap: 8,
}}>
<Upload size={15} style={{ flexShrink: 0 }} />
{t.uploadNote}
</p>
<p style={{ fontSize: 12, fontWeight: 700, color: '#374151', margin: '0 0 10px', textTransform: 'uppercase', letterSpacing: '0.05em' }}> <p style={{ fontSize: 12, fontWeight: 700, color: '#374151', margin: '0 0 10px', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
{t.fullVersionTitle} {t.fullVersionTitle}
</p> </p>
@@ -107,7 +113,7 @@ export default function DemoBanner() {
</a> </a>
</div> </div>
<button onClick={handleClose} style={{ <button onClick={() => setDismissed(true)} style={{
background: '#111827', color: 'white', border: 'none', background: '#111827', color: 'white', border: 'none',
borderRadius: 10, padding: '8px 20px', fontSize: 13, borderRadius: 10, padding: '8px 20px', fontSize: 13,
fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit',
+3
View File
@@ -4,6 +4,7 @@ import { tripsApi } from '../api/client'
import { useAuthStore } from '../store/authStore' import { useAuthStore } from '../store/authStore'
import { useTranslation } from '../i18n' import { useTranslation } from '../i18n'
import Navbar from '../components/Layout/Navbar' import Navbar from '../components/Layout/Navbar'
import DemoBanner from '../components/Layout/DemoBanner'
import TravelStats from '../components/Dashboard/TravelStats' import TravelStats from '../components/Dashboard/TravelStats'
import TripFormModal from '../components/Trips/TripFormModal' import TripFormModal from '../components/Trips/TripFormModal'
import { useToast } from '../components/shared/Toast' import { useToast } from '../components/shared/Toast'
@@ -348,6 +349,7 @@ export default function DashboardPage() {
const navigate = useNavigate() const navigate = useNavigate()
const toast = useToast() const toast = useToast()
const { t, locale } = useTranslation() const { t, locale } = useTranslation()
const { demoMode } = useAuthStore()
useEffect(() => { loadTrips() }, []) useEffect(() => { loadTrips() }, [])
@@ -437,6 +439,7 @@ export default function DashboardPage() {
return ( return (
<div style={{ minHeight: '100vh', background: 'var(--bg-secondary)', ...font }}> <div style={{ minHeight: '100vh', background: 'var(--bg-secondary)', ...font }}>
<Navbar /> <Navbar />
{demoMode && <DemoBanner />}
<div style={{ paddingTop: 56 }}> <div style={{ paddingTop: 56 }}>
<div style={{ maxWidth: 1300, margin: '0 auto', padding: '32px 20px 60px' }}> <div style={{ maxWidth: 1300, margin: '0 auto', padding: '32px 20px 60px' }}>
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "nomad-server", "name": "nomad-server",
"version": "2.2.3", "version": "2.2.4",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
"start": "node --experimental-sqlite src/index.js", "start": "node --experimental-sqlite src/index.js",