Files
app-admin/app/apps/[id]/config/page.tsx
2026-03-24 15:46:27 +03:00

247 lines
12 KiB
TypeScript
Raw Permalink 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.
import { getAppConfigs, upsertConfig, deleteConfig } from "../../../config/actions";
import { getAppById } from "../../actions";
import { Save, Trash2, Smartphone, Globe, Plus, Code, ArrowLeft } from "lucide-react";
import Link from "next/link";
import { notFound } from "next/navigation";
import { revalidatePath } from "next/cache";
export default async function AppConfigPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const appId = parseInt(id);
if (isNaN(appId)) notFound();
const app = await getAppById(appId);
if (!app) notFound();
const configToken = process.env.CONFIG_API_TOKEN ?? "";
const jsonUrl = `/api/config/${app.bundleId}?token=${configToken}`;
const configs = await getAppConfigs(appId);
// --- Server Actions ---
async function handleAddConfig(formData: FormData) {
"use server";
const key = formData.get("key") as string;
const value = formData.get("value") as string;
const type = formData.get("type") as string;
let parsedValue: any = value;
if (type === "boolean") parsedValue = value === "true";
if (type === "number") parsedValue = parseFloat(value);
await upsertConfig(appId, key, parsedValue);
revalidatePath(`/apps/${appId}/config`);
}
async function handleUpdateConfig(formData: FormData) {
"use server";
const key = formData.get("key") as string;
const rawValue = formData.get("value") as string;
// Auto-detect type
let parsedValue: any = rawValue;
if (rawValue === "true") parsedValue = true;
else if (rawValue === "false") parsedValue = false;
else if (rawValue !== "" && !isNaN(Number(rawValue))) parsedValue = Number(rawValue);
await upsertConfig(appId, key, parsedValue);
revalidatePath(`/apps/${appId}/config`);
}
return (
<div className="flex h-screen bg-slate-50 dark:bg-black font-sans text-slate-900 dark:text-slate-100">
{/* Sidebar */}
<aside className="w-64 bg-white dark:bg-zinc-950 border-r border-slate-200 dark:border-zinc-900 flex flex-col shrink-0 shadow-sm">
<div className="p-6 border-b border-slate-100 dark:border-zinc-900 flex items-center gap-3">
<Link href="/" className="w-7 h-7 bg-slate-900 dark:bg-white rounded-md flex items-center justify-center">
<span className="text-white dark:text-black font-bold text-sm">A</span>
</Link>
<span className="font-bold text-lg uppercase tracking-wider">AppAdmin</span>
</div>
<nav className="flex-1 p-4 space-y-1">
<Link href="/" className="flex items-center gap-2.5 px-3 py-2 text-slate-600 dark:text-zinc-400 hover:bg-slate-50 dark:hover:bg-zinc-900 rounded-lg text-sm font-medium">
<Globe size={18} /> Dashboard
</Link>
<Link href="/apps" className="flex items-center gap-2.5 px-3 py-2 bg-slate-900 dark:bg-white text-white dark:text-black rounded-lg text-sm font-semibold">
<Smartphone size={18} /> Uygulamalar
</Link>
</nav>
</aside>
{/* Main */}
<main className="flex-1 overflow-y-auto p-8">
<div className="max-w-5xl mx-auto space-y-8">
{/* Header */}
<div className="flex items-center justify-between border-b border-slate-200 dark:border-zinc-900 pb-6">
<div className="flex items-center gap-4">
<Link href="/apps" className="p-2 bg-white dark:bg-zinc-950 border border-slate-200 dark:border-zinc-800 rounded-lg text-slate-500 hover:text-slate-900 transition-colors">
<ArrowLeft size={20} />
</Link>
<div>
<h1 className="text-2xl font-bold">{app.name} - Remote Config</h1>
<p className="text-sm text-slate-500 font-mono opacity-70">{app.bundleId}</p>
</div>
</div>
<Link
href={jsonUrl}
target="_blank"
className="px-4 py-2 bg-slate-100 dark:bg-zinc-900 rounded-lg text-xs font-bold flex items-center gap-2 hover:bg-slate-200 transition-all border border-slate-200 dark:border-zinc-800"
>
<Code size={14} /> JSON Çıktısı
</Link>
</div>
{/* API Erişim Bilgisi */}
<div className="bg-slate-900 dark:bg-zinc-900 rounded-xl p-5 space-y-3 border border-slate-800">
<p className="text-[10px] font-bold uppercase tracking-widest text-slate-400">Mobil Uygulama Entegrasyonu</p>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<span className="text-[10px] font-bold uppercase text-slate-500 w-16 shrink-0">URL</span>
<code className="flex-1 text-xs text-emerald-400 font-mono bg-black/30 px-3 py-1.5 rounded-lg truncate select-all">
{`/api/config/${app.bundleId}`}
</code>
</div>
<div className="flex items-center gap-3">
<span className="text-[10px] font-bold uppercase text-slate-500 w-16 shrink-0">Token</span>
<code className="flex-1 text-xs text-yellow-400 font-mono bg-black/30 px-3 py-1.5 rounded-lg truncate select-all">
{configToken || "— CONFIG_API_TOKEN tanımlı değil —"}
</code>
</div>
<p className="text-[10px] text-slate-500 pl-20">
{`Authorization: Bearer <token> ya da ?token=<token> query parametresi ile erişin.`}
</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Add New Config Panel */}
<div className="lg:col-span-1">
<div className="bg-white dark:bg-zinc-950 p-6 rounded-xl border border-slate-200 dark:border-zinc-900 shadow-sm sticky top-8">
<h3 className="font-bold text-sm mb-4 flex items-center gap-2 text-slate-900 dark:text-white">
<Plus size={16} className="text-blue-500" /> Yeni Ayar Ekle
</h3>
<form action={handleAddConfig} className="space-y-4">
<div className="space-y-1">
<label className="text-[10px] font-bold uppercase text-slate-400">Anahtar (Key)</label>
<input
name="key"
required
placeholder="örn: maintenance_mode"
className="w-full px-3 py-2 bg-slate-50 dark:bg-zinc-900 border border-slate-200 dark:border-zinc-800 rounded-lg text-sm font-mono text-slate-900 dark:text-white outline-none focus:ring-2 focus:ring-slate-400"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold uppercase text-slate-400">Değer (Value)</label>
<input
name="value"
required
placeholder="örn: true veya 1.0.0"
className="w-full px-3 py-2 bg-slate-50 dark:bg-zinc-900 border border-slate-200 dark:border-zinc-800 rounded-lg text-sm text-slate-900 dark:text-white outline-none focus:ring-2 focus:ring-slate-400"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-bold uppercase text-slate-400">Tip</label>
<select
name="type"
className="w-full px-3 py-2 bg-slate-50 dark:bg-zinc-900 border border-slate-200 dark:border-zinc-800 rounded-lg text-sm text-slate-900 dark:text-white outline-none"
>
<option value="string">String</option>
<option value="boolean">Boolean</option>
<option value="number">Number</option>
</select>
</div>
<button
type="submit"
className="w-full py-2.5 bg-slate-900 dark:bg-white text-white dark:text-black rounded-lg text-sm font-bold hover:opacity-90 transition-opacity"
>
Kaydet
</button>
</form>
</div>
</div>
{/* Config Table */}
<div className="lg:col-span-2">
<div className="bg-white dark:bg-zinc-950 rounded-xl border border-slate-200 dark:border-zinc-900 shadow-sm overflow-hidden">
<table className="w-full text-left">
<thead className="bg-slate-50 dark:bg-zinc-900 border-b border-slate-200 dark:border-zinc-800 text-[10px] font-bold uppercase tracking-widest text-slate-400">
<tr>
<th className="px-5 py-4 w-2/5">Key</th>
<th className="px-5 py-4 w-3/5">Value</th>
<th className="px-5 py-4 text-right"></th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100 dark:divide-zinc-900">
{configs.map((config) => (
<tr
key={config.id}
className="hover:bg-slate-50/70 dark:hover:bg-zinc-900/50 transition-colors group"
>
{/* Key — read only */}
<td className="px-5 py-3 font-mono text-xs font-semibold text-slate-600 dark:text-zinc-400 align-middle">
{config.configKey}
</td>
{/* Value — inline editable */}
<td className="px-3 py-2 align-middle">
<form action={handleUpdateConfig} className="flex items-center gap-1.5">
<input type="hidden" name="key" value={config.configKey} />
<input
name="value"
defaultValue={String(config.configValue)}
className="w-full px-3 py-1.5 rounded-lg text-sm font-mono text-slate-900 dark:text-white bg-slate-50 dark:bg-zinc-900 border border-slate-200 dark:border-zinc-800 outline-none focus:ring-2 focus:ring-slate-400 dark:focus:ring-zinc-600 transition-all"
/>
<button
type="submit"
title="Değeri güncelle"
className="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg text-slate-300 dark:text-zinc-600 hover:text-emerald-600 dark:hover:text-emerald-400 hover:bg-emerald-50 dark:hover:bg-emerald-950/50 transition-all opacity-0 group-hover:opacity-100 focus:opacity-100"
>
<Save size={14} />
</button>
</form>
</td>
{/* Delete */}
<td className="px-5 py-3 text-right align-middle">
<form
action={async () => {
"use server";
await deleteConfig(config.id);
revalidatePath(`/apps/${appId}/config`);
}}
>
<button
type="submit"
title="Sil"
className="p-1.5 rounded-lg text-slate-300 dark:text-zinc-700 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-950/40 transition-all"
>
<Trash2 size={15} />
</button>
</form>
</td>
</tr>
))}
{configs.length === 0 && (
<tr>
<td colSpan={3} className="px-6 py-12 text-center text-slate-400 italic text-sm">
Henüz bir ayar eklenmemiş.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
</div>
);
}