fb
This commit is contained in:
120
app/[lang]/news/NewsClient.tsx
Normal file
120
app/[lang]/news/NewsClient.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { Calendar, ArrowRight } from 'lucide-react'
|
||||
|
||||
|
||||
export default function NewsClient({ lang, dict }: { lang: string, dict: any }) {
|
||||
const newsItems = [
|
||||
{
|
||||
id: 'hidden-gems-oren',
|
||||
title: dict.news_page.list.n1.title,
|
||||
excerpt: dict.news_page.list.n1.excerpt,
|
||||
image: 'https://images.unsplash.com/photo-1544124499-58912cbddaad?q=80&w=2127&auto=format&fit=crop',
|
||||
date: 'April 15, 2026',
|
||||
author: dict.news_page.list.n1.author
|
||||
},
|
||||
{
|
||||
id: 'summer-cocktails-retreat',
|
||||
title: dict.news_page.list.n2.title,
|
||||
excerpt: dict.news_page.list.n2.excerpt,
|
||||
image: 'https://images.unsplash.com/photo-1519046904884-53103b34b206?q=80&w=2073&auto=format&fit=crop',
|
||||
date: 'April 10, 2026',
|
||||
author: dict.news_page.list.n2.author
|
||||
},
|
||||
{
|
||||
id: 'luxury-interior-trends',
|
||||
title: dict.news_page.list.n3.title,
|
||||
excerpt: dict.news_page.list.n3.excerpt,
|
||||
image: 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=1964&auto=format&fit=crop',
|
||||
date: 'April 05, 2026',
|
||||
author: dict.news_page.list.n3.author
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<main className="bg-[#FAF7F0] min-h-screen">
|
||||
|
||||
|
||||
{/* HEADER SECTION */}
|
||||
<section className="pt-44 pb-24 px-6">
|
||||
<div className="max-w-5xl mx-auto text-center space-y-10">
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 1 }}
|
||||
className="text-7xl md:text-[120px] font-serif text-[#1A1A1A] leading-[0.9] tracking-tight uppercase"
|
||||
>
|
||||
{dict.news_page.title}
|
||||
</motion.h1>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
className="text-[#1A1A1A]/70 text-lg md:text-xl max-w-3xl mx-auto font-medium leading-relaxed italic"
|
||||
>
|
||||
{dict.news_page.subtitle}
|
||||
</motion.p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ARTICLES GRID SECTION */}
|
||||
<section className="pb-44 px-6 md:px-12 max-w-[1400px] mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-12">
|
||||
{newsItems.map((article, idx) => (
|
||||
<motion.div
|
||||
key={article.id}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8, delay: idx * 0.15 }}
|
||||
className="group cursor-pointer"
|
||||
>
|
||||
<Link href={`/${lang}/news/${article.id}`}>
|
||||
<div className="space-y-8">
|
||||
{/* Image Card */}
|
||||
<div className="aspect-[4/5] relative overflow-hidden rounded-[2px] shadow-sm">
|
||||
<Image
|
||||
src={article.image}
|
||||
alt={article.title}
|
||||
fill
|
||||
className="object-cover transition-transform duration-1000 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/5 group-hover:bg-transparent transition-colors duration-500" />
|
||||
</div>
|
||||
|
||||
{/* Metadata and Title */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center space-x-6 text-[11px] font-bold tracking-[0.3em] uppercase text-[#1A1A1A]/40 mb-2">
|
||||
<span className="flex items-center space-x-2">
|
||||
<Calendar size={14} className="opacity-50" />
|
||||
<span>{article.date}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 className="text-3xl md:text-4xl font-serif text-[#1A1A1A] leading-tight group-hover:text-[#C88C4B] transition-colors duration-300">
|
||||
{article.title}
|
||||
</h2>
|
||||
|
||||
<p className="text-[#1A1A1A]/60 text-lg leading-relaxed italic line-clamp-2">
|
||||
{article.excerpt}
|
||||
</p>
|
||||
|
||||
<div className="inline-flex items-center space-x-2 text-[12px] font-bold tracking-widest uppercase text-[#1A1A1A] pt-2 border-b-2 border-transparent group-hover:border-[#C88C4B] transition-all pb-1">
|
||||
<span>{dict.news_page.read}</span>
|
||||
<ArrowRight size={16} className="transform group-hover:translate-x-1 transition-transform" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
</main>
|
||||
)
|
||||
}
|
||||
157
app/[lang]/news/[slug]/NewsDetailClient.tsx
Normal file
157
app/[lang]/news/[slug]/NewsDetailClient.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { Calendar, User, Share2, ArrowLeft } from 'lucide-react'
|
||||
|
||||
|
||||
export default function NewsDetailClient({ lang, slug, dict }: { lang: string, slug: string, dict: any }) {
|
||||
// Use dictionary instead of hardcoded data where possible
|
||||
// For demo, we still use a local map but referring to Ören
|
||||
const newsData: Record<string, any> = {
|
||||
'hidden-gems-oren': {
|
||||
title: dict.news_page.list.n1.title,
|
||||
date: 'April 15, 2026',
|
||||
author: dict.news_page.list.n1.author,
|
||||
category: 'Travel Guide',
|
||||
image: 'https://images.unsplash.com/photo-1544124499-58912cbddaad?q=80&w=2127&auto=format&fit=crop',
|
||||
content: [
|
||||
{ type: 'paragraph', text: dict.news_page.list.n1.excerpt + ' ' + (lang === 'tr' ? 'Ören’in saklı koyları, zamanın ötesinde bir huzur sunuyor. Turkuaz suların üzerinden sabah sisi kalkarken, standart haritaların ötesine bakmaya istekli olanlar için gizli sığınaklar kendilerini göstermeye başlıyor. Bugünkü yolculuğumuz bizi Ege’nin saklı kalbinde bir keşfe çıkarıyor.' : 'Beyond the crowded beaches, the secret coves of Oren offer a peace beyond time. As the morning mist lifts from the turquoise waters, hidden havens begin to reveal themselves to those willing to look beyond standard maps. Our journey today takes us on an exploration in the hidden heart of the Aegean.') },
|
||||
{ type: 'quote', text: lang === 'tr' ? 'Gerçek keşif yolculuğu yeni manzaralar aramak değil, yeni gözlere sahip olmaktan geçer.' : 'The real voyage of discovery consists not in seeking new landscapes, but in having new eyes.' },
|
||||
{ type: 'paragraph', text: lang === 'tr' ? 'Gemile koyunun sessiz kıyılarında, zeytinlikler arasında sessizce oturan antik kalıntılar bunlardan sadece biri. Bu sessiz taşların arasında yürürken, Bizanslı tüccarların yankılarını neredeyse duyabilirsiniz. Burası, tarihin, doğanın ve sessiz lüksün harmanlandığı, Ayris Apart\'ın gerçek özünü bulduğumuz yerdir.' : 'The ancient ruins sitting quietly amidst olive groves on the silent shores of Gemile Bay are just one of them. Walking among these silent stones, you can almost hear the echoes of Byzantine merchants. This is where we find the true essence of Ayris Apart, where history, nature, and silent luxury blend.' },
|
||||
{ type: 'image', url: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?q=80&w=2073&auto=format&fit=crop' }
|
||||
]
|
||||
},
|
||||
'summer-cocktails-retreat': {
|
||||
title: dict.news_page.list.n2.title,
|
||||
date: 'April 10, 2026',
|
||||
author: dict.news_page.list.n2.author,
|
||||
category: 'Lifestyle',
|
||||
image: 'https://images.unsplash.com/photo-1519046904884-53103b34b206?q=80&w=2073&auto=format&fit=crop',
|
||||
content: [
|
||||
{ type: 'paragraph', text: dict.news_page.list.n2.excerpt }
|
||||
]
|
||||
},
|
||||
'luxury-interior-trends': {
|
||||
title: dict.news_page.list.n3.title,
|
||||
date: 'April 05, 2026',
|
||||
author: dict.news_page.list.n3.author,
|
||||
category: 'Design',
|
||||
image: 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=1964&auto=format&fit=crop',
|
||||
content: [
|
||||
{ type: 'paragraph', text: dict.news_page.list.n3.excerpt }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const post = newsData[slug] || newsData['hidden-gems-oren']
|
||||
|
||||
return (
|
||||
<main className="bg-[#FAF7F0] min-h-screen">
|
||||
|
||||
|
||||
{/* HEADER SECTION */}
|
||||
<section className="pt-44 pb-20 px-6">
|
||||
<div className="max-w-4xl mx-auto text-center space-y-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="flex items-center justify-center space-x-6 text-[11px] font-bold tracking-[0.4em] uppercase text-[#1A1A1A]/40"
|
||||
>
|
||||
<span>{post.category}</span>
|
||||
<span className="w-1 h-1 bg-[#C88C4B] rounded-full" />
|
||||
<span>{post.date}</span>
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="text-5xl md:text-7xl lg:text-[88px] font-serif text-[#1A1A1A] leading-[1.1] tracking-tight uppercase"
|
||||
>
|
||||
{post.title}
|
||||
</motion.h1>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
className="flex items-center justify-center space-x-4 pt-4"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-full bg-[#1A1A1A]/5 flex items-center justify-center text-[#1A1A1A]/40 overflow-hidden">
|
||||
<User size={24} />
|
||||
</div>
|
||||
<div className="text-left leading-tight">
|
||||
<p className="text-[11px] font-bold tracking-widest uppercase text-[#1A1A1A]/30 mb-1">Written By</p>
|
||||
<p className="text-sm font-medium text-[#1A1A1A]">{post.author}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FEATURE IMAGE */}
|
||||
<section className="px-6 md:px-12 max-w-[1400px] mx-auto overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 1.05 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 1.5 }}
|
||||
className="aspect-[21/9] relative rounded-[2px]"
|
||||
>
|
||||
<Image src={post.image} alt={post.title} fill className="object-cover" />
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* CONTENT SECTION */}
|
||||
<section className="py-24 px-6">
|
||||
<div className="max-w-2xl mx-auto space-y-12">
|
||||
{post.content.map((block: any, idx: number) => {
|
||||
if (block.type === 'paragraph') {
|
||||
return (
|
||||
<p key={idx} className={`text-[#1A1A1A]/80 text-xl leading-relaxed italic ${idx === 0 ? 'first-letter:text-7xl first-letter:font-serif first-letter:mr-3 first-letter:float-left first-letter:leading-[0.8] first-letter:mt-1' : ''}`}>
|
||||
{block.text}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
if (block.type === 'quote') {
|
||||
return (
|
||||
<blockquote key={idx} className="border-l-4 border-[#C88C4B] pl-8 py-4 my-16">
|
||||
<p className="text-3xl font-serif text-[#1A1A1A] leading-snug">
|
||||
“{block.text}”
|
||||
</p>
|
||||
</blockquote>
|
||||
)
|
||||
}
|
||||
if (block.type === 'image') {
|
||||
return (
|
||||
<div key={idx} className="my-16 -mx-6 md:-mx-24 aspect-video relative overflow-hidden rounded-[2px] shadow-2xl">
|
||||
<Image src={block.url} alt="Article imagery" fill className="object-cover" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
|
||||
{/* SHARE & BACK */}
|
||||
<div className="pt-16 border-t border-[#1A1A1A]/10 flex items-center justify-between">
|
||||
<Link
|
||||
href={`/${lang}/news`}
|
||||
className="inline-flex items-center space-x-2 text-[12px] font-bold tracking-widest uppercase text-[#1A1A1A]/40 hover:text-[#1A1A1A] transition-colors"
|
||||
>
|
||||
<ArrowLeft size={16} />
|
||||
<span>{lang === 'tr' ? 'Haberlere Dön' : 'Back to News'}</span>
|
||||
</Link>
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-[11px] font-bold tracking-widest uppercase text-[#1A1A1A]/40">Share</span>
|
||||
<button className="w-10 h-10 rounded-full border border-[#1A1A1A]/10 flex items-center justify-center hover:bg-[#1A1A1A] hover:text-white transition-all">
|
||||
<Share2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
</main>
|
||||
)
|
||||
}
|
||||
20
app/[lang]/news/[slug]/page.tsx
Normal file
20
app/[lang]/news/[slug]/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary"
|
||||
import NewsDetailClient from "./NewsDetailClient"
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: string, slug: string }> }) {
|
||||
const { lang, slug } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
// Dynamic title based on slug if possible, or just section title
|
||||
return {
|
||||
title: `News - Ayris Apart`,
|
||||
description: dict.news_page.subtitle,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function NewsDetailPage({ params }: { params: Promise<{ lang: string, slug: string }> }) {
|
||||
const { lang, slug } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
return <NewsDetailClient lang={lang} slug={slug} dict={dict} />
|
||||
}
|
||||
18
app/[lang]/news/page.tsx
Normal file
18
app/[lang]/news/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary"
|
||||
import NewsClient from "./NewsClient"
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
return {
|
||||
title: `${dict.news_page.title} - Ayris Apart`,
|
||||
description: dict.news_page.subtitle,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function NewsPage({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
return <NewsClient lang={lang} dict={dict} />
|
||||
}
|
||||
Reference in New Issue
Block a user