Files
S. EsmaeiliandCursor 9ad88af20f feat(admin): editable contact fields in sections and larger logos
Allow address, phone, map, and related site chrome to be edited from contact/settings for all editors, and enlarge brand marks on login, header, and preloader.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-12 18:42:59 +03:30

210 lines
8.0 KiB
JavaScript

import { z } from "zod";
import { iconNameSchema, imageRefSchema, linkSchema, req, text } from "./common.js";
/**
* Editable website sections. Each key is one row in the `SiteSection` table whose `data`
* column must match the schema below — validated by the API on write and by the admin forms.
*
* Multi-line titles use "\n" for line breaks.
*/
export const WEEK_DAYS = [
"Saturday",
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
];
export const SOCIAL_TYPES = [
"website",
"telegram",
"instagram",
"linkedin",
"whatsapp",
"bale",
"eitaa",
"aparat",
"youtube",
"email",
"phone",
];
const iconLabel = z.object({ icon: iconNameSchema, label: req(80) });
const iconCard = z.object({ icon: iconNameSchema, title: req(80), desc: text(300) });
export const sectionHeadSchema = z.object({
tag: req(80),
title: req(200),
/** optional accented last line of the title */
highlight: text(120),
desc: text(500),
});
export const settingsSectionSchema = z.object({
name: req(80),
nameEn: req(120),
/** default <title> and og:title */
title: req(120),
/** default meta description */
description: req(400),
/** machine-readable phone for tel: links and schema.org, e.g. +98-21-91008800 */
phone: req(30),
phoneDisplay: req(40),
email: z.email("ایمیل معتبر نیست"),
address: z.object({
street: req(200),
locality: req(80),
country: z.string().trim().length(2).default("IR"),
}),
/** Google Maps / Neshan / etc. share link shown next to the address */
mapUrl: text(500),
/** human-readable working hours, e.g. «شنبه تا پنجشنبه — ۸:۰۰ تا ۱۷:۰۰» */
workingHours: req(120),
openingHours: z.object({
days: z.array(z.enum(WEEK_DAYS)).max(7),
opens: z.string().regex(/^\d{2}:\d{2}$/, "قالب ساعت باید HH:MM باشد"),
closes: z.string().regex(/^\d{2}:\d{2}$/, "قالب ساعت باید HH:MM باشد"),
}),
foundingDate: z.string().regex(/^\d{4}$/, "سال میلادی چهاررقمی"),
/** sticky header CTA (e.g. «درخواست مشاوره») */
headerCta: linkSchema.default({ label: "درخواست مشاوره", href: "/#contact" }),
socials: z.array(z.object({ type: z.enum(SOCIAL_TYPES), url: req(300) })).max(12),
ogImage: imageRefSchema.extend({
width: z.number().int().positive().nullable().default(null),
height: z.number().int().positive().nullable().default(null),
}),
});
export const heroSectionSchema = z.object({
enabled: z.boolean().default(true),
badge: req(120),
title: req(80),
titleHighlight: req(80),
intro: req(600),
primaryCta: linkSchema,
secondaryCta: linkSchema,
chips: z.array(iconLabel).max(6),
miniStats: z.array(z.object({ value: req(20), label: req(80) })).max(4),
image: imageRefSchema,
/** «آخرین بازرسی» date shown on the status card */
lastInspection: text(20),
});
export const tapeSectionSchema = z.object({
enabled: z.boolean().default(true),
items: z.array(req(80)).min(1).max(20),
});
export const statsSectionSchema = z.object({
enabled: z.boolean().default(true),
items: z
.array(z.object({
end: z.number().int().min(0).max(1_000_000_000),
suffix: text(5),
label: req(80),
note: text(40),
}))
.max(8),
});
export const productsIntroSectionSchema = z.object({
enabled: z.boolean().default(true),
head: sectionHeadSchema,
ctaLabel: req(60),
trust: z.array(iconCard).max(6),
});
export const aboutSectionSchema = z.object({
enabled: z.boolean().default(true),
head: sectionHeadSchema,
body: req(2000),
image: imageRefSchema,
badge: z.object({ value: req(20), label: req(80) }),
timeline: z.array(z.object({ year: req(10), text: req(200) })).max(12),
features: z.array(iconLabel).max(6),
});
export const servicesSectionSchema = z.object({
enabled: z.boolean().default(true),
head: sectionHeadSchema,
items: z
.array(z.object({
icon: iconNameSchema,
title: req(80),
desc: req(600),
items: z.array(req(120)).max(8),
}))
.max(10),
});
export const standardsSectionSchema = z.object({
enabled: z.boolean().default(true),
head: sectionHeadSchema,
note: text(500),
ctaLabel: req(80),
items: z.array(z.object({ code: req(30), label: req(80) })).max(12),
});
export const industriesSectionSchema = z.object({
enabled: z.boolean().default(true),
head: sectionHeadSchema,
items: z
.array(z.object({
name: req(60),
nameEn: text(60),
count: text(20),
image: imageRefSchema,
}))
.max(12),
});
export const testimonialsSectionSchema = z.object({
enabled: z.boolean().default(true),
head: sectionHeadSchema,
items: z.array(z.object({ text: req(600), name: req(80), role: text(160) })).max(12),
});
export const contactSectionSchema = z.object({
enabled: z.boolean().default(true),
head: sectionHeadSchema,
emergencyTitle: req(80),
emergencyText: req(400),
formTag: text(80),
formTitle: req(160),
successTitle: req(80),
successText: req(300),
/** submit button on the contact form */
submitLabel: req(80).default("ارسال درخواست مشاوره"),
/** button shown after a successful submit */
resetLabel: req(80).default("ثبت درخواست جدید"),
});
export const footerSectionSchema = z.object({
enabled: z.boolean().default(true),
about: req(500),
copyright: req(160),
certLine: text(160),
emergencyNote: text(80),
/** column headings under the brand block */
linksTitle: req(40).default("دسترسی"),
productsTitle: req(40).default("محصولات"),
contactTitle: req(40).default("ارتباط"),
});
export const sectionSchemas = {
settings: settingsSectionSchema,
hero: heroSectionSchema,
tape: tapeSectionSchema,
stats: statsSectionSchema,
productsIntro: productsIntroSectionSchema,
about: aboutSectionSchema,
services: servicesSectionSchema,
standards: standardsSectionSchema,
industries: industriesSectionSchema,
testimonials: testimonialsSectionSchema,
contact: contactSectionSchema,
footer: footerSectionSchema,
};
export const SECTION_KEYS = Object.keys(sectionSchemas);
export const isSectionKey = (key) => key in sectionSchemas;
/** Persian labels for the admin panel */
export const SECTION_LABELS = {
settings: { title: "تنظیمات سایت", desc: "نام، اطلاعات تماس، ساعات کاری، شبکه‌های اجتماعی و سئوی پیش‌فرض" },
hero: { title: "بخش اول (Hero)", desc: "تیتر اصلی، متن معرفی، دکمه‌ها، نشان‌ها و تصویر پس‌زمینه" },
tape: { title: "نوار متحرک", desc: "عبارت‌های نوار زرد متحرک زیر Hero" },
stats: { title: "آمار", desc: "شمارنده‌های عددی" },
productsIntro: { title: "معرفی محصولات", desc: "عنوان بخش محصولات و کارت‌های اعتماد" },
about: { title: "درباره ما", desc: "متن معرفی، تصویر، تایم‌لاین و ویژگی‌ها" },
services: { title: "خدمات", desc: "فهرست خدمات و جزئیات هر خدمت" },
standards: { title: "استانداردها", desc: "استانداردها و گواهی‌نامه‌ها" },
industries: { title: "صنایع", desc: "صنایع تحت پوشش و تصویر هرکدام" },
testimonials: { title: "نظرات مشتریان", desc: "نقل‌قول‌های مشتریان" },
contact: { title: "تماس با ما", desc: "عنوان‌ها، متن‌های فرم، و ویرایش نشانی و اطلاعات تماس" },
footer: { title: "فوتر", desc: "متن معرفی، عناوین ستون‌ها، کپی‌رایت و توضیحات پایین صفحه" },
};
//# sourceMappingURL=sections.js.map