Extract the monolithic company-profile wizard into _components/ and _hooks/ following Next.js 15 conventions from AGENTS.typescript.md: - _components/constants.ts: wizard steps, legal forms, industries, certifications - _components/types.ts: local interfaces (ProcessingActivity, AISystem, etc.) - _components/activity-data.ts: DSGVO data categories, department/activity templates - _components/ai-system-data.ts: AI system template catalog - _components/StepBasicInfo.tsx: step 1 (company name, legal form, industry) - _components/StepBusinessModel.tsx: step 2 (B2B/B2C, offerings) - _components/StepCompanySize.tsx: step 3 (size, revenue) - _components/StepLocations.tsx: step 4 (headquarters, target markets) - _components/StepDataProtection.tsx: step 5 (DSGVO roles, DPO) - _components/StepProcessing.tsx: processing activities with category checkboxes - _components/StepAISystems.tsx: AI system inventory - _components/StepLegalFramework.tsx: certifications and contacts - _components/StepMachineBuilder.tsx: machine builder profile (step 7) - _components/ProfileSummary.tsx: completion summary view - _hooks/useCompanyProfileForm.ts: form state, auto-save, navigation logic - page.tsx: thin orchestrator (160 LOC), imports and composes sections All 16 files are under 500 LOC (largest: StepProcessing at 343). Build verified: npx next build passes cleanly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
178 lines
6.9 KiB
TypeScript
178 lines
6.9 KiB
TypeScript
'use client'
|
|
|
|
import { CompanyProfile, TargetMarket, TARGET_MARKET_LABELS } from '@/lib/sdk/types'
|
|
|
|
const STATES_BY_COUNTRY: Record<string, { label: string; options: string[] }> = {
|
|
DE: {
|
|
label: 'Bundesland',
|
|
options: [
|
|
'Baden-W\u00FCrttemberg', 'Bayern', 'Berlin', 'Brandenburg', 'Bremen',
|
|
'Hamburg', 'Hessen', 'Mecklenburg-Vorpommern', 'Niedersachsen',
|
|
'Nordrhein-Westfalen', 'Rheinland-Pfalz', 'Saarland', 'Sachsen',
|
|
'Sachsen-Anhalt', 'Schleswig-Holstein', 'Th\u00FCringen',
|
|
],
|
|
},
|
|
AT: {
|
|
label: 'Bundesland',
|
|
options: [
|
|
'Burgenland', 'K\u00E4rnten', 'Nieder\u00F6sterreich', 'Ober\u00F6sterreich',
|
|
'Salzburg', 'Steiermark', 'Tirol', 'Vorarlberg', 'Wien',
|
|
],
|
|
},
|
|
CH: {
|
|
label: 'Kanton',
|
|
options: [
|
|
'Aargau', 'Appenzell Ausserrhoden', 'Appenzell Innerrhoden',
|
|
'Basel-Landschaft', 'Basel-Stadt', 'Bern', 'Freiburg', 'Genf',
|
|
'Glarus', 'Graub\u00FCnden', 'Jura', 'Luzern', 'Neuenburg', 'Nidwalden',
|
|
'Obwalden', 'Schaffhausen', 'Schwyz', 'Solothurn', 'St. Gallen',
|
|
'Tessin', 'Thurgau', 'Uri', 'Waadt', 'Wallis', 'Zug', 'Z\u00FCrich',
|
|
],
|
|
},
|
|
}
|
|
|
|
export function StepLocations({
|
|
data,
|
|
onChange,
|
|
}: {
|
|
data: Partial<CompanyProfile>
|
|
onChange: (updates: Partial<CompanyProfile>) => void
|
|
}) {
|
|
const toggleMarket = (market: TargetMarket) => {
|
|
const current = data.targetMarkets || []
|
|
if (current.includes(market)) {
|
|
onChange({ targetMarkets: current.filter(m => m !== market) })
|
|
} else {
|
|
onChange({ targetMarkets: [...current, market] })
|
|
}
|
|
}
|
|
|
|
const countryStates = data.headquartersCountry ? STATES_BY_COUNTRY[data.headquartersCountry] : null
|
|
const stateLabel = countryStates?.label || 'Region / Provinz'
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
{/* Country */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Land des Hauptsitzes <span className="text-red-500">*</span>
|
|
</label>
|
|
<select
|
|
value={data.headquartersCountry || ''}
|
|
onChange={e => onChange({ headquartersCountry: e.target.value, headquartersCountryOther: '' })}
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
>
|
|
<option value="">Bitte wählen...</option>
|
|
<option value="DE">Deutschland</option>
|
|
<option value="AT">Österreich</option>
|
|
<option value="CH">Schweiz</option>
|
|
<option value="LI">Liechtenstein</option>
|
|
<option value="LU">Luxemburg</option>
|
|
<option value="NL">Niederlande</option>
|
|
<option value="FR">Frankreich</option>
|
|
<option value="IT">Italien</option>
|
|
<option value="other">Anderes Land</option>
|
|
</select>
|
|
</div>
|
|
|
|
{data.headquartersCountry === 'other' && (
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">Land (Freitext)</label>
|
|
<input
|
|
type="text"
|
|
value={data.headquartersCountryOther || ''}
|
|
onChange={e => onChange({ headquartersCountryOther: e.target.value })}
|
|
placeholder="z.B. Vereinigtes Königreich"
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Street + House Number */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">Straße und Hausnummer</label>
|
|
<input
|
|
type="text"
|
|
value={data.headquartersStreet || ''}
|
|
onChange={e => onChange({ headquartersStreet: e.target.value })}
|
|
placeholder="Musterstraße 42"
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
|
|
{/* PLZ + City */}
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">PLZ</label>
|
|
<input
|
|
type="text"
|
|
value={data.headquartersZip || ''}
|
|
onChange={e => onChange({ headquartersZip: e.target.value })}
|
|
placeholder="10115"
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">Stadt</label>
|
|
<input
|
|
type="text"
|
|
value={data.headquartersCity || ''}
|
|
onChange={e => onChange({ headquartersCity: e.target.value })}
|
|
placeholder="Berlin"
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* State / Bundesland / Kanton */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{stateLabel}</label>
|
|
{countryStates ? (
|
|
<select
|
|
value={data.headquartersState || ''}
|
|
onChange={e => onChange({ headquartersState: e.target.value })}
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
>
|
|
<option value="">Bitte wählen...</option>
|
|
{countryStates.options.map(s => (
|
|
<option key={s} value={s}>{s}</option>
|
|
))}
|
|
</select>
|
|
) : (
|
|
<input
|
|
type="text"
|
|
value={data.headquartersState || ''}
|
|
onChange={e => onChange({ headquartersState: e.target.value })}
|
|
placeholder="Region / Provinz"
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-4">
|
|
Zielmärkte <span className="text-red-500">*</span>
|
|
<span className="text-gray-400 font-normal ml-2">Wo verkaufen/operieren Sie?</span>
|
|
</label>
|
|
<div className="space-y-3">
|
|
{Object.entries(TARGET_MARKET_LABELS).map(([value, { label, description }]) => (
|
|
<button
|
|
key={value}
|
|
type="button"
|
|
onClick={() => toggleMarket(value as TargetMarket)}
|
|
className={`w-full p-4 rounded-xl border-2 text-left transition-all ${
|
|
(data.targetMarkets || []).includes(value as TargetMarket)
|
|
? 'border-purple-500 bg-purple-50'
|
|
: 'border-gray-200 hover:border-purple-300'
|
|
}`}
|
|
>
|
|
<div className="font-medium text-gray-900">{label}</div>
|
|
<div className="text-sm text-gray-500">{description}</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|