92 lines
3.7 KiB
TypeScript
92 lines
3.7 KiB
TypeScript
import React from 'react'
|
|
import { Link } from 'react-router-dom'
|
|
import { motion } from 'framer-motion'
|
|
import { ChevronLeftIcon } from 'lucide-react'
|
|
import { usePageHero } from '../api'
|
|
import { useLang } from '../i18n/lang'
|
|
import { useFieldSaver } from '../admin/AdminContext'
|
|
import { SingletonPill } from '../admin/Editable'
|
|
import { InlineEdit } from './admin/InlineEdit'
|
|
import { ImageField } from './admin/ImageField'
|
|
|
|
type PageHeroProps = {
|
|
/** کلید هیرو صفحه در دیتابیس — عنوان/توضیح درجا ویرایشپذیر میشوند */
|
|
pageKey: string
|
|
}
|
|
|
|
export function PageHero({ pageKey }: PageHeroProps) {
|
|
const hero = usePageHero(pageKey)
|
|
const { t, href } = useLang()
|
|
const save = useFieldSaver()
|
|
|
|
const saveField = (field: 'title' | 'lead' | 'image', value: string) =>
|
|
save('page-heroes', hero.id, { [field]: value }, (d) => {
|
|
const h = d.page_heroes.find((p) => p.id === hero.id)
|
|
if (h) (h as Record<string, unknown>)[field] = value
|
|
})
|
|
|
|
return (
|
|
<section className="relative flex h-[58vh] min-h-[420px] items-end overflow-hidden">
|
|
{hero.image && <img src={hero.image} alt="" aria-hidden="true" className="absolute inset-0 h-full w-full object-cover" />}
|
|
|
|
<div className="absolute inset-0 bg-ink/50" aria-hidden="true" />
|
|
|
|
<div
|
|
className="absolute inset-0"
|
|
style={{ background: 'linear-gradient(to top, #27221c 6%, rgba(39,34,28,0.1) 70%)' }}
|
|
aria-hidden="true"
|
|
/>
|
|
|
|
<div className="relative mx-auto w-full max-w-site px-6 pb-16 lg:px-10">
|
|
<nav aria-label={t('breadcrumbAria')} className="flex items-center gap-2 text-xs font-light text-ivory/50">
|
|
<Link to={href('/')} className="transition-colors hover:text-copper">
|
|
{t('home')}
|
|
</Link>
|
|
<ChevronLeftIcon className="h-3.5 w-3.5 ltr:rotate-180" />
|
|
<span className="text-copper">{hero.title}</span>
|
|
</nav>
|
|
<motion.h1
|
|
initial={{ opacity: 0, y: 24 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.7, ease: [0.23, 1, 0.32, 1] }}
|
|
className="mt-5 max-w-[700px]"
|
|
>
|
|
<InlineEdit
|
|
as="span"
|
|
value={hero.title}
|
|
onSave={(v) => saveField('title', v)}
|
|
className="block text-hero font-bold text-ivory"
|
|
/>
|
|
</motion.h1>
|
|
<motion.p
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.7, delay: 0.1, ease: [0.23, 1, 0.32, 1] }}
|
|
className="mt-5 max-w-2xl"
|
|
>
|
|
<InlineEdit
|
|
as="span"
|
|
multiline
|
|
value={hero.lead}
|
|
onSave={(v) => saveField('lead', v)}
|
|
className="block text-base font-light leading-9 text-ivory/65"
|
|
/>
|
|
</motion.p>
|
|
<div className="mt-8 h-px w-28 bg-copper" />
|
|
|
|
{/* ویرایش تصویر پسزمینه و سئوی صفحه — فقط در حالت ویرایش ادمین */}
|
|
<div className="mt-6 flex items-start gap-3 [&>div]:!mt-0">
|
|
<SingletonPill
|
|
resource="seo-meta"
|
|
label="عنوان و توضیح سئوی این صفحه"
|
|
select={(d) => d.seo_meta.find((s) => s.page_key === pageKey)}
|
|
// اگر ردیف سئوی این صفحه هنوز ساخته نشده باشد، همینجا ساخته میشود
|
|
createDefaults={() => ({ page_key: pageKey, title: '', description: '' })}
|
|
/>
|
|
<ImageField label="تصویر پسزمینه" value={hero.image} onSave={(url) => saveField('image', url)} />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|