project setup with core files including configuration, package management, and basic structure. Added .gitignore, README, and various TypeScript types for CMS components. Implemented initial components and layouts for the application.

This commit is contained in:
Peter Meier
2025-12-13 23:26:13 +01:00
parent ea288a5bbc
commit b1a556dc6d
167 changed files with 19057 additions and 131 deletions

View File

@@ -0,0 +1,98 @@
# Type Structure
## Übersicht
Die Typen sind in zwei Kategorien aufgeteilt:
### 1. CMS-Typen (`types/cms/`)
**Zweck:** Struktur, wie Daten vom CMS (Contentful) kommen
- `*Skeleton` - Wrapper mit `contentTypeId` und `fields`
- Verwendet `ComponentLayout` (ist jetzt ein Alias für `contentLayout`)
- Beispiel: `ComponentHeadlineSkeleton`, `HTMLSkeleton`, `MarkdownSkeleton`
**Verwendung:** Nur im Mapper (`mappers/pageMapper.ts`) und Mock-Daten (`adapters/Mock/_cms/`)
**Dateien:**
- `Headline.ts`, `Html.ts`, `Markdown.ts`, `Iframe.ts`, `ImageGallery.ts`, `Image.ts`, `Quote.ts`, `YoutubeVideo.ts`
- `Page.ts`, `Navigation.ts`, `SEO.ts`, `FullwidthBanner.ts`
- `Layout.ts` - `ComponentLayout` (Alias für `contentLayout`)
- `ContentType.enum.ts` - Enum für alle Content-Typen
### 2. Domain-Typen (`types/c_*.ts`, `types/page.ts`, etc.)
**Zweck:** Struktur, wie Daten in der App verwendet werden
- `c_*` - Content-Item-Typen mit `type`-Feld für Discriminated Union
- Verwendet `contentLayout` direkt
- Beispiel: `c_headline`, `c_html`, `c_markdown`, `c_iframe`, `c_imageGallery`, `c_image`, `c_quote`, `c_youtubeVideo`
**Verwendung:** Überall in der App (GraphQL Schema, Astro Components, etc.)
**Dateien:**
- `c_*.ts` - Alle Content-Item-Typen
- `page.ts` - Page-Typ mit `ContentItem` Union und `ContentRow`
- `contentLayout.ts` - Einheitlicher Layout-Typ
- `pageSeo.ts`, `navigation.ts`, `product.ts`, `user.ts` - Weitere Domain-Typen
## Mapping
Der `PageMapper` konvertiert von CMS-Typen zu Domain-Typen:
```typescript
// Beispiel: Headline
CMS: ComponentHeadlineSkeleton {
contentTypeId: ContentType.headline,
fields: {
internal: string,
text: string,
tag: "h1" | "h2" | ...,
align?: "left" | "center" | "right",
layout: ComponentLayout
}
}
(PageMapper.mapContentItem)
Domain: c_headline {
type: "headline",
internal: string,
text: string,
tag: "h1" | "h2" | ...,
align?: "left" | "center" | "right",
layout: contentLayout
}
```
## Layout-Typen
- `ComponentLayout` (in `types/cms/Layout.ts`) = Alias für `contentLayout`
- `contentLayout` (in `types/contentLayout.ts`) = Einheitlicher Layout-Typ für alle Content-Items
**Struktur:**
```typescript
{
mobile: string; // z.B. "12", "6", "4"
tablet?: string; // Optional, z.B. "8", "6"
desktop?: string; // Optional, z.B. "6", "4"
spaceBottom?: number; // Optional, z.B. 0, 0.5, 1, 1.5, 2
}
```
**Warum:** Redundanz vermeiden - beide haben die gleiche Struktur. `ComponentLayout` ist nur ein Alias, um die Semantik klar zu machen (CMS vs Domain).
## Content-Items Union
Alle Content-Items werden in `types/page.ts` zu einer Union zusammengefasst:
```typescript
export type ContentItem =
| c_html
| c_markdown
| c_iframe
| c_imageGallery
| c_image
| c_quote
| c_youtubeVideo
| c_headline;
```
Dies ermöglicht Type-Safe Discriminated Unions über das `type`-Feld.

