İmage upload,sıkıştırma
This commit is contained in:
34
app/(dashboard)/dashboard/sliders/[id]/page.tsx
Normal file
34
app/(dashboard)/dashboard/sliders/[id]/page.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { SliderForm } from "@/components/dashboard/slider-form"
|
||||
import { createClient } from "@/lib/supabase-server"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
interface EditSliderPageProps {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export default async function EditSliderPage({ params }: EditSliderPageProps) {
|
||||
const supabase = createClient()
|
||||
|
||||
const { data: slider } = await supabase
|
||||
.from('sliders')
|
||||
.select('*')
|
||||
.eq('id', params.id)
|
||||
.single()
|
||||
|
||||
if (!slider) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 space-y-4 p-8 pt-6">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<h2 className="text-3xl font-bold tracking-tight">Slider Düzenle</h2>
|
||||
</div>
|
||||
<div className="max-w-2xl">
|
||||
<SliderForm initialData={slider} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
124
app/(dashboard)/dashboard/sliders/actions.ts
Normal file
124
app/(dashboard)/dashboard/sliders/actions.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
"use server"
|
||||
|
||||
import { createClient } from "@/lib/supabase-server"
|
||||
import { createClient as createSupabaseClient } from "@supabase/supabase-js"
|
||||
import { revalidatePath } from "next/cache"
|
||||
|
||||
// Admin client for privileged operations
|
||||
const supabaseAdmin = createSupabaseClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.SUPABASE_SERVICE_ROLE_KEY!,
|
||||
{
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
async function assertAdmin() {
|
||||
const supabase = createClient()
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (!user) throw new Error("Oturum açmanız gerekiyor.")
|
||||
|
||||
const { data: profile } = await supabase.from('profiles').select('role').eq('id', user.id).single()
|
||||
if (profile?.role !== 'admin') throw new Error("Yetkisiz işlem.")
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
export async function getSliders() {
|
||||
const supabase = createClient()
|
||||
// Everyone can read, so normal client is fine
|
||||
const { data, error } = await supabase
|
||||
.from('sliders')
|
||||
.select('*')
|
||||
.order('order', { ascending: true })
|
||||
.order('created_at', { ascending: false })
|
||||
|
||||
if (error) return { error: error.message }
|
||||
return { data }
|
||||
}
|
||||
|
||||
export async function createSlider(data: {
|
||||
title: string
|
||||
description?: string
|
||||
image_url: string
|
||||
link?: string
|
||||
order?: number
|
||||
is_active?: boolean
|
||||
}) {
|
||||
try {
|
||||
await assertAdmin()
|
||||
|
||||
const { error } = await supabaseAdmin.from('sliders').insert({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
image_url: data.image_url,
|
||||
link: data.link,
|
||||
order: data.order || 0,
|
||||
is_active: data.is_active ?? true
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
|
||||
revalidatePath("/dashboard/sliders")
|
||||
revalidatePath("/") // Homepage cache update
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return { error: (error as Error).message }
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateSlider(id: string, data: {
|
||||
title: string
|
||||
description?: string
|
||||
image_url: string
|
||||
link?: string
|
||||
order?: number
|
||||
is_active?: boolean
|
||||
}) {
|
||||
try {
|
||||
await assertAdmin()
|
||||
|
||||
const { error } = await supabaseAdmin
|
||||
.from('sliders')
|
||||
.update({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
image_url: data.image_url,
|
||||
link: data.link,
|
||||
order: data.order,
|
||||
is_active: data.is_active,
|
||||
// updated_at trigger usually handles time, but we don't have it in schema yet, so maybe add later
|
||||
})
|
||||
.eq('id', id)
|
||||
|
||||
if (error) throw error
|
||||
|
||||
revalidatePath("/dashboard/sliders")
|
||||
revalidatePath("/")
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return { error: (error as Error).message }
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteSlider(id: string) {
|
||||
try {
|
||||
await assertAdmin()
|
||||
|
||||
const { error } = await supabaseAdmin
|
||||
.from('sliders')
|
||||
.delete()
|
||||
.eq('id', id)
|
||||
|
||||
if (error) throw error
|
||||
|
||||
revalidatePath("/dashboard/sliders")
|
||||
revalidatePath("/")
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return { error: (error as Error).message }
|
||||
}
|
||||
}
|
||||
14
app/(dashboard)/dashboard/sliders/new/page.tsx
Normal file
14
app/(dashboard)/dashboard/sliders/new/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { SliderForm } from "@/components/dashboard/slider-form"
|
||||
|
||||
export default function NewSliderPage() {
|
||||
return (
|
||||
<div className="flex-1 space-y-4 p-8 pt-6">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<h2 className="text-3xl font-bold tracking-tight">Yeni Slider Oluştur</h2>
|
||||
</div>
|
||||
<div className="max-w-2xl">
|
||||
<SliderForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
85
app/(dashboard)/dashboard/sliders/page.tsx
Normal file
85
app/(dashboard)/dashboard/sliders/page.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import Link from "next/link"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Plus, Pencil, Trash2, GripVertical } from "lucide-react"
|
||||
import { getSliders, deleteSlider } from "./actions"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import Image from "next/image"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
export default async function SlidersPage() {
|
||||
const { data: sliders, error } = await getSliders()
|
||||
|
||||
if (error) {
|
||||
return <div className="p-8 text-red-500">Hata: {error}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 space-y-4 p-8 pt-6">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<h2 className="text-3xl font-bold tracking-tight">Slider Yönetimi</h2>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Link href="/dashboard/sliders/new">
|
||||
<Button>
|
||||
<Plus className="mr-2 h-4 w-4" /> Yeni Slider Ekle
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4">
|
||||
{sliders?.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center p-12 text-muted-foreground">
|
||||
<p>Henüz hiç slider eklenmemiş.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
sliders?.map((slider) => (
|
||||
<Card key={slider.id} className="overflow-hidden">
|
||||
<div className="flex flex-col sm:flex-row items-center p-2 gap-4">
|
||||
<div className="p-2 cursor-move text-muted-foreground">
|
||||
<GripVertical className="h-5 w-5" />
|
||||
</div>
|
||||
|
||||
<div className="relative w-full sm:w-48 h-32 sm:h-24 rounded-md overflow-hidden bg-slate-100 flex-shrink-0">
|
||||
<Image
|
||||
src={slider.image_url}
|
||||
alt={slider.title}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0 grid gap-1 text-center sm:text-left">
|
||||
<div className="flex items-center gap-2 justify-center sm:justify-start">
|
||||
<h3 className="font-semibold truncate">{slider.title}</h3>
|
||||
{!slider.is_active && (
|
||||
<Badge variant="secondary">Pasif</Badge>
|
||||
)}
|
||||
<Badge variant="outline">Sıra: {slider.order}</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground truncate">
|
||||
{slider.description || "Açıklama yok"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 p-2">
|
||||
<Link href={`/dashboard/sliders/${slider.id}`}>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
{/* Delete functionality usually needs a client component or form action,
|
||||
for simplicity here we will just link to edit,
|
||||
or we can add a delete button with server action in a separate client component if needed.
|
||||
Ideally, list items should be client components to handle delete easily.
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user