Files
image-apiv3/app/upload/page.tsx
Beyhan Oğur 031582ea2c first commit
2026-04-26 22:11:03 +03:00

416 lines
15 KiB
TypeScript
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.
"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import Swal from "sweetalert2";
interface Image {
id: string;
originalName: string;
url: string;
width: number | null;
height: number | null;
quality: number | null;
format: string;
fileSize: number;
createdAt: string;
}
export default function UploadPage() {
const [file, setFile] = useState<File | null>(null);
const [width, setWidth] = useState<number>(800);
const [height, setHeight] = useState<number>(600);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = e.target.files?.[0];
if (selectedFile) {
setFile(selectedFile);
// Resmin boyutlarını al
const img = new Image();
const objectUrl = URL.createObjectURL(selectedFile);
img.onload = () => {
setWidth(img.naturalWidth);
setHeight(img.naturalHeight);
URL.revokeObjectURL(objectUrl);
};
img.src = objectUrl;
}
};
const [quality, setQuality] = useState<number>(90);
const [format, setFormat] = useState<string>("avif");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
const [images, setImages] = useState<Image[]>([]);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [checkingAuth, setCheckingAuth] = useState(true);
const router = useRouter();
useEffect(() => {
const checkAuth = async () => {
try {
const response = await fetch("/api/auth/get-session", {
credentials: "include",
});
const data = await response.json();
if (!response.ok || !data.user) {
router.push("/login");
return;
}
setIsAuthenticated(true);
loadImages();
} catch (error) {
console.error("Auth kontrolü başarısız:", error);
router.push("/login");
} finally {
setCheckingAuth(false);
}
};
checkAuth();
}, [router]);
const loadImages = async () => {
try {
const response = await fetch("/api/images", {
credentials: "include",
cache: 'no-store', // Cache'i devre dışı bırak
});
if (response.ok) {
const data = await response.json();
console.log("Yüklenen resimler:", data.images?.length || 0);
setImages(data.images || []);
} else {
console.error("Resimler yüklenemedi, status:", response.status);
}
} catch (error) {
console.error("Resimler yüklenemedi:", error);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setSuccess("");
if (!file) {
setError("Lütfen bir dosya seçin");
return;
}
setLoading(true);
try {
const formData = new FormData();
formData.append("file", file);
formData.append("width", width.toString());
formData.append("height", height.toString());
formData.append("quality", quality.toString());
formData.append("format", format);
const response = await fetch("/api/images/upload", {
method: "POST",
credentials: "include",
body: formData,
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || "Yükleme başarısız");
}
// Formu tamamen resetle
setSuccess("Resim başarıyla yüklendi!");
setFile(null);
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
if (fileInput) {
fileInput.value = '';
}
// Resimleri yeniden yükle
await loadImages();
// Success mesajını kısa süre sonra temizle
setTimeout(() => setSuccess(""), 3000);
} catch (err: any) {
setError(err.message || "Bir hata oluştu");
} finally {
setLoading(false);
}
};
const copyToClipboard = (url: string) => {
navigator.clipboard.writeText(url);
setSuccess("URL kopyalandı!");
setTimeout(() => setSuccess(""), 2000);
};
const downloadImage = (url: string, originalName: string) => {
const link = document.createElement("a");
link.href = url;
link.download = originalName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
const handleDelete = async (imageId: string, originalName: string) => {
const result = await Swal.fire({
title: "Emin misiniz?",
text: `${originalName} adlı resmi silmek istediğinize emin misiniz?`,
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#d33",
cancelButtonColor: "#3085d6",
confirmButtonText: "Evet, Sil!",
cancelButtonText: "İptal",
});
if (result.isConfirmed) {
try {
console.log("Silme isteği gönderiliyor, imageId:", imageId);
console.log("Image objesi:", images.find(img => img.id === imageId));
const response = await fetch(`/api/images/${encodeURIComponent(imageId)}`, {
method: "DELETE",
credentials: "include",
});
const data = await response.json();
if (!response.ok) {
console.error("Silme hatası:", data);
throw new Error(data.message || "Silme işlemi başarısız");
}
Swal.fire("Silindi!", "Resim başarıyla silindi.", "success");
loadImages();
} catch (error: any) {
console.error("Silme hatası:", error);
Swal.fire("Hata!", error.message || "Resim silinirken bir hata oluştu.", "error");
}
}
};
if (checkingAuth) {
return (
<div className="flex min-h-screen items-center justify-center bg-zinc-50 dark:bg-black">
<div className="text-lg text-gray-600 dark:text-gray-400">
Yükleniyor...
</div>
</div>
);
}
if (!isAuthenticated) {
return null;
}
return (
<div className="min-h-screen bg-zinc-50 dark:bg-black">
<div className="mx-auto max-w-6xl px-4 py-8">
<div className="mb-6 flex items-center justify-between">
<h1 className="text-3xl font-bold text-black dark:text-zinc-50">
Resim Yükle ve Manipüle Et
</h1>
<div className="flex gap-4">
<Link
href="/profile"
className="rounded-md bg-gray-200 px-4 py-2 text-gray-700 hover:bg-gray-300 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-700"
>
Profil
</Link>
<Link
href="/"
className="rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Ana Sayfa
</Link>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-3">
{/* Upload Form */}
<div className="rounded-lg bg-white p-6 shadow-lg dark:bg-zinc-900 lg:col-span-1">
<h2 className="mb-4 text-xl font-semibold text-black dark:text-zinc-50">
Resim Yükle
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="rounded-md bg-red-50 p-3 text-sm text-red-800 dark:bg-red-900/20 dark:text-red-400">
{error}
</div>
)}
{success && (
<div className="rounded-md bg-green-50 p-3 text-sm text-green-800 dark:bg-green-900/20 dark:text-green-400">
{success}
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Resim Dosyası
</label>
<input
type="file"
accept="image/*"
onChange={handleFileChange}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-600 dark:bg-zinc-800 dark:text-white"
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Genişlik (px)
</label>
<input
type="number"
value={width}
onChange={(e) => setWidth(parseInt(e.target.value) || 800)}
min="1"
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-600 dark:bg-zinc-800 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Yükseklik (px)
</label>
<input
type="number"
value={height}
onChange={(e) => setHeight(parseInt(e.target.value) || 600)}
min="1"
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-600 dark:bg-zinc-800 dark:text-white"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Kalite (1-100)
</label>
<input
type="number"
value={quality}
onChange={(e) => setQuality(parseInt(e.target.value) || 90)}
min="1"
max="100"
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-600 dark:bg-zinc-800 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Format
</label>
<select
value={format}
onChange={(e) => setFormat(e.target.value)}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-600 dark:bg-zinc-800 dark:text-white"
>
<option value="avif">AVIF</option>
<option value="jpeg">JPEG</option>
<option value="png">PNG</option>
<option value="webp">WebP</option>
</select>
</div>
<button
type="submit"
disabled={loading}
className="w-full rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50"
>
{loading ? "Yükleniyor..." : "Resmi Yükle ve İşle"}
</button>
</form>
</div>
{/* Images List */}
<div className="rounded-lg bg-white p-6 shadow-lg dark:bg-zinc-900 lg:col-span-2">
<h2 className="mb-4 text-xl font-semibold text-black dark:text-zinc-50">
Yüklenen Resimler ({images.length})
</h2>
{images.length === 0 ? (
<div className="flex h-64 items-center justify-center rounded-lg border-2 border-dashed border-gray-300 dark:border-zinc-700">
<p className="text-gray-500 dark:text-gray-400">
Henüz resim yüklenmedi
</p>
</div>
) : (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{images.map((image) => (
<div
key={image.id}
className="group relative overflow-hidden rounded-lg border border-gray-200 bg-white shadow-md transition-shadow hover:shadow-xl dark:border-zinc-700 dark:bg-zinc-800"
>
<div className="relative aspect-square overflow-hidden">
<img
src={image.url}
alt={image.originalName}
className="h-full w-full object-cover transition-transform group-hover:scale-105"
/>
<div className="absolute inset-0 bg-black/0 transition-colors group-hover:bg-black/10" />
</div>
<div className="p-4">
<h3 className="mb-2 truncate text-sm font-semibold text-gray-900 dark:text-zinc-50">
{image.originalName}
</h3>
<div className="mb-3 space-y-1 text-xs text-gray-600 dark:text-gray-400">
<p>
<span className="font-medium">Boyut:</span> {image.width} × {image.height} px
</p>
<p>
<span className="font-medium">Format:</span> {image.format.toUpperCase()}
</p>
<p>
<span className="font-medium">Dosya:</span> {Math.round(image.fileSize / 1024)} KB
</p>
{image.quality && (
<p>
<span className="font-medium">Kalite:</span> {image.quality}%
</p>
)}
</div>
<div className="grid grid-cols-2 gap-2">
<button
onClick={() => copyToClipboard(image.url)}
className="rounded-md bg-green-600 px-3 py-2 text-xs font-medium text-white transition-colors hover:bg-green-700"
title="URL'yi Kopyala"
>
Kopyala
</button>
<button
onClick={() => downloadImage(image.url, image.originalName)}
className="rounded-md bg-blue-600 px-3 py-2 text-xs font-medium text-white transition-colors hover:bg-blue-700"
title="İndir"
>
İndir
</button>
</div>
<button
onClick={() => handleDelete(image.id, image.originalName)}
className="mt-2 w-full rounded-md bg-red-600 px-3 py-2 text-xs font-medium text-white transition-colors hover:bg-red-700"
title="Sil"
>
Sil
</button>
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
</div>
);
}