revert(ui): بازگردانی دکمه‌های فرم تماس، خبرنامه، مقالات، داستان و صفحهٔ مقاله به طرح قبلی

همه جای Button/PremiumButton جابه‌جا شده بودند؛ به Button/ButtonLink قبلی
برگشتند (کامپوننت Button قبلاً بازگردانده شده بود).
This commit is contained in:
aramesh-site deploy
2026-09-28 09:58:49 +00:00
parent 531e8d63c1
commit 5cf7b51686
5 changed files with 53 additions and 99 deletions
+17 -25
View File
@@ -1,21 +1,20 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { CheckCircle2Icon, MailIcon, MapPinIcon, PhoneIcon } from 'lucide-react' import { CheckCircle2Icon, MailIcon, MapPinIcon, PhoneIcon, SendIcon } from 'lucide-react'
import Swal from 'sweetalert2' import Swal from 'sweetalert2'
import { Reveal } from './Reveal' import { Reveal } from './Reveal'
import { PremiumButton } from './PremiumButton' import { Button } from './Button'
import { useSite } from '../api' import { useSite } from '../api'
import { useLang } from '../i18n/lang' import { useLang } from '../i18n/lang'
import { useFieldSaver } from '../admin/AdminContext' import { useFieldSaver } from '../admin/AdminContext'
import { InlineEdit } from './admin/InlineEdit' import { InlineEdit } from './admin/InlineEdit'
import { SingletonPill } from '../admin/Editable' import { SingletonPill } from '../admin/Editable'
import { API_URL } from '../config' import { API_URL } from '../config'
import { track } from '../analytics'
export function ContactSection() { export function ContactSection() {
const { brand, contact_section: content } = useSite() const { brand, contact_section: content } = useSite()
const { t } = useLang() const { t } = useLang()
const save = useFieldSaver() const save = useFieldSaver()
const [form, setForm] = useState({ name: '', phone: '', note: '', website: '' }) const [form, setForm] = useState({ name: '', phone: '', note: '' })
const [sent, setSent] = useState(false) const [sent, setSent] = useState(false)
const [sending, setSending] = useState(false) const [sending, setSending] = useState(false)
@@ -80,7 +79,7 @@ export function ContactSection() {
const res = await fetch(`${API_URL}/api/contact`, { const res = await fetch(`${API_URL}/api/contact`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: nameValue, phone: phoneValue, note: form.note.trim(), website: form.website }), body: JSON.stringify({ name: nameValue, phone: phoneValue, note: form.note.trim() }),
}) })
if (!res.ok) { if (!res.ok) {
@@ -91,7 +90,6 @@ export function ContactSection() {
return return
} }
track('contact-submit')
setSent(true) setSent(true)
} catch { } catch {
showErrorAlert(t('connectionErrorTitle'), t('connectionErrorText')) showErrorAlert(t('connectionErrorTitle'), t('connectionErrorText'))
@@ -154,7 +152,6 @@ export function ContactSection() {
<a <a
href={brand.phone_href} href={brand.phone_href}
dir="ltr" dir="ltr"
onClick={() => track('phone-click', { from: 'contact-section' })}
className="mt-1 block text-sm font-light text-ivory/70 transition-colors hover:text-copper" className="mt-1 block text-sm font-light text-ivory/70 transition-colors hover:text-copper"
> >
<InlineEdit value={brand.phone} onSave={(v) => saveBrand('phone', v)} /> <InlineEdit value={brand.phone} onSave={(v) => saveBrand('phone', v)} />
@@ -197,29 +194,20 @@ export function ContactSection() {
<p className="mt-3 max-w-sm text-sm font-light leading-8 text-ivory/70"> <p className="mt-3 max-w-sm text-sm font-light leading-8 text-ivory/70">
<InlineEdit multiline value={content.success_text} onSave={(v) => saveContent('success_text', v)} /> <InlineEdit multiline value={content.success_text} onSave={(v) => saveContent('success_text', v)} />
</p> </p>
<PremiumButton <Button
type="button"
variant="outline"
className="mt-8 font-light"
onClick={() => { onClick={() => {
setSent(false) setSent(false)
setForm({ name: '', phone: '', note: '', website: '' }) setForm({ name: '', phone: '', note: '' })
}} }}
className="mt-8 font-light"
> >
<InlineEdit value={content.success_button_label} onSave={(v) => saveContent('success_button_label', v)} /> <InlineEdit value={content.success_button_label} onSave={(v) => saveContent('success_button_label', v)} />
</PremiumButton> </Button>
</div> </div>
) : ( ) : (
<form onSubmit={submit} noValidate className="grid gap-6 md:grid-cols-2"> <form onSubmit={submit} noValidate className="grid gap-6 md:grid-cols-2">
{/* honeypot — خارج از دید؛ فقط ربات‌ها آن را پر می‌کنند */}
<input
type="text"
name="website"
value={form.website}
onChange={(e) => setForm({ ...form, website: e.target.value })}
tabIndex={-1}
autoComplete="off"
aria-hidden="true"
className="pointer-events-none absolute -left-[9999px] h-0 w-0 opacity-0"
/>
<div> <div>
<label htmlFor="contact-name" className="block text-sm font-medium text-ivory/90"> <label htmlFor="contact-name" className="block text-sm font-medium text-ivory/90">
<InlineEdit value={content.name_label} onSave={(v) => saveContent('name_label', v)} /> <InlineEdit value={content.name_label} onSave={(v) => saveContent('name_label', v)} />
@@ -265,17 +253,21 @@ export function ContactSection() {
</div> </div>
<div className="md:col-span-2 flex justify-start"> <div className="md:col-span-2 flex justify-start">
<PremiumButton <Button
type="submit" type="submit"
variant="copper"
className="w-full md:w-auto md:px-12 flex items-center justify-center gap-3" className="w-full md:w-auto md:px-12 flex items-center justify-center gap-3"
disabled={sending} disabled={sending}
> >
{sending ? ( {sending ? (
content.sending_label content.sending_label
) : ( ) : (
<InlineEdit value={content.submit_label} onSave={(v) => saveContent('submit_label', v)} /> <>
<InlineEdit value={content.submit_label} onSave={(v) => saveContent('submit_label', v)} />
<SendIcon className="h-4 w-4 transition-transform duration-300 rtl:group-hover:-translate-x-1 ltr:group-hover:translate-x-1" />
</>
)} )}
</PremiumButton> </Button>
</div> </div>
</form> </form>
)} )}
+5 -4
View File
@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { ArrowLeftIcon } from 'lucide-react';
import { Reveal } from '../Reveal'; import { Reveal } from '../Reveal';
import { PremiumButton } from '../PremiumButton'; import { ButtonLink } from '../Button';
import { ArticleCard } from '../ArticleCard'; import { ArticleCard } from '../ArticleCard';
import { useSectionHeading, useSite } from '../../api'; import { useSectionHeading, useSite } from '../../api';
import { useLang } from '../../i18n/lang'; import { useLang } from '../../i18n/lang';
@@ -53,9 +53,10 @@ export function Articles() {
{heading.cta_label && heading.cta_href && ( {heading.cta_label && heading.cta_href && (
<Reveal delay={0.1}> <Reveal delay={0.1}>
<div className="mt-12 flex justify-center"> <div className="mt-12 flex justify-center">
<PremiumButton to={href(heading.cta_href)} className="font-light"> <ButtonLink to={href(heading.cta_href)} variant="outline" className="font-light">
<InlineEdit value={heading.cta_label} onSave={(v) => saveHeading('cta_label', v)} /> <InlineEdit value={heading.cta_label} onSave={(v) => saveHeading('cta_label', v)} />
</PremiumButton> <ArrowLeftIcon className="h-4 w-4 ltr:-scale-x-100" />
</ButtonLink>
</div> </div>
</Reveal> </Reveal>
)} )}
+10 -23
View File
@@ -1,22 +1,20 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { CheckIcon } from 'lucide-react' import { CheckIcon, SendIcon } from 'lucide-react'
import Swal from 'sweetalert2' import Swal from 'sweetalert2'
import { Reveal } from '../Reveal' import { Reveal } from '../Reveal'
import { PremiumButton } from '../PremiumButton' import { Button } from '../Button'
import { useSite } from '../../api' import { useSite } from '../../api'
import { useLang } from '../../i18n/lang' import { useLang } from '../../i18n/lang'
import { useFieldSaver } from '../../admin/AdminContext' import { useFieldSaver } from '../../admin/AdminContext'
import { InlineEdit } from '../admin/InlineEdit' import { InlineEdit } from '../admin/InlineEdit'
import { SingletonPill } from '../../admin/Editable' import { SingletonPill } from '../../admin/Editable'
import { API_URL } from '../../config' import { API_URL } from '../../config'
import { track } from '../../analytics'
export function Newsletter() { export function Newsletter() {
const { newsletter } = useSite() const { newsletter } = useSite()
const { t } = useLang() const { t } = useLang()
const save = useFieldSaver() const save = useFieldSaver()
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const [website, setWebsite] = useState('')
const [status, setStatus] = useState<'idle' | 'error' | 'done'>('idle') const [status, setStatus] = useState<'idle' | 'error' | 'done'>('idle')
const [sending, setSending] = useState(false) const [sending, setSending] = useState(false)
@@ -77,7 +75,7 @@ export function Newsletter() {
const res = await fetch(`${API_URL}/api/newsletter`, { const res = await fetch(`${API_URL}/api/newsletter`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: emailValue, website }), body: JSON.stringify({ email: emailValue }),
}) })
if (!res.ok) { if (!res.ok) {
@@ -89,7 +87,6 @@ export function Newsletter() {
return return
} }
track('newsletter-signup')
setStatus('done') setStatus('done')
setEmail('') setEmail('')
} catch { } catch {
@@ -101,7 +98,7 @@ export function Newsletter() {
} }
return ( return (
<section className="relative overflow-hidden bg-copper py-20 shadow-[inset_0_1px_1px_rgba(255,255,255,0.3)]" aria-labelledby="newsletter-title"> <section className="relative overflow-hidden bg-gradient-to-r from-copper via-[#d8965e] to-copper py-20 shadow-[inset_0_1px_1px_rgba(255,255,255,0.3)]" aria-labelledby="newsletter-title">
<SingletonPill resource="newsletter" label="ویرایش متن‌های خبرنامه" className="absolute left-4 top-4" /> <SingletonPill resource="newsletter" label="ویرایش متن‌های خبرنامه" className="absolute left-4 top-4" />
{/* هاله نور پس‌زمینه برای عمق بصری */} {/* هاله نور پس‌زمینه برای عمق بصری */}
@@ -120,17 +117,6 @@ export function Newsletter() {
<Reveal delay={0.08}> <Reveal delay={0.08}>
<form onSubmit={submit} className="flex flex-col gap-3.5 sm:flex-row" noValidate> <form onSubmit={submit} className="flex flex-col gap-3.5 sm:flex-row" noValidate>
{/* honeypot — خارج از دید؛ فقط ربات‌ها آن را پر می‌کنند */}
<input
type="text"
name="website"
value={website}
onChange={(e) => setWebsite(e.target.value)}
tabIndex={-1}
autoComplete="off"
aria-hidden="true"
className="pointer-events-none absolute -left-[9999px] h-0 w-0 opacity-0"
/>
<div className="flex-1"> <div className="flex-1">
<label htmlFor="newsletter-email" className="sr-only"> <label htmlFor="newsletter-email" className="sr-only">
{newsletter.placeholder} {newsletter.placeholder}
@@ -156,18 +142,19 @@ export function Newsletter() {
)} )}
</div> </div>
<button <Button
type="submit" type="submit"
variant="dark"
disabled={sending} disabled={sending}
className="group/btn relative inline-flex items-center justify-center overflow-hidden rounded-full px-10 py-4 text-sm font-bold tracking-widest text-white border border-ink bg-ink transition-all duration-300 ease-in-out transform hover:scale-105 hover:shadow-lg cursor-pointer select-none disabled:opacity-50 disabled:cursor-not-allowed" className="group flex items-center justify-center gap-2 rounded-full px-8 py-4 shadow-[0_8px_20px_rgba(0,0,0,0.3)] transition-all duration-300 hover:shadow-[0_12px_25px_rgba(0,0,0,0.45)] active:scale-95"
> >
<span className="absolute inset-0 z-0 bg-white scale-y-0 origin-bottom transition-transform duration-500 ease-[cubic-bezier(0.19,1,0.22,1)] group-hover/btn:scale-y-100" /> <SendIcon className="h-4 w-4 transition-transform duration-300 rtl:group-hover:-translate-x-1 ltr:group-hover:translate-x-1" />
<span className="relative z-10 transition-colors duration-500 ease-[cubic-bezier(0.19,1,0.22,1)] group-hover/btn:text-ink"> <span>
{sending ? newsletter.sending_label : ( {sending ? newsletter.sending_label : (
<InlineEdit value={newsletter.button_label} onSave={(v) => saveContent('button_label', v)} /> <InlineEdit value={newsletter.button_label} onSave={(v) => saveContent('button_label', v)} />
)} )}
</span> </span>
</button> </Button>
</form> </form>
</Reveal> </Reveal>
</div> </div>
+5 -4
View File
@@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import { ArrowLeftIcon } from 'lucide-react'
import { Reveal } from '../Reveal' import { Reveal } from '../Reveal'
import { PremiumButton } from '../PremiumButton' import { ButtonLink } from '../Button'
import { useSite } from '../../api' import { useSite } from '../../api'
import { useLang } from '../../i18n/lang' import { useLang } from '../../i18n/lang'
import { useFieldSaver } from '../../admin/AdminContext' import { useFieldSaver } from '../../admin/AdminContext'
@@ -89,9 +89,10 @@ export function Story({ showLink = true, showLabel = true, showQuote = false, al
{showLink && ( {showLink && (
<Reveal delay={0.22}> <Reveal delay={0.22}>
<PremiumButton to={href(story.link_href)} className="mt-10 font-light"> <ButtonLink to={href(story.link_href)} variant="outline" className="mt-10 font-light">
<InlineEdit value={story.link_label} onSave={(v) => saveStory('link_label', v)} /> <InlineEdit value={story.link_label} onSave={(v) => saveStory('link_label', v)} />
</PremiumButton> <ArrowLeftIcon className="h-4 w-4 ltr:-scale-x-100" />
</ButtonLink>
</Reveal> </Reveal>
)} )}
+16 -43
View File
@@ -1,12 +1,11 @@
import React from 'react'; import React from 'react';
import { Link, useParams } from 'react-router-dom'; import { Link, useParams } from 'react-router-dom';
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'; import { ArrowLeftIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
import { renderToHTMLString } from '@tiptap/static-renderer'; import { renderToHTMLString } from '@tiptap/static-renderer';
import { Reveal } from '../components/Reveal'; import { Reveal } from '../components/Reveal';
import { PremiumButton } from '../components/PremiumButton'; import { ButtonLink } from '../components/Button';
import { ArticleCard } from '../components/ArticleCard'; import { ArticleCard } from '../components/ArticleCard';
import { ArticleMeta } from '../components/ArticleMeta'; import { ArticleMeta } from '../components/ArticleMeta';
import { ArticleFaq } from '../components/ArticleFaq';
import { TableOfContents, extractToc, type TocItem } from '../components/TableOfContents'; import { TableOfContents, extractToc, type TocItem } from '../components/TableOfContents';
import { Newsletter } from '../components/home/Newsletter'; import { Newsletter } from '../components/home/Newsletter';
import { useSite } from '../api'; import { useSite } from '../api';
@@ -48,35 +47,6 @@ export function ArticleDetail() {
// عنوان تب، توضیح متا، OG و JSON-LD از خود مقاله // عنوان تب، توضیح متا، OG و JSON-LD از خود مقاله
useEffect(() => { useEffect(() => {
if (!article) return; if (!article) return;
let jsonLd: Record<string, unknown> | Record<string, unknown>[] = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: article.title,
description: article.meta_description || article.excerpt,
image: article.image ? new URL(article.image, window.location.origin).href : undefined,
inLanguage: lang,
author: { '@type': 'Organization', name: article.author?.trim() || brand.name },
publisher: { '@type': 'Organization', name: brand.name },
mainEntityOfPage: new URL(href(`/blog/${article.slug}`), window.location.origin).href,
};
// پرسش‌وپاسخ مقاله → اسکیمای FAQPage در همان بلوک (آرایه‌ای از دو اسکیما)
const articleFaqs = article.faqs ?? [];
if (articleFaqs.length) {
jsonLd = [
jsonLd,
{
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: articleFaqs
.filter((f) => f.question.trim())
.map((f) => ({
'@type': 'Question',
name: f.question,
acceptedAnswer: { '@type': 'Answer', text: f.answer },
})),
},
];
}
applySeo({ applySeo({
title: `${article.title} | ${brand.name}`, title: `${article.title} | ${brand.name}`,
// توضیحات سئوی اختصاصی در اولویت است؛ در نبود آن، چکیده // توضیحات سئوی اختصاصی در اولویت است؛ در نبود آن، چکیده
@@ -85,7 +55,17 @@ export function ArticleDetail() {
image: article.image, image: article.image,
type: 'article', type: 'article',
lang, lang,
jsonLd, jsonLd: {
'@context': 'https://schema.org',
'@type': 'Article',
headline: article.title,
description: article.meta_description || article.excerpt,
image: article.image ? new URL(article.image, window.location.origin).href : undefined,
inLanguage: lang,
author: { '@type': 'Organization', name: article.author?.trim() || brand.name },
publisher: { '@type': 'Organization', name: brand },
mainEntityOfPage: new URL(href(`/blog/${article.slug}`), window.location.origin).href,
},
}); });
}, [article?.id, lang, href]); }, [article?.id, lang, href]);
@@ -94,9 +74,10 @@ export function ArticleDetail() {
<section className="flex min-h-[70vh] flex-col items-center justify-center px-6 text-center"> <section className="flex min-h-[70vh] flex-col items-center justify-center px-6 text-center">
<h1 className="text-section font-bold text-ivory">{t('articleNotFound')}</h1> <h1 className="text-section font-bold text-ivory">{t('articleNotFound')}</h1>
<p className="mt-4 text-sm font-light text-ivory/60">{t('articleNotFoundText')}</p> <p className="mt-4 text-sm font-light text-ivory/60">{t('articleNotFoundText')}</p>
<PremiumButton to={href('/blog')} className="mt-8"> <ButtonLink to={href('/blog')} variant="copper" className="mt-8">
{t('backToBlog')} {t('backToBlog')}
</PremiumButton> <ArrowLeftIcon className="h-4 w-4 ltr:-scale-x-100" />
</ButtonLink>
</section>); </section>);
} }
@@ -190,14 +171,6 @@ export function ArticleDetail() {
</div> </div>
</article> </article>
{/* پرسش‌وپاسخ‌های پایان مقاله — همان ظاهر آکاردئونی FAQ سایت */}
<div className="bg-ink pb-4">
<div className="mx-auto max-w-5xl px-6 lg:px-10">
<h2 className="text-section font-bold text-ivory">{t('fallbackFaqTitle')}</h2>
<ArticleFaq faqs={article.faqs ?? []} />
</div>
</div>
<section className="bg-char py-20 lg:py-28" aria-label={t('relatedArticlesAria')}> <section className="bg-char py-20 lg:py-28" aria-label={t('relatedArticlesAria')}>
<div className="mx-auto max-w-site px-6 lg:px-10"> <div className="mx-auto max-w-site px-6 lg:px-10">
<h2 className="text-section font-bold text-ivory">{t('relatedArticles')}</h2> <h2 className="text-section font-bold text-ivory">{t('relatedArticles')}</h2>