From 3ff4afe04baafaf25a58f583eceb10417222e51c Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Tue, 5 May 2026 08:27:53 +0200 Subject: [PATCH] feat(marketing-pages): add Page + SiteSettings entities + errors --- .../marketing-pages/src/entities/errors.ts | 11 ++++ .../marketing-pages/src/entities/page.test.ts | 58 +++++++++++++++++++ packages/marketing-pages/src/entities/page.ts | 29 ++++++++++ .../src/entities/site-settings.ts | 8 +++ 4 files changed, 106 insertions(+) create mode 100644 packages/marketing-pages/src/entities/errors.ts create mode 100644 packages/marketing-pages/src/entities/page.test.ts create mode 100644 packages/marketing-pages/src/entities/page.ts create mode 100644 packages/marketing-pages/src/entities/site-settings.ts diff --git a/packages/marketing-pages/src/entities/errors.ts b/packages/marketing-pages/src/entities/errors.ts new file mode 100644 index 0000000..d9ee8e6 --- /dev/null +++ b/packages/marketing-pages/src/entities/errors.ts @@ -0,0 +1,11 @@ +export class PageNotFoundError extends Error { + constructor(message = "Page not found", options?: ErrorOptions) { + super(message, options); + } +} + +export class InputParseError extends Error { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + } +} diff --git a/packages/marketing-pages/src/entities/page.test.ts b/packages/marketing-pages/src/entities/page.test.ts new file mode 100644 index 0000000..a5da93c --- /dev/null +++ b/packages/marketing-pages/src/entities/page.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; +import { pageSchema, pageStatusSchema } from "./page"; + +describe("pageSchema", () => { + it("accepts a minimal valid page", () => { + const result = pageSchema.parse({ + id: "p1", + title: "About", + slug: "about", + hero: { heading: "About us" }, + layout: [], + seo: { title: "About — My App" }, + createdAt: new Date(), + updatedAt: new Date(), + }); + expect(result.status).toBe("draft"); + expect(result.publishedAt).toBeNull(); + }); + + it("accepts a published page with publishedAt", () => { + const result = pageSchema.parse({ + id: "p1", + title: "About", + slug: "about", + hero: { heading: "About us" }, + layout: [], + status: "published", + publishedAt: new Date(), + seo: { title: "About" }, + createdAt: new Date(), + updatedAt: new Date(), + }); + expect(result.status).toBe("published"); + expect(result.publishedAt).toBeInstanceOf(Date); + }); + + it("rejects empty title", () => { + expect(() => + pageSchema.parse({ + id: "p1", + title: "", + slug: "about", + hero: { heading: "h" }, + layout: [], + seo: { title: "x" }, + createdAt: new Date(), + updatedAt: new Date(), + }), + ).toThrow(); + }); +}); + +describe("pageStatusSchema", () => { + it("accepts draft and published", () => { + expect(pageStatusSchema.parse("draft")).toBe("draft"); + expect(pageStatusSchema.parse("published")).toBe("published"); + }); +}); diff --git a/packages/marketing-pages/src/entities/page.ts b/packages/marketing-pages/src/entities/page.ts new file mode 100644 index 0000000..9050799 --- /dev/null +++ b/packages/marketing-pages/src/entities/page.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; + +export const pageStatusSchema = z.enum(["draft", "published"]); + +export const heroSchema = z.object({ + heading: z.string().min(1).max(255), + subheading: z.string().optional(), + imageId: z.string().optional(), +}); + +export const pageSchema = z.object({ + id: z.string(), + title: z.string().min(1).max(255), + slug: z.string().min(1).max(255), + hero: heroSchema, + layout: z.array(z.unknown()), + status: pageStatusSchema.default("draft"), + publishedAt: z.date().nullable().default(null), + seo: z.object({ + title: z.string().min(1), + description: z.string().optional(), + }), + createdAt: z.date(), + updatedAt: z.date(), +}); + +export type Page = z.infer; +export type PageStatus = z.infer; +export type Hero = z.infer; diff --git a/packages/marketing-pages/src/entities/site-settings.ts b/packages/marketing-pages/src/entities/site-settings.ts new file mode 100644 index 0000000..3b2b828 --- /dev/null +++ b/packages/marketing-pages/src/entities/site-settings.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; + +export const siteSettingsSchema = z.object({ + siteName: z.string().min(1), + siteDescription: z.string().optional(), +}); + +export type SiteSettings = z.infer;