first commit
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import { ArrowRight, Star } from 'lucide-react'
|
||||
import { ShimmerButton } from '@/components/magicui/shimmer-button'
|
||||
import { cn } from '@/components/common/Navbar'
|
||||
|
||||
const EASE = [0.22, 1, 0.36, 1] as const
|
||||
|
||||
const fadeInUp = {
|
||||
hidden: { opacity: 0, y: 40, filter: 'blur(8px)' },
|
||||
show: { opacity: 1, y: 0, filter: 'blur(0px)', transition: { duration: 0.8, ease: EASE } }
|
||||
}
|
||||
|
||||
export function HomePageClient({
|
||||
dict,
|
||||
tours,
|
||||
categories,
|
||||
reviews
|
||||
}: {
|
||||
dict: any,
|
||||
tours: any[],
|
||||
categories: any[],
|
||||
reviews: any[]
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-background min-h-screen">
|
||||
{/* Hero Section */}
|
||||
<section className="relative h-screen min-h-[700px] flex items-center justify-center overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ scale: 1.1, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ duration: 1.5, ease: EASE }}
|
||||
className="absolute inset-0"
|
||||
>
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1471922694854-ff1b63b20054?auto=format&fit=crop&q=80"
|
||||
alt="Fethiye Hero"
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
sizes="100vw"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-background/40 via-background/20 to-background/90" />
|
||||
</motion.div>
|
||||
|
||||
<div className="relative z-10 text-center px-4 max-w-5xl mx-auto flex flex-col items-center">
|
||||
<motion.div
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
variants={{
|
||||
hidden: { opacity: 0 },
|
||||
show: { opacity: 1, transition: { staggerChildren: 0.2 } }
|
||||
}}
|
||||
>
|
||||
<motion.h1
|
||||
variants={fadeInUp}
|
||||
className="font-heading text-hero text-foreground mb-6 leading-none drop-shadow-2xl"
|
||||
>
|
||||
{dict.hTitle}
|
||||
</motion.h1>
|
||||
<motion.p
|
||||
variants={fadeInUp}
|
||||
className="text-h3 text-foreground/80 mb-12 max-w-2xl mx-auto font-light tracking-wide"
|
||||
>
|
||||
{dict.hSubtitle}
|
||||
</motion.p>
|
||||
<motion.div variants={fadeInUp}>
|
||||
<Link href="/tours">
|
||||
<ShimmerButton
|
||||
className="shadow-2xl"
|
||||
shimmerColor="var(--primary)"
|
||||
background="var(--background)"
|
||||
borderRadius="10px"
|
||||
>
|
||||
<span className="font-bold tracking-widest uppercase text-sm text-foreground flex items-center gap-2">
|
||||
{dict.hCta} <ArrowRight className="w-4 h-4 text-primary" />
|
||||
</span>
|
||||
</ShimmerButton>
|
||||
</Link>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Popular Tours Section */}
|
||||
<section className="py-32 relative z-20">
|
||||
<div className="container mx-auto px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-100px' }}
|
||||
transition={{ duration: 0.8, ease: EASE }}
|
||||
className="flex flex-col md:flex-row justify-between items-end mb-16 gap-6"
|
||||
>
|
||||
<h2 className="font-heading text-h2 text-foreground max-w-md leading-tight">
|
||||
{dict.sPopular}
|
||||
</h2>
|
||||
<Link href="/tours" className="group flex items-center gap-2 text-sm font-bold tracking-widest uppercase text-foreground hover:text-primary transition-colors">
|
||||
{dict.sViewAll}
|
||||
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{tours.slice(0, 4).map((tour, i) => (
|
||||
<motion.div
|
||||
key={tour.id}
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-50px' }}
|
||||
transition={{ duration: 0.8, ease: EASE, delay: i * 0.1 }}
|
||||
className="group relative flex flex-col gap-4"
|
||||
>
|
||||
<Link href={`/tours/${tour.slug}`} className="relative h-[400px] overflow-hidden rounded-2xl">
|
||||
<Image
|
||||
src={tour.imageUrl || ''}
|
||||
alt={tour.title}
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 25vw"
|
||||
className="object-cover transition-transform duration-700 ease-out group-hover:scale-105"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/20 group-hover:bg-transparent transition-colors duration-500" />
|
||||
</Link>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="font-heading text-2xl font-bold text-foreground group-hover:text-primary transition-colors">{tour.title}</h3>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs tracking-widest uppercase text-muted-foreground">{dict.tFrom}</span>
|
||||
<span className="font-sans font-bold text-xl text-primary">${tour.price}</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Editorial Categories */}
|
||||
<section className="py-32 bg-card/30 border-y border-border/30">
|
||||
<div className="container mx-auto px-6">
|
||||
<motion.h2
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="font-heading text-h2 text-center text-foreground mb-20"
|
||||
>
|
||||
{dict.sCategories}
|
||||
</motion.h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{categories.slice(0, 3).map((cat, i) => (
|
||||
<motion.div
|
||||
key={cat.id}
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8, ease: EASE, delay: i * 0.1 }}
|
||||
>
|
||||
<Link href={`/tours?category=${cat.slug}`} className="group block relative h-[500px] overflow-hidden rounded-2xl">
|
||||
<Image
|
||||
src={cat.imageUrl || ''}
|
||||
alt={cat.name}
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
className="object-cover transition-transform duration-700 ease-out group-hover:scale-105"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-background/90 via-background/20 to-transparent" />
|
||||
<div className="absolute bottom-0 left-0 p-8 w-full flex items-end justify-between">
|
||||
<h3 className="font-heading text-3xl font-bold text-foreground">{cat.name}</h3>
|
||||
<div className="w-10 h-10 rounded-full bg-primary/20 backdrop-blur-md flex items-center justify-center group-hover:bg-primary transition-colors">
|
||||
<ArrowRight className="w-5 h-5 text-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Reviews Section */}
|
||||
<section className="py-32">
|
||||
<div className="container mx-auto px-6">
|
||||
<h2 className="font-heading text-h2 text-center text-foreground mb-20">{dict.sReviews}</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{reviews.map((review, i) => (
|
||||
<motion.div
|
||||
key={review.id}
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8, ease: EASE, delay: i * 0.1 }}
|
||||
className="bg-card/40 p-8 rounded-3xl border border-border/50 hover:border-primary/30 transition-colors"
|
||||
>
|
||||
<div className="flex text-primary mb-6">
|
||||
{[...Array(review.rating)].map((_, j) => <Star key={j} className="w-4 h-4 fill-current mr-1" />)}
|
||||
</div>
|
||||
<p className="text-foreground/80 font-serif italic text-lg leading-relaxed mb-8">"{review.comment}"</p>
|
||||
<div>
|
||||
<div className="font-bold text-foreground text-sm tracking-widest uppercase">{review.authorName}</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
'use client'
|
||||
|
||||
import { signOut } from 'next-auth/react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { LayoutDashboard, Users, Settings, LogOut, Menu, X } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function AdminLayout({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname()
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||
|
||||
const navigation = [
|
||||
{ name: 'Dashboard', href: '/admin', icon: LayoutDashboard },
|
||||
{ name: 'Kullanıcılar', href: '/admin/users', icon: Users },
|
||||
{ name: 'Ayarlar', href: '/admin/settings', icon: Settings },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex">
|
||||
{/* Mobile sidebar backdrop */}
|
||||
{sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-gray-900/80 lg:hidden"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className={`
|
||||
fixed inset-y-0 left-0 z-50 w-64 bg-white dark:bg-gray-950 border-r border-gray-200 dark:border-gray-800
|
||||
transform transition-transform duration-200 ease-in-out lg:translate-x-0 lg:static lg:inset-0
|
||||
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
|
||||
`}>
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="h-16 flex items-center px-6 border-b border-gray-200 dark:border-gray-800">
|
||||
<h1 className="text-lg font-bold text-gray-900 dark:text-white">Admin Paneli</h1>
|
||||
<button
|
||||
className="ml-auto lg:hidden text-gray-500"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
|
||||
{navigation.map((item) => {
|
||||
const isActive = pathname.endsWith(item.href)
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className={`
|
||||
flex items-center px-3 py-2.5 text-sm font-medium rounded-md transition-colors
|
||||
${isActive
|
||||
? 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white'}
|
||||
`}
|
||||
>
|
||||
<item.icon className={`mr-3 flex-shrink-0 h-5 w-5 ${isActive ? 'text-gray-900 dark:text-white' : 'text-gray-400'}`} />
|
||||
{item.name}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="p-4 border-t border-gray-200 dark:border-gray-800">
|
||||
<button
|
||||
onClick={() => signOut({ callbackUrl: '/' })}
|
||||
className="flex w-full items-center px-3 py-2.5 text-sm font-medium text-red-600 dark:text-red-400 rounded-md hover:bg-red-50 dark:hover:bg-red-950/30 transition-colors"
|
||||
>
|
||||
<LogOut className="mr-3 h-5 w-5" />
|
||||
Çıkış Yap
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
<header className="h-16 flex items-center lg:hidden bg-white dark:bg-gray-950 border-b border-gray-200 dark:border-gray-800 px-4">
|
||||
<button
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
className="text-gray-500 hover:text-gray-900 dark:hover:text-white focus:outline-none"
|
||||
>
|
||||
<Menu className="h-6 w-6" />
|
||||
</button>
|
||||
<span className="ml-4 text-lg font-bold text-gray-900 dark:text-white">Admin Paneli</span>
|
||||
</header>
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-4 sm:p-6 lg:p-8">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export default async function AdminDashboardPage() {
|
||||
const session = await auth()
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Dashboard</h2>
|
||||
<p className="text-gray-500 dark:text-gray-400 mt-2">
|
||||
Hoş geldiniz, {session?.user?.name || session?.user?.email}. İşte projenizin genel görünümü.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{/* Placeholder Stat Cards */}
|
||||
{[
|
||||
{ name: 'Toplam Kullanıcı', stat: '1,245' },
|
||||
{ name: 'Aktif Oturumlar', stat: '42' },
|
||||
{ name: 'Yeni Kayıtlar', stat: '8' },
|
||||
{ name: 'Sistem Durumu', stat: 'Online' },
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.name}
|
||||
className="overflow-hidden rounded-lg bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 px-4 py-5 shadow-sm sm:p-6"
|
||||
>
|
||||
<dt className="truncate text-sm font-medium text-gray-500 dark:text-gray-400">{item.name}</dt>
|
||||
<dd className="mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
||||
{item.stat}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 shadow-sm">
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white">Son Aktiviteler</h3>
|
||||
<div className="mt-4 border-t border-gray-100 dark:border-gray-800">
|
||||
<div className="py-4 text-sm text-gray-500">
|
||||
Henüz aktivite kaydı bulunmuyor.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import { Mail, MapPin, Phone } from 'lucide-react'
|
||||
|
||||
export default async function ContactPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params
|
||||
setRequestLocale(locale)
|
||||
|
||||
return (
|
||||
<div className="bg-zinc-950 min-h-screen py-16">
|
||||
<div className="container mx-auto px-4 max-w-5xl">
|
||||
<h1 className="text-4xl font-black mb-12 text-center text-white uppercase tracking-tighter">Contact Us</h1>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||
{/* Contact Info */}
|
||||
<div className="space-y-8">
|
||||
<h2 className="text-2xl font-bold text-white">Get In Touch</h2>
|
||||
<p className="text-zinc-300">
|
||||
Pioneer travel is here to help you with all your trips and excursions. Fill out the form or contact us directly using the information below.
|
||||
</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start">
|
||||
<div className="w-12 h-12 bg-orange-500/10 rounded-full flex items-center justify-center text-orange-500 shrink-0 mr-4">
|
||||
<MapPin className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-white mb-1">Office Address</h4>
|
||||
<p className="text-zinc-400">Çarşı Caddesi Tonoz İş Merkezi No:3/1<br/>Ölüdeniz - Fethiye / Türkiye</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<div className="w-12 h-12 bg-orange-500/10 rounded-full flex items-center justify-center text-orange-500 shrink-0 mr-4">
|
||||
<Phone className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-white mb-1">Phone Number</h4>
|
||||
<p className="text-zinc-400">+90 530 378 48 82</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<div className="w-12 h-12 bg-orange-500/10 rounded-full flex items-center justify-center text-orange-500 shrink-0 mr-4">
|
||||
<Mail className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-white mb-1">Email Address</h4>
|
||||
<p className="text-zinc-400">info@fethiyeholiday.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Form */}
|
||||
<div className="bg-zinc-900 p-8 rounded-2xl shadow-xl border border-zinc-800">
|
||||
<h2 className="text-2xl font-bold text-white mb-6">Send a Message</h2>
|
||||
<form className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-zinc-300 mb-1">Your Name</label>
|
||||
<input type="text" className="w-full px-4 py-2 border border-zinc-700 rounded-md bg-zinc-950/50 text-white focus:ring-2 focus:ring-orange-500 focus:border-orange-500 outline-none transition-colors" placeholder="John Doe" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-zinc-300 mb-1">Your Email</label>
|
||||
<input type="email" className="w-full px-4 py-2 border border-zinc-700 rounded-md bg-zinc-950/50 text-white focus:ring-2 focus:ring-orange-500 focus:border-orange-500 outline-none transition-colors" placeholder="john@example.com" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-zinc-300 mb-1">Message</label>
|
||||
<textarea rows={4} className="w-full px-4 py-2 border border-zinc-700 rounded-md bg-zinc-950/50 text-white focus:ring-2 focus:ring-orange-500 focus:border-orange-500 outline-none transition-colors" placeholder="How can we help you?"></textarea>
|
||||
</div>
|
||||
<button type="button" className="w-full py-3 bg-orange-500 text-white font-bold rounded-md hover:bg-orange-600 transition-colors shadow-[0_0_15px_rgba(249,115,22,0.3)]">
|
||||
Send Message
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono, Playfair_Display } from "next/font/google";
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import { getMessages, setRequestLocale } from 'next-intl/server';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { routing } from '@/i18n/routing';
|
||||
import { Navbar } from '@/components/common/Navbar';
|
||||
import { Footer } from '@/components/common/Footer';
|
||||
import "../globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const playfair = Playfair_Display({
|
||||
variable: "--font-playfair",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Fethiye Holiday | Pioneer Travel",
|
||||
description: "Discover the beauty of the Mediterranean with Pioneer Travel.",
|
||||
};
|
||||
|
||||
export function generateStaticParams() {
|
||||
return routing.locales.map((locale) => ({locale}));
|
||||
}
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}>) {
|
||||
const { locale } = await params;
|
||||
|
||||
if (!routing.locales.includes(locale as any)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
setRequestLocale(locale);
|
||||
const messages = await getMessages();
|
||||
|
||||
return (
|
||||
<html
|
||||
lang={locale}
|
||||
className={`${geistSans.variable} ${geistMono.variable} ${playfair.variable} h-full antialiased dark`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col bg-background text-foreground" suppressHydrationWarning>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<Navbar locale={locale} />
|
||||
{children}
|
||||
<Footer />
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { signIn } from 'next-auth/react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter()
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
setError('')
|
||||
|
||||
const result = await signIn('credentials', {
|
||||
redirect: false,
|
||||
email,
|
||||
password,
|
||||
})
|
||||
|
||||
if (result?.error) {
|
||||
setError('Geçersiz e-posta veya şifre')
|
||||
setLoading(false)
|
||||
} else {
|
||||
router.push('/admin')
|
||||
router.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 px-4">
|
||||
<div className="w-full max-w-md bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-100 dark:border-gray-800 overflow-hidden">
|
||||
<div className="p-8">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Admin Girişi</h1>
|
||||
<p className="text-sm text-gray-500 mt-2">Yönetim paneline erişmek için giriş yapın</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 text-red-600 p-3 rounded-md text-sm mb-6 border border-red-100">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" htmlFor="email">
|
||||
E-posta
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-900 text-gray-900 dark:text-white transition-colors"
|
||||
placeholder="admin@ayris.tech"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" htmlFor="password">
|
||||
Şifre
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white dark:bg-gray-900 text-gray-900 dark:text-white transition-colors"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2.5 px-4 rounded-md transition-colors disabled:opacity-70 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? 'Giriş yapılıyor...' : 'Giriş Yap'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center text-xs text-gray-400">
|
||||
Demo credentials: admin@ayris.tech / admin
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server'
|
||||
import { MOCK_CATEGORIES, MOCK_TOURS, MOCK_REVIEWS } from '@/lib/mock'
|
||||
import { HomePageClient } from './HomePageClient'
|
||||
|
||||
export default async function HomePage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params
|
||||
setRequestLocale(locale)
|
||||
|
||||
const h = await getTranslations('hero')
|
||||
const s = await getTranslations('sections')
|
||||
const t = await getTranslations('tour')
|
||||
|
||||
const dict = {
|
||||
hTitle: h('title'),
|
||||
hSubtitle: h('subtitle'),
|
||||
hCta: h('cta'),
|
||||
sPopular: s('popular'),
|
||||
sViewAll: s('view_all'),
|
||||
sCategories: s('categories'),
|
||||
sReviews: s('reviews'),
|
||||
tFrom: t('from')
|
||||
}
|
||||
|
||||
return (
|
||||
<HomePageClient
|
||||
dict={dict}
|
||||
tours={MOCK_TOURS}
|
||||
categories={MOCK_CATEGORIES}
|
||||
reviews={MOCK_REVIEWS}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import { MOCK_TOURS } from '@/lib/mock'
|
||||
import { notFound } from 'next/navigation'
|
||||
import Image from 'next/image'
|
||||
import { Clock, Star } from 'lucide-react'
|
||||
|
||||
export default async function TourDetailPage({ params }: { params: Promise<{ locale: string, slug: string }> }) {
|
||||
const { locale, slug } = await params
|
||||
setRequestLocale(locale)
|
||||
|
||||
const tour = MOCK_TOURS.find(t => t.slug === slug)
|
||||
if (!tour) notFound()
|
||||
|
||||
return (
|
||||
<div className="bg-zinc-950 min-h-screen py-12">
|
||||
<div className="container mx-auto px-4 max-w-6xl">
|
||||
<div className="bg-zinc-900 rounded-2xl overflow-hidden shadow-2xl border border-zinc-800 flex flex-col md:flex-row">
|
||||
<div className="w-full md:w-1/2 relative h-64 md:h-auto">
|
||||
<Image src={tour.imageUrl || ''} alt={tour.title} fill sizes="(max-width: 768px) 100vw, 50vw" className="object-cover" />
|
||||
</div>
|
||||
<div className="w-full md:w-1/2 p-8 md:p-12 flex flex-col">
|
||||
<h1 className="text-3xl md:text-4xl font-black mb-4 text-white">
|
||||
{tour.title}
|
||||
</h1>
|
||||
<div className="flex items-center space-x-6 text-sm text-zinc-400 mb-8">
|
||||
<span className="flex items-center"><Clock className="w-4 h-4 mr-1 text-orange-500" /> {tour.duration}</span>
|
||||
<span className="flex items-center"><Star className="w-4 h-4 mr-1 text-orange-500 fill-current" /> 5.0 (Review)</span>
|
||||
</div>
|
||||
|
||||
<p className="text-zinc-300 mb-8 leading-relaxed text-lg">
|
||||
{tour.description}
|
||||
</p>
|
||||
|
||||
<div className="mt-auto bg-zinc-950 p-6 rounded-xl flex items-center justify-between border border-zinc-800">
|
||||
<div>
|
||||
<span className="text-sm text-zinc-500 uppercase tracking-wider block mb-1">Price per person</span>
|
||||
<span className="font-black text-4xl text-orange-500">${tour.price}</span>
|
||||
</div>
|
||||
<button className="px-8 py-4 bg-orange-500 text-white font-bold rounded-lg hover:bg-orange-600 transition-all duration-300 shadow-[0_0_15px_rgba(249,115,22,0.4)] hover:shadow-[0_0_25px_rgba(249,115,22,0.6)] transform hover:-translate-y-0.5">
|
||||
Book Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server'
|
||||
import { MOCK_TOURS, MOCK_CATEGORIES } from '@/lib/mock'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
|
||||
export default async function ToursPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params
|
||||
setRequestLocale(locale)
|
||||
|
||||
const s = await getTranslations('sections')
|
||||
const t = await getTranslations('tour')
|
||||
|
||||
return (
|
||||
<div className="bg-zinc-950 min-h-screen py-12">
|
||||
<div className="container mx-auto px-4">
|
||||
<h1 className="text-4xl font-black mb-8 text-white uppercase tracking-tighter">
|
||||
{s('categories')}
|
||||
</h1>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{MOCK_TOURS.map(tour => {
|
||||
const cat = MOCK_CATEGORIES.find(c => c.id === tour.categoryId)
|
||||
return (
|
||||
<div key={tour.id} className="bg-zinc-900 rounded-xl overflow-hidden shadow-lg border border-zinc-800 hover:border-orange-500/50 transition-all duration-300 flex flex-col group">
|
||||
<div className="relative h-56 overflow-hidden">
|
||||
<Image src={tour.imageUrl || ''} alt={tour.title} fill sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 25vw" className="object-cover group-hover:scale-110 transition-transform duration-500" />
|
||||
{cat && (
|
||||
<div className="absolute top-4 left-4 bg-orange-500 text-white text-xs font-bold px-3 py-1 rounded-full shadow">
|
||||
{cat.name}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-6 flex-1 flex flex-col">
|
||||
<h3 className="font-bold text-xl mb-3 text-zinc-100 line-clamp-1">{tour.title}</h3>
|
||||
<p className="text-zinc-400 text-sm mb-6 flex-1 line-clamp-3">{tour.description}</p>
|
||||
<div className="flex items-center justify-between border-t border-zinc-800 pt-4 mt-auto">
|
||||
<div>
|
||||
<span className="text-xs text-zinc-500 uppercase tracking-wider block">{t('from')}</span>
|
||||
<span className="font-bold text-2xl text-orange-500">${tour.price}</span>
|
||||
</div>
|
||||
<Link href={`/tours/${tour.slug}`} className="px-5 py-2.5 bg-zinc-800 text-white text-sm font-medium rounded-md hover:bg-orange-500 transition-colors duration-300">
|
||||
{t('book_now')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user