chore: remove dead code, unused CSS, unused components, and clean up git tracking

- Remove storage/logs/ and prod.log from git tracking; add to .gitignore
- Remove unused AvatarCarousel and ArticleSlider components
- Remove unused SkeletonTable export from ui.tsx
- Remove unused ChevronDown import from admin layout
- Remove 13 unused CSS classes (icon-base, nav-*, navigation-icon*, .app.dark,
  text-body, transition-base, card-base, admin-info-grid, .coin.*)
- Remove duplicate translation keys (openMenu/closeMenu in en.json)
- Fix admin-bans to use Prisma-generated bans_type enum
- Create shared AdminPageHeader component and formatDate utility
- Add logger and generateRequestId for structured logging
- All 58 tests pass, typecheck clean, build succeeds
This commit is contained in:
openhands committed 2026-07-08 13:27:01 +02:00
1 parent c5db7f5156
commit fc57fb06d1
18 files changed
+40 -2551

No files matched your search

-143
View File
@@ -1,143 +0,0 @@
"use client";
import Link from "next/link";
import { useCallback, useEffect, useRef, useState } from "react";
/**
* Horizontal, scroll-snap slider for the home page "latest news" row. Keeps the
* exact article-card markup from the server page; the cards are passed in as a
* pre-shaped list so this client component carries no prisma/i18n dependency.
* Prev/next buttons scroll the snap track by one viewport-ish step and disable
* themselves at the ends.
*/
export type SliderArticle = {
slug: string;
title: string;
image: string;
excerpt: string;
};
export function ArticleSlider({ articles }: { articles: SliderArticle[] }) {
const trackRef = useRef<HTMLDivElement>(null);
const [atStart, setAtStart] = useState(true);
const [atEnd, setAtEnd] = useState(false);
const updateEdges = useCallback(() => {
const el = trackRef.current;
if (!el) return;
const maxScroll = el.scrollWidth - el.clientWidth;
setAtStart(el.scrollLeft <= 1);
setAtEnd(el.scrollLeft >= maxScroll - 1);
}, []);
useEffect(() => {
updateEdges();
window.addEventListener("resize", updateEdges);
return () => window.removeEventListener("resize", updateEdges);
}, [updateEdges]);
const scrollByDir = useCallback((dir: 1 | -1) => {
const el = trackRef.current;
if (!el) return;
// Step by roughly one card width (incl. gap) so snapping lands cleanly.
const step = Math.max(el.clientWidth * 0.8, 260);
el.scrollBy({ left: dir * step, behavior: "smooth" });
}, []);
return (
<div style={{ position: "relative" }}>
<button
type="button"
aria-label="Previous"
onClick={() => scrollByDir(-1)}
disabled={atStart}
className="article-slider-nav"
style={{ left: "-0.6rem", opacity: atStart ? 0.35 : 1 }}
>
‹
</button>
<div
ref={trackRef}
onScroll={updateEdges}
style={{
display: "flex",
gap: "1rem",
overflowX: "auto",
scrollSnapType: "x mandatory",
scrollbarWidth: "none",
paddingBottom: "0.25rem",
}}
>
{articles.map((a) => (
<article
key={a.slug}
className="card hover"
style={{
padding: 0,
overflow: "hidden",
flex: "0 0 auto",
width: "clamp(220px, 70vw, 280px)",
scrollSnapAlign: "start",
}}
>
{a.image ? (
// biome-ignore lint/performance/noImgElement: external article image
<img className="article-img" src={a.image} alt="" />
) : (
<div className="article-img" />
)}
<div style={{ padding: "0.85rem" }}>
<h3 style={{ margin: "0 0 0.3rem", fontSize: "1rem" }}>
<Link href={`/news/${a.slug}`}>{a.title}</Link>
</h3>
<p className="muted" style={{ margin: 0 }}>
{a.excerpt}
</p>
</div>
</article>
))}
</div>
<button
type="button"
aria-label="Next"
onClick={() => scrollByDir(1)}
disabled={atEnd}
className="article-slider-nav"
style={{ right: "-0.6rem", opacity: atEnd ? 0.35 : 1 }}
>
›
</button>
<style>{`
.article-slider-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 2;
width: 2.4rem;
height: 2.4rem;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--color-surface);
color: var(--color-text);
box-shadow: var(--shadow-card);
font-size: 1.4rem;
line-height: 1;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.15s ease, box-shadow 0.15s ease;
}
.article-slider-nav:hover:not(:disabled) {
box-shadow: var(--shadow-card-hover);
}
.article-slider-nav:disabled {
cursor: default;
}
`}</style>
</div>
);
}
+1 -23
View File
@@ -135,26 +135,4 @@ export function SkeletonCard({ lines = 3 }: { lines?: number }) {
);
}
export function SkeletonTable({ rows = 5 }: { rows?: number }) {
return (
<div className="content-card" style={{ padding: "0.5rem 1rem 1rem" }}>
{Array.from({ length: rows }).map((_, i) => (
<div key={i} style={{ display: "flex", alignItems: "center", gap: "0.75rem", padding: "0.6rem 0", borderTop: i > 0 ? "1px solid color-mix(in srgb, var(--color-text-muted) 12%, transparent)" : undefined }}>
{i === 0 ? (
<>
<Skeleton style={{ height: "12px", width: "30%" }} />
<Skeleton style={{ height: "12px", width: "20%" }} />
<Skeleton style={{ height: "12px", width: "25%", marginLeft: "auto" }} />
</>
) : (
<>
<Skeleton style={{ width: "28px", height: "28px", borderRadius: "8px" }} />
<Skeleton style={{ height: "14px", width: "35%" }} />
<Skeleton style={{ height: "14px", width: "20%", marginLeft: "auto" }} />
</>
)}
</div>
))}
</div>
);
}