diff --git a/backend/src/index.js b/backend/src/index.js index 8842041..c16df2f 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -69,7 +69,6 @@ fs.readdirSync(toolsDir, { withFileTypes: true }) app.use('/api/tools/snippets', require('./tools/snippets/routes')); app.use('/api/tools/qrcodes', require('./tools/qrcodes/routes')); app.use('/api/upload-shares', require('./tools/dateien/upload-share')); -app.use('/api/tools/media', require('./tools/media/routes')); app.use('/api/search', require('./routes/search')); // Γ–ffentliche Upload-Seite: React-App ausliefern – App erkennt /u/ Pfad selbst diff --git a/backend/src/tools/media/routes.js b/backend/src/tools/media/routes.js index 66a8c34..029268b 100644 --- a/backend/src/tools/media/routes.js +++ b/backend/src/tools/media/routes.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); const db = require('../../db'); -const { requireAdmin } = require('../../middleware/auth'); +const { authenticate, requireAdmin } = require('../../middleware/auth'); const TMDB_BASE = 'https://api.themoviedb.org/3'; @@ -23,8 +23,7 @@ async function tmdb(path, params = {}) { return res.json(); } -// GET /api/tools/media/now-playing -router.get('/now-playing', async (req, res) => { +router.get('/now-playing', authenticate, async (req, res) => { try { const data = await tmdb('/movie/now_playing'); res.json(data.results.slice(0, 10)); @@ -33,8 +32,7 @@ router.get('/now-playing', async (req, res) => { } }); -// GET /api/tools/media/upcoming -router.get('/upcoming', async (req, res) => { +router.get('/upcoming', authenticate, async (req, res) => { try { const data = await tmdb('/movie/upcoming'); res.json(data.results.slice(0, 10)); @@ -43,16 +41,14 @@ router.get('/upcoming', async (req, res) => { } }); -// GET /api/tools/media/favorites -router.get('/favorites', (req, res) => { +router.get('/favorites', authenticate, (req, res) => { const rows = db.prepare( 'SELECT * FROM movie_favorites WHERE user_id=? ORDER BY added_at DESC' ).all(req.user.id); res.json(rows); }); -// POST /api/tools/media/favorites -router.post('/favorites', (req, res) => { +router.post('/favorites', authenticate, (req, res) => { const { tmdb_id, title, poster_path, release_date_de } = req.body; if (!tmdb_id) return res.status(400).json({ error: 'tmdb_id fehlt' }); try { @@ -65,21 +61,18 @@ router.post('/favorites', (req, res) => { } }); -// DELETE /api/tools/media/favorites/:tmdb_id -router.delete('/favorites/:tmdb_id', (req, res) => { +router.delete('/favorites/:tmdb_id', authenticate, (req, res) => { db.prepare('DELETE FROM movie_favorites WHERE user_id=? AND tmdb_id=?') .run(req.user.id, Number(req.params.tmdb_id)); res.json({ ok: true }); }); -// GET /api/tools/media/settings (admin only) -router.get('/settings', requireAdmin, (req, res) => { +router.get('/settings', authenticate, requireAdmin, (req, res) => { const token = getToken(); res.json({ configured: !!token }); }); -// PUT /api/tools/media/settings (admin only) -router.put('/settings', requireAdmin, (req, res) => { +router.put('/settings', authenticate, requireAdmin, (req, res) => { const { tmdb_token } = req.body; if (typeof tmdb_token !== 'string') return res.status(400).json({ error: 'tmdb_token fehlt' }); db.prepare('INSERT OR REPLACE INTO admin_settings (key,value) VALUES (?,?)').run('tmdb_token', tmdb_token.trim()); diff --git a/frontend/src/tools/media.jsx b/frontend/src/tools/media.jsx index 0cf3c9b..f82b3c6 100644 --- a/frontend/src/tools/media.jsx +++ b/frontend/src/tools/media.jsx @@ -1,40 +1,113 @@ -import { useState, useEffect, useCallback } from 'react'; -import { api, S } from '../lib.js'; +import { useState, useEffect, useCallback, useRef } from 'react'; +import { S, api } from '../lib.js'; const POSTER = 'https://image.tmdb.org/t/p/w342'; -const TABS = [ - { id: 'kino', label: '🎬 Kino' }, - { id: 'demnΓ€chst', label: 'πŸ—“ DemnΓ€chst' }, - { id: 'favoriten', label: '❀ Favoriten' }, + +const TOOL_DEFS = [ + { id: 'kino', label: '🎬 Kino', desc: 'Aktuell im Kino – Top 10 (DE)', ready: true }, + { id: 'demnΓ€chst', label: 'πŸ—“ DemnΓ€chst', desc: 'Bald im Kino – Vorschau (DE)', ready: true }, + { id: 'favoriten', label: '❀ Favoriten', desc: 'Deine gespeicherten Lieblingsfilme', ready: true }, ]; -export default function Media() { - const [tab, setTab] = useState('kino'); +const getIcon = lbl => [...lbl][0] ?? '🎬'; +const getText = lbl => { const c = [...lbl]; return c.slice(1).join('').trim() || lbl; }; + +export default function Media({ toast, mobile, nav }) { + const [activeTool, setActiveTool] = useState('kino'); + const chipScrollRef = useRef(null); + const [chipScroll, setChipScroll] = useState({ left: false, right: true }); + + const onChipScroll = () => { + const el = chipScrollRef.current; + if (!el) return; + setChipScroll({ + left: el.scrollLeft > 4, + right: el.scrollLeft < el.scrollWidth - el.clientWidth - 4, + }); + }; + + const active = TOOL_DEFS.find(t => t.id === activeTool); return ( -
-

