240 lines
11 KiB
TypeScript
240 lines
11 KiB
TypeScript
'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>
|
||
)
|
||
}
|