Security: - requireAdmin() session check added to every admin-only server action (previously relied only on middleware path matching, which Next.js Server Actions don't reliably respect) - Real Prisma + bcrypt admin auth, replacing hardcoded credentials; split into an Edge-safe auth.config.ts (used by proxy.ts) and the full Prisma-backed auth.ts (route handler, server actions, server components) - Removed hardcoded fallback secret on the Instagram sync cron endpoint - Honeypot field + per-IP rate limiting on contact/business-submission forms and the analytics events endpoint Features: - AI trip planner (/plan-olustur, /plan/[id]) backed by DeepSeek, grounded to only recommend isLocalApproved listings, with a deterministic link-injection fallback for anything the model doesn't format as markdown - Interactive Leaflet/OpenStreetMap view on category listing pages - Telegram notifications for new contact messages and business submissions SEO: - Brand-consistent favicon/apple-icon/PWA icons and default Open Graph/ Twitter share images, generated via next/og (replacing default Next.js placeholders) - BreadcrumbList structured data on category and listing detail pages - Fixed two remaining raw <img> tags to use next/image Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
import type { Metadata } from 'next'
|
||
import { basicMetadata } from '@/lib/seo'
|
||
import { getTranslations, setRequestLocale } from 'next-intl/server'
|
||
import { mockDb } from '@/lib/mockDb'
|
||
import BusinessForm from './BusinessForm'
|
||
|
||
interface AddBusinessPageProps {
|
||
params: Promise<{ locale: string }>
|
||
}
|
||
|
||
export async function generateMetadata({ params }: AddBusinessPageProps): Promise<Metadata> {
|
||
const { locale } = await params
|
||
const title = locale === 'en' ? 'Add Your Business — Marmaris Local' : locale === 'ru' ? 'Добавить заведение — Marmaris Local' : 'İşletme Ekle — Marmaris Local'
|
||
const description = locale === 'en' ? 'Apply to get your Marmaris business curated and featured with the Yerel Onaylı seal.' : locale === 'ru' ? 'Подайте заявку на включение вашего заведения в гид Marmaris Local.' : 'İşletmenizi Marmaris Local rehberine ekleyin ve Yerel Onaylı mührünü alın.'
|
||
return basicMetadata(title, description, locale, '/add-business')
|
||
}
|
||
|
||
export default async function AddBusinessPage({ params }: AddBusinessPageProps) {
|
||
const { locale } = await params
|
||
setRequestLocale(locale)
|
||
|
||
const t = await getTranslations('forms')
|
||
const navT = await getTranslations('nav')
|
||
|
||
// Fetch categories and neighborhoods to populate form select options
|
||
const categories = await mockDb.getCategories()
|
||
const neighborhoods = await mockDb.getNeighborhoods()
|
||
|
||
const getLocalizedName = (obj: any) => {
|
||
if (!obj) return ''
|
||
return locale === 'ru' ? obj.nameRu : locale === 'en' ? obj.nameEn : obj.nameTr
|
||
}
|
||
|
||
const categoryOptions = categories.map(c => ({
|
||
value: c.id,
|
||
label: getLocalizedName(c)
|
||
}))
|
||
|
||
const neighborhoodOptions = neighborhoods.map(n => ({
|
||
value: n.id,
|
||
label: getLocalizedName(n)
|
||
}))
|
||
|
||
return (
|
||
<div className="flex-1 flex flex-col min-h-screen bg-stone text-ink">
|
||
|
||
<main className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-12 flex-1 w-full">
|
||
<div className="bg-paper p-8 rounded-3xl border border-pine/8 shadow-sm">
|
||
|
||
<div className="mb-8 border-b border-dashed border-pine/8 pb-6">
|
||
<h1 className="text-3xl font-heading font-extrabold text-pine lowercase">
|
||
{navT('addBusiness')}
|
||
</h1>
|
||
<p className="text-xs text-shutter font-mono uppercase tracking-wider mt-1.5">
|
||
marmaris local • yeni başvuru
|
||
</p>
|
||
</div>
|
||
|
||
<BusinessForm
|
||
translations={{
|
||
businessName: t('businessName'),
|
||
category: t('category'),
|
||
neighborhood: t('neighborhood'),
|
||
address: t('address'),
|
||
phone: t('phone'),
|
||
whatsapp: t('whatsapp'),
|
||
description: t('description'),
|
||
image: t('image'),
|
||
submit: t('submit'),
|
||
sending: t('sending'),
|
||
success: t('success'),
|
||
contactName: 'Yetkili Adı Soyadı',
|
||
contactEmail: 'İletişim E-postası'
|
||
}}
|
||
categories={categoryOptions}
|
||
neighborhoods={neighborhoodOptions}
|
||
/>
|
||
|
||
</div>
|
||
</main>
|
||
</div>
|
||
)
|
||
}
|