Update application features and add scripts (core changes)
This commit is contained in:
@@ -1,97 +0,0 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import { Link } from '@/i18n/routing'
|
||||
import Image from 'next/image'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { CheckCircle2, Maximize, Users, BedDouble, ArrowRight } from 'lucide-react'
|
||||
import { db } from '@/lib/db'
|
||||
import openinaryLoader from '@/lib/openinary-loader'
|
||||
|
||||
// Bu sayfa parametre olarak dinamik slug alacak
|
||||
export default async function RoomDetailPage({ params }: { params: Promise<{ locale: string, slug: string }> }) {
|
||||
@@ -11,77 +13,61 @@ export default async function RoomDetailPage({ params }: { params: Promise<{ loc
|
||||
|
||||
const tRooms = await getTranslations('rooms')
|
||||
|
||||
const MOCK_ROOMS = [
|
||||
{
|
||||
slug: '1-0-daire',
|
||||
name: tRooms('room_list.1_0_daire.name'),
|
||||
description: tRooms('room_list.1_0_daire.desc'),
|
||||
capacity: 2,
|
||||
size: '25m²',
|
||||
beds: '1',
|
||||
image: 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80',
|
||||
gallery: [
|
||||
'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80',
|
||||
'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80',
|
||||
'https://images.unsplash.com/photo-1582719508461-905c673771fd?auto=format&fit=crop&q=80'
|
||||
],
|
||||
amenities: ['Mini Mutfak', 'Klima', 'Balkon', 'WiFi', 'TV', 'Saç Kurutma Makinesi']
|
||||
},
|
||||
{
|
||||
slug: '1-1-daire-a',
|
||||
name: tRooms('room_list.1_1_daire_a.name'),
|
||||
description: tRooms('room_list.1_1_daire_a.desc'),
|
||||
capacity: 2,
|
||||
size: '40m²',
|
||||
beds: '1 + Sofa',
|
||||
image: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80',
|
||||
gallery: [
|
||||
'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80',
|
||||
'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80',
|
||||
'https://images.unsplash.com/photo-1582719508461-905c673771fd?auto=format&fit=crop&q=80'
|
||||
],
|
||||
amenities: ['Ayrı Mutfak', 'Klima', 'Balkon', 'WiFi', 'Oturma Alanı', 'Çamaşır Makinesi']
|
||||
},
|
||||
{
|
||||
slug: '1-1-daire-b',
|
||||
name: tRooms('room_list.1_1_daire_b.name'),
|
||||
description: tRooms('room_list.1_1_daire_b.desc'),
|
||||
capacity: 4,
|
||||
size: '50m²',
|
||||
beds: '2',
|
||||
image: 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80',
|
||||
gallery: [
|
||||
'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80',
|
||||
'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80',
|
||||
'https://images.unsplash.com/photo-1582719508461-905c673771fd?auto=format&fit=crop&q=80'
|
||||
],
|
||||
amenities: ['2 Oda', 'Ayrı Mutfak', 'Klima', 'Balkon', 'WiFi', 'Geniş Oturma Alanı']
|
||||
}
|
||||
];
|
||||
const room = await db.room.findUnique({
|
||||
where: { slug }
|
||||
})
|
||||
|
||||
const room = MOCK_ROOMS.find(r => r.slug === slug);
|
||||
|
||||
if (!room) {
|
||||
if (!room || !room.available) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const name = locale === 'tr' ? room.nameTr : locale === 'de' ? room.nameDe : room.nameEn
|
||||
const description = locale === 'tr' ? room.descriptionTr : locale === 'de' ? room.descriptionDe : room.descriptionEn
|
||||
const image = room.imageUrl || 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80'
|
||||
const isExternalImage = image.startsWith('http') && !image.includes('media.ayris.tech')
|
||||
const gallery = room.images && room.images.length > 0 ? room.images : [image]
|
||||
|
||||
// Size helper mapping based on room type
|
||||
const getRoomSize = (type: string) => {
|
||||
if (type === 'STUDIO_1_0') return '25m²'
|
||||
if (type === 'SUITE_1_1') return '45m²'
|
||||
return '20m²'
|
||||
}
|
||||
|
||||
// Bed helper based on capacity/type roughly
|
||||
const getBeds = (type: string, capacity: number) => {
|
||||
if (type === 'STUDIO_1_0') return '1'
|
||||
if (type === 'SUITE_1_1') return '2'
|
||||
if (capacity === 3) return '1 + Ek Yatak'
|
||||
return '1'
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-background min-h-screen text-on-surface">
|
||||
{/* Hero Section */}
|
||||
<section className="relative h-[60vh] md:h-[70vh] w-full">
|
||||
<Image
|
||||
src={room.image}
|
||||
alt={room.name}
|
||||
fill
|
||||
sizes="100vw"
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
{isExternalImage ? (
|
||||
<img
|
||||
src={image}
|
||||
alt={name}
|
||||
className="object-cover w-full h-full"
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
src={image}
|
||||
alt={name}
|
||||
fill
|
||||
loader={openinaryLoader}
|
||||
sizes="100vw"
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
)}
|
||||
<div className="absolute inset-0 bg-black/40" />
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h1 className="font-serif text-[48px] md:text-[64px] text-white font-bold drop-shadow-md">
|
||||
{room.name}
|
||||
{name}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
@@ -102,55 +88,71 @@ export default async function RoomDetailPage({ params }: { params: Promise<{ loc
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-surface-container px-4 py-2 rounded-full text-secondary font-medium">
|
||||
<Maximize className="w-5 h-5 text-primary" />
|
||||
<span>{room.size}</span>
|
||||
<span>{getRoomSize(room.type)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-surface-container px-4 py-2 rounded-full text-secondary font-medium">
|
||||
<BedDouble className="w-5 h-5 text-primary" />
|
||||
<span>{room.beds} Yatak</span>
|
||||
<span>{getBeds(room.type, room.capacity)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="prose prose-lg text-on-surface-variant">
|
||||
<p className="text-[18px] leading-relaxed">
|
||||
{room.description}
|
||||
<p className="text-[18px] leading-relaxed whitespace-pre-wrap">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Amenities */}
|
||||
<div>
|
||||
<h3 className="font-serif text-[28px] text-secondary font-semibold mb-6">
|
||||
{tRooms('details.features')}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{room.amenities.map((amenity, index) => (
|
||||
<div key={index} className="flex items-center gap-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-accent-green flex-shrink-0" />
|
||||
<span className="text-on-surface-variant">{amenity}</span>
|
||||
</div>
|
||||
))}
|
||||
{room.amenities.length > 0 && (
|
||||
<div>
|
||||
<h3 className="font-serif text-[28px] text-secondary font-semibold mb-6">
|
||||
{tRooms('details.features')}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{room.amenities.map((amenity, index) => (
|
||||
<div key={index} className="flex items-center gap-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-accent-green flex-shrink-0" />
|
||||
<span className="text-on-surface-variant">{amenity}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mini Gallery */}
|
||||
<div>
|
||||
<h3 className="font-serif text-[28px] text-secondary font-semibold mb-6">
|
||||
{tRooms('details.gallery')}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
{room.gallery.map((img, i) => (
|
||||
<div key={i} className="relative aspect-square rounded-2xl overflow-hidden shadow-sm hover:shadow-md transition-shadow">
|
||||
<Image
|
||||
src={img}
|
||||
alt={`${room.name} gallery ${i + 1}`}
|
||||
fill
|
||||
sizes="(max-width: 640px) 100vw, 33vw"
|
||||
className="object-cover hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{gallery.length > 0 && (
|
||||
<div>
|
||||
<h3 className="font-serif text-[28px] text-secondary font-semibold mb-6">
|
||||
{tRooms('details.gallery')}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
{gallery.map((img, i) => {
|
||||
const isGalleryExternal = img.startsWith('http') && !img.includes('media.ayris.tech')
|
||||
return (
|
||||
<div key={i} className="relative aspect-square rounded-2xl overflow-hidden shadow-sm hover:shadow-md transition-shadow">
|
||||
{isGalleryExternal ? (
|
||||
<img
|
||||
src={img}
|
||||
alt={`${name} gallery ${i + 1}`}
|
||||
className="object-cover w-full h-full hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
src={img}
|
||||
alt={`${name} gallery ${i + 1}`}
|
||||
fill
|
||||
loader={openinaryLoader}
|
||||
sizes="(max-width: 640px) 100vw, 33vw"
|
||||
className="object-cover hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import { getTranslations, setRequestLocale } from 'next-intl/server'
|
||||
import { Link } from '@/i18n/routing'
|
||||
import Image from 'next/image'
|
||||
import { Users, Square, Info } from 'lucide-react'
|
||||
import { db } from '@/lib/db'
|
||||
import openinaryLoader from '@/lib/openinary-loader'
|
||||
|
||||
export default async function RoomsPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params
|
||||
@@ -10,35 +12,17 @@ export default async function RoomsPage({ params }: { params: Promise<{ locale:
|
||||
const tNav = await getTranslations('nav')
|
||||
const tRooms = await getTranslations('rooms')
|
||||
|
||||
const MOCK_ROOMS = [
|
||||
{
|
||||
slug: '1-0-daire',
|
||||
name: tRooms('room_list.1_0_daire.name'),
|
||||
description: tRooms('room_list.1_0_daire.desc'),
|
||||
capacity: 2,
|
||||
size: '25m²',
|
||||
image: 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80',
|
||||
amenities: ['Mini Mutfak', 'Klima', 'Balkon', 'WiFi']
|
||||
},
|
||||
{
|
||||
slug: '1-1-daire-a',
|
||||
name: tRooms('room_list.1_1_daire_a.name'),
|
||||
description: tRooms('room_list.1_1_daire_a.desc'),
|
||||
capacity: 2,
|
||||
size: '40m²',
|
||||
image: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80',
|
||||
amenities: ['Ayrı Mutfak', 'Klima', 'Balkon', 'WiFi', 'Oturma Alanı']
|
||||
},
|
||||
{
|
||||
slug: '1-1-daire-b',
|
||||
name: tRooms('room_list.1_1_daire_b.name'),
|
||||
description: tRooms('room_list.1_1_daire_b.desc'),
|
||||
capacity: 4,
|
||||
size: '50m²',
|
||||
image: 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80',
|
||||
amenities: ['2 Oda', 'Ayrı Mutfak', 'Klima', 'Balkon', 'WiFi']
|
||||
}
|
||||
];
|
||||
const dbRooms = await db.room.findMany({
|
||||
where: { available: true },
|
||||
orderBy: { createdAt: 'asc' }
|
||||
})
|
||||
|
||||
// Size helper mapping based on room type
|
||||
const getRoomSize = (type: string) => {
|
||||
if (type === 'STUDIO_1_0') return '25m²'
|
||||
if (type === 'SUITE_1_1') return '45m²'
|
||||
return '20m²'
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 bg-gray-50 py-12 pt-32">
|
||||
@@ -51,55 +35,73 @@ export default async function RoomsPage({ params }: { params: Promise<{ locale:
|
||||
</div>
|
||||
|
||||
<div className="space-y-12 max-w-5xl mx-auto">
|
||||
{MOCK_ROOMS.map((room) => (
|
||||
<div key={room.slug} className="bg-white rounded-2xl overflow-hidden border shadow-sm flex flex-col md:flex-row group">
|
||||
<div className="relative w-full md:w-2/5 h-64 md:h-auto bg-gray-100">
|
||||
<Image
|
||||
src={room.image}
|
||||
alt={room.name}
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, 40vw"
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-6 md:p-8 flex-1 flex flex-col">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<h2 className="text-2xl font-bold text-gray-900">{room.name}</h2>
|
||||
{dbRooms.map((room) => {
|
||||
const name = locale === 'tr' ? room.nameTr : locale === 'de' ? room.nameDe : room.nameEn
|
||||
const description = locale === 'tr' ? room.descriptionTr : locale === 'de' ? room.descriptionDe : room.descriptionEn
|
||||
const image = room.imageUrl || 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80'
|
||||
const isExternal = image.startsWith('http') && !image.includes('media.ayris.tech')
|
||||
|
||||
return (
|
||||
<div key={room.id} className="bg-white rounded-2xl overflow-hidden border shadow-sm flex flex-col md:flex-row group">
|
||||
<div className="relative w-full md:w-2/5 h-64 md:h-auto bg-gray-100">
|
||||
{isExternal ? (
|
||||
<img
|
||||
src={image}
|
||||
alt={name}
|
||||
className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
src={image}
|
||||
alt={name}
|
||||
fill
|
||||
loader={openinaryLoader}
|
||||
sizes="(max-width: 768px) 100vw, 40vw"
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 mb-6 flex-1">
|
||||
{room.description}
|
||||
</p>
|
||||
<div className="p-6 md:p-8 flex-1 flex flex-col">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<h2 className="text-2xl font-bold text-gray-900">{name}</h2>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 mb-6 flex-1 line-clamp-3">
|
||||
{description}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-4 mb-8">
|
||||
<div className="flex items-center gap-1.5 text-sm text-gray-500 font-medium">
|
||||
<Users className="w-4 h-4 text-[#2FB6C4]" />
|
||||
<span>{room.capacity} {tRooms('capacity')}</span>
|
||||
<div className="flex flex-wrap gap-4 mb-8">
|
||||
<div className="flex items-center gap-1.5 text-sm text-gray-500 font-medium">
|
||||
<Users className="w-4 h-4 text-[#2FB6C4]" />
|
||||
<span>{room.capacity} {tRooms('capacity')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-sm text-gray-500 font-medium">
|
||||
<Square className="w-4 h-4 text-[#2FB6C4]" />
|
||||
<span>{getRoomSize(room.type)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-sm text-gray-500 font-medium">
|
||||
<Info className="w-4 h-4 text-[#2FB6C4]" />
|
||||
<span>{room.amenities.length} {tRooms('amenities')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-sm text-gray-500 font-medium">
|
||||
<Square className="w-4 h-4 text-[#2FB6C4]" />
|
||||
<span>{room.size}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-sm text-gray-500 font-medium">
|
||||
<Info className="w-4 h-4 text-[#2FB6C4]" />
|
||||
<span>{room.amenities.length} {tRooms('amenities')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between mt-auto border-t pt-6">
|
||||
<div className="text-sm text-gray-500">
|
||||
<span className="font-bold text-gray-900 text-lg">{tRooms('ask_price')}</span> {tRooms('per_night')}
|
||||
<div className="flex items-center justify-between mt-auto border-t pt-6">
|
||||
<div className="text-sm text-gray-500">
|
||||
<span className="font-bold text-gray-900 text-lg">
|
||||
{room.price ? `${room.price}₺` : tRooms('ask_price')}
|
||||
</span> {room.price ? '' : tRooms('per_night')}
|
||||
</div>
|
||||
<Link
|
||||
href={`/dairelerimiz/${room.slug}`}
|
||||
className="px-6 py-2.5 bg-[#2FB6C4] hover:bg-[#2597a3] text-white rounded-md font-medium transition-colors duration-300 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[#2FB6C4] focus-visible:ring-offset-2"
|
||||
>
|
||||
{tRooms('details_btn')}
|
||||
</Link>
|
||||
</div>
|
||||
<Link
|
||||
href={`/dairelerimiz/${room.slug}`}
|
||||
className="px-6 py-2.5 bg-[#2FB6C4] hover:bg-[#2597a3] text-white rounded-md font-medium transition-colors duration-300 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[#2FB6C4] focus-visible:ring-offset-2"
|
||||
>
|
||||
{tRooms('details_btn')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,68 +6,34 @@ import Image from 'next/image'
|
||||
import BougainvilleaMotif from '@/components/BougainvilleaMotif'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { X } from 'lucide-react'
|
||||
import openinaryLoader from '@/lib/openinary-loader'
|
||||
|
||||
export default function GalleryPage() {
|
||||
const t = useTranslations('gallery')
|
||||
const [activeFilter, setActiveFilter] = useState('all')
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null)
|
||||
|
||||
const filters = [
|
||||
{ id: 'all', label: t('filters.all') },
|
||||
{ id: 'rooms', label: t('filters.rooms') },
|
||||
{ id: 'exterior', label: t('filters.exterior') },
|
||||
{ id: 'view', label: t('filters.view') },
|
||||
{ id: 'balcony', label: t('filters.balcony') },
|
||||
const galleryImages = [
|
||||
'DSC01477.jpg',
|
||||
'DSC01478.jpg',
|
||||
'DSC01479.jpg',
|
||||
'DSC01480.jpg',
|
||||
'DSC01481.jpg',
|
||||
'DSC01482.jpg',
|
||||
'DSC01483.jpg',
|
||||
'DSC01487.jpg',
|
||||
'DSC01535.jpg',
|
||||
'DSC01544.jpg',
|
||||
'DSC01554.jpg',
|
||||
'DSC01560.jpg',
|
||||
'DSC01563.jpg'
|
||||
]
|
||||
|
||||
const galleryItems = [
|
||||
{
|
||||
id: 1,
|
||||
category: 'rooms',
|
||||
title: t('items.1'),
|
||||
src: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80',
|
||||
colSpan: 'col-span-1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
category: 'exterior',
|
||||
title: t('items.2'),
|
||||
src: 'https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?auto=format&fit=crop&q=80',
|
||||
colSpan: 'col-span-1 md:col-span-2 lg:col-span-1',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
category: 'view',
|
||||
title: t('items.3'),
|
||||
src: 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80',
|
||||
colSpan: 'col-span-1',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
category: 'balcony',
|
||||
title: t('items.4'),
|
||||
src: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80',
|
||||
colSpan: 'col-span-1 md:col-span-2',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
category: 'rooms',
|
||||
title: t('items.5'),
|
||||
src: 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80',
|
||||
colSpan: 'col-span-1',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
category: 'view',
|
||||
title: t('items.6'),
|
||||
src: 'https://images.unsplash.com/photo-1582719508461-905c673771fd?auto=format&fit=crop&q=80',
|
||||
colSpan: 'col-span-1 md:col-span-2 lg:col-span-1',
|
||||
},
|
||||
]
|
||||
|
||||
const filteredItems = galleryItems.filter(item =>
|
||||
activeFilter === 'all' || item.category === activeFilter
|
||||
)
|
||||
const galleryItems = galleryImages.map((file, idx) => ({
|
||||
id: idx + 1,
|
||||
title: t('items.' + ((idx % 6) + 1)), // Use existing translations just to not break them
|
||||
src: `https://media.ayris.tech/t/starapart/gallery/${file}`,
|
||||
colSpan: idx % 4 === 1 ? 'col-span-1 md:col-span-2 lg:col-span-1' : 'col-span-1'
|
||||
}))
|
||||
|
||||
return (
|
||||
<div className="bg-background min-h-screen text-on-surface pt-32 pb-section-gap container mx-auto px-4">
|
||||
@@ -84,27 +50,10 @@ export default function GalleryPage() {
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{/* Filter Bar */}
|
||||
<div className="flex flex-wrap items-center gap-4 mb-12">
|
||||
{filters.map((filter) => (
|
||||
<button
|
||||
key={filter.id}
|
||||
onClick={() => setActiveFilter(filter.id)}
|
||||
className={`px-6 py-2 rounded-full font-sans font-bold text-[14px] uppercase tracking-wider transition-all duration-300 border-2 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary ${
|
||||
activeFilter === filter.id
|
||||
? 'border-primary text-primary bg-primary/10'
|
||||
: 'border-transparent text-secondary hover:border-primary/30'
|
||||
}`}
|
||||
>
|
||||
{filter.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Masonry-like Grid */}
|
||||
<motion.div layout className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<AnimatePresence>
|
||||
{filteredItems.map((item) => (
|
||||
{galleryItems.map((item) => (
|
||||
<motion.div
|
||||
key={item.id}
|
||||
layout
|
||||
@@ -127,14 +76,10 @@ export default function GalleryPage() {
|
||||
src={item.src}
|
||||
alt={item.title}
|
||||
fill
|
||||
loader={openinaryLoader}
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
className="object-cover transform group-hover:scale-105 transition-transform duration-700"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity flex items-end p-6">
|
||||
<span className="text-white font-serif text-[24px] font-medium drop-shadow-md">
|
||||
{item.title}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
@@ -169,6 +114,7 @@ export default function GalleryPage() {
|
||||
src={selectedImage}
|
||||
alt="Enlarged view"
|
||||
fill
|
||||
loader={openinaryLoader}
|
||||
className="object-contain"
|
||||
sizes="100vw"
|
||||
quality={90}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { motion } from 'framer-motion'
|
||||
import Image from 'next/image'
|
||||
import BougainvilleaMotif from '@/components/BougainvilleaMotif'
|
||||
import { MapPin, Waves, ShoppingBasket, Coffee, PersonStanding, Phone, Mail } from 'lucide-react'
|
||||
import { MapPin, Waves, ShoppingBasket, Coffee, PersonStanding, Phone, Mail, Clock } from 'lucide-react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
const EASE: [number, number, number, number] = [0.22, 1, 0.36, 1]
|
||||
@@ -175,6 +175,16 @@ export default function ContactPage() {
|
||||
<p className="font-sans text-[16px] text-secondary font-medium">iletisim@sitarapart.com</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
|
||||
<Clock className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-sans font-bold text-[12px] text-on-surface-variant uppercase tracking-wider">{t('connect.check_in')}</p>
|
||||
<p className="font-sans text-[16px] text-secondary font-medium">{t('connect.check_out')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Decorative Image */}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { motion, useReducedMotion } from 'framer-motion'
|
||||
import { MapPin, Wifi, UtensilsCrossed, Waves, Car } from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import BougainvilleaMotif from '@/components/BougainvilleaMotif'
|
||||
import openinaryLoader from '@/lib/openinary-loader'
|
||||
|
||||
const EASE: [number, number, number, number] = [0.22, 1, 0.36, 1]
|
||||
|
||||
@@ -36,7 +37,7 @@ export default function HomePage() {
|
||||
<header className="relative h-screen w-full flex items-center justify-center overflow-hidden">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1582719508461-905c673771fd?auto=format&fit=crop&q=80"
|
||||
src="https://media.ayris.tech/t/starapart/hero/media__1784311319733.jpg"
|
||||
alt="Sitar Apart Hotel View"
|
||||
fill
|
||||
className="object-cover"
|
||||
@@ -125,22 +126,22 @@ export default function HomePage() {
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-gutter">
|
||||
{[
|
||||
{
|
||||
id: '1-0-daire',
|
||||
id: 'standart-oda',
|
||||
title: tRooms('room_1.title'),
|
||||
desc: tRooms('room_1.desc'),
|
||||
img: 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80'
|
||||
img: 'https://media.ayris.tech/t/starapart/SUITE_1_1/DSC01491 (1).JPG'
|
||||
},
|
||||
{
|
||||
id: '1-1-daire-a',
|
||||
id: 'kucuk-oda',
|
||||
title: tRooms('room_2.title'),
|
||||
desc: tRooms('room_2.desc'),
|
||||
img: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80'
|
||||
img: 'https://media.ayris.tech/t/starapart/STUDIO_1_0/DSC01465 (1).JPG'
|
||||
},
|
||||
{
|
||||
id: '1-1-daire-b',
|
||||
id: '1-1-daire',
|
||||
title: tRooms('room_3.title'),
|
||||
desc: tRooms('room_3.desc'),
|
||||
img: 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80'
|
||||
img: 'https://media.ayris.tech/t/starapart/SUITE_1_1/DSC01491 (1).JPG'
|
||||
}
|
||||
].map((room, idx) => (
|
||||
<motion.div
|
||||
@@ -156,6 +157,7 @@ export default function HomePage() {
|
||||
src={room.img}
|
||||
alt={room.title}
|
||||
fill
|
||||
loader={openinaryLoader}
|
||||
className="object-cover transition-transform duration-700 group-hover:scale-110"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user