View File

@@ -0,0 +1,10 @@
import type { contentLayout } from "./contentLayout";
export type c_headline = {
type: "headline";
internal: string;
text: string;
tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
align?: "left" | "center" | "right";
layout: contentLayout;
};

View File

@@ -0,0 +1,8 @@
import type { contentLayout } from "./contentLayout";
export type c_html = {
type: "html";
name: string;
html: string;
layout: contentLayout;
};

View File

@@ -0,0 +1,10 @@
import type { contentLayout } from "./contentLayout";
export type c_iframe = {
type: "iframe";
name: string;
content: string;
iframe: string;
overlayImageUrl?: string;
layout: contentLayout;
};

View File

@@ -0,0 +1,11 @@
import type { contentLayout } from "./contentLayout";
export type c_image = {
type: "image";
name: string;
imageUrl: string;
caption: string;
maxWidth?: number;
aspectRatio?: number;
layout: contentLayout;
};

View File

@@ -0,0 +1,13 @@
import type { contentLayout } from "./contentLayout";
export type c_imageGallery = {
type: "imageGallery";
name: string;
images: Array<{
url: string;
title?: string;
description?: string;
}>;
description?: string;
layout: contentLayout;
};

View File

@@ -0,0 +1,9 @@
import type { contentLayout } from "./contentLayout";
export type c_markdown = {
type: "markdown";
name: string;
content: string;
layout: contentLayout;
alignment: "left" | "center" | "right";
};

View File

@@ -0,0 +1,9 @@
import type { contentLayout } from "./contentLayout";
export type c_quote = {
type: "quote";
quote: string;
author: string;
variant: "left" | "right";
layout: contentLayout;
};

View File

@@ -0,0 +1,11 @@
import type { contentLayout } from "./contentLayout";
export type c_youtubeVideo = {
type: "youtubeVideo";
id: string;
youtubeId: string;
params?: string;
title?: string;
description?: string;
layout: contentLayout;
};

View File

@@ -0,0 +1,16 @@
export interface CloudinaryImage {
bytes: number;
created_at: string;
format: string;
height: number;
original_secure_url: string;
original_url: string;
public_id: string;
resource_type: string;
secure_url: string;
type: string;
url: string;
version: number;
width: number;
}

View File

@@ -0,0 +1,42 @@
import type { HTMLSkeleton } from "./Html";
import type { MarkdownSkeleton } from "./Markdown";
import type { ComponentIframeSkeleton } from "./Iframe";
import type { ImageGallerySkeleton } from "./ImageGallery";
import type { ComponentImageSkeleton } from "./Image";
import type { QuoteSkeleton } from "./Quote";
import type { ComponentYoutubeVideoSkeleton } from "./YoutubeVideo";
import type { ComponentHeadlineSkeleton } from "./Headline";
export type rowJutify =
| "start"
| "end"
| "center"
| "between"
| "around"
| "evenly";
export type rowAlignItems = "start" | "end" | "center" | "baseline" | "stretch";
export type ContentEntry =
| HTMLSkeleton
| MarkdownSkeleton
| ComponentIframeSkeleton
| ImageGallerySkeleton
| ComponentImageSkeleton
| QuoteSkeleton
| ComponentYoutubeVideoSkeleton
| ComponentHeadlineSkeleton;
export interface Content {
row1JustifyContent: rowJutify;
row1AlignItems: rowAlignItems;
row1Content: ContentEntry[];
row2JustifyContent: rowJutify;
row2AlignItems: rowAlignItems;
row2Content: ContentEntry[];
row3JustifyContent: rowJutify;
row3AlignItems: rowAlignItems;
row3Content: ContentEntry[];
}

View File

