feat: implement public contact and reservation forms connected to DB
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { prisma } from '@/lib/db';
|
||||||
|
|
||||||
|
export async function submitContactMessage(formData: FormData) {
|
||||||
|
const name = formData.get('name') as string;
|
||||||
|
const email = formData.get('email') as string;
|
||||||
|
const subject = formData.get('subject') as string | null;
|
||||||
|
const message = formData.get('message') as string;
|
||||||
|
|
||||||
|
if (!name || !email || !message) {
|
||||||
|
return { error: 'Gerekli alanları doldurunuz.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prisma.contactMessage.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
subject,
|
||||||
|
message,
|
||||||
|
isRead: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Mesaj gönderilirken hata:', error);
|
||||||
|
return { error: 'Mesajınız gönderilirken bir hata oluştu. Lütfen tekrar deneyin.' };
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
-4
@@ -1,8 +1,30 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { MapPin, Phone, Mail, Clock } from "lucide-react";
|
import { MapPin, Phone, Mail, Clock, Send } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { submitContactMessage } from "@/app/actions";
|
||||||
|
|
||||||
export default function Contact({ dict, dbSettings }: { dict: any, dbSettings?: any }) {
|
export default function Contact({ dict, dbSettings }: { dict: any, dbSettings?: any }) {
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [status, setStatus] = useState<{ type: 'success' | 'error', message: string } | null>(null);
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setStatus(null);
|
||||||
|
|
||||||
|
const formData = new FormData(e.currentTarget);
|
||||||
|
const result = await submitContactMessage(formData);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
setStatus({ type: 'error', message: result.error });
|
||||||
|
} else {
|
||||||
|
setStatus({ type: 'success', message: 'Mesajınız başarıyla gönderildi. En kısa sürede dönüş yapacağız.' });
|
||||||
|
(e.target as HTMLFormElement).reset();
|
||||||
|
}
|
||||||
|
setIsSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="contact" className="py-24 bg-sand-light text-sea-dark relative">
|
<section id="contact" className="py-24 bg-sand-light text-sea-dark relative">
|
||||||
<div className="container mx-auto px-6 max-w-7xl">
|
<div className="container mx-auto px-6 max-w-7xl">
|
||||||
@@ -11,7 +33,7 @@ export default function Contact({ dict, dbSettings }: { dict: any, dbSettings?:
|
|||||||
<h3 className="text-4xl md:text-5xl font-serif">{dict.contact.heading}</h3>
|
<h3 className="text-4xl md:text-5xl font-serif">{dict.contact.heading}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid lg:grid-cols-2 gap-12 items-start">
|
<div className="grid lg:grid-cols-2 gap-12 items-start mb-16">
|
||||||
|
|
||||||
{/* İletişim Bilgileri */}
|
{/* İletişim Bilgileri */}
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
@@ -69,8 +91,46 @@ export default function Contact({ dict, dbSettings }: { dict: any, dbSettings?:
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* İletişim Formu */}
|
||||||
|
<div className="bg-white rounded-2xl shadow-lg border border-gray-100 p-8">
|
||||||
|
<h4 className="font-bold text-2xl mb-6 font-serif">Bize Ulaşın</h4>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Adınız Soyadınız</label>
|
||||||
|
<input type="text" name="name" required className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors" placeholder="Adınız Soyadınız" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">E-Posta Adresi</label>
|
||||||
|
<input type="email" name="email" required className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors" placeholder="ornek@email.com" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Konu</label>
|
||||||
|
<input type="text" name="subject" className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors" placeholder="Konu (İsteğe bağlı)" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Mesajınız</label>
|
||||||
|
<textarea name="message" required rows={4} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors resize-none" placeholder="Mesajınızı buraya yazabilirsiniz..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{status && (
|
||||||
|
<div className={`p-4 rounded-xl text-sm ${status.type === 'success' ? 'bg-green-50 text-green-700 border border-green-200' : 'bg-red-50 text-red-700 border border-red-200'}`}>
|
||||||
|
{status.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button type="submit" disabled={isSubmitting} className="w-full bg-sea-dark hover:bg-sea text-white font-medium py-4 rounded-xl transition-colors shadow-md hover:shadow-lg flex items-center justify-center gap-2 disabled:opacity-70">
|
||||||
|
<Send className="w-5 h-5" />
|
||||||
|
{isSubmitting ? 'Gönderiliyor...' : 'Mesajı Gönder'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Google Maps Embed */}
|
{/* Google Maps Embed */}
|
||||||
<div className="w-full h-[400px] lg:h-full min-h-[400px] bg-gray-200 rounded-2xl overflow-hidden shadow-lg border-4 border-white">
|
<div className="w-full h-[400px] bg-gray-200 rounded-2xl overflow-hidden shadow-lg border-4 border-white">
|
||||||
<iframe
|
<iframe
|
||||||
src="https://maps.google.com/maps?hl=tr&gl=TR&cid=17585469489776666725&output=embed"
|
src="https://maps.google.com/maps?hl=tr&gl=TR&cid=17585469489776666725&output=embed"
|
||||||
width="100%"
|
width="100%"
|
||||||
@@ -84,7 +144,6 @@ export default function Contact({ dict, dbSettings }: { dict: any, dbSettings?:
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-18
@@ -1,9 +1,53 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Calendar, Users, Coffee } from "lucide-react";
|
import { Calendar, Users, Coffee, Mail } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { submitContactMessage } from "@/app/actions";
|
||||||
|
|
||||||
export default function Reservation() {
|
export default function Reservation() {
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [status, setStatus] = useState<{ type: 'success' | 'error', message: string } | null>(null);
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setStatus(null);
|
||||||
|
|
||||||
|
const form = e.currentTarget;
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
// Construct message from reservation fields
|
||||||
|
const date = formData.get('date') as string;
|
||||||
|
const guests = formData.get('guests') as string;
|
||||||
|
const serviceType = formData.get('serviceType') as string;
|
||||||
|
const name = formData.get('name') as string;
|
||||||
|
const phone = formData.get('phone') as string;
|
||||||
|
const email = formData.get('email') as string;
|
||||||
|
|
||||||
|
const messageBody = `REZERVASYON TALEBİ
|
||||||
|
Tarih: ${date}
|
||||||
|
Kişi Sayısı: ${guests}
|
||||||
|
Hizmet Tipi: ${serviceType}
|
||||||
|
Telefon: ${phone}`;
|
||||||
|
|
||||||
|
const contactFormData = new FormData();
|
||||||
|
contactFormData.append('name', name);
|
||||||
|
contactFormData.append('email', email);
|
||||||
|
contactFormData.append('subject', 'Yeni Rezervasyon Talebi');
|
||||||
|
contactFormData.append('message', messageBody);
|
||||||
|
|
||||||
|
const result = await submitContactMessage(contactFormData);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
setStatus({ type: 'error', message: result.error });
|
||||||
|
} else {
|
||||||
|
setStatus({ type: 'success', message: 'Rezervasyon talebiniz alınmıştır. Ekibimiz en kısa sürede sizinle iletişime geçecektir.' });
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
setIsSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="reservation" className="py-24 bg-white relative overflow-hidden">
|
<section id="reservation" className="py-24 bg-white relative overflow-hidden">
|
||||||
{/* Decorative background blobs */}
|
{/* Decorative background blobs */}
|
||||||
@@ -33,10 +77,9 @@ export default function Reservation() {
|
|||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.6 }}
|
transition={{ duration: 0.6 }}
|
||||||
className="relative z-10 bg-white p-7 md:p-10 rounded-2xl shadow-xl max-w-4xl mx-auto"
|
className="relative z-10 bg-white p-7 md:p-10 rounded-2xl shadow-xl max-w-4xl mx-auto"
|
||||||
onSubmit={(e) => e.preventDefault()}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
<div className="grid md:grid-cols-3 gap-5 mb-6">
|
<div className="grid md:grid-cols-3 gap-5 mb-6">
|
||||||
|
|
||||||
{/* Date */}
|
{/* Date */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-xs font-semibold text-gray-600 uppercase tracking-wide flex items-center gap-1.5">
|
<label className="text-xs font-semibold text-gray-600 uppercase tracking-wide flex items-center gap-1.5">
|
||||||
@@ -44,6 +87,8 @@ export default function Reservation() {
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
|
name="date"
|
||||||
|
required
|
||||||
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors text-sm"
|
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,12 +98,12 @@ export default function Reservation() {
|
|||||||
<label className="text-xs font-semibold text-gray-600 uppercase tracking-wide flex items-center gap-1.5">
|
<label className="text-xs font-semibold text-gray-600 uppercase tracking-wide flex items-center gap-1.5">
|
||||||
<Users size={13} className="text-coral" /> Kişi Sayısı
|
<Users size={13} className="text-coral" /> Kişi Sayısı
|
||||||
</label>
|
</label>
|
||||||
<select className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors bg-white text-sm">
|
<select name="guests" className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors bg-white text-sm">
|
||||||
<option>1 Kişi</option>
|
<option value="1 Kişi">1 Kişi</option>
|
||||||
<option>2 Kişi</option>
|
<option value="2 Kişi">2 Kişi</option>
|
||||||
<option>3 Kişi</option>
|
<option value="3 Kişi">3 Kişi</option>
|
||||||
<option>4 Kişi</option>
|
<option value="4 Kişi">4 Kişi</option>
|
||||||
<option>5+ Kişi</option>
|
<option value="5+ Kişi">5+ Kişi</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -67,34 +112,51 @@ export default function Reservation() {
|
|||||||
<label className="text-xs font-semibold text-gray-600 uppercase tracking-wide flex items-center gap-1.5">
|
<label className="text-xs font-semibold text-gray-600 uppercase tracking-wide flex items-center gap-1.5">
|
||||||
<Coffee size={13} className="text-coral" /> Hizmet Tipi
|
<Coffee size={13} className="text-coral" /> Hizmet Tipi
|
||||||
</label>
|
</label>
|
||||||
<select className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors bg-white text-sm">
|
<select name="serviceType" className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors bg-white text-sm">
|
||||||
<option>Beach Club (Şezlong/Loca)</option>
|
<option value="Beach Club (Şezlong/Loca)">Beach Club (Şezlong/Loca)</option>
|
||||||
<option>Restoran Akşam Yemeği</option>
|
<option value="Restoran Akşam Yemeği">Restoran Akşam Yemeği</option>
|
||||||
<option>Konaklama</option>
|
<option value="Konaklama">Konaklama</option>
|
||||||
<option>Özel Etkinlik / Organizasyon</option>
|
<option value="Özel Etkinlik / Organizasyon">Özel Etkinlik / Organizasyon</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-5 mb-7">
|
<div className="grid md:grid-cols-3 gap-5 mb-7">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
name="name"
|
||||||
|
required
|
||||||
placeholder="Adınız Soyadınız"
|
placeholder="Adınız Soyadınız"
|
||||||
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors text-sm"
|
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors text-sm"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
|
name="phone"
|
||||||
|
required
|
||||||
placeholder="Telefon Numaranız"
|
placeholder="Telefon Numaranız"
|
||||||
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors text-sm"
|
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors text-sm"
|
||||||
/>
|
/>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
required
|
||||||
|
placeholder="E-Posta Adresiniz"
|
||||||
|
className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-coral/40 focus:border-coral transition-colors text-sm"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Shimmer submit button */}
|
{status && (
|
||||||
|
<div className={`p-4 mb-6 rounded-xl text-sm ${status.type === 'success' ? 'bg-green-50 text-green-700 border border-green-200' : 'bg-red-50 text-red-700 border border-red-200'}`}>
|
||||||
|
{status.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn-shimmer w-full bg-coral hover:bg-coral-dark text-white font-medium py-4 rounded-xl transition-colors shadow-lg hover:shadow-xl text-sm tracking-wide uppercase"
|
disabled={isSubmitting}
|
||||||
|
className="btn-shimmer w-full bg-coral hover:bg-coral-dark text-white font-medium py-4 rounded-xl transition-colors shadow-lg hover:shadow-xl text-sm tracking-wide uppercase disabled:opacity-70 flex justify-center items-center"
|
||||||
>
|
>
|
||||||
Rezervasyon Talebi Gönder
|
{isSubmitting ? 'Talebiniz Gönderiliyor...' : 'Rezervasyon Talebi Gönder'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p className="text-[11px] text-center text-gray-400 mt-4">
|
<p className="text-[11px] text-center text-gray-400 mt-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user