feat(frontend): admin dashboard + içerik kataloğu UI, realtime sync

- index.css: IBM Plex Sans + Bricolage Grotesque font'ları import edildi;
  CSS custom property sistemi (--bg-base, --accent-main vb.) tanımlandı;
  body'ye fixed radial gradient + grid overlay arka plan eklendi.

- main.tsx: MantineProvider tema güncellendi — IBM Plex Sans/Bricolage
  Grotesque font ailesi, responsive heading boyutları, özel radius/shadow
  değerleri ayarlandı.

- App.css: Gereksiz yorum temizlendi, stil yönetimi route-level CSS'e taşındı.

- MoviesPage.tsx (büyük güncelleme):
  • Katalog görünümü: film/dizi grid, arama, sıralama, backdrop modal.
  • Admin Dashboard görünümü: cache özeti, content istatistikleri, job
    durum sayaçları, failed job listesi, cache expiry tablosu, metrics
    (hit/miss oranı, kaynak dağılımı).
  • Admin aksiyonlar: cache temizleme, cache ısıtma, başarısız job
    yeniden kuyruklama, eski içerik yenileme.
  • Socket.IO entegrasyonu: content:event dinlenerek katalog anlık
    güncelleniyor; metrics:updated ile dashboard metrikleri canlı akıyor.
  • Reconnect davranışı: bağlantı kopunca her görünüm kendi snapshot'ını
    otomatik yeniliyor.

- movies-page.css (yeni): MoviesPage'e özel kart, backdrop, istatistik
  kutusu ve animasyon stilleri.

- vite.config.ts: /socket.io proxy kuralı eklendi (ws: true) — dev
  sunucusu üzerinden WebSocket bağlantısı backend'e yönlendiriliyor.

- frontend/.env.example (yeni): VITE_API_BASE_URL, VITE_WEB_API_KEY,
  VITE_ADMIN_API_KEY değişken şablonu eklendi.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 12:00:22 +03:00
parent 5c496277f2
commit c0841aab20
7 changed files with 1679 additions and 140 deletions

View File

