Refactor site header, heroes, and design system
This commit is contained in:
@@ -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
@@ -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,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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user