'use client' import { useState, useMemo } from 'react' import { useRouter } from 'next/navigation' import Link from 'next/link' import { App, AppEnvironment, AuditLog, ConfigEntry, ConfigType, Environment } from '@/types' import { cn, envBadgeStyles, maskSecret, typeColor } from '@/lib/utils' import { AppIcon } from './AppIcon' import { IconPicker } from './IconPicker' import { Eye, EyeOff, Copy, Plus, Trash2, Edit2, Check, X, Key, RefreshCw, Clock, Search, Download, FileCode, Shield, Code2, AlertTriangle, Terminal, Smartphone, Globe, Sparkles, Filter, ChevronRight, CheckCircle2, Lock, ExternalLink, HelpCircle, Settings, Settings2, ImageIcon } from 'lucide-react' interface Props { app: App environments: AppEnvironment[] configs: ConfigEntry[] auditLogs: AuditLog[] } type Tab = 'config' | 'api' | 'audit' export function AppDetailClient({ app, environments, configs, auditLogs }: Props) { const router = useRouter() const [activeEnv, setActiveEnv] = useState('production') const [activeTab, setActiveTab] = useState('config') const [searchQuery, setSearchQuery] = useState('') const [typeFilter, setTypeFilter] = useState('all') const [showApiKey, setShowApiKey] = useState(false) const [copiedKey, setCopiedKey] = useState(false) const [copiedEnv, setCopiedEnv] = useState(false) const [copiedSlug, setCopiedSlug] = useState(false) const [showAddModal, setShowAddModal] = useState(false) const [showEditModal, setShowEditModal] = useState(false) const [revealedIds, setRevealedIds] = useState>(new Set()) const [codeSnippetTab, setCodeSnippetTab] = useState<'rn' | 'next' | 'curl'>('rn') const [isDeletingApp, setIsDeletingApp] = useState(false) const currentEnv = environments.find(e => e.name === activeEnv) // Filter configs for active environment, search query and type filter const envConfigs = useMemo(() => { return configs.filter(c => c.environment_id === currentEnv?.id) }, [configs, currentEnv]) const filteredConfigs = useMemo(() => { return envConfigs.filter(c => { const matchesSearch = c.key.toLowerCase().includes(searchQuery.toLowerCase()) || (c.description && c.description.toLowerCase().includes(searchQuery.toLowerCase())) || (c.type !== 'secret' && c.value.toLowerCase().includes(searchQuery.toLowerCase())) const matchesType = typeFilter === 'all' || c.type === typeFilter return matchesSearch && matchesType }) }, [envConfigs, searchQuery, typeFilter]) function copyToClipboard(text: string, setCopied?: (v: boolean) => void) { navigator.clipboard.writeText(text) if (setCopied) { setCopied(true) setTimeout(() => setCopied(false), 2000) } } function exportAsEnv() { const lines = envConfigs.map(c => { const comment = c.description ? `# ${c.description}\n` : '' return `${comment}${c.key}="${c.value.replace(/"/g, '\\"')}"` }).join('\n') copyToClipboard(lines, setCopiedEnv) } function exportAsJson() { const obj: Record = {} envConfigs.forEach(c => { obj[c.key] = c.value }) const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(obj, null, 2)) const downloadAnchor = document.createElement('a') downloadAnchor.setAttribute("href", dataStr) downloadAnchor.setAttribute("download", `${app.slug}-${activeEnv}-config.json`) document.body.appendChild(downloadAnchor) downloadAnchor.click() downloadAnchor.remove() } async function rotateApiKey() { if (!confirm('API anahtarını yenilemek istediğinize emin misiniz? Bu anahtarı kullanan tüm uygulamalar yenilenene kadar çalışmayacaktır.')) return await fetch(`/api/apps/${app.id}/rotate-key`, { method: 'POST' }) router.refresh() } async function handleDeleteApp() { const confirmation = prompt(`Bu uygulamayı ve tüm ayarlarını silmek için lütfen uygulamanın slug değerini yazın: "${app.slug}"`) if (confirmation !== app.slug) { alert('Slug eşleşmedi, silme işlemi iptal edildi.') return } setIsDeletingApp(true) await fetch(`/api/apps/${app.id}`, { method: 'DELETE' }) router.push('/dashboard') } return (
{/* ─── APP HEADER ─── */}
setShowEditModal(true)} title="İkonu Değiştir">

