feat: added new onboarding step (Dialektzugehörigkeit)

This commit is contained in:
averel10
2026-04-17 07:55:43 +02:00
parent f0c48efc66
commit 89d55f982b

View File

@@ -12,7 +12,7 @@ interface OnboardingFormViewProps {
} }
interface ResidenceEntry { interface ResidenceEntry {
region: string; canton: string;
years: string; years: string;
} }
@@ -20,12 +20,37 @@ interface FormData {
age?: string; age?: string;
participateInRaffle?: boolean; participateInRaffle?: boolean;
residences?: ResidenceEntry[]; residences?: ResidenceEntry[];
ownDialectRegion?: string;
dialectQualities?: Record<string, string>; dialectQualities?: Record<string, string>;
listeningExperience?: string; listeningExperience?: string;
additionalNotes?: string; additionalNotes?: string;
} }
type Step = 1 | 2 | 3; type Step = 1 | 2 | 3 | 4;
const AVAILABLE_CANTONS = {
ag: 'Aargau',
ar: 'Appenzell Ausserrhoden',
ai: 'Appenzell Innerrhoden',
bl: 'Basel-Landschaft',
bs: 'Basel-Stadt',
be: 'Bern',
gl: 'Glarus',
gr: 'Graubünden',
lu: 'Luzern',
nw: 'Nidwalden',
ow: 'Obwalden',
sh: 'Schaffhausen',
sz: 'Schwyz',
so: 'Solothurn',
sg: 'St. Gallen',
tg: 'Thurgau',
ur: 'Uri',
vs: 'Wallis',
zg: 'Zug',
zh: 'Zürich',
other: 'Anderer Kanton/Ausland'
};
export default function OnboardingFormView({ experimentId, onBack, userEmail }: OnboardingFormViewProps) { export default function OnboardingFormView({ experimentId, onBack, userEmail }: OnboardingFormViewProps) {
const router = useRouter(); const router = useRouter();
@@ -39,7 +64,8 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
const [formData, setFormData] = useState<FormData>({ const [formData, setFormData] = useState<FormData>({
age: '', age: '',
participateInRaffle: false, participateInRaffle: false,
residences: [{ region: '', years: '' }], residences: [{ canton: '', years: '' }],
ownDialectRegion: '',
dialectQualities: initializeDialectQualities() dialectQualities: initializeDialectQualities()
}); });
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -84,7 +110,7 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
const addResidence = () => { const addResidence = () => {
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
residences: [...(prev.residences || []), { region: '', years: '' }], residences: [...(prev.residences || []), { canton: '', years: '' }],
})); }));
}; };
@@ -99,20 +125,27 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
switch (step) { switch (step) {
case 1: case 1:
if (!formData.age) { if (!formData.age) {
setError('Bitte wählen Sie eine Altersgruppe aus.'); setError('Bitte wählen deine Altersgruppe aus.');
return false; return false;
} }
break; break;
case 2: case 2:
const hasValidResidence = formData.residences?.some( const allResidencesValid = formData.residences?.every((r) => {
(r) => r.region && r.years const years = parseInt(r.years);
); return r.canton && r.years && years >= 1 && years <= 100;
if (!hasValidResidence) { });
setError('Bitte fügen Sie mindestens einen Wohnort mit Jahresangabe hinzu.'); if (!allResidencesValid) {
setError('Bitte füllen alle deine Aufenthaltsorte vollständig aus und stelle sicher, dass die Jahres-Angaben zwischen 1 und 100 liegen.');
return false; return false;
} }
break; break;
case 3: case 3:
if (!formData.ownDialectRegion) {
setError('Bitte wählen deine Region deines eigenen Dialekts aus.');
return false;
}
break;
case 4:
const allDialects = Object.keys(DIALECT_LABELS_WITHOUT_DE); const allDialects = Object.keys(DIALECT_LABELS_WITHOUT_DE);
const hasAllDialectQualities = allDialects.every( const hasAllDialectQualities = allDialects.every(
(region) => !!formData.dialectQualities?.[region] (region) => !!formData.dialectQualities?.[region]
@@ -129,7 +162,7 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
const handleNext = () => { const handleNext = () => {
setError(null); setError(null);
if (validateStep(currentStep)) { if (validateStep(currentStep)) {
if (currentStep < 3) { if (currentStep < 4) {
setCurrentStep((currentStep + 1) as Step); setCurrentStep((currentStep + 1) as Step);
} }
} }
@@ -145,6 +178,9 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
}; };
const handleSubmit = () => { const handleSubmit = () => {
if(!validateStep(4)) {
return;
}
setError(null); setError(null);
startTransition(async () => { startTransition(async () => {
@@ -161,8 +197,9 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
const stepTitles: Record<Step, string> = { const stepTitles: Record<Step, string> = {
1: 'Persönliche Informationen', 1: 'Persönliche Informationen',
2: 'Aufenthaltsorte', 2: 'Aufenthaltsorte in der Deutschschweiz',
3: 'Selbsteinschätzung', 3: 'Dialektzuordnung',
4: 'Selbsteinschätzung',
}; };
return ( return (
@@ -173,14 +210,14 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
<div className="mb-8"> <div className="mb-8">
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
<h2 className="text-sm font-semibold text-gray-600"> <h2 className="text-sm font-semibold text-gray-600">
Schritt {currentStep} von 3 Schritt {currentStep} von 4
</h2> </h2>
<div className="text-sm text-gray-500">{Math.round((currentStep / 3) * 100)}%</div> <div className="text-sm text-gray-500">{Math.round((currentStep / 4) * 100)}%</div>
</div> </div>
<div className="w-full h-2 bg-gray-200 rounded-full overflow-hidden"> <div className="w-full h-2 bg-gray-200 rounded-full overflow-hidden">
<div <div
className="h-full bg-blue-600 transition-all duration-300" className="h-full bg-blue-600 transition-all duration-300"
style={{ width: `${(currentStep / 3) * 100}%` }} style={{ width: `${(currentStep / 4) * 100}%` }}
/> />
</div> </div>
</div> </div>
@@ -204,7 +241,7 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<label htmlFor="age" className="block text-s font-medium text-gray-900 mb-2"> <label htmlFor="age" className="block text-s font-medium text-gray-900 mb-2">
Altersgruppe <span className="text-red-600">*</span> Meine Altersgruppe <span className="text-red-600">*</span>
</label> </label>
<select <select
id="age" id="age"
@@ -249,21 +286,17 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
{currentStep === 2 && ( {currentStep === 2 && (
<div className="space-y-6"> <div className="space-y-6">
<p className="text-s text-gray-600 mb-8"> <p className="text-s text-gray-600 mb-8">
Bitte gib die Regionen an, in denen du länger als 1 Jahr gelebt hast, sowie die Anzahl der Jahre. So können wir deine Dialektkenntnisse besser einschätzen. Bitte gib an, in welchen deutschschweizer Kantonen du länger als 1 Jahr gelebt hast und wie viele Jahre insgesamt.
<br />
Wenn du mehrmals im gleichen Kanton gewohnt hast, zähle die Zeit zusammen und mache nur einen Eintrag pro Kanton.
<br />
Falls ein Kanton nicht in der Liste ist oder du im Ausland gelebt hast, wähle Anderer Kanton/Ausland
</p> </p>
{/* Regions Map */}
<div className="rounded-lg overflow-hidden border border-gray-200">
<img
src="/swiss_regions_map.png"
alt="Schweizer Dialektregionen"
className="w-full h-auto"
/>
</div>
{/* Residence Entries */} {/* Residence Entries */}
<div> <div>
<label className="block text-s font-medium text-gray-900 mb-4"> <label className="block text-s font-medium text-gray-900 mb-4">
Aufenthaltsorte (mind. 1 Jahr) <span className="text-red-600">*</span> Meine Aufenthaltsorte (mind. 1 Jahr) <span className="text-red-600">*</span>
</label> </label>
<div className="space-y-4"> <div className="space-y-4">
@@ -271,22 +304,33 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
<div key={index} className="flex gap-3 items-end"> <div key={index} className="flex gap-3 items-end">
<div className="flex-1"> <div className="flex-1">
<label className="block text-xs font-medium text-gray-700 mb-1"> <label className="block text-xs font-medium text-gray-700 mb-1">
Region Kanton
</label> </label>
<select <select
value={residence.region} value={residence.canton}
onChange={(e) => onChange={(e) =>
handleResidenceChange(index, 'region', e.target.value) handleResidenceChange(index, 'canton', e.target.value)
} }
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
> >
<option value="">-- Wählen --</option> <option value="">-- Wählen --</option>
{Object.entries(DIALECT_LABELS_WITHOUT_DE).map( {Object.entries(AVAILABLE_CANTONS).map(
([key, label]) => ( ([key, label]) => {
<option key={key} value={key}> // Hide cantons already selected in other residence entries
{label} const isSelectedElsewhere = formData.residences?.some(
</option> (r, i) => i !== index && r.canton === key
) );
if (isSelectedElsewhere) {
return null;
}
return (
<option key={key} value={key}>
{label}
</option>
);
}
)} )}
</select> </select>
</div> </div>
@@ -312,7 +356,7 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
type="button" type="button"
onClick={() => removeResidence(index)} onClick={() => removeResidence(index)}
disabled={(formData.residences?.length || 0) <= 1} disabled={(formData.residences?.length || 0) <= 1}
className="px-3 py-2 bg-red-100 hover:bg-red-200 disabled:bg-gray-100 text-red-600 disabled:text-gray-400 font-medium rounded-lg transition-colors text-sm" className="px-3 py-2 bg-blue-100 hover:bg-blue-200 disabled:bg-gray-100 text-blue-600 disabled:text-gray-400 font-medium rounded-lg transition-colors text-sm"
> >
</button> </button>
@@ -331,11 +375,52 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
</div> </div>
)} )}
{/* Step 3: Selbsteinschätzung */} {/* Step 3: Dialektzuordnung */}
{currentStep === 3 && ( {currentStep === 3 && (
<div className="space-y-6"> <div className="space-y-6">
<p className="text-s text-gray-600 mb-8"> <p className="text-s text-gray-600 mb-8">
Bewerte deine Fähigkeit, die folgenden Dialekte zu erkennen. Diese Selbsteinschätzung hilft uns, deine Kalibrierungsergebnisse besser zu interpretieren. Zu welcher Region würdest du deinen eigenen Dialekt am ehesten zuordnen?
<br />
Orientiere dich an der folgenden Karte der deutschschweizer Dialektregionen.
<br />
Wenn du keinen deutschschweizer Dialekt sprichst, wähle "Ich spreche keinen deutschschweizer Dialekt" aus.
</p>
<div className="flex justify-center mb-8">
<img
src="/swiss_regions_map.png"
alt="Karte der deutschschweizer Dialektregionen"
className="w-full max-w-md h-auto"
/>
</div>
<div>
<label htmlFor="ownDialect" className="block text-s font-medium text-gray-900 mb-4">
Meine Dialektregion <span className="text-red-600">*</span>
</label>
<select
id="ownDialect"
value={formData.ownDialectRegion || ''}
onChange={(e) => handleChange('ownDialectRegion', e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="">-- Bitte wählen --</option>
{Object.entries(DIALECT_LABELS_WITHOUT_DE).map(([key, label]) => (
<option key={key} value={key}>
{label}
</option>
))}
<option value="none">Ich spreche keinen deutschschweizer Dialekt</option>
</select>
</div>
</div>
)}
{/* Step 4: Selbsteinschätzung */}
{currentStep === 4 && (
<div className="space-y-6">
<p className="text-s text-gray-600 mb-8">
Bewerte deine Fähigkeit, die folgenden deutschschweizer Dialekte zu erkennen. Diese Selbsteinschätzung hilft uns, deine Kalibrierungsergebnisse besser zu interpretieren.
</p> </p>
<ul className="text-s text-gray-600 mb-8 space-y-1"> <ul className="text-s text-gray-600 mb-8 space-y-1">
@@ -404,7 +489,7 @@ export default function OnboardingFormView({ experimentId, onBack, userEmail }:
Zurück Zurück
</button> </button>
{currentStep < 3 ? ( {currentStep < 4 ? (
<button <button
type="button" type="button"
onClick={handleNext} onClick={handleNext}