From 316e4b6fe9d446b57baedb50f0566b8818d3120a Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Fri, 30 Jan 2026 11:04:48 +0100 Subject: [PATCH] web app form --- src/components/ContactForm.tsx | 42 ++++-- .../ContactForm/components/Checkbox.tsx | 6 +- .../components/PriceCalculation.tsx | 2 +- .../ContactForm/components/RepeatableList.tsx | 20 +-- src/components/ContactForm/constants.tsx | 4 + src/components/ContactForm/steps/ApiStep.tsx | 67 ++++++--- .../ContactForm/steps/AssetsStep.tsx | 14 +- src/components/ContactForm/steps/BaseStep.tsx | 79 +++------- .../ContactForm/steps/ContactStep.tsx | 66 ++++++--- .../ContactForm/steps/ContentStep.tsx | 56 +++---- .../ContactForm/steps/DesignStep.tsx | 108 ++++---------- .../ContactForm/steps/FeaturesStep.tsx | 16 +- .../ContactForm/steps/FunctionsStep.tsx | 97 +++++++++--- .../ContactForm/steps/LanguageStep.tsx | 58 ++++---- .../ContactForm/steps/TimelineStep.tsx | 16 +- src/components/ContactForm/steps/TypeStep.tsx | 8 +- .../ContactForm/steps/WebAppStep.tsx | 139 ++++++++++++++++++ src/components/ContactForm/types.ts | 7 +- src/components/EstimationPDF.tsx | 69 ++++++--- 19 files changed, 540 insertions(+), 334 deletions(-) create mode 100644 src/components/ContactForm/steps/WebAppStep.tsx diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx index a2bd637..93893e4 100644 --- a/src/components/ContactForm.tsx +++ b/src/components/ContactForm.tsx @@ -24,6 +24,7 @@ import { ContentStep } from './ContactForm/steps/ContentStep'; import { LanguageStep } from './ContactForm/steps/LanguageStep'; import { TimelineStep } from './ContactForm/steps/TimelineStep'; import { ContactStep } from './ContactForm/steps/ContactStep'; +import { WebAppStep } from './ContactForm/steps/WebAppStep'; import { ConceptTarget, @@ -84,7 +85,11 @@ export function ContactForm() { languagesCount: state.languagesCount, deadline: state.deadline, designVibe: state.designVibe, - colorScheme: state.colorScheme + colorScheme: state.colorScheme, + targetAudience: state.targetAudience, + userRoles: state.userRoles, + dataSensitivity: state.dataSensitivity, + platformType: state.platformType }; const stateString = btoa(unescape(encodeURIComponent(JSON.stringify(configData)))); @@ -182,11 +187,22 @@ export function ContactForm() { { id: 'language', title: 'Sprachen', description: 'Globale Reichweite planen.', illustration: }, { id: 'timeline', title: 'Zeitplan', description: 'Wann soll das Projekt live gehen?', illustration: }, { id: 'contact', title: 'Abschluss', description: 'Erzählen Sie mir mehr über Ihr Vorhaben.', illustration: }, + { id: 'webapp', title: 'Web App Details', description: 'Spezifische Anforderungen für Ihre Anwendung.', illustration: }, ]; const activeSteps = useMemo(() => { - if (state.projectType === 'website') return steps; - return [steps[0], steps[9], steps[10]]; + if (state.projectType === 'website') { + return steps.filter(s => s.id !== 'webapp'); + } + // Web App flow + return [ + steps.find(s => s.id === 'type')!, + steps.find(s => s.id === 'webapp')!, + { ...steps.find(s => s.id === 'functions')!, title: 'Funktionen', description: 'Kern-Features Ihrer Anwendung.' }, + { ...steps.find(s => s.id === 'api')!, title: 'Integrationen', description: 'Anbindung an bestehende Systeme.' }, + steps.find(s => s.id === 'timeline')!, + steps.find(s => s.id === 'contact')!, + ]; }, [state.projectType]); useEffect(() => { @@ -218,6 +234,8 @@ export function ContactForm() { return ; case 'contact': return ; + case 'webapp': + return ; default: return null; } }; @@ -264,7 +282,7 @@ export function ContactForm() {
{activeSteps[stepIndex].illustration}
- Schritt {stepIndex + 1} von {activeSteps.length} + Schritt {stepIndex + 1} von {activeSteps.length}

{activeSteps[stepIndex].title}

{activeSteps[stepIndex].description}

@@ -291,7 +309,7 @@ export function ContactForm() { initial={{ opacity: 0, y: 5, x: '-50%' }} animate={{ opacity: 1, y: 0, x: '-50%' }} exit={{ opacity: 0, y: 5, x: '-50%' }} - className="absolute bottom-full left-1/2 mb-1 px-3 py-1.5 bg-slate-900 text-white text-[10px] font-bold uppercase tracking-wider rounded-lg whitespace-nowrap pointer-events-none z-50 shadow-xl" + className="absolute bottom-full left-1/2 mb-1 px-4 py-2 bg-slate-900 text-white text-sm font-bold uppercase tracking-wider rounded-lg whitespace-nowrap pointer-events-none z-50 shadow-xl" > {step.title}
@@ -303,16 +321,16 @@ export function ContactForm() {
-
+
{stepIndex > 0 ? ( - ) :
}
{stepIndex < activeSteps.length - 1 && ( - )}
@@ -321,8 +339,8 @@ export function ContactForm() {
{renderStepContent()}
- {stepIndex > 0 ? () :
} - {stepIndex < activeSteps.length - 1 ? () : ()} + {stepIndex > 0 ? () :
} + {stepIndex < activeSteps.length - 1 ? () : ()}
diff --git a/src/components/ContactForm/components/Checkbox.tsx b/src/components/ContactForm/components/Checkbox.tsx index 3ab56c0..1ceea8d 100644 --- a/src/components/ContactForm/components/Checkbox.tsx +++ b/src/components/ContactForm/components/Checkbox.tsx @@ -15,7 +15,7 @@ export function Checkbox({ label, desc, checked, onChange }: CheckboxProps) {
-

{label}

-

{desc}

+

{label}

+

{desc}

); diff --git a/src/components/ContactForm/components/PriceCalculation.tsx b/src/components/ContactForm/components/PriceCalculation.tsx index 7c8884f..598aee2 100644 --- a/src/components/ContactForm/components/PriceCalculation.tsx +++ b/src/components/ContactForm/components/PriceCalculation.tsx @@ -90,7 +90,7 @@ export function PriceCalculation({
-

Apps und Individual-Software werden nach tatsächlichem Aufwand abgerechnet.

+

Web Apps und Individual-Software werden nach tatsächlichem Aufwand abgerechnet.

{PRICING.APP_HOURLY} € / Std.

diff --git a/src/components/ContactForm/components/RepeatableList.tsx b/src/components/ContactForm/components/RepeatableList.tsx index d85aabd..986c221 100644 --- a/src/components/ContactForm/components/RepeatableList.tsx +++ b/src/components/ContactForm/components/RepeatableList.tsx @@ -20,8 +20,8 @@ export function RepeatableList({ }: RepeatableListProps) { const [input, setInput] = useState(''); return ( -
-
+
+
-
+
{items.map((item, i) => ( - {item} - ))} diff --git a/src/components/ContactForm/constants.tsx b/src/components/ContactForm/constants.tsx index 0df242f..19aa4a2 100644 --- a/src/components/ContactForm/constants.tsx +++ b/src/components/ContactForm/constants.tsx @@ -45,6 +45,10 @@ export const initialState: FormState = { expectedAdjustments: 'low', languagesCount: 1, deadline: 'flexible', + targetAudience: 'internal', + userRoles: [], + dataSensitivity: 'standard', + platformType: 'web-only', }; export const PAGE_SAMPLES = [ diff --git a/src/components/ContactForm/steps/ApiStep.tsx b/src/components/ContactForm/steps/ApiStep.tsx index c6eaec8..3dce25d 100644 --- a/src/components/ContactForm/steps/ApiStep.tsx +++ b/src/components/ContactForm/steps/ApiStep.tsx @@ -2,10 +2,8 @@ import * as React from 'react'; import { FormState } from '../types'; -import { API_OPTIONS } from '../constants'; import { Checkbox } from '../components/Checkbox'; import { RepeatableList } from '../components/RepeatableList'; -import { Info } from 'lucide-react'; interface ApiStepProps { state: FormState; @@ -14,33 +12,56 @@ interface ApiStepProps { } export function ApiStep({ state, updateState, toggleItem }: ApiStepProps) { + const isWebApp = state.projectType === 'web-app'; + return ( -
-
- -
-

Wichtig zu wissen

-

Ich biete diese Drittsysteme nicht selbst an, sondern entwickle die Schnittstelle (API), damit Ihre Website nahtlos mit ihnen kommunizieren kann.

+
+
+

+ {isWebApp ? 'Integrationen & Datenquellen' : 'Schnittstellen (API)'} +

+

+ {isWebApp + ? 'Mit welchen Systemen soll die Web App kommunizieren?' + : 'Datenaustausch mit Drittsystemen zur Automatisierung.'} +

+
+ updateState({ apiSystems: toggleItem(state.apiSystems, 'crm_erp') })} + /> + updateState({ apiSystems: toggleItem(state.apiSystems, 'payment') })} + /> + updateState({ apiSystems: toggleItem(state.apiSystems, 'marketing') })} + /> + updateState({ apiSystems: toggleItem(state.apiSystems, 'ecommerce') })} + />
-
- {API_OPTIONS.map(opt => ( - updateState({ apiSystems: toggleItem(state.apiSystems, opt.id) })} - /> - ))} -
-
-

Weitere Systeme?

- +

Weitere Systeme oder eigene APIs?

+ updateState({ otherTech: [...state.otherTech, v] })} - onRemove={(i) => updateState({ otherTech: state.otherTech.filter((_, idx) => idx !== i) })} - placeholder="z.B. Personio, DATEV, Salesforce..." + onRemove={(i) => updateTech(i)} + placeholder="z.B. Microsoft Graph, Google Maps, Custom REST API..." />
); + + function updateTech(index: number) { + updateState({ otherTech: state.otherTech.filter((_, idx) => idx !== index) }); + } } diff --git a/src/components/ContactForm/steps/AssetsStep.tsx b/src/components/ContactForm/steps/AssetsStep.tsx index f1f72e0..f49bff2 100644 --- a/src/components/ContactForm/steps/AssetsStep.tsx +++ b/src/components/ContactForm/steps/AssetsStep.tsx @@ -14,8 +14,8 @@ interface AssetsStepProps { export function AssetsStep({ state, updateState, toggleItem }: AssetsStepProps) { return ( -
-
+
+
{ASSET_OPTIONS.map(opt => ( ))}
-
-

Weitere Materialien?

- +

Weitere vorhandene Unterlagen?

+ updateState({ otherAssets: [...state.otherAssets, v] })} onRemove={(i) => updateState({ otherAssets: state.otherAssets.filter((_, idx) => idx !== i) })} - placeholder="z.B. Stock-Fotos, Video-Footage, Präsentationen..." + placeholder="z.B. Lastenheft, Wireframes, Bilddatenbank..." />
diff --git a/src/components/ContactForm/steps/BaseStep.tsx b/src/components/ContactForm/steps/BaseStep.tsx index 232b7b5..40340fe 100644 --- a/src/components/ContactForm/steps/BaseStep.tsx +++ b/src/components/ContactForm/steps/BaseStep.tsx @@ -2,10 +2,8 @@ import * as React from 'react'; import { FormState } from '../types'; -import { PAGE_SAMPLES } from '../constants'; import { Checkbox } from '../components/Checkbox'; import { RepeatableList } from '../components/RepeatableList'; -import { Info, FileText, Upload, X } from 'lucide-react'; interface BaseStepProps { state: FormState; @@ -15,66 +13,31 @@ interface BaseStepProps { export function BaseStep({ state, updateState, toggleItem }: BaseStepProps) { return ( -
-
- {PAGE_SAMPLES.map(p => ( +
+
+ {[ + { 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.' }, + ].map(opt => ( updateState({ selectedPages: toggleItem(state.selectedPages, p.id) })} + key={opt.id} label={opt.label} desc={opt.desc} + checked={state.selectedPages.includes(opt.id)} + onChange={() => updateState({ selectedPages: toggleItem(state.selectedPages, opt.id) })} /> ))}
-
-
-

Weitere Seiten?

- updateState({ otherPages: [...state.otherPages, v] })} - onRemove={(i) => updateState({ otherPages: state.otherPages.filter((_, idx) => idx !== i) })} - placeholder="z.B. Team-Detail, FAQ..." - /> -
-
-

Sitemap hochladen (optional)

-
{ e.preventDefault(); e.stopPropagation(); }} - onDrop={(e) => { - e.preventDefault(); - e.stopPropagation(); - const file = e.dataTransfer.files?.[0]; - if (file) updateState({ sitemapFile: file }); - }} - onClick={() => document.getElementById('sitemap-upload')?.click()} - > - { - const file = e.target.files?.[0]; - if (file) updateState({ sitemapFile: file }); - }} /> - {state.sitemapFile ? ( -
- - {state.sitemapFile.name} - -
- ) : ( - <> - -

Sitemap hierher ziehen oder klicken

- - )} -
-
-
-
- -
-

Was zählt als Seite?

-

Eine Seite ist ein eigenständiges Layout. Wenn Sie 10 Leistungen haben, die alle das gleiche Layout nutzen, zählt das als 1 Seite plus ein "System-Modul" für die Verwaltung der Leistungen.

-
+
+

Weitere individuelle Seiten?

+ updateState({ otherPages: [...state.otherPages, v] })} + onRemove={(i) => updateState({ otherPages: state.otherPages.filter((_, idx) => idx !== i) })} + placeholder="z.B. Karriere, FAQ, Team-Detail..." + />
); diff --git a/src/components/ContactForm/steps/ContactStep.tsx b/src/components/ContactForm/steps/ContactStep.tsx index 7b517ae..8e5a0fb 100644 --- a/src/components/ContactForm/steps/ContactStep.tsx +++ b/src/components/ContactForm/steps/ContactStep.tsx @@ -11,18 +11,44 @@ interface ContactStepProps { export function ContactStep({ state, updateState }: ContactStepProps) { return ( -
-
- updateState({ name: e.target.value })} className="w-full p-8 bg-white border border-slate-100 rounded-[2rem] focus:outline-none focus:border-slate-900 transition-colors rounded-[2rem]" /> - updateState({ email: e.target.value })} className="w-full p-8 bg-white border border-slate-100 rounded-[2rem] focus:outline-none focus:border-slate-900 transition-colors rounded-[2rem]" /> +
+
+ updateState({ name: e.target.value })} + className="w-full p-8 bg-white border border-slate-100 rounded-[2rem] focus:outline-none focus:border-slate-900 transition-colors text-lg" + /> + updateState({ email: e.target.value })} + className="w-full p-8 bg-white border border-slate-100 rounded-[2rem] focus:outline-none focus:border-slate-900 transition-colors text-lg" + />
- updateState({ role: e.target.value })} className="w-full p-8 bg-white border border-slate-100 rounded-[2rem] focus:outline-none focus:border-slate-900 transition-colors rounded-[2rem]" /> -