@@ -0,0 +1,431 @@
.catalog-page {
position: relative;
}
.catalog-hero {
background:
linear-gradient(130deg, rgba(235, 35, 56, 0.14), rgba(13, 17, 28, 0.96) 48%),
radial-gradient(circle at 80% 18%, rgba(255, 155, 66, 0.2), transparent 45%);
border-color: rgba(255, 255, 255, 0.14);
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.28);
}
.eyebrow {
font-size: 0.72rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.68);
}
.stats-wrap {
align-items: stretch;
}
.stat-pill {
min-width: 88px;
text-align: right;
background: rgba(255, 255, 255, 0.02);
border-color: rgba(255, 255, 255, 0.15);
}
.toolbar {
position: sticky;
top: 16px;
z-index: 20;
backdrop-filter: blur(8px);
background: rgba(14, 18, 28, 0.82);
border-color: rgba(255, 255, 255, 0.12);
}
.admin-panel {
background: linear-gradient(150deg, rgba(15, 20, 32, 0.88), rgba(11, 14, 23, 0.92));
border-color: rgba(255, 255, 255, 0.13);
}
.view-nav {
position: sticky;
top: 12px;
z-index: 30;
backdrop-filter: blur(10px);
background: rgba(10, 15, 26, 0.84);
border-color: rgba(255, 255, 255, 0.14);
}
.catalog-view-shell {
padding-inline: clamp(0.5rem, 1.4vw, 1.35rem);
}
.nav-pill {
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.18);
background: rgba(255, 255, 255, 0.02);
transition: transform 180ms ease, border-color 180ms ease, background-color 180ms ease;
}
.nav-pill .mantine-Button-label,
.nav-pill .mantine-Button-section {
transition: color 180ms ease, opacity 180ms ease;
}
.nav-pill.is-active {
border-color: rgba(235, 35, 56, 0.95);
box-shadow: 0 0 0 1px rgba(235, 35, 56, 0.26) inset;
}
.nav-pill.is-active .mantine-Button-label,
.nav-pill.is-active .mantine-Button-section {
color: #f8fbff;
opacity: 1;
}
.nav-pill.is-inactive {
border-color: rgba(255, 255, 255, 0.24);
}
.nav-pill.is-inactive .mantine-Button-label,
.nav-pill.is-inactive .mantine-Button-section {
color: rgba(218, 226, 240, 0.86);
opacity: 0.94;
}
.nav-pill:hover {
transform: translateY(-1px);
border-color: rgba(255, 155, 66, 0.45);
}
.nav-pill.is-inactive:hover .mantine-Button-label,
.nav-pill.is-inactive:hover .mantine-Button-section {
color: rgba(246, 250, 255, 0.96);
}
.admin-card {
background: rgba(255, 255, 255, 0.02);
border-color: rgba(255, 255, 255, 0.1);
}
.list-row {
padding: 8px 10px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 10px;
background: rgba(255, 255, 255, 0.01);
min-width: 0;
}
.cache-key-text {
min-width: 0;
flex: 1;
margin-right: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.search-input {
min-width: 260px;
}
.genre-chip {
cursor: pointer;
border-color: rgba(255, 255, 255, 0.22);
transition: transform 140ms ease, box-shadow 140ms ease, border-color 140ms ease;
}
.genre-chip:hover {
transform: translateY(-1px);
}
.genre-chip:focus-visible {
outline: 2px solid rgba(255, 155, 66, 0.95);
outline-offset: 2px;
border-color: rgba(255, 155, 66, 0.95);
}
.catalog-card {
position: relative;
overflow: hidden;
cursor: pointer;
background: linear-gradient(165deg, rgba(29, 33, 44, 0.95), rgba(15, 19, 30, 0.96));
border-color: rgba(255, 255, 255, 0.11);
transition: transform 220ms ease, box-shadow 220ms ease;
}
.catalog-card:hover {
transform: translateY(-7px);
box-shadow: 0 28px 65px rgba(0, 0, 0, 0.42);
}
.catalog-card:focus-visible {
outline: 2px solid rgba(255, 155, 66, 0.92);
outline-offset: 3px;
transform: translateY(-4px);
box-shadow: 0 24px 54px rgba(0, 0, 0, 0.42);
}
.live-fade-in {
animation: live-fade-in 1500ms ease;
}
@keyframes live-fade-in {
0% {
opacity: 0.55;
transform: translateY(10px) scale(0.985);
box-shadow: 0 0 0 rgba(235, 35, 56, 0), 0 0 0 rgba(255, 214, 102, 0);
}
20% {
opacity: 1;
transform: translateY(0) scale(1.004);
box-shadow: 0 0 0 2px rgba(255, 214, 102, 0.56), 0 0 28px rgba(255, 214, 102, 0.42);
}
55% {
opacity: 1;
transform: translateY(0) scale(1);
box-shadow: 0 0 0 2px rgba(235, 35, 56, 0.35), 0 0 20px rgba(235, 35, 56, 0.22);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
box-shadow: 0 0 0 rgba(235, 35, 56, 0), 0 0 0 rgba(255, 214, 102, 0);
}
}
.live-ttl-flash {
animation: ttl-flash 1050ms cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes ttl-flash {
0% {
border-color: rgba(255, 255, 255, 0.08);
box-shadow: inset 0 0 0 rgba(0, 0, 0, 0);
background: rgba(255, 255, 255, 0.01);
}
38% {
border-color: rgba(255, 255, 255, 0.08);
box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.34), inset 0 0 28px rgba(0, 0, 0, 0.42);
background: linear-gradient(90deg, rgba(0, 0, 0, 0.32), rgba(255, 255, 255, 0.012));
}
100% {
border-color: rgba(255, 255, 255, 0.08);
box-shadow: inset 0 0 0 rgba(0, 0, 0, 0);
background: rgba(255, 255, 255, 0.01);
}
}
.media-wrap {
position: relative;
}
.image-shell {
position: relative;
height: 190px;
}
.image-skeleton {
position: absolute;
inset: 0;
z-index: 1;
}
.lazy-image {
position: relative;
z-index: 2;
}
.lazy-image img {
filter: blur(14px);
transform: scale(1.05);
opacity: 0.86;
transition: filter 320ms ease, transform 420ms ease, opacity 240ms ease;
}
.lazy-image.is-loaded img {
filter: blur(0);
transform: scale(1);
opacity: 1;
}
.media-fallback {
background: linear-gradient(160deg, rgba(35, 38, 52, 0.95), rgba(24, 28, 39, 0.95));
color: rgba(255, 255, 255, 0.34);
}
.media-overlay {
position: absolute;
inset: 0;
pointer-events: none;
background: linear-gradient(to top, rgba(8, 10, 16, 0.86), rgba(8, 10, 16, 0.05) 55%);
}
.detail-modal-content {
background: linear-gradient(150deg, rgba(16, 20, 31, 0.97), rgba(11, 14, 24, 0.97));
border: 1px solid rgba(255, 255, 255, 0.12);
}
.detail-modal-header {
background: transparent;
}
.detail-modal-body {
padding-top: 0.6rem;
position: relative;
}
.detail-media-wrap {
position: relative;
overflow: hidden;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.12);
}
.detail-media-overlay {
position: absolute;
inset: 0;
pointer-events: none;
background:
linear-gradient(to top, rgba(8, 10, 16, 0.92), rgba(8, 10, 16, 0.1) 58%),
radial-gradient(circle at 76% 22%, rgba(235, 35, 56, 0.22), transparent 42%);
}
.detail-title-group {
position: absolute;
left: 16px;
bottom: 14px;
right: 16px;
z-index: 2;
display: flex;
flex-direction: column;
gap: 8px;
}
.detail-title {
color: #f5f8fc;
text-shadow: 0 5px 18px rgba(0, 0, 0, 0.4);
}
.detail-plot {
line-height: 1.62;
}
.detail-brand-stamp {
position: absolute;
right: 20px;
bottom: 16px;
width: 34px;
height: 34px;
object-fit: contain;
opacity: 0.96;
filter: drop-shadow(0 7px 16px rgba(0, 0, 0, 0.44));
pointer-events: none;
}
.card-content {
position: relative;
z-index: 2;
}
.card-title {
font-family: 'Bricolage Grotesque', 'IBM Plex Sans', sans-serif;
letter-spacing: 0.01em;
}
.brand-stamp {
position: absolute;
right: 12px;
bottom: 12px;
width: 30px;
height: 30px;
object-fit: contain;
opacity: 0.92;
filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.38));
}
.empty-state {
background: linear-gradient(145deg, rgba(22, 26, 37, 0.9), rgba(14, 17, 26, 0.95));
border-color: rgba(255, 255, 255, 0.12);
}
.admin-toast {
position: fixed;
right: 22px;
bottom: 22px;
z-index: 80;
max-width: min(420px, calc(100vw - 24px));
padding: 12px 14px;
border-radius: 12px;
border: 1px solid rgba(84, 224, 187, 0.4);
background: linear-gradient(145deg, rgba(8, 40, 42, 0.95), rgba(10, 20, 32, 0.95));
color: #dffcf2;
font-size: 0.92rem;
box-shadow: 0 18px 38px rgba(0, 0, 0, 0.35);
animation: toast-in 180ms ease;
}
@keyframes toast-in {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reveal {
opacity: 0;
transform: translateY(10px);
animation: reveal-in 420ms ease forwards;
}
@keyframes reveal-in {
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 48rem) {
.view-nav {
top: 8px;
}
.view-nav > div {
overflow-x: auto;
white-space: nowrap;
width: 100%;
}
.toolbar {
top: 10px;
}
.search-input {
min-width: 100%;
}
.catalog-view-shell {
padding-inline: 0;
}
.stats-wrap {
width: 100%;
justify-content: flex-start;
}
.admin-toast {
right: 10px;
bottom: 10px;
}
}
@media (prefers-reduced-motion: reduce) {
.reveal,
.catalog-card,
.genre-chip,
.lazy-image img,
.live-fade-in,
.live-ttl-flash {
animation: none;
transition: none;
transform: none;
}
}