Add SEO meta descriptions and a dynamic sitemap.

Expose optional metaDescription for blogs, reportages, and brands in admin/API, apply it on detail pages, and serve an up-to-date /sitemap.xml for approved content plus static site pages.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-11 18:37:43 +03:30
co-authored by Cursor
parent 5088e0f4ae
commit 16bc154fb0
20 changed files with 281 additions and 0 deletions
+6
View File
@@ -32,6 +32,7 @@ export function getBlog(id: string) {
export function createBlog(input: {
title: string;
abstract?: string;
metaDescription?: string;
content?: string;
imageUrl?: string | null;
tags?: string[];
@@ -50,6 +51,7 @@ export function updateBlog(
input: {
title?: string;
abstract?: string;
metaDescription?: string;
content?: string;
imageUrl?: string | null;
tags?: string[];
@@ -76,6 +78,7 @@ export function createReportage(input: {
title: string;
businessOwner: string;
abstract?: string;
metaDescription?: string;
content?: string;
imageUrl?: string | null;
tags?: string[];
@@ -95,6 +98,7 @@ export function updateReportage(
title?: string;
businessOwner?: string;
abstract?: string;
metaDescription?: string;
content?: string;
imageUrl?: string | null;
tags?: string[];
@@ -120,6 +124,7 @@ export function getBrand(id: string) {
export function createBrand(input: {
title: string;
abstract?: string;
metaDescription?: string;
content?: string;
imageUrl?: string | null;
galleryUrls?: string[];
@@ -143,6 +148,7 @@ export function updateBrand(
input: {
title?: string;
abstract?: string;
metaDescription?: string;
content?: string;
imageUrl?: string | null;
galleryUrls?: string[];
+3
View File
@@ -24,6 +24,7 @@ export interface Blog {
title: string;
slug: string;
abstract: string | null;
metaDescription: string | null;
content: string;
imageUrl: string | null;
tags: string[];
@@ -42,6 +43,7 @@ export interface Reportage {
slug: string;
businessOwner: string;
abstract: string | null;
metaDescription: string | null;
content: string;
imageUrl: string | null;
tags: string[];
@@ -71,6 +73,7 @@ export interface Brand {
title: string;
slug: string;
abstract: string | null;
metaDescription: string | null;
content: string;
imageUrl: string | null;
galleryUrls: string[];
+17
View File
@@ -28,6 +28,7 @@ export function BlogNewPage() {
const [title, setTitle] = useState("");
const [image, setImage] = useState<string | null>(null);
const [abstract, setAbstract] = useState("");
const [metaDescription, setMetaDescription] = useState("");
const [content, setContent] = useState("");
const [tags, setTags] = useState<string[]>([]);
const [error, setError] = useState("");
@@ -49,6 +50,7 @@ export function BlogNewPage() {
if (cancelled) return;
setTitle(blog.title);
setAbstract(blog.abstract ?? "");
setMetaDescription(blog.metaDescription ?? "");
setContent(blog.content);
setImage(blog.imageUrl);
setTags(blog.tags);
@@ -86,6 +88,7 @@ export function BlogNewPage() {
const payload = {
title: title.trim(),
abstract: abstract.trim() || undefined,
metaDescription: metaDescription.trim() || undefined,
content,
imageUrl: image,
tags,
@@ -175,6 +178,20 @@ export function BlogNewPage() {
placeholder="Short summary shown in lists and previews"
/>
</div>
<div className="field">
<label className="field__label" htmlFor="post-meta-description">
Meta description
</label>
<textarea
id="post-meta-description"
className="field-textarea"
value={metaDescription}
onChange={(e) => setMetaDescription(e.target.value)}
placeholder="SEO description for search engines (recommended under 160 characters)"
maxLength={300}
/>
</div>
</div>
</div>
+17
View File
@@ -46,6 +46,7 @@ export function BrandNewPage() {
const [categories, setCategories] = useState<string[]>([]);
const [name, setName] = useState("");
const [about, setAbout] = useState("");
const [metaDescription, setMetaDescription] = useState("");
const [image, setImage] = useState<string | null>(null);
const [gallery, setGallery] = useState<string[]>([]);
const [content, setContent] = useState("");
@@ -76,6 +77,7 @@ export function BrandNewPage() {
if (cancelled) return;
setName(brand.title);
setAbout(brand.abstract ?? "");
setMetaDescription(brand.metaDescription ?? "");
setContent(brand.content);
setImage(brand.imageUrl);
setGallery(brand.galleryUrls ?? []);
@@ -118,6 +120,7 @@ export function BrandNewPage() {
const payload = {
title: name.trim(),
abstract: about.trim() || undefined,
metaDescription: metaDescription.trim() || undefined,
content,
imageUrl: image,
galleryUrls: gallery,
@@ -214,6 +217,20 @@ export function BrandNewPage() {
placeholder="Short description of the business"
/>
</div>
<div className="field">
<label className="field__label" htmlFor="brand-meta-description">
Meta description
</label>
<textarea
id="brand-meta-description"
className="field-textarea"
value={metaDescription}
onChange={(e) => setMetaDescription(e.target.value)}
placeholder="SEO description for search engines (recommended under 160 characters)"
maxLength={300}
/>
</div>
</div>
</div>
+17
View File
@@ -29,6 +29,7 @@ export function ReportageNewPage() {
const [businessOwner, setBusinessOwner] = useState("");
const [image, setImage] = useState<string | null>(null);
const [abstract, setAbstract] = useState("");
const [metaDescription, setMetaDescription] = useState("");
const [content, setContent] = useState("");
const [tags, setTags] = useState<string[]>([]);
const [error, setError] = useState("");
@@ -51,6 +52,7 @@ export function ReportageNewPage() {
setTitle(reportage.title);
setBusinessOwner(reportage.businessOwner);
setAbstract(reportage.abstract ?? "");
setMetaDescription(reportage.metaDescription ?? "");
setContent(reportage.content);
setImage(reportage.imageUrl);
setTags(reportage.tags);
@@ -91,6 +93,7 @@ export function ReportageNewPage() {
title: title.trim(),
businessOwner: businessOwner.trim(),
abstract: abstract.trim() || undefined,
metaDescription: metaDescription.trim() || undefined,
content,
imageUrl: image,
tags,
@@ -194,6 +197,20 @@ export function ReportageNewPage() {
placeholder="Short summary for listings"
/>
</div>
<div className="field">
<label className="field__label" htmlFor="reportage-meta-description">
Meta description
</label>
<textarea
id="reportage-meta-description"
className="field-textarea"
value={metaDescription}
onChange={(e) => setMetaDescription(e.target.value)}
placeholder="SEO description for search engines (recommended under 160 characters)"
maxLength={300}
/>
</div>
</div>
</div>