{app.name}

{app.description ? (

{app.description}

) : (

Açıklama bulunmuyor

)}
{/* Quick App Actions */}
{/* ─── NAVIGATION TABS ─── */}
{/* ─── TAB 1: CONFIG ENTRIES ─── */} {activeTab === 'config' && (
{/* Environment Switcher & Top Toolbar */}
{/* Environment Pills */}
{environments.map(env => { const styles = envBadgeStyles(env.name) const isActive = activeEnv === env.name const count = configs.filter(c => c.environment_id === env.id).length return ( ) })}
{/* Quick Export Tools */}
{/* Search & Type Filter Bar */}
setSearchQuery(e.target.value)} placeholder="Key, açıklama veya değer ara..." className="w-full glass-input rounded-xl pl-10 pr-4 py-2 text-xs sm:text-sm text-white placeholder-slate-500 focus:outline-none" /> {searchQuery && ( )}
{/* Config Table Card */}
{/* Header row */}
Anahtar (Key) & Açıklama
Tip
Değer (Value)
İşlemler
{/* Empty State */} {filteredConfigs.length === 0 && (

{searchQuery || typeFilter !== 'all' ? 'Filtreyle eşleşen config bulunamadı' : `${activeEnv.toUpperCase()} ortamında henüz değişken yok`}

{searchQuery || typeFilter !== 'all' ? 'Arama kriterlerinizi temizleyerek tekrar deneyin.' : 'Uygulamanızın ihtiyaç duyduğu API URL, Secret veya Flag değişkenlerini ekleyin.'}

{!searchQuery && typeFilter === 'all' && ( )}
)} {/* Rows */} {filteredConfigs.map(entry => ( { const next = new Set(revealedIds) if (next.has(entry.id)) next.delete(entry.id) else next.add(entry.id) setRevealedIds(next) }} onDeleted={() => router.refresh()} onSaved={() => router.refresh()} /> ))}
)} {/* ─── TAB 2: API & INTEGRATION ─── */} {activeTab === 'api' && (
{/* API Key Box */}

Uygulama API Anahtarı (X-Api-Key)

Mobil uygulamanız veya backend servisiniz başlangıçta config verilerini güvenle çekmek için bu anahtarı kullanır.

{showApiKey ? app.api_key : maskSecret(app.api_key)} {showApiKey && ( Aktif )}
{/* Code Snippets Accordion / Tabs */}

Hızlı Entegrasyon Kodları

Projenize doğrudan yapıştırabileceğiniz hazır SDK ve istemci fonksiyonları:

{/* Code display */}
                  {codeSnippetTab === 'rn' && `// lib/config.ts
import AsyncStorage from '@react-native-async-storage/async-storage';

const API_KEY = '${showApiKey ? app.api_key : '<>'}';
const ENDPOINT = '${typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000'}/api/v1/config';

export async function initRemoteConfig(env: 'development' | 'staging' | 'production' = 'production') {
  try {
    const res = await fetch(\`\${ENDPOINT}?env=\${env}\`, {
      headers: { 'X-Api-Key': API_KEY },
    });
    if (!res.ok) throw new Error('Config fetch failed');
    const remoteConfig = await res.json();
    
    // Offline kullanım için depola
    await AsyncStorage.setItem('@app_config', JSON.stringify(remoteConfig));
    return remoteConfig;
  } catch (error) {
    const cached = await AsyncStorage.getItem('@app_config');
    if (cached) return JSON.parse(cached);
    throw error;
  }
}`}

                  {codeSnippetTab === 'next' && `// lib/remoteConfig.ts
export async function getAppConfig(env = 'production') {
  const res = await fetch(\`${typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000'}/api/v1/config?env=\${env}\`, {
    headers: {
      'X-Api-Key': '${showApiKey ? app.api_key : '<>'}',
    },
    next: { revalidate: 60 }, // 60 saniye ISR cache
  });

  if (!res.ok) {
    throw new Error('Config load failed');
  }

  return res.json();
}`}

                  {codeSnippetTab === 'curl' && `curl -X GET "${typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000'}/api/v1/config?env=production" \\
  -H "X-Api-Key: ${showApiKey ? app.api_key : '<>'}"`}
                
{/* Danger Zone */}

Kritik İşlemler (Danger Zone)

API Anahtarını Yenile (Rotate)

Eski anahtar anında geçersiz olur. Canlıdaki uygulamalar yeni anahtarı alana kadar config çekemez.

Uygulamayı ve Tüm Verileri Sil

Bu uygulama, tüm ortamlardaki değişkenler ve audit logları kalıcı olarak silinecektir.

)} {/* ─── TAB 3: AUDIT LOGS ─── */} {activeTab === 'audit' && (

İşlem & Güvenlik Denetim Kayıtları (Audit Log)

Son 30 konfigürasyon ve anahtar işlemi

{auditLogs.length} Kayıt
{auditLogs.length === 0 ? (
Henüz kayıtlı bir işlem bulunmuyor
) : (
{auditLogs.map(log => { const actionStyles = { create: { badge: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20', label: 'Eklendi' }, update: { badge: 'bg-cyan-500/10 text-cyan-400 border-cyan-500/20', label: 'Güncellendi' }, delete: { badge: 'bg-rose-500/10 text-rose-400 border-rose-500/20', label: 'Silindi' }, rotate_key: { badge: 'bg-amber-500/10 text-amber-400 border-amber-500/20', label: 'Key Yenilendi' }, create_app: { badge: 'bg-purple-500/10 text-purple-400 border-purple-500/20', label: 'App Oluşturuldu' }, delete_app: { badge: 'bg-rose-500/10 text-rose-400 border-rose-500/20', label: 'App Silindi' }, }[log.action] || { badge: 'bg-slate-500/10 text-slate-400 border-slate-500/20', label: log.action } return (
{actionStyles.label}
{log.key || log.action} {log.environment && ( {log.environment} )}
{log.old_value && (

Eski değer: {maskSecret(log.old_value)}

)}
{log.actor}
{new Date(log.created_at).toLocaleString('tr-TR')}
) })}
)}
)} {/* ─── ADD CONFIG MODAL ─── */} {showAddModal && ( setShowAddModal(false)} onSaved={() => { setShowAddModal(false) router.refresh() }} /> )} {/* ─── EDIT APP MODAL ─── */} {showEditModal && ( setShowEditModal(false)} onSaved={() => { setShowEditModal(false) router.refresh() }} /> )}
) } // ─── ADD CONFIG MODAL ──────────────────────────────────────────────── function AddConfigModal({ environmentId, environment, appId, onClose, onSaved }: { environmentId: string environment: string appId: string onClose: () => void onSaved: () => void }) { const [key, setKey] = useState('') const [value, setValue] = useState('') const [type, setType] = useState('text') const [description, setDescription] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState('') async function handleSave(e: React.FormEvent) { e.preventDefault() if (!key || !value) return setLoading(true) setError('') const res = await fetch(`/api/apps/${appId}/config`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ environment_id: environmentId, key, value, type, description, environment }), }) const data = await res.json() setLoading(false) if (!res.ok) { setError(data.error || 'Kaydedilirken hata oluştu') return } onSaved() } return (
{/* Glow accent */}

Yeni Config / Secret Ekle

Hedef Ortam: {environment}

setKey(e.target.value.toUpperCase().replace(/\s/g, '_'))} placeholder="SUPABASE_ANON_KEY" required autoFocus className="w-full glass-input rounded-xl px-3.5 py-2.5 text-white text-xs sm:text-sm font-mono focus:outline-none" />
{type === 'json' ? (