- 🎬 Media -

- -
- {TABS.map(t => ( - - ))} +
+
+

Media

+ Kino Β· Vorschau Β· Favoriten
- {tab === 'kino' && } - {tab === 'demnΓ€chst' && } - {tab === 'favoriten' && } + {/* Tool-Auswahl */} + {mobile ? ( +
+
+ {chipScroll.left && ( + + )} +
+ {TOOL_DEFS.map(t => ( + + ))} +
+ {chipScroll.right && ( + + )} +
+
+ ) : ( +
+ {TOOL_DEFS.map(t => ( + + ))} +
+ )} + + {/* Inhalts-Card */} +
+
+
{active?.label}
+ {active?.desc} +
+ + {activeTool === 'kino' && } + {activeTool === 'demnΓ€chst' && } + {activeTool === 'favoriten' && } +
); } @@ -49,41 +122,37 @@ function MovieCard({ movie, favIds, onToggleFav }) { return (
{movie.poster_path ? {movie.title} :
🎬
+ justifyContent:'center', fontSize:36, color:'rgba(255,255,255,0.1)' }}>🎬
} {movie.vote_average > 0 && (
- β˜… {movie.vote_average.toFixed(1)} -
+ }}>β˜… {movie.vote_average.toFixed(1)}
)} + position:'absolute', top:5, right:5, background:'rgba(0,0,0,0.65)', + border:'none', borderRadius:'50%', width:30, height:30, cursor:'pointer', + fontSize:14, display:'flex', alignItems:'center', justifyContent:'center', + }}>{isFav ? '❀' : '🀍'}
-
-
+
- {movie.title} -
+ display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical', + }}>{movie.title}
{releaseDE && ( -
+
{releaseDE}
)} @@ -92,8 +161,8 @@ function MovieCard({ movie, favIds, onToggleFav }) { ); } -// ── Grid (now-playing / upcoming) ───────────────────────────────────────────── -function MovieGrid({ endpoint, emptyText }) { +// ── Kino/DemnΓ€chst Grid ─────────────────────────────────────────────────────── +function KinoGrid({ endpoint, emptyText }) { const [movies, setMovies] = useState([]); const [favIds, setFavIds] = useState(new Set()); const [loading, setLoading] = useState(true); @@ -134,21 +203,23 @@ function MovieGrid({ endpoint, emptyText }) { }; if (loading) return ⏳ LΓ€dt…; - if (error) return - ⚠ {error} - {error.includes('Token') && ( -
- β†’ Einstellungen β†’ Benutzer β†’ TMDB API (KINO-FEATURE) -
- )} -
; + if (error) return ( + +
⚠ {error}
+ {error.includes('Token') && ( +
+ Token eintragen: Einstellungen β†’ Benutzer β†’ TMDB API +
+ )} +
+ ); if (!movies.length) return {emptyText}; return (
{movies.map(m => ( @@ -157,8 +228,8 @@ function MovieGrid({ endpoint, emptyText }) { ); } -// ── Favoriten ───────────────────────────────────────────────────────────────── -function FavoritenTab() { +// ── Favoriten Grid ──────────────────────────────────────────────────────────── +function FavoritenGrid() { const [favs, setFavs] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); @@ -180,33 +251,33 @@ function FavoritenTab() { if (!favs.length) return Noch keine Favoriten – markiere Filme mit ❀; return ( -
+
{favs.map(f => (
{f.poster_path ? {f.title} :
🎬
+ justifyContent:'center', fontSize:36, color:'rgba(255,255,255,0.1)' }}>🎬
}
-
-
+
- {f.title} -
+ display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical', + }}>{f.title}
{f.release_date_de && ( -
+
πŸ‡©πŸ‡ͺ {f.release_date_de}
)} @@ -220,9 +291,9 @@ function FavoritenTab() { function StatusBox({ children, color }) { return (
{children}