ci: migrate GitHub actions to Gitea actions
Deploy CV Site (andreknie.de) / deploy (push) Canceled after 0s
Deploy CV Site (andreknie.de) / deploy (push) Canceled after 0s
This commit is contained in:
@@ -1,13 +1,23 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useContent, useContentTags } from '../hooks/useContent'
|
||||
import { Search } from 'lucide-react'
|
||||
import { useContent } from '../hooks/useContent'
|
||||
import { formatDate } from '../utils/formatDate'
|
||||
import { decodeHtml } from '../utils/decodeHtml'
|
||||
|
||||
export default function Kniepunkt() {
|
||||
const [tag, setTag] = useState(null)
|
||||
const [page, setPage] = useState(1)
|
||||
const { items, totalPages, hasNext, hasPrev } = useContent('kniepunkt', { tag, page })
|
||||
const tags = useContentTags('kniepunkt')
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const { items } = useContent('kniepunkt', { pageSize: 1000 })
|
||||
|
||||
const filteredItems = items.filter(item => {
|
||||
if (!searchQuery) return true
|
||||
const query = searchQuery.toLowerCase()
|
||||
return (
|
||||
(item.title && item.title.toLowerCase().includes(query)) ||
|
||||
(item.summary && item.summary.toLowerCase().includes(query)) ||
|
||||
(item.body && item.body.toLowerCase().includes(query))
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<main style={{ paddingTop: '120px', minHeight: '100vh' }}>
|
||||
@@ -17,33 +27,48 @@ export default function Kniepunkt() {
|
||||
<p className="section-subtitle">Wöchentliche KI-Kolumne. Nachrichten und Absurditäten rund um KI.</p>
|
||||
</div>
|
||||
|
||||
{tags.length > 0 && (
|
||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', justifyContent: 'center', marginBottom: '40px' }}>
|
||||
<button onClick={() => setTag(null)} className={`secondary-button ${!tag ? 'active' : ''}`} style={{ padding: '8px 16px', fontSize: '0.85rem' }}>Alle</button>
|
||||
{tags.map(t => (
|
||||
<button key={t} onClick={() => { setTag(t); setPage(1) }} className={`secondary-button ${tag === t ? 'active' : ''}`} style={{ padding: '8px 16px', fontSize: '0.85rem' }}>{t}</button>
|
||||
))}
|
||||
<div style={{ maxWidth: '700px', margin: '0 auto 40px auto', position: 'relative' }}>
|
||||
<div style={{ position: 'absolute', left: '16px', top: '50%', transform: 'translateY(-50%)', color: 'var(--text-secondary)' }}>
|
||||
<Search size={20} />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Kniepunkte durchsuchen..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '16px 80px 16px 48px',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
background: 'var(--glass-bg)',
|
||||
color: 'var(--text-primary)',
|
||||
fontSize: '1rem',
|
||||
outline: 'none'
|
||||
}}
|
||||
/>
|
||||
<div style={{ position: 'absolute', right: '16px', top: '50%', transform: 'translateY(-50%)', color: 'var(--text-secondary)', fontSize: '0.9rem' }}>
|
||||
{filteredItems?.length || 0} / {items?.length || 0}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', maxWidth: '700px', margin: '0 auto' }}>
|
||||
{items.map(issue => (
|
||||
<Link to={`/kniepunkt/${issue.slug}`} key={issue.slug} className="glass-panel" style={{ padding: '24px', display: 'block', transition: 'transform 0.3s ease' }}>
|
||||
<h3 style={{ marginBottom: '8px' }}>{issue.title}</h3>
|
||||
<p style={{ color: 'var(--text-secondary)', margin: 0, fontSize: '0.9rem' }}>{formatDate(issue.date)}</p>
|
||||
{issue.summary && <p style={{ color: 'var(--text-secondary)', margin: '8px 0 0', fontSize: '0.95rem' }}>{issue.summary}</p>}
|
||||
</Link>
|
||||
))}
|
||||
{items.length === 0 && <p style={{ textAlign: 'center', color: 'var(--text-secondary)' }}>Noch keine Ausgaben vorhanden.</p>}
|
||||
</div>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '16px', marginTop: '40px' }}>
|
||||
{hasPrev && <button onClick={() => setPage(p => p - 1)} className="secondary-button">Zurück</button>}
|
||||
<span style={{ color: 'var(--text-secondary)', alignSelf: 'center' }}>Seite {page} / {totalPages}</span>
|
||||
{hasNext && <button onClick={() => setPage(p => p + 1)} className="secondary-button">Weiter</button>}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', maxWidth: '700px', margin: '0 auto' }}>
|
||||
{filteredItems.map(issue => (
|
||||
<Link to={`/kniepunkt/${issue.slug}`} key={issue.slug} className="glass-panel" style={{ padding: '0', display: 'flex', flexDirection: 'column', transition: 'transform 0.3s ease', overflow: 'hidden' }}>
|
||||
{issue.image && (
|
||||
<div style={{ width: '100%', height: '240px', overflow: 'hidden' }}>
|
||||
<img src={issue.image} alt={decodeHtml(issue.title)} style={{ width: '100%', height: '100%', objectFit: 'cover', borderBottom: '1px solid rgba(255,255,255,0.1)' }} />
|
||||
</div>
|
||||
)}
|
||||
<div style={{ padding: '24px' }}>
|
||||
<h3 style={{ marginBottom: '8px' }}>{decodeHtml(issue.title)}</h3>
|
||||
<p style={{ color: 'var(--text-secondary)', margin: 0, fontSize: '0.9rem' }}>{formatDate(issue.date)}</p>
|
||||
{issue.summary && <p style={{ color: 'var(--text-secondary)', margin: '8px 0 0', fontSize: '0.95rem' }}>{decodeHtml(issue.summary)}</p>}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
{filteredItems.length === 0 && <p style={{ textAlign: 'center', color: 'var(--text-secondary)' }}>Keine Kniepunkte für diese Suche gefunden.</p>}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user