@@ -0,0 +1,32 @@
export enum ContentType {
"componentLinkList" = "componentLinkList",
"badges" = "badges",
"componentPostOverview" = "componentPostOverview",
"footer" = "footer",
"fullwidthBanner" = "fullwidthBanner",
"headline" = "headline",
"html" = "html",
"image" = "image",
"img" = "img",
"iframe" = "iframe",
"imgGallery" = "imageGallery",
"internalReference" = "internalComponent",
"link" = "link",
"list" = "list",
"markdown" = "markdown",
"navigation" = "navigation",
"page" = "page",
"pageConfig" = "pageConfig",
"picture" = "picture",
"post" = "post",
"postComponent" = "postComponent",
"quote" = "quoteComponent",
"richtext" = "richtext",
"row" = "row",
"rowLayout" = "rowLayout",
"tag" = "tag",
"youtubeVideo" = "youtubeVideo",
"campaign" = "campaign",
"campaigns" = "campaigns",
}

View File

@@ -0,0 +1,24 @@
import type { ComponentImgSkeleton } from "./Img";
import type { CloudinaryImage } from "./CloudinaryImage";
import type { ContentType } from "./ContentType.enum";
export enum FullwidthBannerVariant {
"dark" = "dark",
"light" = "light",
}
export interface FullwidthBanner {
name: string;
variant: FullwidthBannerVariant;
headline: string;
subheadline: string;
text: string;
image: CloudinaryImage[];
img: ComponentImgSkeleton;
}
export type FullwidthBannerSkeleton = {
contentTypeId: ContentType.fullwidthBanner;
fields: FullwidthBanner;
};

View File

@@ -0,0 +1,21 @@
import type { ContentType } from "./ContentType.enum";
import type { ComponentLayout } from "./Layout";
export type Component_Headline_Align = "left" | "center" | "right";
export type Component_Headline_Tag = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
export type alignTextClasses = "text-left" | "text-center" | "text-right";
export interface ComponentHeadline {
internal: string;
text: string;
tag: Component_Headline_Tag;
layout: ComponentLayout;
align?: Component_Headline_Align;
}
export interface ComponentHeadlineSkeleton {
contentTypeId: ContentType.headline;
fields: ComponentHeadline;
}

View File

@@ -0,0 +1,14 @@
import type { ContentType } from "./ContentType.enum";
import type { ComponentLayout } from "./Layout";
export interface HTML {
id: string;
html: string;
layout: ComponentLayout;
}
export type HTMLSkeleton = {
contentTypeId: ContentType.html;
fields: HTML;
};

View File

@@ -0,0 +1,17 @@
import type { ContentType } from "./ContentType.enum";
import type { ComponentImgSkeleton } from "./Img";
import type { ComponentLayout } from "./Layout";
export interface ComponentIframe {
name: string;
content: string;
iframe: string;
overlayImage?: ComponentImgSkeleton;
layout: ComponentLayout;
}
export interface ComponentIframeSkeleton {
contentTypeId: ContentType.iframe;
fields: ComponentIframe;
}

View File

@@ -0,0 +1,18 @@
import type { ContentType } from "./ContentType.enum";
import type { ComponentImgSkeleton } from "./Img";
import type { ComponentLayout } from "./Layout";
export interface ComponentImage {
name: string;
image: ComponentImgSkeleton;
caption: string;
layout: ComponentLayout;
maxWidth?: number;
aspectRatio?: number;
}
export interface ComponentImageSkeleton {
contentTypeId: ContentType.image;
fields: ComponentImage;
}

View File

@@ -0,0 +1,16 @@
import type { ContentType } from "./ContentType.enum";
import type { ComponentImgSkeleton } from "./Img";
import type { ComponentLayout } from "./Layout";
export interface ImageGallery {
name: string;
images: ComponentImgSkeleton[];
layout: ComponentLayout;
description?: string;
}
export interface ImageGallerySkeleton {
contentTypeId: ContentType.imgGallery;
fields: ImageGallery;
}

View File

