fix: media – authenticate middleware, doppelter Route-Load, DevTools-Layout
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 (
|
||||
<div style={{ padding: '0 0 40px' }}>
|
||||
<h1 style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:20, margin:'0 0 20px' }}>
|
||||
🎬 Media
|
||||
</h1>
|
||||
<div style={{ padding: mobile ? '14px 14px 90px' : '36px 44px', maxWidth: 860 }}>
|
||||
<div style={{ display:'flex', alignItems:'center', gap:12, marginBottom:16, flexWrap:'wrap' }}>
|
||||
<h1 style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize: mobile?17:22, margin:0 }}>Media</h1>
|
||||
<span style={{ color:'rgba(255,255,255,0.5)', fontFamily:'monospace', fontSize:10 }}>Kino · Vorschau · Favoriten</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display:'flex', gap:8, marginBottom:24, flexWrap:'wrap' }}>
|
||||
{TABS.map(t => (
|
||||
<button key={t.id} onClick={() => setTab(t.id)} style={{
|
||||
padding:'7px 16px', borderRadius:8, cursor:'pointer',
|
||||
border: tab===t.id ? '1px solid #4ecdc4' : '1px solid rgba(255,255,255,0.12)',
|
||||
background: tab===t.id ? 'rgba(78,205,196,0.15)' : 'rgba(255,255,255,0.04)',
|
||||
color: tab===t.id ? '#4ecdc4' : 'rgba(255,255,255,0.6)',
|
||||
fontFamily:"'Space Mono',monospace", fontSize:13,
|
||||
fontWeight: tab===t.id ? 700 : 400, transition:'all 0.15s',
|
||||
{/* Tool-Auswahl */}
|
||||
{mobile ? (
|
||||
<div style={{ marginBottom:16 }}>
|
||||
<div style={{ display:'flex', alignItems:'center', gap:4 }}>
|
||||
{chipScroll.left && (
|
||||
<button onClick={() => chipScrollRef.current?.scrollBy({ left:-160, behavior:'smooth' })}
|
||||
style={{ flexShrink:0, background:'rgba(255,255,255,0.06)', border:'1px solid rgba(255,255,255,0.12)',
|
||||
borderRadius:20, color:'rgba(255,255,255,0.6)', cursor:'pointer', padding:'6px 8px', fontSize:14, lineHeight:1 }}>‹</button>
|
||||
)}
|
||||
<div ref={chipScrollRef} onScroll={onChipScroll}
|
||||
style={{ display:'flex', gap:6, overflowX:'auto', flex:1,
|
||||
padding:'3px 2px 6px', scrollbarWidth:'none', WebkitOverflowScrolling:'touch' }}>
|
||||
{TOOL_DEFS.map(t => (
|
||||
<button key={t.id} onClick={() => setActiveTool(t.id)} style={{
|
||||
display:'flex', alignItems:'center', gap:6, flexShrink:0,
|
||||
padding:'7px 12px', borderRadius:20, fontFamily:'monospace', fontSize:11,
|
||||
cursor:'pointer', whiteSpace:'nowrap',
|
||||
background: activeTool===t.id ? 'rgba(78,205,196,0.15)' : 'rgba(255,255,255,0.05)',
|
||||
border: activeTool===t.id ? '1px solid #4ecdc4' : '1px solid rgba(255,255,255,0.1)',
|
||||
color: activeTool===t.id ? '#4ecdc4' : 'rgba(255,255,255,0.75)',
|
||||
fontWeight: activeTool===t.id ? 700 : 400,
|
||||
}}>
|
||||
{t.label}
|
||||
<span style={{ fontSize:15 }}>{getIcon(t.label)}</span>
|
||||
<span>{getText(t.label)}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{chipScroll.right && (
|
||||
<button onClick={() => chipScrollRef.current?.scrollBy({ left:160, behavior:'smooth' })}
|
||||
style={{ flexShrink:0, background:'rgba(255,255,255,0.06)', border:'1px solid rgba(255,255,255,0.12)',
|
||||
borderRadius:20, color:'rgba(255,255,255,0.6)', cursor:'pointer', padding:'6px 8px', fontSize:14, lineHeight:1 }}>›</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display:'grid', gridTemplateColumns:'repeat(2,1fr)', gap:5, marginBottom:16 }}>
|
||||
{TOOL_DEFS.map(t => (
|
||||
<button key={t.id} title={t.desc} onClick={() => setActiveTool(t.id)} style={{
|
||||
display:'flex', alignItems:'center', gap:9,
|
||||
padding:'8px 11px', borderRadius:9, fontFamily:'monospace',
|
||||
cursor:'pointer', textAlign:'left', border:'none',
|
||||
background: activeTool===t.id ? 'rgba(78,205,196,0.15)' : 'rgba(255,255,255,0.04)',
|
||||
outline: activeTool===t.id ? '1px solid #4ecdc4' : 'none',
|
||||
}}>
|
||||
<span style={{ fontSize:17, flexShrink:0, lineHeight:1, minWidth:22, textAlign:'center' }}>{getIcon(t.label)}</span>
|
||||
<div style={{ minWidth:0, flex:1 }}>
|
||||
<div style={{
|
||||
color: activeTool===t.id ? '#4ecdc4' : 'rgba(255,255,255,0.85)',
|
||||
fontSize:12, fontWeight: activeTool===t.id?700:500,
|
||||
overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap',
|
||||
}}>
|
||||
{getText(t.label)}
|
||||
</div>
|
||||
<div style={{ color:'rgba(255,255,255,0.28)', fontSize:9, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', marginTop:1 }}>
|
||||
{t.desc}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === 'kino' && <MovieGrid endpoint="/tools/media/now-playing" emptyText="Keine aktuellen Kinofilme" />}
|
||||
{tab === 'demnächst' && <MovieGrid endpoint="/tools/media/upcoming" emptyText="Keine kommenden Filme" />}
|
||||
{tab === 'favoriten' && <FavoritenTab />}
|
||||
{/* Inhalts-Card */}
|
||||
<div style={{ ...S.card }}>
|
||||
<div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:18, flexWrap:'wrap' }}>
|
||||
<div style={{ ...S.head, marginBottom:0 }}>{active?.label}</div>
|
||||
<span style={{ color:'rgba(255,255,255,0.5)', fontFamily:'monospace', fontSize:10 }}>{active?.desc}</span>
|
||||
</div>
|
||||
|
||||
{activeTool === 'kino' && <KinoGrid endpoint="/tools/media/now-playing" emptyText="Keine aktuellen Kinofilme gefunden" />}
|
||||
{activeTool === 'demnächst' && <KinoGrid endpoint="/tools/media/upcoming" emptyText="Keine kommenden Filme gefunden" />}
|
||||
{activeTool === 'favoriten' && <FavoritenGrid />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -49,41 +122,37 @@ function MovieCard({ movie, favIds, onToggleFav }) {
|
||||
return (
|
||||
<div style={{
|
||||
background:'rgba(255,255,255,0.03)', border:'1px solid rgba(255,255,255,0.08)',
|
||||
borderRadius:12, overflow:'hidden', display:'flex', flexDirection:'column',
|
||||
borderRadius:10, overflow:'hidden', display:'flex', flexDirection:'column',
|
||||
}}>
|
||||
<div style={{ position:'relative', aspectRatio:'2/3', background:'#111', overflow:'hidden' }}>
|
||||
{movie.poster_path
|
||||
? <img src={POSTER + movie.poster_path} alt={movie.title}
|
||||
style={{ width:'100%', height:'100%', objectFit:'cover', display:'block' }} />
|
||||
: <div style={{ width:'100%', height:'100%', display:'flex', alignItems:'center',
|
||||
justifyContent:'center', fontSize:40, color:'rgba(255,255,255,0.15)' }}>🎬</div>
|
||||
justifyContent:'center', fontSize:36, color:'rgba(255,255,255,0.1)' }}>🎬</div>
|
||||
}
|
||||
{movie.vote_average > 0 && (
|
||||
<div style={{
|
||||
position:'absolute', top:8, left:8,
|
||||
background:'rgba(0,0,0,0.75)', borderRadius:6,
|
||||
padding:'2px 7px', fontSize:11, color:'#ffe66d', fontWeight:700,
|
||||
position:'absolute', top:6, left:6,
|
||||
background:'rgba(0,0,0,0.8)', borderRadius:5,
|
||||
padding:'2px 6px', fontSize:10, color:'#ffe66d', fontWeight:700,
|
||||
fontFamily:"'Space Mono',monospace",
|
||||
}}>
|
||||
★ {movie.vote_average.toFixed(1)}
|
||||
</div>
|
||||
}}>★ {movie.vote_average.toFixed(1)}</div>
|
||||
)}
|
||||
<button onClick={() => onToggleFav(movie, isFav)} style={{
|
||||
position:'absolute', top:6, right:6, background:'rgba(0,0,0,0.6)',
|
||||
border:'none', borderRadius:'50%', width:32, height:32, cursor:'pointer',
|
||||
fontSize:16, display:'flex', alignItems:'center', justifyContent:'center',
|
||||
}}>
|
||||
{isFav ? '❤' : '🤍'}
|
||||
</button>
|
||||
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 ? '❤' : '🤍'}</button>
|
||||
</div>
|
||||
<div style={{ padding:'10px 12px', flex:1, display:'flex', flexDirection:'column', gap:4 }}>
|
||||
<div style={{ fontSize:13, fontWeight:700, color:'#fff', lineHeight:1.3,
|
||||
<div style={{ padding:'8px 10px', flex:1, display:'flex', flexDirection:'column', gap:3 }}>
|
||||
<div style={{
|
||||
fontSize:12, fontWeight:700, color:'#fff', lineHeight:1.3,
|
||||
fontFamily:"'Space Mono',monospace", overflow:'hidden',
|
||||
display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical' }}>
|
||||
{movie.title}
|
||||
</div>
|
||||
display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical',
|
||||
}}>{movie.title}</div>
|
||||
{releaseDE && (
|
||||
<div style={{ fontSize:11, color:'rgba(255,255,255,0.4)', fontFamily:"'Space Mono',monospace" }}>
|
||||
<div style={{ fontSize:10, color:'rgba(255,255,255,0.35)', fontFamily:"'Space Mono',monospace" }}>
|
||||
{releaseDE}
|
||||
</div>
|
||||
)}
|
||||
@@ -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 <StatusBox>⏳ Lädt…</StatusBox>;
|
||||
if (error) return <StatusBox color="#ff6b9d">
|
||||
⚠ {error}
|
||||
if (error) return (
|
||||
<StatusBox color="#ff6b9d">
|
||||
<div>⚠ {error}</div>
|
||||
{error.includes('Token') && (
|
||||
<div style={{ marginTop:8, fontSize:12, color:'rgba(255,255,255,0.4)' }}>
|
||||
→ Einstellungen → Benutzer → <span style={{ color:'#4ecdc4' }}>TMDB API (KINO-FEATURE)</span>
|
||||
<div style={{ marginTop:8, fontSize:11, color:'rgba(255,255,255,0.4)' }}>
|
||||
Token eintragen: Einstellungen → Benutzer → <span style={{ color:'#4ecdc4' }}>TMDB API</span>
|
||||
</div>
|
||||
)}
|
||||
</StatusBox>;
|
||||
</StatusBox>
|
||||
);
|
||||
if (!movies.length) return <StatusBox>{emptyText}</StatusBox>;
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display:'grid',
|
||||
gridTemplateColumns:'repeat(auto-fill, minmax(140px, 1fr))',
|
||||
gap:14,
|
||||
gridTemplateColumns:'repeat(auto-fill, minmax(130px, 1fr))',
|
||||
gap:12,
|
||||
}}>
|
||||
{movies.map(m => (
|
||||
<MovieCard key={m.id} movie={m} favIds={favIds} onToggleFav={toggleFav} />
|
||||
@@ -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 <StatusBox>Noch keine Favoriten – markiere Filme mit ❤</StatusBox>;
|
||||
|
||||
return (
|
||||
<div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(140px, 1fr))', gap:14 }}>
|
||||
<div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(130px, 1fr))', gap:12 }}>
|
||||
{favs.map(f => (
|
||||
<div key={f.tmdb_id} style={{
|
||||
background:'rgba(255,255,255,0.03)', border:'1px solid rgba(255,255,255,0.08)',
|
||||
borderRadius:12, overflow:'hidden', display:'flex', flexDirection:'column',
|
||||
borderRadius:10, overflow:'hidden', display:'flex', flexDirection:'column',
|
||||
}}>
|
||||
<div style={{ position:'relative', aspectRatio:'2/3', background:'#111', overflow:'hidden' }}>
|
||||
{f.poster_path
|
||||
? <img src={POSTER + f.poster_path} alt={f.title}
|
||||
style={{ width:'100%', height:'100%', objectFit:'cover', display:'block' }} />
|
||||
: <div style={{ width:'100%', height:'100%', display:'flex', alignItems:'center',
|
||||
justifyContent:'center', fontSize:40, color:'rgba(255,255,255,0.15)' }}>🎬</div>
|
||||
justifyContent:'center', fontSize:36, color:'rgba(255,255,255,0.1)' }}>🎬</div>
|
||||
}
|
||||
<button onClick={() => removeFav(f.tmdb_id)} style={{
|
||||
position:'absolute', top:6, right:6, background:'rgba(0,0,0,0.6)',
|
||||
border:'none', borderRadius:'50%', width:32, height:32, cursor:'pointer',
|
||||
fontSize:16, display:'flex', alignItems:'center', justifyContent:'center',
|
||||
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',
|
||||
}}>❤</button>
|
||||
</div>
|
||||
<div style={{ padding:'10px 12px', flex:1, display:'flex', flexDirection:'column', gap:4 }}>
|
||||
<div style={{ fontSize:13, fontWeight:700, color:'#fff', lineHeight:1.3,
|
||||
<div style={{ padding:'8px 10px', flex:1, display:'flex', flexDirection:'column', gap:3 }}>
|
||||
<div style={{
|
||||
fontSize:12, fontWeight:700, color:'#fff', lineHeight:1.3,
|
||||
fontFamily:"'Space Mono',monospace", overflow:'hidden',
|
||||
display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical' }}>
|
||||
{f.title}
|
||||
</div>
|
||||
display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical',
|
||||
}}>{f.title}</div>
|
||||
{f.release_date_de && (
|
||||
<div style={{ fontSize:11, color:'rgba(255,255,255,0.4)', fontFamily:"'Space Mono',monospace" }}>
|
||||
<div style={{ fontSize:10, color:'rgba(255,255,255,0.35)', fontFamily:"'Space Mono',monospace" }}>
|
||||
🇩🇪 {f.release_date_de}
|
||||
</div>
|
||||
)}
|
||||
@@ -220,9 +291,9 @@ function FavoritenTab() {
|
||||
function StatusBox({ children, color }) {
|
||||
return (
|
||||
<div style={{
|
||||
background:'rgba(255,255,255,0.03)', border:'1px solid rgba(255,255,255,0.08)',
|
||||
borderRadius:12, padding:'32px 24px', textAlign:'center',
|
||||
color: color || 'rgba(255,255,255,0.35)', fontFamily:"'Space Mono',monospace", fontSize:14,
|
||||
padding:'28px 20px', textAlign:'center',
|
||||
color: color || 'rgba(255,255,255,0.35)',
|
||||
fontFamily:"'Space Mono',monospace", fontSize:13,
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user