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>
210 lines
8.0 KiB
JavaScript
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
|