feat: i18n fixes, blog rename, footer cleanup, and contact info update
- Rename /news route to /blog across all pages, links, and sitemap - Fix hardcoded English text in Experiences, QuoteSection, and Footer by wiring all content through the TR/EN dictionaries - Clean up Footer: remove non-existent page links, add contact column - Update contact info: new phone numbers, bilgi@ email, No:71 address, Instagram @ayrisapart link - Add suppressHydrationWarning to <body> to silence browser-extension attribute mismatch errors - Add sizes prop to all fill-mode Next.js Image components Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
export default function NewsClient({ lang, dict, posts }: { lang: string, dict: any, posts: any[] }) {
|
||||
return (
|
||||
<main className="bg-[#FAF7F0] min-h-screen">
|
||||
{/* HEADER */}
|
||||
<section className="pt-44 pb-20 px-6">
|
||||
<div className="max-w-4xl mx-auto text-center space-y-10">
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
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 }}
|
||||
className="text-[#1A1A1A]/60 text-lg md:text-xl max-w-2xl mx-auto font-medium leading-relaxed italic"
|
||||
>
|
||||
{dict.news_page.excerpt}
|
||||
</motion.p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* POSTS GRID */}
|
||||
<section className="pb-44 px-6 md:px-12 max-w-[1400px] mx-auto">
|
||||
{posts.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-20">
|
||||
{posts.map((post, idx) => (
|
||||
<motion.div
|
||||
key={post.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: idx * 0.1 }}
|
||||
className="group"
|
||||
>
|
||||
<Link href={`/${lang}/blog/${post.slug}`}>
|
||||
<div className="aspect-[16/10] relative overflow-hidden rounded-[2px] shadow-sm bg-white">
|
||||
{post.image && (
|
||||
<Image
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, 33vw"
|
||||
className="object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 space-y-4">
|
||||
<div className="flex items-center space-x-4 text-[11px] font-bold tracking-[0.2em] uppercase text-[#1A1A1A]/40">
|
||||
<span>{post.author}</span>
|
||||
<span>•</span>
|
||||
<span>{new Date(post.createdAt).toLocaleDateString(lang === 'tr' ? 'tr-TR' : 'en-US')}</span>
|
||||
</div>
|
||||
|
||||
<h2 className="text-3xl font-serif text-[#1A1A1A] group-hover:text-[#C88C4B] transition-colors leading-tight">
|
||||
{post.title}
|
||||
</h2>
|
||||
|
||||
<p className="text-[#1A1A1A]/60 text-base leading-relaxed line-clamp-2 italic">
|
||||
{post.excerpt}
|
||||
</p>
|
||||
|
||||
<div className="inline-flex items-center space-x-3 text-[12px] font-bold tracking-[0.3em] uppercase border-b border-[#1A1A1A] pb-1">
|
||||
<span>{dict.news_page.read}</span>
|
||||
<span className="text-lg">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#1A1A1A]/40 italic">{lang === 'tr' ? 'Henüz yazı bulunmuyor.' : 'No stories found yet.'}</p>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { ArrowLeft, Clock, User } from 'lucide-react'
|
||||
|
||||
export default function NewsDetailClient({ lang, dict, post }: { lang: string, dict: any, post: any }) {
|
||||
return (
|
||||
<main className="bg-[#FAF7F0] min-h-screen">
|
||||
{/* HEADER & IMAGE */}
|
||||
<section className="pt-44 pb-20 px-6 max-w-[1000px] mx-auto">
|
||||
<div className="space-y-12">
|
||||
{/* Back Button */}
|
||||
<Link href={`/${lang}/blog`} className="inline-flex items-center space-x-2 text-[11px] font-bold tracking-[0.4em] 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="space-y-8 text-center md:text-left">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="flex flex-wrap justify-center md:justify-start items-center gap-6 text-[12px] font-bold tracking-[0.2em] uppercase text-[#1A1A1A]/30"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<User size={14} className="text-[#C88C4B]" />
|
||||
<span>{post.author}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Clock size={14} className="text-[#C88C4B]" />
|
||||
<span>{new Date(post.createdAt).toLocaleDateString(lang === 'tr' ? 'tr-TR' : 'en-US')}</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="text-5xl md:text-[80px] font-serif text-[#1A1A1A] leading-[1.1] tracking-tight"
|
||||
>
|
||||
{post.title}
|
||||
</motion.h1>
|
||||
</div>
|
||||
|
||||
{post.image && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.98 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 1, delay: 0.2 }}
|
||||
className="aspect-[21/10] relative overflow-hidden rounded-[2px] shadow-2xl"
|
||||
>
|
||||
<Image
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
fill
|
||||
sizes="100vw"
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CONTENT */}
|
||||
<section className="pb-44 px-6 max-w-[800px] mx-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
className="prose prose-neutral prose-xl max-w-none"
|
||||
>
|
||||
<p className="text-2xl md:text-3xl font-serif text-[#1A1A1A] italic leading-relaxed mb-12 opacity-80 border-l-4 border-[#C88C4B] pl-8">
|
||||
{post.excerpt}
|
||||
</p>
|
||||
|
||||
<div className="text-[#1A1A1A]/80 leading-loose text-lg md:text-xl space-y-8 whitespace-pre-wrap">
|
||||
{post.content}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<div className="mt-32 pt-12 border-t border-[#1A1A1A]/10 text-center">
|
||||
<Link
|
||||
href={`/${lang}/contact`}
|
||||
className="inline-flex items-center space-x-4 text-[13px] font-bold tracking-[0.4em] uppercase border-b-2 border-[#1A1A1A] pb-2 group"
|
||||
>
|
||||
<span>{dict.footer.book}</span>
|
||||
<span className="transform group-hover:translate-x-1 transition-transform">→</span>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary"
|
||||
import NewsDetailClient from "./NewsDetailClient"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: string, slug: string }> }) {
|
||||
const { lang, slug } = await params
|
||||
|
||||
const post = await prisma.post.findUnique({
|
||||
where: { slug }
|
||||
})
|
||||
|
||||
if (!post) return { title: 'Post Not Found' }
|
||||
|
||||
return {
|
||||
title: `${post.title} - Ayris Apart`,
|
||||
description: post.excerpt,
|
||||
}
|
||||
}
|
||||
|
||||
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')
|
||||
|
||||
const post = await prisma.post.findUnique({
|
||||
where: { slug }
|
||||
})
|
||||
|
||||
if (!post) notFound()
|
||||
|
||||
return <NewsDetailClient lang={lang} dict={dict} post={post} />
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary"
|
||||
import NewsClient from "./NewsClient"
|
||||
import { prisma } from "@/lib/prisma"
|
||||
|
||||
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.excerpt,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function NewsPage({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
// Fetch posts from database based on the selected language
|
||||
const posts = await prisma.post.findMany({
|
||||
where: {
|
||||
lang: lang,
|
||||
published: true
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
}
|
||||
})
|
||||
|
||||
return <NewsClient lang={lang} dict={dict} posts={posts} />
|
||||
}
|
||||
Reference in New Issue
Block a user