chore: overhaul infrastructure and integrate @mintel packages
Some checks failed
🧪 CI (QA) / 🧪 Quality Assurance (push) Failing after 1m3s
Some checks failed
🧪 CI (QA) / 🧪 Quality Assurance (push) Failing after 1m3s
- Restructure to pnpm monorepo (site moved to apps/web) - Integrate @mintel/tsconfig, @mintel/eslint-config, @mintel/husky-config - Implement Docker service architecture (Varnish, Directus, Gatekeeper) - Setup environment-aware Gitea Actions deployment
This commit is contained in:
46
apps/web/src/logic/content-provider.ts
Normal file
46
apps/web/src/logic/content-provider.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const DOCS_DIR = path.join(process.cwd(), 'docs');
|
||||
|
||||
export function getTechDetails() {
|
||||
try {
|
||||
const content = fs.readFileSync(path.join(DOCS_DIR, 'TECH.md'), 'utf-8');
|
||||
const sections = content.split('⸻').map(s => s.trim());
|
||||
|
||||
// Extract items (Speed, Responsive, Stability, etc.)
|
||||
// Logic: Look for section headers and their summaries
|
||||
const items = [
|
||||
{ t: 'Geschwindigkeit & Performance', d: 'Kurze Ladezeiten, bessere Nutzererfahrung und messbar bessere Werte bei Google PageSpeed & Core Web Vitals. Die Seiten werden nicht „zusammengeklickt“, sondern technisch optimiert ausgeliefert.' },
|
||||
{ t: 'Responsives Design', d: 'Jede Website ist von Grund auf responsiv. Layout, Inhalte und Funktionen passen sich automatisch an Smartphones, Tablets, Laptops und große Bildschirme an.' },
|
||||
{ t: 'Stabilität & Betriebssicherheit', d: 'Im Hintergrund laufen Überwachungs- und Kontrollmechanismen, die technische Probleme automatisch erkennen, bevor sie zum Risiko werden.' },
|
||||
{ t: 'Datenschutz & DSGVO', d: 'Ich setze konsequent auf freie, selbst betriebene Software statt auf große externe Plattformen. Keine Weitergabe von Nutzerdaten an Dritte, keine versteckten Tracker.' },
|
||||
{ t: 'Unabhängigkeit & Kostenkontrolle', d: 'Da ich keine proprietären Systeme oder Lizenzmodelle einsetze, entstehen keine laufenden Tool-Gebühren oder plötzliche Preiserhöhungen.' },
|
||||
{ t: 'Wartbarkeit & Erweiterbarkeit', d: 'Inhalte und Funktionen können sauber ergänzt werden, ohne das ganze System zu gefährden. Das schützt Ihre Investition langfristig.' }
|
||||
];
|
||||
|
||||
return items;
|
||||
} catch (e) {
|
||||
console.error('Failed to read TECH.md', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function getPrinciples() {
|
||||
try {
|
||||
const content = fs.readFileSync(path.join(DOCS_DIR, 'PRINCIPLES.md'), 'utf-8');
|
||||
// Simplified extraction for now, mirroring the structure in the PDF
|
||||
const principles = [
|
||||
{ t: '1. Volle Preis-Transparenz', d: 'Alle Kosten sind offen und nachvollziehbar. Es gibt keine versteckten Gebühren, keine Abos, keine Lock-ins. Jeder Kunde sieht genau, wofür er bezahlt.' },
|
||||
{ t: '2. Quellcode & Projektzugang', d: 'Auf Wunsch erhalten Kunden jederzeit den vollständigen Source Code. Damit kann jeder andere Entwickler problemlos weiterarbeiten.' },
|
||||
{ t: '3. Best Practices & saubere Technik', d: 'Ich setze konsequent bewährte Standards ein. Das sorgt dafür, dass Systeme wartbar, verständlich und erweiterbar bleiben.' },
|
||||
{ t: '4. Verantwortung & Fairness', d: 'Ich übernehme die technische Verantwortung. Ich garantiere keine Umsätze, nur saubere Umsetzung und stabile Systeme. Wenn etwas nicht sinnvoll ist, sage ich es ehrlich.' },
|
||||
{ t: '5. Langfristiger Wert', d: 'Eine Website ist ein Investment. Ich baue sie so, dass Anpassungen und Übergaben an andere Entwickler problemlos möglich sind.' },
|
||||
{ t: '6. Zusammenarbeit ohne Tricks', d: 'Keine künstlichen Deadlines, kein unnötiger Overhead. Kommunikation ist klar, Entscheidungen nachvollziehbar, Übergaben sauber dokumentiert.' }
|
||||
];
|
||||
return principles;
|
||||
} catch (e) {
|
||||
console.error('Failed to read PRINCIPLES.md', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
202
apps/web/src/logic/pricing/calculator.ts
Normal file
202
apps/web/src/logic/pricing/calculator.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import { FormState, Position, Totals } from './types';
|
||||
import { FEATURE_LABELS, FUNCTION_LABELS, API_LABELS, PAGE_LABELS } from './constants';
|
||||
|
||||
export function calculateTotals(state: FormState, pricing: any): Totals {
|
||||
if (state.projectType !== 'website') {
|
||||
return {
|
||||
totalPrice: 0,
|
||||
monthlyPrice: 0,
|
||||
totalPagesCount: 0,
|
||||
totalFeatures: 0,
|
||||
totalFunctions: 0,
|
||||
totalApis: 0,
|
||||
languagesCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
const sitemapPagesCount = state.sitemap?.reduce((acc: number, cat: any) => acc + (cat.pages?.length || 0), 0) || 0;
|
||||
const totalPagesCount = Math.max(
|
||||
(state.selectedPages?.length || 0) + (state.otherPages?.length || 0) + (state.otherPagesCount || 0),
|
||||
sitemapPagesCount
|
||||
);
|
||||
|
||||
const totalFeatures = (state.features?.length || 0) + (state.otherFeatures?.length || 0) + (state.otherFeaturesCount || 0);
|
||||
const totalFunctions = (state.functions?.length || 0) + (state.otherFunctions?.length || 0) + (state.otherFunctionsCount || 0);
|
||||
const totalApis = (state.apiSystems?.length || 0) + (state.otherTech?.length || 0) + (state.otherTechCount || 0);
|
||||
|
||||
let total = pricing.BASE_WEBSITE;
|
||||
total += totalPagesCount * pricing.PAGE;
|
||||
total += totalFeatures * pricing.FEATURE;
|
||||
total += totalFunctions * pricing.FUNCTION;
|
||||
total += totalApis * pricing.API_INTEGRATION;
|
||||
total += (state.newDatasets || 0) * pricing.NEW_DATASET;
|
||||
|
||||
if (state.cmsSetup) {
|
||||
total += pricing.CMS_SETUP;
|
||||
total += totalFeatures * pricing.CMS_CONNECTION_PER_FEATURE;
|
||||
}
|
||||
|
||||
// Optional visual/complexity boosters (from calculator.ts logic)
|
||||
if (state.visualStaging && !isNaN(Number(state.visualStaging)) && Number(state.visualStaging) > 0) {
|
||||
total += Number(state.visualStaging) * pricing.VISUAL_STAGING;
|
||||
}
|
||||
if (state.complexInteractions && !isNaN(Number(state.complexInteractions)) && Number(state.complexInteractions) > 0) {
|
||||
total += Number(state.complexInteractions) * pricing.COMPLEX_INTERACTION;
|
||||
}
|
||||
|
||||
const languagesCount = state.languagesList?.length || 1;
|
||||
if (languagesCount > 1) {
|
||||
total *= (1 + (languagesCount - 1) * 0.2);
|
||||
}
|
||||
|
||||
const monthlyPrice = pricing.HOSTING_MONTHLY + ((state.storageExpansion || 0) * pricing.STORAGE_EXPANSION_MONTHLY);
|
||||
|
||||
return {
|
||||
totalPrice: Math.round(total),
|
||||
monthlyPrice: Math.round(monthlyPrice),
|
||||
totalPagesCount,
|
||||
totalFeatures,
|
||||
totalFunctions,
|
||||
totalApis,
|
||||
languagesCount
|
||||
};
|
||||
}
|
||||
|
||||
export function calculatePositions(state: FormState, pricing: any): Position[] {
|
||||
const positions: Position[] = [];
|
||||
let pos = 1;
|
||||
|
||||
if (state.projectType === 'website') {
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'Das technische Fundament',
|
||||
desc: 'Projekt-Setup, Infrastruktur, Hosting-Bereitstellung, Grundstruktur & Design-Vorlage, technisches SEO-Basics, Analytics.',
|
||||
qty: 1,
|
||||
price: pricing.BASE_WEBSITE
|
||||
});
|
||||
|
||||
const sitemapPagesCount = state.sitemap?.reduce((acc: number, cat: any) => acc + (cat.pages?.length || 0), 0) || 0;
|
||||
const totalPagesCount = Math.max(
|
||||
(state.selectedPages?.length || 0) + (state.otherPages?.length || 0) + (state.otherPagesCount || 0),
|
||||
sitemapPagesCount
|
||||
);
|
||||
|
||||
const allPages = [
|
||||
...(state.selectedPages || []).map((p: string) => PAGE_LABELS[p] || p),
|
||||
...(state.otherPages || []),
|
||||
...(state.sitemap?.flatMap((cat: any) => cat.pages?.map((p: any) => p.title)) || [])
|
||||
];
|
||||
|
||||
// Deduplicate labels
|
||||
const uniquePages = Array.from(new Set(allPages));
|
||||
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'Individuelle Seiten',
|
||||
desc: `Gestaltung und Umsetzung von ${totalPagesCount} individuellen Seiten-Layouts (${uniquePages.join(', ')}).`,
|
||||
qty: totalPagesCount,
|
||||
price: totalPagesCount * pricing.PAGE
|
||||
});
|
||||
|
||||
if (state.features.length > 0 || (state.otherFeatures?.length || 0) > 0) {
|
||||
const allFeatures = [...state.features.map((f: string) => FEATURE_LABELS[f] || f), ...(state.otherFeatures || [])];
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'System-Module (Features)',
|
||||
desc: `Implementierung funktionaler Bereiche: ${allFeatures.join(', ')}. Inklusive Datenstruktur und Darstellung.`,
|
||||
qty: allFeatures.length,
|
||||
price: allFeatures.length * pricing.FEATURE
|
||||
});
|
||||
}
|
||||
|
||||
if (state.functions.length > 0 || (state.otherFunctions?.length || 0) > 0) {
|
||||
const allFunctions = [...state.functions.map((f: string) => FUNCTION_LABELS[f] || f), ...(state.otherFunctions || [])];
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'Logik-Funktionen',
|
||||
desc: `Implementierung technischer Logik: ${allFunctions.join(', ')}.`,
|
||||
qty: allFunctions.length,
|
||||
price: allFunctions.length * pricing.FUNCTION
|
||||
});
|
||||
}
|
||||
|
||||
if (state.apiSystems.length > 0 || (state.otherTech?.length || 0) > 0) {
|
||||
const allApis = [...state.apiSystems.map((a: string) => API_LABELS[a] || a), ...(state.otherTech || [])];
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'Schnittstellen (API)',
|
||||
desc: `Anbindung externer Systeme zur Datensynchronisation: ${allApis.join(', ')}.`,
|
||||
qty: allApis.length,
|
||||
price: allApis.length * pricing.API_INTEGRATION
|
||||
});
|
||||
}
|
||||
|
||||
if (state.cmsSetup) {
|
||||
const totalFeatures = state.features.length + (state.otherFeatures?.length || 0) + (state.otherFeaturesCount || 0);
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'Inhaltsverwaltung (CMS)',
|
||||
desc: 'Einrichtung eines Systems zur eigenständigen Pflege von Inhalten und Datensätzen.',
|
||||
qty: 1,
|
||||
price: pricing.CMS_SETUP + totalFeatures * pricing.CMS_CONNECTION_PER_FEATURE
|
||||
});
|
||||
}
|
||||
|
||||
if (state.newDatasets > 0) {
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'Inhaltliche Initial-Pflege',
|
||||
desc: `Manuelle Übernahme und Aufbereitung von ${state.newDatasets} Datensätzen (Produkte, Artikel) in das Zielsystem.`,
|
||||
qty: state.newDatasets,
|
||||
price: state.newDatasets * pricing.NEW_DATASET
|
||||
});
|
||||
}
|
||||
|
||||
if ((state.visualStaging && Number(state.visualStaging) > 0) || (state.complexInteractions && Number(state.complexInteractions) > 0)) {
|
||||
const vsCount = Number(state.visualStaging || 0);
|
||||
const ciCount = Number(state.complexInteractions || 0);
|
||||
const totalCount = vsCount + ciCount;
|
||||
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'Inszenierung & Interaktion',
|
||||
desc: `Umsetzung von ${totalCount} speziellen Sektionen, Hero-Stories oder Konfiguratoren zur Steigerung der Conversion.`,
|
||||
qty: totalCount,
|
||||
price: (vsCount * pricing.VISUAL_STAGING) + (ciCount * pricing.COMPLEX_INTERACTION)
|
||||
});
|
||||
}
|
||||
|
||||
const languagesCount = state.languagesList.length || 1;
|
||||
if (languagesCount > 1) {
|
||||
const subtotal = positions.reduce((sum, p) => sum + p.price, 0);
|
||||
const factorPrice = subtotal * ((languagesCount - 1) * 0.2);
|
||||
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'Mehrsprachigkeit',
|
||||
desc: `Erweiterung des Systems auf ${languagesCount} Sprachen (Struktur & Logik).`,
|
||||
qty: languagesCount,
|
||||
price: Math.round(factorPrice)
|
||||
});
|
||||
}
|
||||
|
||||
const monthlyRate = pricing.HOSTING_MONTHLY + (state.storageExpansion * pricing.STORAGE_EXPANSION_MONTHLY);
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'Sorglos-Paket (Betrieb & Pflege)',
|
||||
desc: `1 Jahr Sicherung des technischen Betriebs, Instandhaltung, Sicherheits-Updates und Inhalts-Aktualisierungen gemäß AGB Punkt 7a.`,
|
||||
qty: 1,
|
||||
price: monthlyRate * 12
|
||||
});
|
||||
} else {
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'Web App / Software Entwicklung',
|
||||
desc: 'Individuelle Software-Entwicklung nach Aufwand. Abrechnung erfolgt auf Stundenbasis.',
|
||||
qty: 1,
|
||||
price: 0
|
||||
});
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
226
apps/web/src/logic/pricing/constants.ts
Normal file
226
apps/web/src/logic/pricing/constants.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import { FormState } from './types';
|
||||
|
||||
export const PRICING = {
|
||||
BASE_WEBSITE: 4000,
|
||||
PAGE: 600,
|
||||
FEATURE: 1500,
|
||||
FUNCTION: 800,
|
||||
NEW_DATASET: 200,
|
||||
HOSTING_MONTHLY: 250,
|
||||
STORAGE_EXPANSION_MONTHLY: 10,
|
||||
CMS_SETUP: 1500,
|
||||
CMS_CONNECTION_PER_FEATURE: 800,
|
||||
API_INTEGRATION: 800,
|
||||
APP_HOURLY: 120,
|
||||
VISUAL_STAGING: 2000,
|
||||
COMPLEX_INTERACTION: 1500,
|
||||
};
|
||||
|
||||
export const initialState: FormState = {
|
||||
projectType: 'website',
|
||||
// Company
|
||||
companyName: '',
|
||||
employeeCount: '',
|
||||
// Existing Presence
|
||||
existingWebsite: '',
|
||||
socialMedia: [],
|
||||
socialMediaUrls: {},
|
||||
existingDomain: '',
|
||||
wishedDomain: '',
|
||||
// Project
|
||||
websiteTopic: '',
|
||||
selectedPages: ['Home'],
|
||||
otherPages: [],
|
||||
otherPagesCount: 0,
|
||||
features: [],
|
||||
otherFeatures: [],
|
||||
otherFeaturesCount: 0,
|
||||
functions: [],
|
||||
otherFunctions: [],
|
||||
otherFunctionsCount: 0,
|
||||
apiSystems: [],
|
||||
otherTech: [],
|
||||
otherTechCount: 0,
|
||||
assets: [],
|
||||
otherAssets: [],
|
||||
otherAssetsCount: 0,
|
||||
newDatasets: 0,
|
||||
cmsSetup: false,
|
||||
storageExpansion: 0,
|
||||
name: '',
|
||||
email: '',
|
||||
role: '',
|
||||
message: '',
|
||||
sitemapFile: null,
|
||||
contactFiles: [],
|
||||
// Design
|
||||
designVibe: 'minimal',
|
||||
colorScheme: ['#ffffff', '#f8fafc', '#0f172a'],
|
||||
references: [],
|
||||
designWishes: '',
|
||||
// Maintenance
|
||||
expectedAdjustments: 'low',
|
||||
languagesList: ['Deutsch'],
|
||||
personName: '',
|
||||
// Timeline
|
||||
deadline: 'flexible',
|
||||
// Web App specific
|
||||
targetAudience: 'internal',
|
||||
userRoles: [],
|
||||
dataSensitivity: 'standard',
|
||||
platformType: 'web-only',
|
||||
// Meta
|
||||
dontKnows: [],
|
||||
visualStaging: 'standard',
|
||||
complexInteractions: 'standard',
|
||||
// AI generated / Post-processed
|
||||
briefingSummary: '',
|
||||
designVision: '',
|
||||
positionDescriptions: {},
|
||||
taxId: '',
|
||||
sitemap: [],
|
||||
};
|
||||
|
||||
export const PAGE_SAMPLES = [
|
||||
{ id: 'Home', label: 'Startseite', desc: 'Der erste Eindruck Ihrer Marke.' },
|
||||
{ id: 'About', label: 'Über uns', desc: 'Ihre Geschichte und Ihr Team.' },
|
||||
{ id: 'Services', label: 'Leistungen', desc: 'Übersicht Ihres Angebots.' },
|
||||
{ id: 'Contact', label: 'Kontakt', desc: 'Anlaufstelle für Ihre Kunden.' },
|
||||
{ id: 'Landing', label: 'Landingpage', desc: 'Optimiert für Marketing-Kampagnen.' },
|
||||
{ id: 'Legal', label: 'Rechtliches', desc: 'Impressum & Datenschutz.' },
|
||||
];
|
||||
|
||||
export const FEATURE_OPTIONS = [
|
||||
{ id: 'blog_news', label: 'Blog / News', desc: 'Ein Bereich für aktuelle Beiträge und Neuigkeiten.' },
|
||||
{ id: 'products', label: 'Produktbereich', desc: 'Katalog Ihrer Leistungen oder Produkte.' },
|
||||
{ id: 'jobs', label: 'Karriere / Jobs', desc: 'Stellenanzeigen und Bewerbungsoptionen.' },
|
||||
{ id: 'refs', label: 'Referenzen / Cases', desc: 'Präsentation Ihrer Projekte.' },
|
||||
{ id: 'events', label: 'Events / Termine', desc: 'Veranstaltungskalender.' },
|
||||
];
|
||||
|
||||
export const FUNCTION_OPTIONS = [
|
||||
{ id: 'search', label: 'Suche', desc: 'Volltextsuche über alle Inhalte.' },
|
||||
{ id: 'filter', label: 'Filter-Systeme', desc: 'Kategorisierung und Sortierung.' },
|
||||
{ id: 'pdf', label: 'PDF-Export', desc: 'Automatisierte PDF-Erstellung.' },
|
||||
{ id: 'forms', label: 'Individuelle Formular-Logik', desc: 'Smarte Validierung & mehrstufige Prozesse.' },
|
||||
];
|
||||
|
||||
export const API_OPTIONS = [
|
||||
{ id: 'crm', label: 'CRM System', desc: 'HubSpot, Salesforce, Pipedrive etc.' },
|
||||
{ id: 'erp', label: 'ERP / Warenwirtschaft', desc: 'SAP, Microsoft Dynamics, Xentral etc.' },
|
||||
{ id: 'stripe', label: 'Stripe / Payment', desc: 'Zahlungsabwicklung und Abonnements.' },
|
||||
{ id: 'newsletter', label: 'Newsletter / Marketing', desc: 'Mailchimp, Brevo, ActiveCampaign etc.' },
|
||||
{ id: 'ecommerce', label: 'E-Commerce / Shop', desc: 'Shopify, WooCommerce, Shopware Sync.' },
|
||||
{ id: 'hr', label: 'HR / Recruiting', desc: 'Personio, Workday, Recruitee etc.' },
|
||||
{ id: 'realestate', label: 'Immobilien', desc: 'OpenImmo, FlowFact, Immowelt Sync.' },
|
||||
{ id: 'calendar', label: 'Termine / Booking', desc: 'Calendly, Shore, Doctolib etc.' },
|
||||
{ id: 'social', label: 'Social Media Sync', desc: 'Automatisierte Posts oder Feeds.' },
|
||||
{ id: 'maps', label: 'Google Maps / Places', desc: 'Standortsuche und Kartenintegration.' },
|
||||
{ id: 'analytics', label: 'Custom Analytics', desc: 'Anbindung an spezialisierte Tracking-Tools.' },
|
||||
];
|
||||
|
||||
export const ASSET_OPTIONS = [
|
||||
{ id: 'existing_website', label: 'Bestehende Website', desc: 'Inhalte oder Struktur können übernommen werden.' },
|
||||
{ id: 'logo', label: 'Logo', desc: 'Vektordatei Ihres Logos.' },
|
||||
{ id: 'styleguide', label: 'Styleguide', desc: 'Farben, Schriften, Design-Vorgaben.' },
|
||||
{ id: 'content_concept', label: 'Inhalts-Konzept', desc: 'Struktur und Texte sind bereits geplant.' },
|
||||
{ id: 'media', label: 'Bild/Video-Material', desc: 'Professionelles Bildmaterial vorhanden.' },
|
||||
{ id: 'icons', label: 'Icons', desc: 'Eigene Icon-Sets vorhanden.' },
|
||||
{ id: 'illustrations', label: 'Illustrationen', desc: 'Eigene Illustrationen vorhanden.' },
|
||||
{ id: 'fonts', label: 'Fonts', desc: 'Lizenzen für Hausschriften vorhanden.' },
|
||||
];
|
||||
|
||||
export const DESIGN_OPTIONS = [
|
||||
{ id: 'minimal', label: 'Minimalistisch', desc: 'Viel Weißraum, klare Typografie.' },
|
||||
{ id: 'bold', label: 'Mutig & Laut', desc: 'Starke Kontraste, große Schriften.' },
|
||||
{ id: 'nature', label: 'Natürlich', desc: 'Sanfte Erdtöne, organische Formen.' },
|
||||
{ id: 'tech', label: 'Technisch', desc: 'Präzise Linien, dunkle Akzente.' },
|
||||
];
|
||||
|
||||
export const EMPLOYEE_OPTIONS = [
|
||||
{ id: '1-5', label: '1-5 Mitarbeiter' },
|
||||
{ id: '6-20', label: '6-20 Mitarbeiter' },
|
||||
{ id: '21-100', label: '21-100 Mitarbeiter' },
|
||||
{ id: '100+', label: '100+ Mitarbeiter' },
|
||||
];
|
||||
|
||||
export const SOCIAL_MEDIA_OPTIONS = [
|
||||
{ id: 'instagram', label: 'Instagram' },
|
||||
{ id: 'linkedin', label: 'LinkedIn' },
|
||||
{ id: 'facebook', label: 'Facebook' },
|
||||
{ id: 'twitter', label: 'Twitter / X' },
|
||||
{ id: 'tiktok', label: 'TikTok' },
|
||||
{ id: 'youtube', label: 'YouTube' },
|
||||
];
|
||||
|
||||
export const VIBE_LABELS: Record<string, string> = {
|
||||
minimal: 'Minimalistisch',
|
||||
bold: 'Mutig & Laut',
|
||||
nature: 'Natürlich',
|
||||
tech: 'Technisch'
|
||||
};
|
||||
|
||||
export const DEADLINE_LABELS: Record<string, string> = {
|
||||
asap: 'So schnell wie möglich',
|
||||
'2-3-months': 'In 2-3 Monaten',
|
||||
'3-6-months': 'In 3-6 Monaten',
|
||||
flexible: 'Flexibel'
|
||||
};
|
||||
|
||||
export const ASSET_LABELS: Record<string, string> = {
|
||||
existing_website: 'Bestehende Website',
|
||||
logo: 'Logo',
|
||||
styleguide: 'Styleguide',
|
||||
content_concept: 'Inhalts-Konzept',
|
||||
media: 'Bild/Video-Material',
|
||||
icons: 'Icons',
|
||||
illustrations: 'Illustrationen',
|
||||
fonts: 'Fonts'
|
||||
};
|
||||
|
||||
export const FEATURE_LABELS: Record<string, string> = {
|
||||
blog_news: 'Blog / News',
|
||||
products: 'Produktbereich',
|
||||
jobs: 'Karriere / Jobs',
|
||||
refs: 'Referenzen / Cases',
|
||||
events: 'Events / Termine'
|
||||
};
|
||||
|
||||
export const FUNCTION_LABELS: Record<string, string> = {
|
||||
search: 'Suche',
|
||||
filter: 'Filter-Systeme',
|
||||
pdf: 'PDF-Export',
|
||||
forms: 'Individuelle Formular-Logik',
|
||||
members: 'Mitgliederbereich',
|
||||
calendar: 'Event-Kalender',
|
||||
multilang: 'Mehrsprachigkeit',
|
||||
chat: 'Echtzeit-Chat'
|
||||
};
|
||||
|
||||
export const API_LABELS: Record<string, string> = {
|
||||
crm_erp: 'CRM / ERP',
|
||||
payment: 'Payment',
|
||||
marketing: 'Marketing',
|
||||
ecommerce: 'E-Commerce',
|
||||
maps: 'Google Maps / Places',
|
||||
social: 'Social Media Sync',
|
||||
analytics: 'Custom Analytics'
|
||||
};
|
||||
|
||||
export const SOCIAL_LABELS: Record<string, string> = {
|
||||
instagram: 'Instagram',
|
||||
linkedin: 'LinkedIn',
|
||||
facebook: 'Facebook',
|
||||
twitter: 'Twitter / X',
|
||||
tiktok: 'TikTok',
|
||||
youtube: 'YouTube'
|
||||
};
|
||||
|
||||
export const PAGE_LABELS: Record<string, string> = {
|
||||
Home: 'Startseite',
|
||||
About: 'Über uns',
|
||||
Services: 'Leistungen',
|
||||
Contact: 'Kontakt',
|
||||
Landing: 'Landingpage',
|
||||
Legal: 'Impressum & Datenschutz'
|
||||
};
|
||||
3
apps/web/src/logic/pricing/index.ts
Normal file
3
apps/web/src/logic/pricing/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './constants';
|
||||
export * from './calculator';
|
||||
export * from './types';
|
||||
89
apps/web/src/logic/pricing/types.ts
Normal file
89
apps/web/src/logic/pricing/types.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
export type ProjectType = 'website' | 'web-app';
|
||||
|
||||
export interface FormState {
|
||||
projectType: ProjectType;
|
||||
// Company
|
||||
companyName: string;
|
||||
employeeCount: string;
|
||||
// Existing Presence
|
||||
existingWebsite: string;
|
||||
socialMedia: string[];
|
||||
socialMediaUrls: Record<string, string>;
|
||||
existingDomain: string;
|
||||
wishedDomain: string;
|
||||
// Project
|
||||
websiteTopic: string;
|
||||
selectedPages: string[];
|
||||
otherPages: string[];
|
||||
otherPagesCount: number;
|
||||
features: string[];
|
||||
otherFeatures: string[];
|
||||
otherFeaturesCount: number;
|
||||
functions: string[];
|
||||
otherFunctions: string[];
|
||||
otherFunctionsCount: number;
|
||||
apiSystems: string[];
|
||||
otherTech: string[];
|
||||
otherTechCount: number;
|
||||
assets: string[];
|
||||
otherAssets: string[];
|
||||
otherAssetsCount: number;
|
||||
newDatasets: number;
|
||||
cmsSetup: boolean;
|
||||
storageExpansion: number;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
message: string;
|
||||
sitemapFile: any; // Using any for File/null to be CLI-compatible
|
||||
contactFiles: any[]; // Using any[] for File[]
|
||||
// Design
|
||||
designVibe: string;
|
||||
colorScheme: string[];
|
||||
references: string[];
|
||||
designWishes: string;
|
||||
// Maintenance
|
||||
expectedAdjustments: string;
|
||||
languagesList: string[];
|
||||
// Timeline
|
||||
deadline: string;
|
||||
// Web App specific
|
||||
targetAudience: string;
|
||||
userRoles: string[];
|
||||
dataSensitivity: string;
|
||||
platformType: string;
|
||||
// Meta
|
||||
dontKnows: string[];
|
||||
visualStaging: string;
|
||||
complexInteractions: string;
|
||||
gridDontKnows?: Record<string, string>;
|
||||
briefingSummary?: string;
|
||||
companyAddress?: string;
|
||||
companyPhone?: string;
|
||||
personName?: string;
|
||||
taxId?: string;
|
||||
designVision?: string;
|
||||
positionDescriptions?: Record<string, string>;
|
||||
sitemap?: {
|
||||
category: string;
|
||||
pages: { title: string; desc: string }[];
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
pos: number;
|
||||
title: string;
|
||||
desc: string;
|
||||
qty: number;
|
||||
price: number;
|
||||
isRecurring?: boolean;
|
||||
}
|
||||
export interface Totals {
|
||||
totalPrice: number;
|
||||
monthlyPrice: number;
|
||||
totalPagesCount: number;
|
||||
totalFeatures: number;
|
||||
totalFunctions: number;
|
||||
totalApis: number;
|
||||
languagesCount: number;
|
||||
}
|
||||
Reference in New Issue
Block a user