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:
Maurice
2026-03-19 12:44:22 +01:00
parent f000943489
commit 74f19f3312
44 changed files with 1714 additions and 363 deletions

View File

@@ -2,6 +2,37 @@
@tailwind components;
@tailwind utilities;
/* Reorder buttons: desktop = original style; mobile = always visible, larger touch targets */
.reorder-buttons {
flex-direction: column;
opacity: 0;
}
.reorder-buttons button {
background: none !important;
padding: 1px 2px;
}
@media (max-width: 767px) {
.reorder-buttons {
flex-direction: row !important;
opacity: 1 !important;
align-items: center;
margin-left: auto;
}
.reorder-buttons button {
background: var(--bg-tertiary) !important;
border-radius: 6px;
width: 32px;
height: 32px;
padding: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
.note-edit-buttons {
opacity: 1 !important;
}
}
/* Ort-Zeile Hover: Sortier-Buttons anzeigen */
.place-row:hover .reorder-btns {
opacity: 1 !important;
@@ -84,6 +115,49 @@ body {
transition: background-color 0.2s, color 0.2s;
}
/* ── Marker cluster custom styling ────────────── */
.marker-cluster-wrapper {
background: transparent !important;
border: none !important;
}
.marker-cluster-custom {
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #111827;
border: 2.5px solid rgba(255, 255, 255, 0.9);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25), 0 0 0 2px rgba(17, 24, 39, 0.15);
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.marker-cluster-custom:hover {
transform: scale(1.1);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35), 0 0 0 3px rgba(17, 24, 39, 0.2);
}
.marker-cluster-custom span {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif;
font-size: 12px;
font-weight: 700;
color: #ffffff;
line-height: 1;
}
/* Hide default markercluster styles we don't need */
.marker-cluster-small,
.marker-cluster-medium,
.marker-cluster-large {
background: transparent !important;
}
.marker-cluster-small div,
.marker-cluster-medium div,
.marker-cluster-large div {
background: transparent !important;
}
/* ── Leaflet z-index ───────────────────────────── */
.leaflet-container {
z-index: 0;