@@ -0,0 +1,26 @@
import type { ContentType } from "./ContentType.enum";
export interface ComponentImgDetails {
size: number;
image: {
width: number;
height: number;
};
}
export interface ComponentImg {
title: string;
description: string;
file: {
url: string;
details: ComponentImgDetails;
fileName: string;
contentType: string;
};
}
export interface ComponentImgSkeleton {
contentTypeId: ContentType.img;
fields: ComponentImg;
}

View File

@@ -0,0 +1,13 @@
import type { ContentType } from "./ContentType.enum";
import type { contentLayout } from "../contentLayout";
/**
* CMS-spezifisches Layout (wird vom Mapper zu contentLayout konvertiert)
* Verwendet contentLayout direkt, um Redundanz zu vermeiden
*/
export type ComponentLayout = contentLayout;
export interface ComponentLayoutSkeleton {
contentTypeId: ContentType.rowLayout;
fields: ComponentLayout;
}

View File

@@ -0,0 +1,9 @@
export interface Link {
name: string;
internal: string;
linkName: string;
url: string;
icon?: string;
newTab?: boolean;
}

View File

@@ -0,0 +1,16 @@
import type { ContentType } from "./ContentType.enum";
import type { ComponentLayout } from "./Layout";
import type { TextAlignment } from "./TextAlignment";
export interface Markdown {
name: string;
content: string;
layout: ComponentLayout;
alignment: TextAlignment;
}
export type MarkdownSkeleton = {
contentTypeId: ContentType.markdown;
fields: Markdown;
};

View File

@@ -0,0 +1,15 @@
import type { ContentType } from "./ContentType.enum";
import type { Link } from "./Link";
import type { Page } from "./Page";
export interface Navigation {
name: string;
internal: string;
links: Array<{ fields: Link | Page }>;
}
export interface NavigationSkeleton {
contentTypeId: ContentType.navigation;
fields: Navigation;
}

View File

@@ -0,0 +1,20 @@
import type { ContentType } from "./ContentType.enum";
import type { FullwidthBannerSkeleton } from "./FullwidthBanner";
import type { Content } from "./Content";
import type { SEO } from "./SEO";
export interface Page extends Content, SEO {
slug: string;
name: string;
linkName: string;
icon?: string;
headline: string;
subheadline: string;
topFullwidthBanner: FullwidthBannerSkeleton;
}
export type PageSkeleton = {
contentTypeId: ContentType.page;
fields: Page;
};

View File

@@ -0,0 +1,19 @@
import type { ContentType } from "./ContentType.enum";
import type { ComponentImgSkeleton } from "./Img";
export interface PageConfig {
logo: ComponentImgSkeleton;
footerText1: string;
seoTitle: string;
seoDescription: string;
blogTagPageHeadline: string;
blogPostsPageHeadline: string;
blogPostsPageSubHeadline: string;
website: string;
}
export interface PageConfigSkeleton {
contentTypeId: ContentType.pageConfig;
fields: PageConfig;
}

View File

@@ -0,0 +1,15 @@
import type { ContentType } from "./ContentType.enum";
import type { ComponentLayout } from "./Layout";
export interface Quote {
quote: string;
author: string;
variant: "left" | "right";
layout: ComponentLayout;
}
export type QuoteSkeleton = {
contentTypeId: ContentType.quote;
fields: Quote;
};

View File

@@ -0,0 +1,12 @@
export type metaRobots =
| "index, follow"
| "noindex, follow"
| "index, nofollow"
| "noindex, nofollow";
export interface SEO {
seoTitle: string;
seoMetaRobots: metaRobots;
seoDescription: string;
}

View File

@@ -0,0 +1,7 @@
export type TextAlignment = "left" | "center" | "right";
export enum TextAlignmentClasses {
"left" = "text-left",
"center" = "text-center",
"right" = "text-right",
}

View File

@@ -0,0 +1,17 @@
import type { ContentType } from "./ContentType.enum";
import type { ComponentLayout } from "./Layout";
export interface YoutubeVideo {
id: string;
youtubeId: string;
params?: string;
title?: string;
description?: string;
layout: ComponentLayout;
}
export interface ComponentYoutubeVideoSkeleton {
contentTypeId: ContentType.youtubeVideo;
fields: YoutubeVideo;
}

