chore : move all to root

This commit is contained in:
Thibault Pouch
2026-02-26 16:16:44 +01:00
parent 308a758e79
commit c2d94a349c
44 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
import { useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { MOCK_CATEGORIES, MOCK_THREADS } from '../../data/mockData';
import { timeAgo } from '../../utils/format';
import type { ForumCategory, ForumThread } from '../../types';
// ── Sub-components ─────────────────────────────────────────────────────────────
function CategoryCard({ category, threads }: { category: ForumCategory; threads: ForumThread[] }) {
const pinned = threads.filter((t) => t.isPinned && t.categoryId === category.id);
const regular = threads.filter((t) => !t.isPinned && t.categoryId === category.id);
const categoryThreads = [...pinned, ...regular];
return (
<section className="crt-box" style={{ marginBottom: '1.5rem' }}>
{/* Category header */}
<div
style={{
padding: '1rem 1.5rem',
borderBottom: '1px solid var(--color-border)',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: '1rem',
flexWrap: 'wrap',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<span
style={{
fontFamily: 'var(--font-mono)',
color: 'var(--color-green)',
fontSize: '0.75rem',
opacity: 0.7,
}}
>
{category.icon}
</span>
<div>
<h2
style={{
fontFamily: 'var(--font-heading)',
color: 'var(--color-text)',
fontSize: '1.1rem',
margin: 0,
}}
>
{category.name}
</h2>
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.75rem', margin: 0 }}>
{category.description}
</p>
</div>
</div>
<div
style={{
fontFamily: 'var(--font-mono)',
color: 'var(--color-text-muted)',
fontSize: '0.72rem',
textAlign: 'right',
}}
>
<span style={{ color: 'var(--color-green)' }}>{category.threadCount}</span> threads
</div>
</div>
{/* Threads */}
<div>
{categoryThreads.length === 0 ? (
<div style={{ padding: '1.5rem', color: 'var(--color-text-muted)', fontSize: '0.8rem', textAlign: 'center' }}>
No threads yet. Be the first to post.
</div>
) : (
categoryThreads.map((thread, idx) => (
<div
key={thread.id}
style={{
padding: '0.85rem 1.5rem',
borderBottom: idx < categoryThreads.length - 1 ? '1px solid var(--color-border)' : 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: '1rem',
flexWrap: 'wrap',
background: thread.isPinned ? 'rgba(217,119,6,0.05)' : 'transparent',
}}
>
<div style={{ flex: 1, minWidth: '0' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap', marginBottom: '0.25rem' }}>
{thread.isPinned && (
<span className="badge badge-progress">Pinned</span>
)}
{thread.isLocked && (
<span className="badge badge-closed">Locked</span>
)}
<Link
to={`/forum/thread/${thread.id}`}
style={{
fontFamily: 'var(--font-mono)',
color: 'var(--color-text)',
fontSize: '0.87rem',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{thread.title}
</Link>
</div>
<div style={{ color: 'var(--color-text-muted)', fontSize: '0.72rem', fontFamily: 'var(--font-mono)' }}>
by <span style={{ color: 'var(--color-text-dim)' }}>{thread.authorName}</span>
{' '}&mdash; {timeAgo(thread.createdAt)}
</div>
</div>
<div style={{ textAlign: 'right', fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--color-text-muted)', flexShrink: 0 }}>
<div style={{ color: 'var(--color-green)' }}>{thread.replyCount}</div>
<div>replies</div>
</div>
</div>
))
)}
</div>
</section>
);
}
// ── Forum Page ─────────────────────────────────────────────────────────────────
export default function ForumPage() {
const [search, setSearch] = useState('');
const filteredCategories = useMemo(() => {
if (!search.trim()) return MOCK_CATEGORIES;
const q = search.toLowerCase();
return MOCK_CATEGORIES.filter((cat) =>
cat.name.toLowerCase().includes(q) ||
MOCK_THREADS.some((t) => t.categoryId === cat.id && t.title.toLowerCase().includes(q))
);
}, [search]);
return (
<div style={{ maxWidth: '960px', margin: '0 auto', padding: '4rem 1.5rem' }}>
{/* Header */}
<div style={{ marginBottom: '2.5rem', display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', flexWrap: 'wrap', gap: '1.5rem' }}>
<div>
<div className="section-label">Community</div>
<h1
style={{
fontFamily: 'var(--font-heading)',
color: 'var(--color-text)',
fontSize: 'clamp(2rem, 5vw, 3rem)',
marginTop: '0.5rem',
}}
>
FORUM
</h1>
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.8rem', marginTop: '0.5rem', fontFamily: 'var(--font-mono)' }}>
Read freely. Login to post.
</p>
</div>
{/* Search */}
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
<input
className="input-terminal"
type="search"
placeholder="Search threads..."
value={search}
onChange={(e) => setSearch(e.target.value)}
style={{ width: '220px' }}
aria-label="Search forum threads"
/>
</div>
</div>
{/* Categories */}
{filteredCategories.length === 0 ? (
<div className="crt-box" style={{ padding: '2rem', textAlign: 'center', color: 'var(--color-text-muted)', fontFamily: 'var(--font-mono)' }}>
No results found for "{search}"
</div>
) : (
filteredCategories.map((cat) => (
<CategoryCard key={cat.id} category={cat} threads={MOCK_THREADS} />
))
)}
</div>
);
}