Refactor site header, heroes, and design system

This commit is contained in:
MOH
2026-03-08 00:29:51 +01:00
parent a18370d003
commit 0c2b67e378
56 changed files with 2714 additions and 1318 deletions
+20
View File
@@ -6,6 +6,8 @@ export {
SITE_SETTINGS_ENTITY_ID,
SITE_SETTINGS_ENTITY_TYPE,
SITE_SETTINGS_FAVICON_FIELD_KEY,
SITE_SETTINGS_LOGO_DARK_FIELD_KEY,
SITE_SETTINGS_LOGO_LIGHT_FIELD_KEY,
SITE_SETTINGS_KEY,
DEFAULT_SITE_NAME,
buildDefaultSiteSettings,
@@ -21,6 +23,8 @@ import {
SITE_SETTINGS_ENTITY_ID,
SITE_SETTINGS_ENTITY_TYPE,
SITE_SETTINGS_FAVICON_FIELD_KEY,
SITE_SETTINGS_LOGO_DARK_FIELD_KEY,
SITE_SETTINGS_LOGO_LIGHT_FIELD_KEY,
SITE_SETTINGS_KEY,
buildDefaultSiteSettings,
getDefaultSiteSettingsMediaBindings,
@@ -112,6 +116,22 @@ export async function getSiteSettingsMediaBindings(): Promise<SiteSettingsMediaB
return usages.reduce<SiteSettingsMediaBindings>(
(result, usage) => {
if (usage.fieldKey === SITE_SETTINGS_LOGO_LIGHT_FIELD_KEY) {
result.siteLogoLight = {
assetId: usage.asset.id,
url: usage.asset.url,
version: usage.updatedAt.toISOString(),
};
}
if (usage.fieldKey === SITE_SETTINGS_LOGO_DARK_FIELD_KEY) {
result.siteLogoDark = {
assetId: usage.asset.id,
url: usage.asset.url,
version: usage.updatedAt.toISOString(),
};
}
if (usage.fieldKey === SITE_SETTINGS_FAVICON_FIELD_KEY) {
result.favicon = {
assetId: usage.asset.id,
+55 -17
View File
@@ -25,23 +25,61 @@ export const categoryInputSchema = z.object({
isActive: z.boolean(),
});
export const sectionInputSchema = z.object({
id: z.string().trim().optional(),
type: z.nativeEnum(PortfolioSectionType),
titleAr: requiredText("Section titleAr"),
titleEn: requiredText("Section titleEn"),
titleDe: requiredText("Section titleDe"),
bodyAr: requiredText("Section bodyAr"),
bodyEn: requiredText("Section bodyEn"),
bodyDe: requiredText("Section bodyDe"),
imagePath: optionalTrimmedText,
media: mediaFieldInputSchema.optional(),
linkUrl: optionalTrimmedText.refine(
(value) => value === "" || /^https?:\/\//.test(value) || value.startsWith("/"),
"Section linkUrl must be an absolute URL or start with /.",
),
sortOrder: z.coerce.number().int().min(0).max(9999),
});
export const sectionInputSchema = z
.object({
id: z.string().trim().optional(),
type: z.nativeEnum(PortfolioSectionType),
titleAr: requiredText("Section titleAr"),
titleEn: requiredText("Section titleEn"),
titleDe: requiredText("Section titleDe"),
bodyAr: optionalTrimmedText,
bodyEn: optionalTrimmedText,
bodyDe: optionalTrimmedText,
imagePath: optionalTrimmedText,
media: mediaFieldInputSchema.optional(),
linkUrl: optionalTrimmedText.refine(
(value) => value === "" || /^https?:\/\//.test(value) || value.startsWith("/"),
"Section linkUrl must be an absolute URL or start with /.",
),
sortOrder: z.coerce.number().int().min(0).max(9999),
})
.superRefine((value, context) => {
const hasBody = Boolean(value.bodyAr && value.bodyEn && value.bodyDe);
const hasImage = value.media?.mode === "library"
? Boolean(value.media.assetId)
: value.media?.mode === "upload"
? true
: Boolean(value.imagePath);
if (
(value.type === PortfolioSectionType.RICH_TEXT ||
value.type === PortfolioSectionType.STATS ||
value.type === PortfolioSectionType.DELIVERABLES) &&
!hasBody
) {
context.addIssue({
code: z.ZodIssueCode.custom,
path: ["bodyAr"],
message: "This section type requires body content in all languages.",
});
}
if (value.type === PortfolioSectionType.GALLERY && !hasImage) {
context.addIssue({
code: z.ZodIssueCode.custom,
path: ["media"],
message: "Single image sections require an image.",
});
}
if (value.type === PortfolioSectionType.LINK && !value.linkUrl) {
context.addIssue({
code: z.ZodIssueCode.custom,
path: ["linkUrl"],
message: "Link sections require a link URL.",
});
}
});
export const assetInputSchema = z.object({
id: z.string().trim().optional(),
+6
View File
@@ -6,6 +6,8 @@ export const SITE_SETTINGS_ENTITY_TYPE = "site-settings";
export const SITE_SETTINGS_ENTITY_ID = "global";
export const SITE_SETTINGS_FAVICON_FIELD_KEY = "favicon";
export const SITE_SETTINGS_DEFAULT_OG_IMAGE_FIELD_KEY = "defaultOgImage";
export const SITE_SETTINGS_LOGO_LIGHT_FIELD_KEY = "siteLogoLight";
export const SITE_SETTINGS_LOGO_DARK_FIELD_KEY = "siteLogoDark";
export const PAGE_TITLE_TOKEN = "{pageTitle}";
export const SITE_NAME_TOKEN = "{siteName}";
@@ -30,6 +32,8 @@ export type SiteSettingsMediaBinding = {
};
export type SiteSettingsMediaBindings = {
siteLogoLight: SiteSettingsMediaBinding | null;
siteLogoDark: SiteSettingsMediaBinding | null;
favicon: SiteSettingsMediaBinding | null;
defaultOgImage: SiteSettingsMediaBinding | null;
};
@@ -62,6 +66,8 @@ function normalizeSiteLocaleSettings(
export function getDefaultSiteSettingsMediaBindings(): SiteSettingsMediaBindings {
return {
siteLogoLight: null,
siteLogoDark: null,
favicon: null,
defaultOgImage: null,
};