feat: migrate npm registry from Verdaccio to Gitea Packages
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧹 Lint (push) Failing after 35s
Monorepo Pipeline / 🧪 Test (push) Failing after 35s
Monorepo Pipeline / 🏗️ Build (push) Failing after 12s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Image Processor (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped

This commit is contained in:
2026-02-27 00:12:00 +01:00
parent efd1341762
commit 5da88356a8
69 changed files with 5397 additions and 114 deletions

View File

@@ -0,0 +1,70 @@
"use client";
import * as React from "react";
import { Page as PDFPage, Document as PDFDocument } from "@react-pdf/renderer";
import { pdfStyles } from "./pdf/SharedUI.js";
import { SimpleLayout } from "./pdf/SimpleLayout.js";
// Modules
import { FrontPageModule } from "./pdf/modules/FrontPageModule.js";
import { BriefingModule } from "./pdf/modules/BriefingModule.js";
import { SitemapModule } from "./pdf/modules/SitemapModule.js";
import { ClosingModule } from "./pdf/modules/CommonModules.js";
export const ConceptPDF = ({
concept,
headerIcon,
footerLogo,
}: any) => {
const date = new Date().toLocaleDateString("de-DE", {
year: "numeric",
month: "long",
day: "numeric",
});
// Flatten the ProjectConcept to match what the legacy modules expect
const flatState = {
...concept.auditedFacts,
briefingSummary: concept.strategy?.briefingSummary || "",
designVision: concept.strategy?.designVision || "",
sitemap: concept.architecture?.sitemap || [],
websiteTopic: concept.architecture?.websiteTopic || concept.auditedFacts?.websiteTopic || "",
};
const companyData = {
name: "Marc Mintel",
address1: "Georg-Meistermann-Straße 7",
address2: "54586 Schüller",
ustId: "DE367588065",
};
const commonProps = {
state: flatState,
date,
headerIcon,
footerLogo,
companyData,
};
return (
<PDFDocument title={`Projektkonzept - ${flatState.companyName || "Projekt"}`}>
<PDFPage size="A4" style={pdfStyles.titlePage}>
<FrontPageModule state={flatState} headerIcon={headerIcon} date={date} />
</PDFPage>
<SimpleLayout {...commonProps}>
<BriefingModule state={flatState} />
</SimpleLayout>
{flatState.sitemap && flatState.sitemap.length > 0 && (
<SimpleLayout {...commonProps}>
<SitemapModule state={flatState} />
</SimpleLayout>
)}
<SimpleLayout {...commonProps}>
<ClosingModule />
</SimpleLayout>
</PDFDocument>
);
};

View File

@@ -7,8 +7,6 @@ import { SimpleLayout } from "./pdf/SimpleLayout.js";
// Modules
import { FrontPageModule } from "./pdf/modules/FrontPageModule.js";
import { BriefingModule } from "./pdf/modules/BriefingModule.js";
import { SitemapModule } from "./pdf/modules/SitemapModule.js";
import { EstimationModule } from "./pdf/modules/EstimationModule.js";
import { TransparenzModule } from "./pdf/modules/TransparenzModule.js";
import { ClosingModule } from "./pdf/modules/CommonModules.js";
@@ -64,16 +62,6 @@ export const EstimationPDF = ({
<FrontPageModule state={state} headerIcon={headerIcon} date={date} />
</PDFPage>
<SimpleLayout {...commonProps}>
<BriefingModule state={state} />
</SimpleLayout>
{state.sitemap && state.sitemap.length > 0 && (
<SimpleLayout {...commonProps}>
<SitemapModule state={state} />
</SimpleLayout>
)}
<SimpleLayout {...commonProps}>
<EstimationModule
state={state}

View File

@@ -0,0 +1,172 @@
"use client";
import * as React from "react";
import {
Page as PDFPage,
Text as PDFText,
View as PDFView,
StyleSheet as PDFStyleSheet,
Document as PDFDocument,
} from "@react-pdf/renderer";
import {
pdfStyles,
DocumentTitle,
COLORS,
FONT_SIZES,
Divider,
} from "./pdf/SharedUI.js";
import { SimpleLayout } from "./pdf/SimpleLayout.js";
const styles = PDFStyleSheet.create({
section: {
marginBottom: 24,
},
textLead: {
fontSize: FONT_SIZES.BODY,
color: COLORS.TEXT_MAIN,
lineHeight: 1.6,
marginBottom: 16,
},
textRegular: {
fontSize: FONT_SIZES.BODY,
color: COLORS.TEXT_DIM,
lineHeight: 1.6,
marginBottom: 12,
},
bulletPoint: {
flexDirection: "row",
marginBottom: 6,
paddingLeft: 10,
},
bullet: {
width: 15,
fontSize: FONT_SIZES.BODY,
color: COLORS.TEXT_LIGHT,
},
bulletText: {
flex: 1,
fontSize: FONT_SIZES.BODY,
color: COLORS.TEXT_DIM,
lineHeight: 1.6,
},
quoteBox: {
marginTop: 20,
marginBottom: 20,
padding: 20,
backgroundColor: COLORS.GRID,
borderLeftWidth: 3,
borderLeftColor: COLORS.CHARCOAL,
},
quoteText: {
fontSize: FONT_SIZES.LABEL,
fontWeight: "bold",
color: COLORS.CHARCOAL,
lineHeight: 1.4,
},
headingSmall: {
fontSize: FONT_SIZES.LABEL,
fontWeight: "bold",
color: COLORS.CHARCOAL,
textTransform: "uppercase",
letterSpacing: 1,
marginBottom: 12,
marginTop: 8,
},
});
export const InfoPDF = ({ headerIcon, footerLogo }: { headerIcon?: string; footerLogo?: string }) => {
const companyData = {
name: "Marc Mintel",
address1: "Georg-Meistermann-Straße 7",
address2: "54586 Schüller",
ustId: "DE367588065",
};
const bankData = {
name: "N26",
bic: "NTSBDEB1XXX",
iban: "DE50 1001 1001 2620 4328 65",
};
const content = (
<PDFView>
<DocumentTitle
title="Arbeitsweise & Philosophie"
subLines={["Digital Architect — Marc Mintel"]}
isHero={true}
/>
<PDFView style={styles.section}>
<PDFText style={styles.headingSmall}>Hintergrund & Motivation</PDFText>
<PDFText style={styles.textLead}>
Ich baue Websites und Systeme seit über 15 Jahren. Nicht weil ich Websites so liebe sondern weil ich es hasse, wenn Dinge nicht funktionieren.
</PDFText>
<PDFText style={styles.textRegular}>
In diesen 15 Jahren habe ich Agenturen von innen gesehen, Konzerne erlebt, Startups aufgebaut und gelernt, wie man Dinge baut, die einfach laufen. Heute mache ich das ohne Agentur-Zwischenschichten: Direkt. Sauber. Verantwortlich.
</PDFText>
</PDFView>
<PDFView style={styles.quoteBox}>
<PDFText style={styles.quoteText}>
"Das Problem ist selten Technik. Es ist immer Zuständigkeit. Wenn keiner verantwortlich ist, passiert nichts."
</PDFText>
</PDFView>
<PDFView style={styles.section}>
<PDFText style={styles.headingSmall}>System-Architektur statt Baukasten</PDFText>
<PDFText style={styles.textRegular}>
Als Senior Developer in Umgebungen mit Millionenumsätzen habe ich gelernt: Performance ist nicht optional, Sicherheit kein Nice-to-Have. Deshalb sind meine Lösungen:
</PDFText>
<PDFView style={styles.bulletPoint}>
<PDFText style={styles.bullet}></PDFText>
<PDFText style={styles.bulletText}>Schnell, stabil und "boring" (im besten Sinne).</PDFText>
</PDFView>
<PDFView style={styles.bulletPoint}>
<PDFText style={styles.bullet}></PDFText>
<PDFText style={styles.bulletText}>Wartungsarm und unabhängig von Plugins oder Agenturen.</PDFText>
</PDFView>
<PDFView style={styles.bulletPoint}>
<PDFText style={styles.bullet}></PDFText>
<PDFText style={styles.bulletText}>Technologisch auf Augenhöhe mit Konzern-Standards, ohne bürokratischen Overhead.</PDFText>
</PDFView>
</PDFView>
<PDFView style={styles.section}>
<PDFText style={styles.headingSmall}>Ihre Vorteile</PDFText>
<PDFText style={styles.textRegular}>
Sie bekommen keinen Projektmanager, keinen starren Prozess und kein CMS-Drama.
</PDFText>
<PDFView style={{ flexDirection: 'row', marginTop: 10 }}>
<PDFView style={{ flex: 1, paddingRight: 20 }}>
<PDFText style={{ fontSize: FONT_SIZES.TINY, color: COLORS.TEXT_LIGHT, marginBottom: 4 }}>KOMMUNIKATION</PDFText>
<PDFText style={styles.textRegular}>Ein Ansprechpartner. Eine kurze Mail reicht oft aus. Keine endlosen Meetings.</PDFText>
</PDFView>
<PDFView style={{ flex: 1 }}>
<PDFText style={{ fontSize: FONT_SIZES.TINY, color: COLORS.TEXT_LIGHT, marginBottom: 4 }}>VERANTWORTUNG</PDFText>
<PDFText style={styles.textRegular}>Ich übernehme das Thema komplett, damit es für Sie kein Thema mehr ist.</PDFText>
</PDFView>
</PDFView>
</PDFView>
<Divider style={{ marginTop: 20, marginBottom: 20, backgroundColor: COLORS.GRID }} />
<PDFText style={[styles.textRegular, { fontSize: FONT_SIZES.SMALL, textAlign: 'center', color: COLORS.TEXT_LIGHT }]}>
Marc Mintel Digital Architect & Senior Software Developer
</PDFText>
</PDFView>
);
return (
<PDFDocument title="Marc Mintel - Arbeitsweise">
<SimpleLayout
companyData={companyData}
bankData={bankData}
headerIcon={headerIcon}
footerLogo={footerLogo}
showPageNumber={false}
>
{content}
</SimpleLayout>
</PDFDocument>
);
};

View File

@@ -24,9 +24,10 @@ const styles = StyleSheet.create({
borderBottomColor: COLORS.GRID,
alignItems: "flex-start",
},
colPos: { width: "8%" },
colDesc: { width: "62%" },
colQty: { width: "10%", textAlign: "center" },
colPos: { width: "6%" },
colDesc: { width: "46%", paddingRight: 10 },
colQty: { width: "8%", textAlign: "center" },
colUnitPrice: { width: "20%", textAlign: "right", paddingRight: 10 },
colPrice: { width: "20%", textAlign: "right" },
headerText: {
fontSize: FONT_SIZES.TINY,
@@ -111,7 +112,8 @@ export const EstimationModule = ({
Beschreibung
</PDFText>
<PDFText style={[styles.headerText, styles.colQty]}>Menge</PDFText>
<PDFText style={[styles.headerText, styles.colPrice]}>Betrag</PDFText>
<PDFText style={[styles.headerText, styles.colUnitPrice]}>E-Preis</PDFText>
<PDFText style={[styles.headerText, styles.colPrice]}>Gesamt</PDFText>
</PDFView>
{positions.map((item: any, i: number) => (
<PDFView key={i} style={styles.tableRow} wrap={false}>
@@ -125,6 +127,11 @@ export const EstimationModule = ({
</PDFText>
</PDFView>
<PDFText style={[styles.posText, styles.colQty]}>{item.qty}</PDFText>
<PDFText style={[styles.priceText, styles.colUnitPrice, { fontSize: FONT_SIZES.SMALL, color: COLORS.TEXT_MAIN, fontWeight: "normal" }]}>
{item.price > 0 && item.qty > 0
? `${(item.price / item.qty).toLocaleString("de-DE")}`
: "n. A."}
</PDFText>
<PDFText style={[styles.priceText, styles.colPrice]}>
{item.price > 0
? `${item.price.toLocaleString("de-DE")}`

View File

@@ -17,64 +17,61 @@ const styles = StyleSheet.create({
marginBottom: 24,
textAlign: "justify",
},
sitemapTree: { marginTop: 8 },
rootNode: {
padding: 12,
backgroundColor: COLORS.GRID,
marginBottom: 20,
borderLeftWidth: 2,
borderLeftColor: COLORS.CHARCOAL,
sitemapTree: {
marginTop: 8,
borderLeftWidth: 1,
borderLeftColor: COLORS.GRID,
marginLeft: 4,
paddingLeft: 16,
},
rootTitle: {
fontSize: FONT_SIZES.HEADING,
fontSize: FONT_SIZES.LABEL,
fontWeight: "bold",
color: COLORS.CHARCOAL,
letterSpacing: 0.5,
textTransform: "uppercase",
letterSpacing: 1,
marginBottom: 16,
marginLeft: -16, // offset the padding
},
categorySection: { marginBottom: 20 },
categorySection: { marginBottom: 16 },
categoryHeader: {
flexDirection: "row",
alignItems: "center",
paddingBottom: 6,
borderBottomWidth: 1,
borderBottomColor: COLORS.BLUEPRINT,
marginBottom: 10,
marginBottom: 8,
},
categoryIcon: {
width: 8,
height: 8,
backgroundColor: COLORS.GRID,
borderInlineWidth: 1,
borderColor: COLORS.DIVIDER,
marginRight: 10,
width: 6,
height: 6,
backgroundColor: COLORS.CHARCOAL,
marginRight: 8,
},
categoryTitle: {
fontSize: FONT_SIZES.BODY,
fontWeight: "bold",
color: COLORS.CHARCOAL,
textTransform: "uppercase",
letterSpacing: 1,
},
pagesGrid: { flexDirection: "row", flexWrap: "wrap" },
pageCard: {
width: "48%",
marginRight: "2%",
marginBottom: 12,
padding: 10,
borderWidth: 1,
borderColor: COLORS.GRID,
backgroundColor: "#fafafa",
pageRow: {
flexDirection: "row",
alignItems: "flex-start",
marginBottom: 6,
paddingLeft: 14,
},
pageBullet: {
fontSize: FONT_SIZES.BODY,
color: COLORS.TEXT_LIGHT,
marginRight: 8,
width: 10,
},
pageTitle: {
fontSize: FONT_SIZES.BODY,
fontWeight: "bold",
color: COLORS.TEXT_MAIN,
marginBottom: 4,
fontWeight: "bold",
},
pageDesc: {
fontSize: FONT_SIZES.TINY,
fontSize: FONT_SIZES.SMALL,
color: COLORS.TEXT_DIM,
lineHeight: 1.3,
marginLeft: 6,
marginTop: 1,
},
});
@@ -83,16 +80,13 @@ export const SitemapModule = ({ state }: any) => (
<DocumentTitle title="Informationsarchitektur" isHero={true} />
<PDFView style={styles.section}>
<PDFText style={styles.intro}>
Die folgende Struktur definiert die logische Hierarchie und
Benutzerführung. Sie dient als Bauplan für die technische Umsetzung und
stellt sicher, dass alle relevanten Geschäftsbereiche intuitiv
auffindbar sind.
Die folgende Baumstruktur definiert die logische Hierarchie und
Benutzerführung. Sie dient als kompakter Bauplan für die technische
Umsetzung aller relevanten Geschäftsbereiche.
</PDFText>
<PDFView style={styles.sitemapTree}>
<PDFView style={styles.rootNode}>
<PDFText style={styles.rootTitle}>Seitenstruktur</PDFText>
</PDFView>
<PDFText style={styles.rootTitle}>/ Root (Startseite)</PDFText>
{state.sitemap?.map((cat: any, i: number) => (
<PDFView key={i} style={styles.categorySection} wrap={false}>
@@ -101,18 +95,13 @@ export const SitemapModule = ({ state }: any) => (
<PDFText style={styles.categoryTitle}>{cat.category}</PDFText>
</PDFView>
<PDFView style={styles.pagesGrid}>
<PDFView>
{cat.pages.map((p: any, j: number) => (
<PDFView
key={j}
style={[
styles.pageCard,
j % 2 === 1 ? { marginRight: 0 } : {},
]}
>
<PDFView key={j} style={styles.pageRow}>
<PDFText style={styles.pageBullet}></PDFText>
<PDFText style={styles.pageTitle}>{p.title}</PDFText>
{p.desc && (
<PDFText style={styles.pageDesc}>{p.desc}</PDFText>
<PDFText style={styles.pageDesc}> {p.desc}</PDFText>
)}
</PDFView>
))}

View File

@@ -81,7 +81,7 @@ export const TransparenzModule = ({ pricing }: any) => {
},
{
l: "Sprachversionen",
d: "Skalierung der System-Architektur auf Zweit-Sprachen.",
d: "Skalierung der Architektur für weitere Sprachen (+20% Aufschlag auf die Zwischensumme aller vorherigen Positionen).",
p: "+20%",
},
{

View File

@@ -2,6 +2,8 @@ export * from "./logic/pricing/types.js";
export * from "./logic/pricing/constants.js";
export * from "./logic/pricing/calculator.js";
export * from "./components/EstimationPDF.js";
export * from "./components/ConceptPDF.js";
export * from "./components/InfoPDF.js";
export * from "./components/pdf/SimpleLayout.js";
export * from "./components/pdf/SharedUI.js";
export * from "./components/pdf/modules/FrontPageModule.js";
@@ -12,4 +14,5 @@ export * from "./components/pdf/modules/CommonModules.js";
export * from "./components/pdf/modules/BrandingModules.js";
export * from "./components/pdf/modules/TransparenzModule.js";
export * from "./components/AgbsPDF.js";
export * from "./components/InfoPDF.js";
export * from "./components/CombinedQuotePDF.js";

View File

@@ -1,7 +1,7 @@
import { FormState } from "./types.js";
export const PRICING = {
BASE_WEBSITE: 5440, // Updated to match AI prompt requirement in Pass 1
BASE_WEBSITE: 4000, // Foundation server infrastructure setup
PAGE: 600,
FEATURE: 1500,
FUNCTION: 800,

View File

@@ -1,3 +1,2 @@
export * from "./index.js";
export * from "./services/AcquisitionService.js";
export * from "./services/PdfEngine.js";

View File

@@ -1,6 +1,27 @@
import { renderToFile } from "@react-pdf/renderer";
import { renderToFile, Font } from "@react-pdf/renderer";
import { createElement } from "react";
// Standard Font Registrations to prevent crashes when PDFs use custom web fonts
Font.register({
family: 'Outfit',
fonts: [
{ src: 'Helvetica' },
{ src: 'Helvetica-Bold', fontWeight: 'bold' },
],
});
Font.register({
family: 'Inter',
fonts: [
{ src: 'Helvetica' },
{ src: 'Helvetica-Bold', fontWeight: 'bold' },
],
});
import { EstimationPDF } from "../components/EstimationPDF.js";
import { ConceptPDF } from "../components/ConceptPDF.js";
import { InfoPDF } from "../components/InfoPDF.js";
import { AgbsPDF } from "../components/AgbsPDF.js";
import { PRICING } from "../logic/pricing/constants.js";
import { calculateTotals } from "../logic/pricing/calculator.js";
@@ -21,4 +42,33 @@ export class PdfEngine {
return outputPath;
}
async generateConceptPdf(concept: any, outputPath: string): Promise<string> {
await renderToFile(
createElement(ConceptPDF as any, {
concept,
} as any) as any,
outputPath
);
return outputPath;
}
async generateInfoPdf(outputPath: string, options: { headerIcon?: string; footerLogo?: string } = {}): Promise<string> {
await renderToFile(
createElement(InfoPDF as any, options as any) as any,
outputPath
);
return outputPath;
}
async generateAgbsPdf(outputPath: string, options: { headerIcon?: string; footerLogo?: string; mode?: "estimation" | "full" } = {}): Promise<string> {
await renderToFile(
createElement(AgbsPDF as any, options as any) as any,
outputPath
);
return outputPath;
}
}