first commit
This commit is contained in:
415
app/upload/page.tsx
Normal file
415
app/upload/page.tsx
Normal file
@@ -0,0 +1,415 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user