Files
config-vault/components/DashboardClient.tsx
T
2026-08-26 14:21:53 +03:00

240 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState } from 'react'
import Link from 'next/link'
import { Plus, Search, Layers, Key, ArrowUpRight, Clock, Sparkles, SlidersHorizontal, ShieldCheck, Zap } from 'lucide-react'
import { App } from '@/types'
import { AppIcon } from './AppIcon'
interface DashboardApp extends App {
config_count?: number | string
}
interface Props {
initialApps: DashboardApp[]
totalConfigsCount: number
totalAuditCount: number
}
export function DashboardClient({ initialApps, totalConfigsCount, totalAuditCount }: Props) {
const [search, setSearch] = useState('')
const filteredApps = initialApps.filter(app =>
app.name.toLowerCase().includes(search.toLowerCase()) ||
app.slug.toLowerCase().includes(search.toLowerCase()) ||
(app.description && app.description.toLowerCase().includes(search.toLowerCase()))
)
return (
<div className="space-y-8 animate-fade-in">
{/* Hero / Stat cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Stat 1 */}
<div className="glass-panel p-5 rounded-2xl relative overflow-hidden group">
<div className="absolute top-0 right-0 w-24 h-24 bg-emerald-500/10 rounded-full blur-2xl group-hover:bg-emerald-500/20 transition-colors" />
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Kayıtlı Uygulamalar</span>
<div className="w-8 h-8 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400">
<Layers className="w-4 h-4" />
</div>
</div>
<div className="flex items-baseline gap-2">
<span className="text-3xl font-extrabold text-white tracking-tight">{initialApps.length}</span>
<span className="text-xs text-emerald-400 font-medium">Aktif Servis</span>
</div>
</div>
{/* Stat 2 */}
<div className="glass-panel p-5 rounded-2xl relative overflow-hidden group">
<div className="absolute top-0 right-0 w-24 h-24 bg-cyan-500/10 rounded-full blur-2xl group-hover:bg-cyan-500/20 transition-colors" />
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Toplam Config & Secret</span>
<div className="w-8 h-8 rounded-xl bg-cyan-500/10 border border-cyan-500/20 flex items-center justify-center text-cyan-400">
<Key className="w-4 h-4" />
</div>
</div>
<div className="flex items-baseline gap-2">
<span className="text-3xl font-extrabold text-white tracking-tight">{totalConfigsCount}</span>
<span className="text-xs text-cyan-400 font-medium">Değer Tanımlı</span>
</div>
</div>
{/* Stat 3 */}
<div className="glass-panel p-5 rounded-2xl relative overflow-hidden group">
<div className="absolute top-0 right-0 w-24 h-24 bg-purple-500/10 rounded-full blur-2xl group-hover:bg-purple-500/20 transition-colors" />
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Ortam Desteği</span>
<div className="w-8 h-8 rounded-xl bg-purple-500/10 border border-purple-500/20 flex items-center justify-center text-purple-400">
<Zap className="w-4 h-4" />
</div>
</div>
<div className="flex items-baseline gap-2">
<span className="text-3xl font-extrabold text-white tracking-tight">3</span>
<span className="text-xs text-purple-400 font-medium">Dev · Stage · Prod</span>
</div>
</div>
{/* Stat 4 */}
<div className="glass-panel p-5 rounded-2xl relative overflow-hidden group">
<div className="absolute top-0 right-0 w-24 h-24 bg-amber-500/10 rounded-full blur-2xl group-hover:bg-amber-500/20 transition-colors" />
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider">İşlem Denetimi</span>
<div className="w-8 h-8 rounded-xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-center text-amber-400">
<ShieldCheck className="w-4 h-4" />
</div>
</div>
<div className="flex items-baseline gap-2">
<span className="text-3xl font-extrabold text-white tracking-tight">{totalAuditCount}</span>
<span className="text-xs text-amber-400 font-medium">Audit Kaydı</span>
</div>
</div>
</div>
{/* Action Header & Search */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-4 pt-2">
<div className="relative flex-1 max-w-md">
<Search className="w-4 h-4 text-slate-400 absolute left-3.5 top-1/2 -translate-y-1/2" />
<input
type="text"
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Uygulama adı veya slug ara..."
className="w-full glass-input rounded-xl pl-10 pr-4 py-2.5 text-sm text-white placeholder-slate-500 focus:outline-none"
/>
{search && (
<button
onClick={() => setSearch('')}
className="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-slate-400 hover:text-white"
>
Temizle
</button>
)}
</div>
<Link
href="/dashboard/apps/new"
className="inline-flex items-center justify-center gap-2 bg-emerald-500 hover:bg-emerald-400 active:bg-emerald-600 text-slate-950 font-bold text-sm px-5 py-2.5 rounded-xl shadow-lg shadow-emerald-500/20 transition-all hover:scale-[1.02]"
>
<Plus className="w-4 h-4 stroke-[2.5]" />
<span>Yeni Uygulama Ekle</span>
</Link>
</div>
{/* App Cards Grid */}
{filteredApps.length === 0 ? (
<div className="glass-panel text-center py-16 px-4 rounded-3xl border border-dashed border-slate-800">
<div className="w-14 h-14 bg-slate-900 border border-slate-800 rounded-2xl flex items-center justify-center mx-auto mb-4 text-slate-500">
<SlidersHorizontal className="w-7 h-7" />
</div>
<h3 className="text-white text-lg font-bold mb-1">
{search ? 'Eşleşen uygulama bulunamadı' : 'Henüz hiç uygulama eklenmemiş'}
</h3>
<p className="text-slate-400 text-sm max-w-md mx-auto mb-6">
{search
? `"${search}" aramasıyla eşleşen bir sonuç yok. Başka bir anahtar kelime deneyin.`
: 'ConfigVault ile mobil ve web uygulamalarınızın ortam değişkenlerini dinamik olarak yönetmeye başlamak için ilk uygulamanızı ekleyin.'}
</p>
{!search && (
<Link
href="/dashboard/apps/new"
className="inline-flex items-center gap-2 bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-bold text-sm px-5 py-2.5 rounded-xl shadow-lg shadow-emerald-500/25 transition-transform hover:scale-105"
>
<Plus className="w-4 h-4 stroke-[2.5]" />
<span>İlk Uygulamayı Oluştur</span>
</Link>
)}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
{filteredApps.map(app => (
<AppCard key={app.id} app={app} />
))}
</div>
)}
</div>
)
}
function AppCard({ app }: { app: DashboardApp }) {
const formattedDate = new Date(app.updated_at).toLocaleDateString('tr-TR', {
day: 'numeric',
month: 'short',
year: 'numeric',
})
// Color theme generator based on app name initial
const initial = app.name.charAt(0).toUpperCase()
const gradients = [
'from-emerald-500 to-teal-700',
'from-cyan-500 to-blue-700',
'from-indigo-500 to-purple-700',
'from-amber-500 to-orange-700',
'from-rose-500 to-pink-700',
]
const gradientIndex = (initial.charCodeAt(0) || 0) % gradients.length
const bgGradient = gradients[gradientIndex]
return (
<Link
href={`/dashboard/apps/${app.id}`}
className="glass-panel-interactive rounded-2xl p-6 flex flex-col justify-between group relative overflow-hidden"
>
{/* Ambient background hover flare */}
<div className="absolute top-0 right-0 w-32 h-32 bg-emerald-500/5 group-hover:bg-emerald-500/10 rounded-full blur-2xl transition-all duration-300 pointer-events-none" />
<div>
{/* Top bar: Avatar & Arrow */}
<div className="flex items-start justify-between mb-4">
<AppIcon name={app.name} iconUrl={app.icon_url} size="lg" />
<div className="w-8 h-8 rounded-xl bg-slate-800/60 border border-slate-700/50 flex items-center justify-center text-slate-400 group-hover:text-emerald-400 group-hover:border-emerald-500/30 group-hover:bg-emerald-500/10 transition-all">
<ArrowUpRight className="w-4 h-4 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
</div>
</div>
{/* Title & Slug */}
<h3 className="text-white text-lg font-bold group-hover:text-emerald-300 transition-colors tracking-tight">
{app.name}
</h3>
<div className="inline-flex items-center gap-1 mt-1 px-2 py-0.5 rounded-md bg-slate-900/90 border border-slate-800 text-[11px] font-mono text-slate-400">
<span>slug:</span>
<span className="text-emerald-400 font-semibold">{app.slug}</span>
</div>
{/* Description */}
<p className="text-slate-400 text-xs mt-3 line-clamp-2 leading-relaxed min-h-[32px]">
{app.description || <span className="text-slate-600 italic">Açıklama girilmemiş</span>}
</p>
</div>
{/* Bottom meta details */}
<div className="mt-6 pt-4 border-t border-slate-800/80 space-y-3">
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-1.5 text-slate-400">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
<span className="font-mono text-[11px]">
{Number(app.config_count) || 0} config kaydı
</span>
</div>
<div className="flex items-center gap-1 text-[11px] text-slate-500">
<Clock className="w-3 h-3" />
<span>{formattedDate}</span>
</div>
</div>
{/* Env tag pills */}
<div className="flex items-center gap-1.5">
<span className="text-[10px] font-medium px-2 py-0.5 rounded bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
prod
</span>
<span className="text-[10px] font-medium px-2 py-0.5 rounded bg-amber-500/10 text-amber-400 border border-amber-500/20">
stage
</span>
<span className="text-[10px] font-medium px-2 py-0.5 rounded bg-cyan-500/10 text-cyan-400 border border-cyan-500/20">
dev
</span>
</div>
</div>
</Link>
)
}