import { useRef, useState } from 'react'; import { View, ScrollView, Text, Alert, findNodeHandle, UIManager } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { AppHeader } from '../../components/AppHeader'; import { ProfileHeader, type AuthProvider } from '../../components/profile/ProfileHeader'; import { StatsBar } from '../../components/profile/StatsBar'; import { ApprovedDomainsList, type ApprovedDomain } 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 { colors } from '../../lib/theme'; import type { Plan } from '../../hooks/useUserPlan'; import { useMe } from '../../hooks/useMe'; import { useAuthStore } from '../../stores/auth'; // TODO Phase C: GET /api/profile/me — aggregate endpoint (profile + stats + streak + // recentCooldowns + demographics + sosInsights). Until backend live: // - Core User-Felder (nickname/email/avatar/plan) kommen aus useMe-Hook (live) // - Stats/Streak/Cooldowns/Demographics bleiben dummy const DUMMY_PROFILE_FALLBACK = { memberSince: 'April 2026', // TODO Phase C: aus profile.created_at provider: 'email' as AuthProvider, // TODO Phase C: aus user.app_metadata.provider }; const DUMMY_STATS = { postsCount: 12, followersCount: 47, // Approved Domains = Community-Beitrag (KEIN Plan-Slot, kein Cap). // Source: domain_submissions WHERE userId=me AND status='approved'. // TODO Phase C: GET /api/profile/me/approved-domains (Endpoint existiert noch NICHT // — gefunden wurden nur admin-side aggregate counts in // backend/server/api/admin/stats.get.ts und backend/server/api/blocklist/stats.get.ts). // Neuer Endpoint nötig: GET /api/profile/me/approved-domains → { count, list[] }. approvedDomainsCount: 5, }; const DUMMY_STREAK = { currentDays: 23, longestDays: 41, startDate: '14. April 2026', }; // TODO: GET /api/profile/me/cooldown-history?cursor=... const DUMMY_COOLDOWNS: CooldownEntry[] = [ { id: 'c1', startedAt: '06.05.', durationLabel: '24h', status: 'active', reason: null, }, { id: 'c2', startedAt: '02.05.', durationLabel: '4h', status: 'cancelled', reason: null, }, { id: 'c3', startedAt: '18.04.', durationLabel: '16h', status: 'resolved', reason: 'Stress nach Arbeit', }, ]; // TODO: GET /api/profile/me/approved-domains const DUMMY_APPROVED_DOMAINS: ApprovedDomain[] = [ { domain: 'tipico.de', approvedAt: '12.04.' }, { domain: 'bwin.com', approvedAt: '15.04.' }, { domain: 'merkur24.com', approvedAt: '20.04.' }, { domain: 'sunmaker.com', approvedAt: '28.04.' }, { domain: 'lottoland.com', approvedAt: '02.05.' }, ]; // TODO: GET /api/profile/me/sos-insights const DUMMY_HELPED_BY: HelpedByEntry[] = [ { key: 'breathing', label: 'Atemübung', count: 3 }, { key: 'game', label: 'Spiel', count: 1 }, { key: 'talk', label: 'Reden mit Lyra', count: 1 }, ]; // TODO: GET /api/profile/me/demographics — gehört zur me-aggregat-response const DUMMY_DEMOGRAPHICS: Demographics = { birthYear: 1989, gender: 'diverse', maritalStatus: null, profession: null, bundesland: 'BY', city: null, }; function isDemographicsComplete(d: Demographics): boolean { return ( d.birthYear !== null && !!d.gender && !!d.maritalStatus && !!d.profession && !!d.bundesland && !!d.city ); } export default function ProfileScreen() { const insets = useSafeAreaInsets(); const [bannerDismissed, setBannerDismissed] = useState(false); const [demographics, setDemographics] = useState(DUMMY_DEMOGRAPHICS); const [demographicsExpanded, setDemographicsExpanded] = useState(false); const { me } = useMe(); const { user } = useAuthStore(); const scrollViewRef = useRef(null); const demographicsAnchorRef = useRef(null); // Live-Daten aus DB (für Avatar / Nickname / Plan / Email). // Provider-Detection: user.app_metadata.provider vom Supabase-OAuth-Flow. 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: DUMMY_PROFILE_FALLBACK.memberSince, provider, }; const showDigaBanner = DUMMY_STREAK.currentDays >= 30 && !bannerDismissed; const demoComplete = 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, () => { // measure failure — silent }, (_x, y) => { scroll.scrollTo({ y: Math.max(0, y - 16), animated: true }); }, ); } function openDemographics() { setDemographicsExpanded(true); scrollToDemographics(); } return ( { // TODO Phase C: AvatarPickerSheet (preset-grid + custom-upload via expo-image-picker) Alert.alert( 'Avatar bearbeiten', 'Hero-Auswahl + Foto-Upload kommt in der nächsten Iteration.', ); }} onEditNickname={() => { // TODO Phase C: NicknameEditSheet → PATCH /api/auth/me Alert.alert( 'Nickname bearbeiten', 'Inline-Edit + Save kommt in der nächsten Iteration.', ); }} /> { // TODO: Phase C — navigate to user's own posts list }} onFollowersPress={() => { // TODO: Phase C — open FollowersSheet }} onApprovedDomainsPress={() => { // TODO: Phase C — scroll to ApprovedDomainsList + auto-expand }} /> {showDigaBanner ? ( { // TODO: AsyncStorage persist `diga_banner_dismissed_at` setBannerDismissed(true); }} onContribute={() => { setBannerDismissed(true); scrollToDemographics(); }} /> ) : null} {/* Anchor: Hint-Tap im Header scrollt hierhin */} { // TODO Phase C: PATCH /api/profile/me/demographics — Body: next // Endpoint: profile.demographics_consent_at = NOW() bei erstem Save (DSGVO-Audit-Trail). // Plan-Trial-Trigger: wenn alle 6 Felder gefüllt + plan='free' → server setzt // pro_trial_started_at + pro_trial_expires_at + pro_trial_source='demographics_complete'. setDemographics(next); }} onRevokeConsent={() => { // TODO: Phase C — DELETE /api/profile/me/demographics, confirm-alert first }} /> Profil-Skeleton (dummy data) — Backend wired in Phase C ); }