first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 22:09:32 +03:00
commit 71eff2d979
78 changed files with 10173 additions and 0 deletions

415
app/upload/page.tsx Normal file
View 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>
);
}