import { useRef, useState } from 'react'; import { View, ScrollView, Text, Alert, findNodeHandle, UIManager } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { AppHeader } from '../../components/AppHeader'; import { ProfileHeader, type AuthProvider } from '../../components/profile/ProfileHeader'; import { StatsBar } from '../../components/profile/StatsBar'; import { ApprovedDomainsList } from '../../components/profile/ApprovedDomainsList'; import { StreakSection, type CooldownEntry } from '../../components/profile/StreakSection'; import { UrgeStatsCard, type HelpedByEntry } from '../../components/profile/UrgeStatsCard'; import { DemographicsAccordion, type Demographics } from '../../components/profile/DemographicsAccordion'; import { DigaMissionBanner } from '../../components/profile/DigaMissionBanner'; import { useColors } from '../../lib/theme'; import type { Plan } from '../../hooks/useUserPlan'; import { useMe } from '../../hooks/useMe'; import { useAuthStore } from '../../stores/auth'; import { useSocialStats, useApprovedDomains, useCooldownHistory, useSosInsights, useDemographics, } from '../../hooks/useProfileData'; import { apiFetch } from '../../lib/api'; const EMPTY_COOLDOWNS: CooldownEntry[] = []; function isDemographicsComplete(d: Demographics): boolean { const base = d.birthYear !== null && !!d.gender && !!d.maritalStatus && !!d.employmentStatus && !!d.bundesland && !!d.city; if (!base) return false; const status = d.employmentStatus!; const needsShift = ['employed', 'self_employed'].includes(status); const needsIndustry = ['employed', 'self_employed', 'in_training'].includes(status); const needsTenure = ['employed', 'self_employed'].includes(status); if (needsShift && d.shiftWork === null) return false; if (needsIndustry && !d.industry) return false; if (needsTenure && !d.jobTenure) return false; return true; } const EMPTY_DEMOGRAPHICS: Demographics = { birthYear: null, gender: null, maritalStatus: null, employmentStatus: null, shiftWork: null, industry: null, jobTenure: null, bundesland: null, city: null, }; function formatMemberSince(isoString: string | undefined): string { if (!isoString) return ''; const d = new Date(isoString); return d.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' }); } function formatStreakStartDate(isoString: string | undefined): string { if (!isoString) return ''; const d = new Date(isoString); return d.toLocaleDateString('de-DE', { day: 'numeric', month: 'long', year: 'numeric' }); } function mapHelpedBy(helpedBy: { breathing: number; game: number; talk: number; other: number; }): HelpedByEntry[] { const entries: HelpedByEntry[] = [ { key: 'breathing', label: 'Atemübung', count: helpedBy.breathing }, { key: 'game', label: 'Spiel', count: helpedBy.game }, { key: 'talk', label: 'Reden mit Lyra', count: helpedBy.talk }, { key: 'other', label: 'Sonstiges', count: helpedBy.other }, ]; return entries.filter((e) => e.count > 0); } export default function ProfileScreen() { const router = useRouter(); const insets = useSafeAreaInsets(); const colors = useColors(); const [bannerDismissed, setBannerDismissed] = useState(false); const [demographicsExpanded, setDemographicsExpanded] = useState(false); const { me } = useMe(); const { user } = useAuthStore(); const { stats: socialStats } = useSocialStats(me?.id); const { domains: approvedDomainsData } = useApprovedDomains(); const { cooldownHistory } = useCooldownHistory(); const { sosInsights } = useSosInsights(); const { demographics: serverDemographics, withdrawnAt, reload: reloadDemographics, } = useDemographics(); const demographics: Demographics = serverDemographics ?? EMPTY_DEMOGRAPHICS; const scrollViewRef = useRef(null); const demographicsAnchorRef = useRef(null); const provider: AuthProvider = ((user?.app_metadata as { provider?: string } | undefined)?.provider as AuthProvider) ?? 'email'; const profile = { nickname: me?.nickname ?? user?.email?.split('@')[0] ?? 'User', email: user?.email ?? '', avatar: me?.avatar ?? null, plan: ((me as { plan?: string } | null | undefined)?.plan ?? 'free') as Plan, memberSince: formatMemberSince(me?.created_at), provider, }; const currentStreak = me?.streak ?? 0; // TODO(backend): longestDays + streakStartDate fehlen in /api/auth/me. // Backend-Agent: Profile-Tabelle braucht longestStreak:Int + streakStartedAt:DateTime. // Tracking: streakStartedAt wird bei jedem Streak-Reset auf NOW() gesetzt. const longestDays = currentStreak; const streakStartDate = formatStreakStartDate(me?.created_at); const showDigaBanner = currentStreak >= 30 && !bannerDismissed; const demoComplete = !withdrawnAt && isDemographicsComplete(demographics); function scrollToDemographics() { const node = demographicsAnchorRef.current; const scroll = scrollViewRef.current; if (!node || !scroll) return; const handle = findNodeHandle(node); const scrollHandle = findNodeHandle(scroll); if (!handle || !scrollHandle) return; UIManager.measureLayout( handle, scrollHandle, () => {}, (_x, y) => { scroll.scrollTo({ y: Math.max(0, y - 16), animated: true }); }, ); } function openDemographics() { setDemographicsExpanded(true); scrollToDemographics(); } return ( router.push('/profile/edit')} onEditNickname={() => router.push('/profile/edit')} /> {}} onFollowersPress={() => {}} onApprovedDomainsPress={() => {}} /> {showDigaBanner ? ( { setBannerDismissed(true); apiFetch('/api/profile/me/diga-banner-dismiss', { method: 'POST' }).catch(() => {}); }} onContribute={() => { setBannerDismissed(true); scrollToDemographics(); }} /> ) : null} { try { const result = await apiFetch<{ trialAwarded: boolean; expiresAt: string | null }>( '/api/profile/me/demographics', { method: 'PATCH', body: next }, ); reloadDemographics(); if (result.trialAwarded) { Alert.alert( 'Pro-Woche freigeschaltet', 'Danke fur deine DiGA-Daten. Du hast 7 Tage Pro kostenlos erhalten.', ); } } catch { // write failed — optimistic update not applied, server state preserved } }} onRevokeConsent={() => { Alert.alert( 'Daten zuruckziehen', 'Alle demografischen Angaben werden geloscht. Fortfahren?', [ { text: 'Abbrechen', style: 'cancel' }, { text: 'Loschen', style: 'destructive', onPress: () => { apiFetch('/api/profile/me/demographics', { method: 'DELETE' }) .then(() => reloadDemographics()) .catch(() => {}); }, }, ], ); }} /> ); }