import { useState } from 'react'; import { Text, TextInput, View } from 'react-native'; import { useTranslation } from 'react-i18next'; import { Ionicons } from '@expo/vector-icons'; import { useColors } from '../../../lib/theme'; import { apiFetch } from '../../../lib/api'; import { invalidateMe } from '../../../hooks/useMe'; import { OnboardingShell } from '../OnboardingShell'; import { LyraBubble } from '../LyraBubble'; import { CTABar } from '../CTABar'; type RedeemError = 'not_found' | 'already_used' | 'expired' | 'invalid_input'; /** * Live-Format-Mask für DiGA-Codes. User tippt "REBREAKTEST001" → wird * automatisch zu "REBREAK-TEST-001". Strip-then-segment: * 1. Alles außer alphanumerisch entfernen * 2. Erste 7 Zeichen = "REBREAK"-Block, Rest in Gruppen * * Liberal: erlaubt User auch händisch Dashes zu setzen (wird neu segmentiert). */ function formatDigaCode(raw: string): string { const clean = raw .toUpperCase() .replace(/[^A-Z0-9]/g, '') .slice(0, 18); // 7 (REBREAK) + 4 (TEST/RXxx) + 7 (xxx-xxx) ohne Dashes if (clean.length <= 7) return clean; // Block 1: REBREAK (7 chars) const block1 = clean.slice(0, 7); if (clean.length <= 11) return `${block1}-${clean.slice(7)}`; // Block 2: TEST oder ähnliches (4 chars) const block2 = clean.slice(7, 11); // Block 3: laufende Nummer const block3 = clean.slice(11); return `${block1}-${block2}-${block3}`; } export function DigaCodeSlide({ onSuccess, onBack, current, total, }: { /** Wird gerufen wenn der Code erfolgreich eingelöst wurde. Backend hat dann * plan='legend' + onboarding_step='pre_protection' gesetzt. */ onSuccess: () => void; /** Zurück zum DigaChoiceSlide (User hat sich's anders überlegt). */ onBack: () => void; current: number; total: number; }) { const { t } = useTranslation(); const colors = useColors(); const [code, setCode] = useState(''); const [submitting, setSubmitting] = useState(false); const [errorKey, setErrorKey] = useState(null); const trimmed = code.trim(); const valid = trimmed.length >= 6; async function redeem() { if (!valid || submitting) return; setSubmitting(true); setErrorKey(null); try { await apiFetch('/api/onboarding/redeem-diga-code', { method: 'POST', body: { code: trimmed }, }); invalidateMe(); onSuccess(); } catch (e: any) { // apiFetch wirft Error mit `code` Feld bei strukturierten 4xx const code = (e?.code ?? e?.data?.error) as RedeemError | undefined; setErrorKey(code ?? 'not_found'); } finally { setSubmitting(false); } } return ( } > {t('onboarding.diga_code.label')} { setCode(formatDigaCode(v)); if (errorKey) setErrorKey(null); }} onSubmitEditing={redeem} placeholder="REBREAK-XXXX-XXX" placeholderTextColor="#a3a3a3" autoCapitalize="characters" autoCorrect={false} maxLength={32} returnKeyType="done" style={{ fontSize: 16, lineHeight: 22, paddingVertical: 14, paddingHorizontal: 16, color: colors.text, fontFamily: 'Nunito_700Bold', letterSpacing: 1, backgroundColor: colors.surfaceElevated, borderRadius: 12, borderWidth: 2, borderColor: errorKey ? colors.error : valid ? colors.brandOrange : 'transparent', }} /> {errorKey ? ( {t(`onboarding.diga_code.error_${errorKey}`)} ) : ( {t('onboarding.diga_code.hint')} )} ); }