View File

@@ -0,0 +1,22 @@
// Re-export all types for easier imports
export * from "./ContentType.enum";
export * from "./Layout";
export * from "./Html";
export * from "./Markdown";
export * from "./Img";
export * from "./Iframe";
export * from "./ImageGallery";
export * from "./Image";
export * from "./Quote";
export * from "./YoutubeVideo";
export * from "./Headline";
export * from "./FullwidthBanner";
export * from "./Content";
export * from "./SEO";
export * from "./Page";
export * from "./TextAlignment";
export * from "./CloudinaryImage";
export * from "./Navigation";
export * from "./Link";
export * from "./PageConfig";

View File

@@ -0,0 +1,6 @@
export type contentLayout = {
mobile: string;
tablet?: string;
desktop?: string;
spaceBottom?: number;
};

View File

@@ -0,0 +1,4 @@
export type { PageSeo } from "./pageSeo";
export type { Page } from "./page";
export type { Navigation, NavigationLink } from "./navigation";
export type { Product } from "./product";

View File

@@ -0,0 +1,15 @@
export interface NavigationLink {
slug?: string;
name: string;
linkName: string;
url?: string;
icon?: string;
newTab?: boolean;
}
export interface Navigation {
name: string;
internal: string;
links: NavigationLink[];
}

48
middlelayer/types/page.ts Normal file
View File

@@ -0,0 +1,48 @@
import type { contentLayout } from "./contentLayout";
import type { c_html } from "./c_html";
import type { c_markdown } from "./c_markdown";
import type { c_iframe } from "./c_iframe";
import type { c_imageGallery } from "./c_imageGallery";
import type { c_image } from "./c_image";
import type { c_quote } from "./c_quote";
import type { c_youtubeVideo } from "./c_youtubeVideo";
import type { c_headline } from "./c_headline";
export type { contentLayout };
export type ContentItem =
| c_html
| c_markdown
| c_iframe
| c_imageGallery
| c_image
| c_quote
| c_youtubeVideo
| c_headline;
export type ContentRow = {
justifyContent: "start" | "end" | "center" | "between" | "around" | "evenly";
alignItems: "start" | "end" | "center" | "baseline" | "stretch";
content: ContentItem[];
};
export interface Page {
slug: string;
name: string;
linkName: string;
headline: string;
subheadline: string;
seoTitle: string;
seoMetaRobots: string;
seoDescription: string;
topFullwidthBanner?: {
name: string;
variant: string;
headline: string;
subheadline: string;
text: string;
imageUrl?: string;
};
row1?: ContentRow;
row2?: ContentRow;
row3?: ContentRow;
}

View File

@@ -0,0 +1,7 @@
export interface PageSeo {
title: string;
description: string;
metaRobotsIndex?: string;
metaRobotsFollow?: string;
}

View File

@@ -0,0 +1,17 @@
export type ProductPromotion = {
category: "sale" | "topseller";
text: string; // z.B. "-30%", "top"
};
export type Product = {
id: string;
name: string;
description?: string;
price: number;
originalPrice?: number; // Streichpreis
currency: string;
imageUrl?: string;
category?: string;
inStock: boolean;
promotion?: ProductPromotion;
};

45
middlelayer/types/user.ts Normal file
View File

@@ -0,0 +1,45 @@
/**
* User-Rollen für Authorization
*/
export enum UserRole {
ADMIN = "admin",
CUSTOMER = "customer",
GUEST = "guest",
}
/**
* User-Interface
*/
export interface User {
id: string;
email: string;
name: string;
role: UserRole;
createdAt: Date;
}
/**
* JWT Payload
*/
export interface JWTPayload {
userId: string;
email: string;
role: UserRole;
}
/**
* Login Credentials
*/
export interface LoginCredentials {
email: string;
password: string;
}
/**
* Register Data
*/
export interface RegisterData {
email: string;
password: string;
name: string;
}