# 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:

```typescript
// Ö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:**

```typescript
// 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:

```tsx
<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

```typescript
// 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

```typescript
// Site ayarları için
const filePath = `${fileName}`;  // Root seviyede

// Places için
const filePath = `places/${fileName}`;  // places/ klasöründe
```

### Public URL Alma

```typescript
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
