38980-vm/app-9w9pd00g5j41/ADMIN_IMAGE_MANAGEMENT.md
2026-03-04 18:25:09 +00:00

8.2 KiB
Raw Permalink Blame History

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_image ayarı 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

  1. Admin panelinden Ayarlar sayfasına gidin
  2. 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
  3. Dosya Seç butonuna tıklayın
  4. 1MB'dan küçük bir resim seçin
  5. Resim otomatik olarak yüklenecek ve önizleme gösterilecek

Places - Resim Yönetimi

  1. Admin panelinden Yerler sayfasına gidin
  2. Yeni yer eklemek için Yeni Yer Ekle butonuna tıklayın
  3. Form içinde Yer Görseli bölümünü bulun
  4. İki seçenek var:
    • Dosya Yükle: Bilgisayarınızdan resim seçin
    • Manuel URL: Harici bir resim URL'i girin
  5. Resim yüklendikten sonra önizleme gösterilir
  6. Resmi silmek için sağ üstteki X butonuna tıklayın
  7. Formu kaydettiğinizde resim URL'i veritabanına kaydedilir

Mevcut Yeri Düzenleme

  1. Yer listesinde düzenlemek istediğiniz yerin yanındaki Düzenle butonuna tıklayın
  2. Mevcut resim varsa önizleme gösterilir
  3. Yeni resim yükleyerek güncelleyebilirsiniz
  4. 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-assets bucket'ı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