diff --git a/PLANLAMA.md b/PLANLAMA.md index 933128c..c2af977 100644 --- a/PLANLAMA.md +++ b/PLANLAMA.md @@ -22,7 +22,11 @@ - [x] Ürün Yönetimi (Temel CRUD). - [x] Kategori Yönetimi (Arayüz hazır, veritabanı bekleniyor). - -## 3. NetGSm Entegrasyonu -- Login için Sms doğrulama entegrasyonu yapılacak. +## 3. Tamamlanan Ek Özellikler (28.01.2026) +- [x] **NetGSM Entegrasyonu:** Login için SMS doğrulama (2FA) eklendi. +- [x] **Oto-Çıkış:** 15dk hareketsizlikte otomatik çıkış. +- [x] **Ürün Geliştirmeleri:** + - Aktif/Pasif durumu. + - Çoklu resim yükleme. + - Resim optimizasyonu. - \ No newline at end of file diff --git a/app/(dashboard)/dashboard/products/actions.ts b/app/(dashboard)/dashboard/products/actions.ts index abee491..d04c349 100644 --- a/app/(dashboard)/dashboard/products/actions.ts +++ b/app/(dashboard)/dashboard/products/actions.ts @@ -9,26 +9,41 @@ interface ProductData { description?: string price: number image_url?: string + is_active?: boolean + images?: string[] } export async function createProduct(data: ProductData) { const supabase = createClient() - // Validate data manually or use Zod schema here again securely - // For simplicity, we assume data is coming from the strongly typed Client Form - // In production, ALWAYS validate server-side strictly. - try { - const { error } = await supabase.from("products").insert({ + // 1. Create Product + const { data: product, error } = await supabase.from("products").insert({ name: data.name, category: data.category, description: data.description, price: data.price, - image_url: data.image_url, - }) + image_url: data.image_url, // Main image (can be first of images) + is_active: data.is_active ?? true + }).select().single() if (error) throw error + // 2. Insert Images (if any) + if (data.images && data.images.length > 0) { + const imageInserts = data.images.map((url, index) => ({ + product_id: product.id, + image_url: url, + display_order: index + })) + + const { error: imgError } = await supabase.from("product_images").insert(imageInserts) + if (imgError) { + console.error("Error inserting images:", imgError) + // We don't throw here to avoid failing the whole product creation if just images fail + } + } + revalidatePath("/dashboard/products") return { success: true } } catch (error) { @@ -40,16 +55,38 @@ export async function updateProduct(id: number, data: ProductData) { const supabase = createClient() try { + // 1. Update Product const { error } = await supabase.from("products").update({ name: data.name, category: data.category, description: data.description, price: data.price, image_url: data.image_url, + is_active: data.is_active }).eq("id", id) if (error) throw error + // 2. Update Images + // Strategy: Delete all and re-insert is simplest for now. + // Or better: Differential update. For simplicity in MVP: Delete all for this product and re-insert *if* new images provided. + // Actually, if we want to keep existing ones, we need more complex logic. + // For now, let's assume the form sends the FULL list of current images. + if (data.images) { + // Delete old + await supabase.from("product_images").delete().eq("product_id", id) + + // Insert new + if (data.images.length > 0) { + const imageInserts = data.images.map((url, index) => ({ + product_id: id, + image_url: url, + display_order: index + })) + await supabase.from("product_images").insert(imageInserts) + } + } + revalidatePath("/dashboard/products") revalidatePath(`/dashboard/products/${id}`) return { success: true } diff --git a/app/(dashboard)/layout.tsx b/app/(dashboard)/layout.tsx index 53d555d..9c0dd6e 100644 --- a/app/(dashboard)/layout.tsx +++ b/app/(dashboard)/layout.tsx @@ -4,6 +4,8 @@ import { redirect } from "next/navigation" import { Sidebar } from "@/components/dashboard/sidebar" import { DashboardHeader } from "@/components/dashboard/header" +import { AutoLogoutHandler } from "@/components/dashboard/auto-logout-handler" + export default async function DashboardLayout({ children, }: Readonly<{ @@ -20,6 +22,7 @@ export default async function DashboardLayout({ return (
+