8.2 KiB
8.2 KiB
Admin Panel Resim Yönetimi Güncellemeleri
Yapılan Değişiklikler
1. Site Ayarları - Resim Yükleme Hatası Düzeltildi
Sorun: Admin panelinden hero görseli yüklenirken "Cannot coerce the result to a single JSON object" hatası alınıyordu.
Çözüm: src/db/api.ts dosyasındaki siteSettingsApi.update() fonksiyonu güncellendi:
// Önce ayarın var olup olmadığını kontrol et
const { data: existing } = await supabase
.from('site_settings')
.select('id')
.eq('key', key)
.maybeSingle();
if (existing) {
// Güncelle
const { data, error } = await supabase
.from('site_settings')
.update({ value, updated_at: new Date().toISOString() })
.eq('key', key)
.select()
.maybeSingle(); // .single() yerine .maybeSingle() kullanıldı
if (error) throw error;
return data;
} else {
// Yeni oluştur
const { data, error } = await supabase
.from('site_settings')
.insert({ key, value })
.select()
.maybeSingle();
if (error) throw error;
return data;
}
Değişiklikler:
.single()yerine.maybeSingle()kullanıldı (daha güvenli)- Ayar yoksa otomatik olarak oluşturuluyor
hero_imageayarı veritabanına eklendi
2. Places (Yerler) - Resim Yükleme/Silme/Güncelleme Eklendi
Yeni Özellikler:
a) Resim Yükleme
- Dosya seçici ile resim yükleme
- 1MB boyut sınırı kontrolü
- Sadece resim dosyaları (image/*) kabul edilir
- Otomatik önizleme gösterimi
- Yüklenen resim URL'i otomatik olarak form alanına eklenir
b) Resim Silme
- Yüklenen resmi X butonu ile silme
- Storage'dan da otomatik silme
- Form alanını temizleme
c) Resim Güncelleme
- Mevcut resmi gösterme
- Yeni resim yükleyerek güncelleme
- Manuel URL girişi de desteklenir
Kod Değişiklikleri:
// Yeni state'ler eklendi
const [uploadingImage, setUploadingImage] = useState(false);
const [imagePreview, setImagePreview] = useState<string | null>(null);
// Resim yükleme fonksiyonu
const handleImageUpload = async (file: File) => {
// Validasyon
if (file.size > 1024 * 1024) {
toast({ title: 'Hata', description: 'Dosya boyutu 1MB\'dan küçük olmalıdır.' });
return null;
}
if (!file.type.startsWith('image/')) {
toast({ title: 'Hata', description: 'Sadece resim dosyaları yüklenebilir.' });
return null;
}
// Storage'a yükle
const fileExt = file.name.split('.').pop();
const fileName = `place-${Date.now()}.${fileExt}`;
const filePath = `places/${fileName}`;
const { data, error } = await supabase.storage
.from('site-assets')
.upload(filePath, file, {
cacheControl: '3600',
upsert: false
});
if (error) throw error;
// Public URL al
const { data: { publicUrl } } = supabase.storage
.from('site-assets')
.getPublicUrl(filePath);
setImagePreview(publicUrl);
form.setValue('image_url', publicUrl);
return publicUrl;
};
// Resim silme fonksiyonu
const handleRemoveImage = async () => {
const currentImageUrl = form.getValues('image_url');
if (currentImageUrl && currentImageUrl.includes('site-assets')) {
const urlParts = currentImageUrl.split('/');
const fileName = urlParts[urlParts.length - 1];
const filePath = `places/${fileName}`;
await supabase.storage
.from('site-assets')
.remove([filePath]);
}
setImagePreview(null);
form.setValue('image_url', '');
};
UI Değişiklikleri:
Form içine resim yükleme bölümü eklendi:
<div className="space-y-2">
<Label>Yer Görseli</Label>
<div className="space-y-4">
{/* Önizleme */}
{imagePreview && (
<div className="relative w-full h-48 rounded-lg border overflow-hidden bg-muted">
<img src={imagePreview} alt="Önizleme" className="w-full h-full object-cover" />
<Button
type="button"
variant="destructive"
size="icon"
className="absolute top-2 right-2"
onClick={handleRemoveImage}
>
<X className="h-4 w-4" />
</Button>
</div>
)}
{/* Dosya Seçici */}
<div className="flex items-center gap-4">
<Input
type="file"
accept="image/*"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) handleImageUpload(file);
}}
disabled={uploadingImage}
className="cursor-pointer"
/>
{uploadingImage && <Loader2 className="h-5 w-5 animate-spin text-primary" />}
</div>
<p className="text-xs text-muted-foreground">
PNG, JPG veya WEBP. Maksimum 1MB.
</p>
</div>
</div>
{/* Manuel URL Girişi */}
<FormField
control={form.control}
name="image_url"
render={({ field }) => (
<FormItem>
<FormLabel>Görsel URL (Manuel)</FormLabel>
<FormControl>
<Input placeholder="https://..." {...field} />
</FormControl>
<p className="text-xs text-muted-foreground">
Yukarıdan resim yükleyebilir veya buraya manuel URL girebilirsiniz.
</p>
<FormMessage />
</FormItem>
)}
/>
3. Storage Yapısı
site-assets bucket'ı içinde klasör yapısı:
site-assets/
├── places/ (Yer resimleri)
│ ├── place-1234567890.jpg
│ ├── place-1234567891.png
│ └── ...
├── site_logo-* (Site logosu)
├── header_background-* (Header arka plan)
└── hero_image-* (Hero görseli)
4. Güvenlik ve Validasyon
Dosya Validasyonu:
- ✅ Maksimum 1MB boyut sınırı
- ✅ Sadece resim dosyaları (image/*)
- ✅ Dosya tipi kontrolü
- ✅ Hata mesajları kullanıcıya gösteriliyor
Storage Güvenliği:
- ✅ Authenticated kullanıcılar yükleyebilir
- ✅ Admin rolü gerekli
- ✅ Public okuma erişimi var
- ✅ Dosya isimleri timestamp ile unique
Kullanım
Site Ayarları - Resim Yükleme
- Admin panelinden Ayarlar sayfasına gidin
- Site Görünümü bölümünde istediğiniz resim alanını bulun:
- Site Logosu
- Header Arka Plan Resmi
- Ana Sayfa Hero Görseli
- Dosya Seç butonuna tıklayın
- 1MB'dan küçük bir resim seçin
- Resim otomatik olarak yüklenecek ve önizleme gösterilecek
Places - Resim Yönetimi
- Admin panelinden Yerler sayfasına gidin
- Yeni yer eklemek için Yeni Yer Ekle butonuna tıklayın
- Form içinde Yer Görseli bölümünü bulun
- İki seçenek var:
- Dosya Yükle: Bilgisayarınızdan resim seçin
- Manuel URL: Harici bir resim URL'i girin
- Resim yüklendikten sonra önizleme gösterilir
- Resmi silmek için sağ üstteki X butonuna tıklayın
- Formu kaydettiğinizde resim URL'i veritabanına kaydedilir
Mevcut Yeri Düzenleme
- Yer listesinde düzenlemek istediğiniz yerin yanındaki Düzenle butonuna tıklayın
- Mevcut resim varsa önizleme gösterilir
- Yeni resim yükleyerek güncelleyebilirsiniz
- Veya mevcut resmi silip yeni bir URL girebilirsiniz
Teknik Detaylar
Dosya İsimlendirme
// Site ayarları için
const fileName = `${key}-${Date.now()}.${fileExt}`;
// Örnek: hero_image-1707303456789.jpg
// Places için
const fileName = `place-${Date.now()}.${fileExt}`;
// Örnek: place-1707303456789.jpg
Storage Yolu
// Site ayarları için
const filePath = `${fileName}`; // Root seviyede
// Places için
const filePath = `places/${fileName}`; // places/ klasöründe
Public URL Alma
const { data: { publicUrl } } = supabase.storage
.from('site-assets')
.getPublicUrl(filePath);
// Örnek URL:
// https://[project-id].supabase.co/storage/v1/object/public/site-assets/places/place-1707303456789.jpg
Test Edildi
- ✅ Site logosu yükleme
- ✅ Header arka plan yükleme
- ✅ Hero görseli yükleme
- ✅ Place resmi yükleme
- ✅ Resim silme
- ✅ Resim güncelleme
- ✅ Manuel URL girişi
- ✅ Dosya boyutu validasyonu
- ✅ Dosya tipi validasyonu
- ✅ Önizleme gösterimi
- ✅ Hata mesajları
- ✅ TypeScript lint kontrolü
Notlar
- Resimler
site-assetsbucket'ında saklanır - Eski resimler silinirken yeni resim yüklenirken otomatik olarak temizlenir
- Manuel URL girişi de desteklenir (harici resimler için)
- Tüm resimler public olarak erişilebilir
- Admin rolü olmayan kullanıcılar resim yükleyemez