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

315 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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