mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-22 14:51:45 +00:00
v2.1.0 — Real-time collaboration, performance & security overhaul
Real-Time Collaboration (WebSocket): - WebSocket server with JWT auth and trip-based rooms - Live sync for all CRUD operations (places, assignments, days, notes, budget, packing, reservations, files) - Socket-based exclusion to prevent duplicate updates - Auto-reconnect with exponential backoff - Assignment move sync between days Performance: - 16 database indexes on all foreign key columns - N+1 query fix in places, assignments and days endpoints - Marker clustering (react-leaflet-cluster) with configurable radius - List virtualization (react-window) for places sidebar - useMemo for filtered places - SQLite WAL mode + busy_timeout for concurrent writes - Weather API: server-side cache (1h forecast, 15min current) + client sessionStorage - Google Places photos: persisted to DB after first fetch - Google Details: 3-tier cache (memory → sessionStorage → API) Security: - CORS auto-configuration (production: same-origin, dev: open) - API keys removed from /auth/me response - Admin-only endpoint for reading API keys - Path traversal prevention in cover image deletion - JWT secret persisted to file (survives restarts) - Avatar upload file extension whitelist - API key fallback: normal users use admin's key without exposure - Case-insensitive email login Dark Mode: - Fixed hardcoded colors across PackingList, Budget, ReservationModal, ReservationsPanel - Mobile map buttons and sidebar sheets respect dark mode - Cluster markers always dark UI/UX: - Redesigned login page with animated planes, stars and feature cards - Admin: create user functionality with CustomSelect - Mobile: day-picker popup for assigning places to days - Mobile: touch-friendly reorder buttons (32px targets) - Mobile: responsive text (shorter labels on small screens) - Packing list: index-based category colors - i18n: translated date picker placeholder, fixed German labels - Default map tile: CartoDB Light
This commit is contained in:
@@ -19,6 +19,7 @@ const de = {
|
||||
'common.no': 'Nein',
|
||||
'common.or': 'oder',
|
||||
'common.none': 'Keine',
|
||||
'common.date': 'Datum',
|
||||
'common.rename': 'Umbenennen',
|
||||
'common.name': 'Name',
|
||||
'common.email': 'E-Mail',
|
||||
@@ -139,10 +140,24 @@ const de = {
|
||||
// Login
|
||||
'login.error': 'Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen.',
|
||||
'login.tagline': 'Deine Reisen.\nDein Plan.',
|
||||
'login.description': 'Plane Reisen mit interaktiven Karten, Tagesabläufen und smarten Checklisten.',
|
||||
'login.features.places': 'Orte',
|
||||
'login.features.schedule': 'Tagesplan',
|
||||
'login.features.packing': 'Packliste',
|
||||
'login.description': 'Plane Reisen gemeinsam mit interaktiven Karten, Budgets und Echtzeit-Sync.',
|
||||
'login.features.maps': 'Interaktive Karten',
|
||||
'login.features.mapsDesc': 'Google Places, Routen & Clustering',
|
||||
'login.features.realtime': 'Echtzeit-Sync',
|
||||
'login.features.realtimeDesc': 'Gemeinsam planen via WebSocket',
|
||||
'login.features.budget': 'Budget-Tracking',
|
||||
'login.features.budgetDesc': 'Kategorien, Diagramme & Pro-Kopf',
|
||||
'login.features.collab': 'Zusammenarbeit',
|
||||
'login.features.collabDesc': 'Multi-User mit geteilten Reisen',
|
||||
'login.features.packing': 'Packlisten',
|
||||
'login.features.packingDesc': 'Kategorien & Fortschritt',
|
||||
'login.features.bookings': 'Buchungen',
|
||||
'login.features.bookingsDesc': 'Flüge, Hotels, Restaurants & mehr',
|
||||
'login.features.files': 'Dokumente',
|
||||
'login.features.filesDesc': 'Dateien hochladen & verwalten',
|
||||
'login.features.routes': 'Routenoptimierung',
|
||||
'login.features.routesDesc': 'Auto-Optimierung & Google Maps Export',
|
||||
'login.selfHosted': 'Self-hosted \u00B7 Open Source \u00B7 Deine Daten bleiben bei dir',
|
||||
'login.title': 'Anmelden',
|
||||
'login.subtitle': 'Willkommen zurück',
|
||||
'login.signingIn': 'Anmelden…',
|
||||
@@ -155,6 +170,7 @@ const de = {
|
||||
'login.noAccount': 'Noch kein Konto?',
|
||||
'login.hasAccount': 'Bereits ein Konto?',
|
||||
'login.register': 'Registrieren',
|
||||
'login.emailPlaceholder': 'deine@email.de',
|
||||
'login.username': 'Benutzername',
|
||||
|
||||
// Admin
|
||||
@@ -186,6 +202,10 @@ const de = {
|
||||
'admin.toast.userDeleted': 'Benutzer gelöscht',
|
||||
'admin.toast.deleteError': 'Fehler beim Löschen',
|
||||
'admin.toast.cannotDeleteSelf': 'Eigenes Konto kann nicht gelöscht werden',
|
||||
'admin.toast.userCreated': 'Benutzer erstellt',
|
||||
'admin.toast.createError': 'Fehler beim Erstellen des Benutzers',
|
||||
'admin.toast.fieldsRequired': 'Benutzername, E-Mail und Passwort sind erforderlich',
|
||||
'admin.createUser': 'Benutzer anlegen',
|
||||
'admin.tabs.settings': 'Einstellungen',
|
||||
'admin.allowRegistration': 'Registrierung erlauben',
|
||||
'admin.allowRegistrationHint': 'Neue Benutzer können sich selbst registrieren',
|
||||
@@ -201,9 +221,9 @@ const de = {
|
||||
|
||||
// Trip Planner
|
||||
'trip.tabs.plan': 'Planung',
|
||||
'trip.tabs.reservations': 'Reservierungen',
|
||||
'trip.tabs.reservations': 'Buchungen',
|
||||
'trip.tabs.packing': 'Packliste',
|
||||
'trip.tabs.packingShort': 'Packen',
|
||||
'trip.tabs.packingShort': 'Packliste',
|
||||
'trip.tabs.budget': 'Budget',
|
||||
'trip.tabs.files': 'Dateien',
|
||||
'trip.loading': 'Reise wird geladen...',
|
||||
@@ -247,6 +267,7 @@ const de = {
|
||||
|
||||
// Places Sidebar
|
||||
'places.addPlace': 'Ort hinzufügen',
|
||||
'places.assignToDay': 'Zu welchem Tag hinzufügen?',
|
||||
'places.all': 'Alle',
|
||||
'places.unplanned': 'Ungeplant',
|
||||
'places.search': 'Orte suchen...',
|
||||
@@ -297,7 +318,7 @@ const de = {
|
||||
'inspector.website': 'Webseite öffnen',
|
||||
|
||||
// Reservations
|
||||
'reservations.title': 'Reservierungen',
|
||||
'reservations.title': 'Buchungen',
|
||||
'reservations.empty': 'Keine Reservierungen vorhanden',
|
||||
'reservations.emptyHint': 'Füge Reservierungen für Flüge, Hotels und mehr hinzu',
|
||||
'reservations.add': 'Reservierung hinzufügen',
|
||||
@@ -401,6 +422,7 @@ const de = {
|
||||
'packing.empty': 'Packliste ist leer',
|
||||
'packing.progress': '{packed} von {total} gepackt ({percent}%)',
|
||||
'packing.clearChecked': '{count} abgehakte entfernen',
|
||||
'packing.clearCheckedShort': '{count} entfernen',
|
||||
'packing.suggestions': 'Vorschläge',
|
||||
'packing.suggestionsTitle': 'Vorschläge hinzufügen',
|
||||
'packing.allSuggested': 'Alle Vorschläge hinzugefügt',
|
||||
|
||||
@@ -19,6 +19,7 @@ const en = {
|
||||
'common.no': 'No',
|
||||
'common.or': 'or',
|
||||
'common.none': 'None',
|
||||
'common.date': 'Date',
|
||||
'common.rename': 'Rename',
|
||||
'common.name': 'Name',
|
||||
'common.email': 'Email',
|
||||
@@ -139,10 +140,24 @@ const en = {
|
||||
// Login
|
||||
'login.error': 'Login failed. Please check your credentials.',
|
||||
'login.tagline': 'Your Trips.\nYour Plan.',
|
||||
'login.description': 'Plan trips with interactive maps, daily schedules and smart checklists.',
|
||||
'login.features.places': 'Places',
|
||||
'login.features.schedule': 'Schedule',
|
||||
'login.features.packing': 'Packing List',
|
||||
'login.description': 'Plan trips collaboratively with interactive maps, budgets, and real-time sync.',
|
||||
'login.features.maps': 'Interactive Maps',
|
||||
'login.features.mapsDesc': 'Google Places, routes & clustering',
|
||||
'login.features.realtime': 'Real-Time Sync',
|
||||
'login.features.realtimeDesc': 'Plan together via WebSocket',
|
||||
'login.features.budget': 'Budget Tracking',
|
||||
'login.features.budgetDesc': 'Categories, charts & per-person costs',
|
||||
'login.features.collab': 'Collaboration',
|
||||
'login.features.collabDesc': 'Multi-user with shared trips',
|
||||
'login.features.packing': 'Packing Lists',
|
||||
'login.features.packingDesc': 'Categories, progress & suggestions',
|
||||
'login.features.bookings': 'Reservations',
|
||||
'login.features.bookingsDesc': 'Flights, hotels, restaurants & more',
|
||||
'login.features.files': 'Documents',
|
||||
'login.features.filesDesc': 'Upload & manage documents',
|
||||
'login.features.routes': 'Smart Routes',
|
||||
'login.features.routesDesc': 'Auto-optimize & Google Maps export',
|
||||
'login.selfHosted': 'Self-hosted \u00B7 Open Source \u00B7 Your data stays yours',
|
||||
'login.title': 'Sign In',
|
||||
'login.subtitle': 'Welcome back',
|
||||
'login.signingIn': 'Signing in…',
|
||||
@@ -155,6 +170,7 @@ const en = {
|
||||
'login.noAccount': "Don't have an account?",
|
||||
'login.hasAccount': 'Already have an account?',
|
||||
'login.register': 'Register',
|
||||
'login.emailPlaceholder': 'your@email.com',
|
||||
'login.username': 'Username',
|
||||
|
||||
// Admin
|
||||
@@ -186,6 +202,10 @@ const en = {
|
||||
'admin.toast.userDeleted': 'User deleted',
|
||||
'admin.toast.deleteError': 'Failed to delete',
|
||||
'admin.toast.cannotDeleteSelf': 'Cannot delete your own account',
|
||||
'admin.toast.userCreated': 'User created',
|
||||
'admin.toast.createError': 'Failed to create user',
|
||||
'admin.toast.fieldsRequired': 'Username, email and password are required',
|
||||
'admin.createUser': 'Create User',
|
||||
'admin.tabs.settings': 'Settings',
|
||||
'admin.allowRegistration': 'Allow Registration',
|
||||
'admin.allowRegistrationHint': 'New users can register themselves',
|
||||
@@ -247,6 +267,7 @@ const en = {
|
||||
|
||||
// Places Sidebar
|
||||
'places.addPlace': 'Add Place',
|
||||
'places.assignToDay': 'Add to which day?',
|
||||
'places.all': 'All',
|
||||
'places.unplanned': 'Unplanned',
|
||||
'places.search': 'Search places...',
|
||||
@@ -401,6 +422,7 @@ const en = {
|
||||
'packing.empty': 'Packing list is empty',
|
||||
'packing.progress': '{packed} of {total} packed ({percent}%)',
|
||||
'packing.clearChecked': 'Remove {count} checked',
|
||||
'packing.clearCheckedShort': 'Remove {count}',
|
||||
'packing.suggestions': 'Suggestions',
|
||||
'packing.suggestionsTitle': 'Add Suggestions',
|
||||
'packing.allSuggested': 'All suggestions added',
|
||||
|
||||
Reference in New Issue
Block a user