diff --git a/client/src/components/PDF/TripPDF.jsx b/client/src/components/PDF/TripPDF.jsx index fdacc91..1857dd0 100644 --- a/client/src/components/PDF/TripPDF.jsx +++ b/client/src/components/PDF/TripPDF.jsx @@ -356,10 +356,37 @@ ${daysHtml} ` - // Open in new window - const win = window.open('', '_blank') - if (win) { - win.document.write(html) - win.document.close() - } + // Open in modal with srcdoc iframe (no URL loading = no X-Frame-Options issue) + const overlay = document.createElement('div') + overlay.id = 'pdf-preview-overlay' + overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:9999;display:flex;align-items:center;justify-content:center;padding:8px;' + overlay.onclick = (e) => { if (e.target === overlay) overlay.remove() } + + const card = document.createElement('div') + card.style.cssText = 'width:100%;max-width:1000px;height:95vh;background:var(--bg-card);border-radius:12px;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,0.3);' + + const header = document.createElement('div') + header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:10px 16px;border-bottom:1px solid var(--border-primary);flex-shrink:0;' + header.innerHTML = ` + ${escHtml(trip?.name || tr('pdf.travelPlan'))} +
+ + +
+ ` + + const iframe = document.createElement('iframe') + iframe.style.cssText = 'flex:1;width:100%;border:none;' + iframe.sandbox = 'allow-same-origin allow-modals' + iframe.srcdoc = html + + card.appendChild(header) + card.appendChild(iframe) + overlay.appendChild(card) + document.body.appendChild(overlay) + + header.querySelector('#pdf-close-btn').onclick = () => overlay.remove() + header.querySelector('#pdf-print-btn').onclick = () => { iframe.contentWindow?.print() } } diff --git a/server/package.json b/server/package.json index b6d09ff..50a6753 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "nomad-server", - "version": "2.2.2", + "version": "2.2.3", "main": "src/index.js", "scripts": { "start": "node --experimental-sqlite src/index.js", diff --git a/server/src/middleware/auth.js b/server/src/middleware/auth.js index c855d61..2dd4b85 100644 --- a/server/src/middleware/auth.js +++ b/server/src/middleware/auth.js @@ -53,4 +53,11 @@ const adminOnly = (req, res, next) => { next(); }; -module.exports = { authenticate, optionalAuth, adminOnly }; +const demoUploadBlock = (req, res, next) => { + if (process.env.DEMO_MODE === 'true' && req.user?.email === 'demo@nomad.app') { + return res.status(403).json({ error: 'Uploads are disabled in demo mode. Self-host NOMAD for full functionality.' }); + } + next(); +}; + +module.exports = { authenticate, optionalAuth, adminOnly, demoUploadBlock }; diff --git a/server/src/routes/auth.js b/server/src/routes/auth.js index f18ac2c..f8da4ee 100644 --- a/server/src/routes/auth.js +++ b/server/src/routes/auth.js @@ -7,7 +7,7 @@ const fs = require('fs'); const { v4: uuid } = require('uuid'); const fetch = require('node-fetch'); const { db } = require('../db/database'); -const { authenticate } = require('../middleware/auth'); +const { authenticate, demoUploadBlock } = require('../middleware/auth'); const router = express.Router(); const { JWT_SECRET } = require('../config'); @@ -243,7 +243,7 @@ router.get('/me/settings', authenticate, (req, res) => { }); // POST /api/auth/avatar — upload avatar -router.post('/avatar', authenticate, avatarUpload.single('avatar'), (req, res) => { +router.post('/avatar', authenticate, demoUploadBlock, avatarUpload.single('avatar'), (req, res) => { if (!req.file) return res.status(400).json({ error: 'No image uploaded' }); const current = db.prepare('SELECT avatar FROM users WHERE id = ?').get(req.user.id); diff --git a/server/src/routes/files.js b/server/src/routes/files.js index e5e29ed..cca09e2 100644 --- a/server/src/routes/files.js +++ b/server/src/routes/files.js @@ -4,7 +4,7 @@ const path = require('path'); const fs = require('fs'); const { v4: uuidv4 } = require('uuid'); const { db, canAccessTrip } = require('../db/database'); -const { authenticate } = require('../middleware/auth'); +const { authenticate, demoUploadBlock } = require('../middleware/auth'); const { broadcast } = require('../websocket'); const router = express.Router({ mergeParams: true }); @@ -72,7 +72,7 @@ router.get('/', authenticate, (req, res) => { }); // POST /api/trips/:tripId/files -router.post('/', authenticate, upload.single('file'), (req, res) => { +router.post('/', authenticate, demoUploadBlock, upload.single('file'), (req, res) => { const { tripId } = req.params; const { place_id, description, reservation_id } = req.body; diff --git a/server/src/routes/photos.js b/server/src/routes/photos.js index aafa725..c9bf568 100644 --- a/server/src/routes/photos.js +++ b/server/src/routes/photos.js @@ -4,7 +4,7 @@ const path = require('path'); const fs = require('fs'); const { v4: uuidv4 } = require('uuid'); const { db, canAccessTrip } = require('../db/database'); -const { authenticate } = require('../middleware/auth'); +const { authenticate, demoUploadBlock } = require('../middleware/auth'); const router = express.Router({ mergeParams: true }); @@ -68,7 +68,7 @@ router.get('/', authenticate, (req, res) => { }); // POST /api/trips/:tripId/photos -router.post('/', authenticate, upload.array('photos', 20), (req, res) => { +router.post('/', authenticate, demoUploadBlock, upload.array('photos', 20), (req, res) => { const { tripId } = req.params; const { day_id, place_id, caption } = req.body; diff --git a/server/src/routes/trips.js b/server/src/routes/trips.js index 92f83e1..87db2fc 100644 --- a/server/src/routes/trips.js +++ b/server/src/routes/trips.js @@ -4,7 +4,7 @@ const path = require('path'); const fs = require('fs'); const { v4: uuidv4 } = require('uuid'); const { db, canAccessTrip, isOwner } = require('../db/database'); -const { authenticate } = require('../middleware/auth'); +const { authenticate, demoUploadBlock } = require('../middleware/auth'); const { broadcast } = require('../websocket'); const router = express.Router(); @@ -139,7 +139,7 @@ router.put('/:id', authenticate, (req, res) => { }); // POST /api/trips/:id/cover -router.post('/:id/cover', authenticate, uploadCover.single('cover'), (req, res) => { +router.post('/:id/cover', authenticate, demoUploadBlock, uploadCover.single('cover'), (req, res) => { if (!isOwner(req.params.id, req.user.id)) return res.status(403).json({ error: 'Nur der Eigentümer kann das Titelbild ändern' });