feat: integrate Cloudinary image upload, Instagram Feed, and fix Next.js warnings

This commit is contained in:
2026-06-09 21:28:45 +03:00
parent 2b3392dcb1
commit a4ad9344a0
52 changed files with 4300 additions and 263 deletions
+67
View File
@@ -0,0 +1,67 @@
"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
export async function getAboutSettings() {
try {
let settings = await prisma.aboutSettings.findUnique({
where: { id: "default" }
});
if (!settings) {
settings = await prisma.aboutSettings.create({
data: {
id: "default",
title: "Sonsuz Mavilikte Bir Kaçış",
description: "Kozmos Beach & More, denizin esintisiyle doğanın ritmini bir araya getiren eşsiz bir deneyim sunuyor. Gündüz güneşin ve berrak denizin tadını çıkarırken, gün batımıyla birlikte DJ performansları ve özel kokteyller eşliğinde unutulmaz anılar biriktirin.",
card1Title: "Kusursuz Sahil",
card1Desc: "İncecik altın kumu ve turkuaz sularıyla kendinizi tamamen yenileyin.",
card2Title: "Gastronomi",
card2Desc: "Dünya mutfağından özenle seçilmiş lezzetler ve imza kokteyller.",
card3Title: "Canlı Eğlence",
card3Desc: "Her akşam gün batımında başlayan benzersiz DJ performansları.",
image: "https://images.unsplash.com/photo-1544148103-0773bf10d330?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
}
});
}
return { success: true, data: settings };
} catch (error) {
console.error("Error fetching about settings:", error);
return { success: false, error: "Hakkımızda ayarları alınırken bir hata oluştu." };
}
}
export async function updateAboutSettings(formData: FormData) {
try {
const title = formData.get("title") as string;
const description = formData.get("description") as string;
const card1Title = formData.get("card1Title") as string;
const card1Desc = formData.get("card1Desc") as string;
const card2Title = formData.get("card2Title") as string;
const card2Desc = formData.get("card2Desc") as string;
const card3Title = formData.get("card3Title") as string;
const card3Desc = formData.get("card3Desc") as string;
const image = formData.get("image") as string;
if (!title || !description || !card1Title || !card1Desc || !card2Title || !card2Desc || !card3Title || !card3Desc || !image) {
return { success: false, error: "Tüm alanların doldurulması zorunludur." };
}
await prisma.aboutSettings.upsert({
where: { id: "default" },
update: { title, description, card1Title, card1Desc, card2Title, card2Desc, card3Title, card3Desc, image },
create: { id: "default", title, description, card1Title, card1Desc, card2Title, card2Desc, card3Title, card3Desc, image }
});
revalidatePath("/admin/about");
revalidatePath("/");
revalidatePath("/tr");
revalidatePath("/en");
return { success: true };
} catch (error) {
console.error("Error updating about settings:", error);
return { success: false, error: "Hakkımızda ayarları güncellenirken bir hata oluştu." };
}
}
+103
View File
@@ -0,0 +1,103 @@
"use server";
import { prisma } from "@/lib/prisma";
import bcrypt from "bcryptjs";
import { revalidatePath } from "next/cache";
export async function getAdmins() {
try {
const admins = await prisma.admin.findMany({
select: {
id: true,
name: true,
email: true,
createdAt: true,
updatedAt: true,
},
orderBy: { createdAt: 'desc' }
});
return { success: true, data: admins };
} catch (error) {
console.error("Error fetching admins:", error);
return { success: false, error: "Adminleri çekerken bir hata oluştu." };
}
}
export async function createAdmin(formData: FormData) {
try {
const name = formData.get("name") as string;
const email = formData.get("email") as string;
const password = formData.get("password") as string;
if (!name || !email || !password) {
return { success: false, error: "Lütfen tüm alanları doldurun." };
}
const existingAdmin = await prisma.admin.findUnique({
where: { email },
});
if (existingAdmin) {
return { success: false, error: "Bu e-posta adresi zaten kullanımda." };
}
const hashedPassword = await bcrypt.hash(password, 10);
await prisma.admin.create({
data: {
name,
email,
password: hashedPassword,
},
});
revalidatePath("/admin/admins");
return { success: true };
} catch (error) {
console.error("Error creating admin:", error);
return { success: false, error: "Admin oluşturulurken bir hata oluştu." };
}
}
export async function updateAdmin(id: string, formData: FormData) {
try {
const name = formData.get("name") as string;
const email = formData.get("email") as string;
const password = formData.get("password") as string;
if (!name || !email) {
return { success: false, error: "İsim ve e-posta zorunludur." };
}
const dataToUpdate: any = { name, email };
if (password) {
dataToUpdate.password = await bcrypt.hash(password, 10);
}
await prisma.admin.update({
where: { id },
data: dataToUpdate,
});
revalidatePath("/admin/admins");
return { success: true };
} catch (error) {
console.error("Error updating admin:", error);
return { success: false, error: "Admin güncellenirken bir hata oluştu." };
}
}
export async function deleteAdmin(id: string) {
try {
await prisma.admin.delete({
where: { id },
});
revalidatePath("/admin/admins");
return { success: true };
} catch (error) {
console.error("Error deleting admin:", error);
return { success: false, error: "Admin silinirken bir hata oluştu." };
}
}
+44
View File
@@ -0,0 +1,44 @@
"use server";
import { prisma } from "@/lib/prisma";
import bcrypt from "bcryptjs";
import { signToken, setAuthCookie, removeAuthCookie } from "@/lib/auth";
import { redirect } from "next/navigation";
export async function login(formData: FormData) {
try {
const email = formData.get("email") as string;
const password = formData.get("password") as string;
if (!email || !password) {
return { success: false, error: "Lütfen email ve şifre giriniz." };
}
const admin = await prisma.admin.findUnique({
where: { email },
});
if (!admin) {
return { success: false, error: "Geçersiz e-posta veya şifre." };
}
const isMatch = await bcrypt.compare(password, admin.password);
if (!isMatch) {
return { success: false, error: "Geçersiz e-posta veya şifre." };
}
// Başarılı giriş, token oluştur ve cookie'ye kaydet
const token = await signToken({ id: admin.id, email: admin.email, name: admin.name });
await setAuthCookie(token);
return { success: true };
} catch (error) {
console.error("Login error:", error);
return { success: false, error: "Giriş yapılırken beklenmeyen bir hata oluştu." };
}
}
export async function logout() {
await removeAuthCookie();
redirect("/admin/login");
}
+63
View File
@@ -0,0 +1,63 @@
"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
export async function getBeachSettings() {
try {
let settings = await prisma.beachSettings.findUnique({
where: { id: "default" }
});
if (!settings) {
settings = await prisma.beachSettings.create({
data: {
id: "default",
title: "Ayrıcalıklı Bir Gevşeme Alanı",
description: "Tertemiz sularla buluşan özel plajımız, kendinizi tamamen yenileyebilmeniz için rahatlığın merkezinde. Altın kumların ve berrak denizin tadını çıkarırken, dünya standartlarındaki hizmetimizle her anınızın özel hissettirmesini sağlıyoruz.",
reservationInfo: "VIP cabana ve ön sıra şezlonglar yoğun talep görmektedir. Yeriniz garantilemek için en az bir gün önceden bizimle iletişime geçmenizi öneririz.",
image1: "https://images.unsplash.com/photo-1544148103-0773bf10d330?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
image2: "https://images.unsplash.com/photo-1519046904884-53103b34b206?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
image3: "https://images.unsplash.com/photo-1499793983690-e29da59ef1c2?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
image4: "https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
}
});
}
return { success: true, data: settings };
} catch (error) {
console.error("Error fetching beach settings:", error);
return { success: false, error: "Plaj ayarları alınırken bir hata oluştu." };
}
}
export async function updateBeachSettings(formData: FormData) {
try {
const title = formData.get("title") as string;
const description = formData.get("description") as string;
const reservationInfo = formData.get("reservationInfo") as string;
const image1 = formData.get("image1") as string;
const image2 = formData.get("image2") as string;
const image3 = formData.get("image3") as string;
const image4 = formData.get("image4") as string;
if (!title || !description || !reservationInfo || !image1 || !image2 || !image3 || !image4) {
return { success: false, error: "Tüm alanların doldurulması zorunludur." };
}
await prisma.beachSettings.upsert({
where: { id: "default" },
update: { title, description, reservationInfo, image1, image2, image3, image4 },
create: { id: "default", title, description, reservationInfo, image1, image2, image3, image4 }
});
revalidatePath("/admin/beach");
revalidatePath("/");
revalidatePath("/tr");
revalidatePath("/en");
return { success: true };
} catch (error) {
console.error("Error updating beach settings:", error);
return { success: false, error: "Plaj ayarları güncellenirken bir hata oluştu." };
}
}
+57
View File
@@ -0,0 +1,57 @@
"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
export async function getContactSettings() {
try {
let settings = await prisma.contactSettings.findUnique({
where: { id: "default" }
});
if (!settings) {
settings = await prisma.contactSettings.create({
data: {
id: "default",
address: "Akyaka, Gökova Körfezi, Muğla",
phone: "905000000000",
instagram: "https://www.instagram.com/kozmosbeach/",
mapUrl: "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d539.7850236154288!2d28.2165012!3d37.0413013527448!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x14bfa1006d98f019%3A0x85a41319cb3b8be!2sKozmos%20Beach%20%26%20More!5e1!3m2!1sen!2str!4v1781027409075!5m2!1sen!2str"
}
});
}
return { success: true, data: settings };
} catch (error) {
console.error("Error fetching contact settings:", error);
return { success: false, error: "İletişim ayarları alınırken bir hata oluştu." };
}
}
export async function updateContactSettings(formData: FormData) {
try {
const address = formData.get("address") as string;
const phone = formData.get("phone") as string;
const instagram = formData.get("instagram") as string;
const mapUrl = formData.get("mapUrl") as string;
if (!address || !phone || !instagram || !mapUrl) {
return { success: false, error: "Tüm alanların doldurulması zorunludur." };
}
await prisma.contactSettings.upsert({
where: { id: "default" },
update: { address, phone, instagram, mapUrl },
create: { id: "default", address, phone, instagram, mapUrl }
});
revalidatePath("/admin/contact");
revalidatePath("/");
revalidatePath("/tr");
revalidatePath("/en");
return { success: true };
} catch (error) {
console.error("Error updating contact settings:", error);
return { success: false, error: "İletişim ayarları güncellenirken bir hata oluştu." };
}
}
+104
View File
@@ -0,0 +1,104 @@
"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
export async function getEvents() {
try {
const events = await prisma.event.findMany({
orderBy: { createdAt: 'desc' }
});
return { success: true, data: events };
} catch (error) {
console.error("Error fetching events:", error);
return { success: false, error: "Etkinlikleri çekerken bir hata oluştu." };
}
}
export async function createEvent(formData: FormData) {
try {
const title = formData.get("title") as string;
const time = formData.get("time") as string;
const tag = formData.get("tag") as string;
const iconType = formData.get("iconType") as string || "music";
const colorTheme = formData.get("colorTheme") as string || "cyan";
const isFeatured = formData.get("isFeatured") === "on";
if (!title || !time || !tag) {
return { success: false, error: "Başlık, zaman ve etiket alanları zorunludur." };
}
await prisma.event.create({
data: {
title,
time,
tag,
iconType,
colorTheme,
isFeatured,
},
});
revalidatePath("/admin/events");
revalidatePath("/");
revalidatePath("/tr");
revalidatePath("/en");
return { success: true };
} catch (error) {
console.error("Error creating event:", error);
return { success: false, error: "Etkinlik oluşturulurken bir hata oluştu." };
}
}
export async function updateEvent(id: string, formData: FormData) {
try {
const title = formData.get("title") as string;
const time = formData.get("time") as string;
const tag = formData.get("tag") as string;
const iconType = formData.get("iconType") as string || "music";
const colorTheme = formData.get("colorTheme") as string || "cyan";
const isFeatured = formData.get("isFeatured") === "on";
if (!title || !time || !tag) {
return { success: false, error: "Başlık, zaman ve etiket alanları zorunludur." };
}
await prisma.event.update({
where: { id },
data: {
title,
time,
tag,
iconType,
colorTheme,
isFeatured,
},
});
revalidatePath("/admin/events");
revalidatePath("/");
revalidatePath("/tr");
revalidatePath("/en");
return { success: true };
} catch (error) {
console.error("Error updating event:", error);
return { success: false, error: "Etkinlik güncellenirken bir hata oluştu." };
}
}
export async function deleteEvent(id: string) {
try {
await prisma.event.delete({
where: { id },
});
revalidatePath("/admin/events");
revalidatePath("/");
revalidatePath("/tr");
revalidatePath("/en");
return { success: true };
} catch (error) {
console.error("Error deleting event:", error);
return { success: false, error: "Etkinlik silinirken bir hata oluştu." };
}
}
+97
View File
@@ -0,0 +1,97 @@
"use server";
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
export async function getGalleryImages() {
try {
const images = await prisma.galleryImage.findMany({
orderBy: [
{ order: 'asc' },
{ createdAt: 'desc' }
]
});
return { success: true, data: images };
} catch (error) {
console.error("Error fetching gallery images:", error);
return { success: false, error: "Galeri görsellerini çekerken bir hata oluştu." };
}
}
export async function createGalleryImage(formData: FormData) {
try {
const src = formData.get("src") as string;
const alt = formData.get("alt") as string || "";
const orderStr = formData.get("order") as string;
const order = orderStr ? parseInt(orderStr, 10) : 0;
if (!src) {
return { success: false, error: "Görsel URL'si zorunludur." };
}
await prisma.galleryImage.create({
data: {
src,
alt,
order,
},
});
revalidatePath("/admin/gallery");
revalidatePath("/");
revalidatePath("/tr");
revalidatePath("/en");
return { success: true };
} catch (error) {
console.error("Error creating gallery image:", error);
return { success: false, error: "Galeri görseli eklenirken bir hata oluştu." };
}
}
export async function updateGalleryImage(id: string, formData: FormData) {
try {
const src = formData.get("src") as string;
const alt = formData.get("alt") as string || "";
const orderStr = formData.get("order") as string;
const order = orderStr ? parseInt(orderStr, 10) : 0;
if (!src) {
return { success: false, error: "Görsel URL'si zorunludur." };
}
await prisma.galleryImage.update({
where: { id },
data: {
src,
alt,
order,
},
});
revalidatePath("/admin/gallery");
revalidatePath("/");
revalidatePath("/tr");
revalidatePath("/en");
return { success: true };
} catch (error) {
console.error("Error updating gallery image:", error);
return { success: false, error: "Galeri görseli güncellenirken bir hata oluştu." };
}
}
export async function deleteGalleryImage(id: string) {
try {
await prisma.galleryImage.delete({
where: { id },
});
revalidatePath("/admin/gallery");
revalidatePath("/");
revalidatePath("/tr");
revalidatePath("/en");
return { success: true };
} catch (error) {
console.error("Error deleting gallery image:", error);
return { success: false, error: "Galeri görseli silinirken bir hata oluştu." };
}
}
+68
View File
@@ -0,0 +1,68 @@
"use server";
export async function getInstagramPosts() {
try {
const response = await fetch('https://instagram120.p.rapidapi.com/api/instagram/posts?v=1', {
method: 'POST',
headers: {
'x-rapidapi-key': process.env.RAPIDAPI_KEY || '762dc41c1dmshc120ecf29aa240ap11dc66jsn87f410b870f5',
'x-rapidapi-host': 'instagram120.p.rapidapi.com',
'Content-Type': 'application/json'
},
body: JSON.stringify({ username: 'kozmosbeach', maxId: '' }),
// CRITICAL ADIM: Veriyi 24 saat boyunca önbellekte tut (Saniye cinsinden: 24 * 60 * 60)
next: { revalidate: 86400 }
});
if (!response.ok) {
throw new Error("Instagram API request failed");
}
const data = await response.json();
// The RapidAPI endpoint usually returns an array of posts or { data: { items: [...] } }
// Based on standard Instagram120 response, it returns { result: { edges: [{node: {}}] } }
let posts = [];
if (data && data.result && data.result.edges && Array.isArray(data.result.edges)) {
posts = data.result.edges.map((edge: any) => edge.node);
} else if (data && Array.isArray(data)) {
posts = data;
} else if (data && data.data && Array.isArray(data.data)) {
posts = data.data;
} else if (data && data.items && Array.isArray(data.items)) {
posts = data.items;
}
// Map to a cleaner format and take first 4
const formattedPosts = posts.slice(0, 4).map((post: any) => {
// Instagram API has multiple variants for post schema
const id = post.id || post.pk;
const code = post.code;
// Try to find the highest res image
let imageUrl = "";
if (post.image_versions2 && post.image_versions2.candidates) {
imageUrl = post.image_versions2.candidates[0].url;
} else if (post.carousel_media && post.carousel_media.length > 0) {
imageUrl = post.carousel_media[0].image_versions2.candidates[0].url;
} else if (post.display_url) {
imageUrl = post.display_url;
}
const caption = post.caption?.text || post.caption || "";
const url = code ? `https://instagram.com/p/${code}` : `https://instagram.com/kozmosbeach`;
return {
id,
imageUrl,
url,
caption
};
});
return { success: true, data: formattedPosts };
} catch (error) {
console.error("Error fetching Instagram posts:", error);
return { success: false, error: "Instagram gönderileri alınamadı." };
}
}