/** * ProfileScreen — zoomer-profil, stats, badges */ import { useCallback, useEffect, useState } from 'react' import { View, Text, ScrollView, TouchableOpacity, StyleSheet, ActivityIndicator, Alert, RefreshControl, } from 'react-native' import { auth, profile, type Zoomer } from '../lib/api' const LEVEL_LABELS: Record = { 1: 'Rookie Zoomer', 2: 'Junior Zoomer', 3: 'Zoomer', 4: 'Senior Zoomer', 5: 'Elite Zoomer', 6: 'Legend Zoomer', } const LEVEL_COLOR: Record = { 1: '#6b7280', 2: '#10b981', 3: '#3b82f6', 4: '#8b5cf6', 5: '#f59e0b', 6: '#ef4444', } function levelProgress(level: number, totalEarned: number): number { // Simple XP curve: each level needs level*500 kr more const levelThreshold = level * 50000 // in öre = level * 500 SEK const prevThreshold = (level - 1) * 50000 const progress = (totalEarned - prevThreshold) / (levelThreshold - prevThreshold) return Math.min(Math.max(progress, 0), 1) } export function ProfileScreen() { const [zoomer, setZoomer] = useState(null) const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) const load = useCallback(async () => { try { const data = await profile.get() setZoomer(data) } catch { // silent } finally { setLoading(false) setRefreshing(false) } }, []) useEffect(() => { load() }, []) const onRefresh = useCallback(() => { setRefreshing(true); load() }, [load]) const handleSignOut = () => { Alert.alert( 'Logga ut', 'Är du säker på att du vill logga ut?', [ { text: 'Avbryt', style: 'cancel' }, { text: 'Logga ut', style: 'destructive', onPress: async () => { await auth.logout() // AppNavigator will detect missing token and show AuthScreen }, }, ], ) } if (loading) { return ( ) } const level = zoomer?.level ?? 1 const levelLabel = LEVEL_LABELS[level] ?? `Nivå ${level}` const levelColor = LEVEL_COLOR[level] ?? '#6366f1' const progress = zoomer ? levelProgress(level, zoomer.total_earned) : 0 const initial = (zoomer?.display_name ?? zoomer?.email ?? '?')[0].toUpperCase() return ( } > {/* Avatar + name */} {initial} {zoomer?.display_name || 'Namnlös zoomer'} {zoomer?.email} {/* Level badge */} ⭐ {levelLabel} {/* XP bar */} Nivå {level} → {level + 1} ({(progress * 100).toFixed(0)}%) {/* Stats */} {zoomer ? (zoomer.total_earned / 100).toFixed(0) : '0'} kr Totalt intjänat {zoomer?.missions_completed ?? 0} Uppdrag klara {level} Nivå {zoomer?.missions_completed ?? 0} Uppdrag {/* Badges — kommer när backend har stöd */} Badges 🏅 Slutför ditt första uppdrag för att låsa upp din första badge! {/* Member since */} {zoomer?.joined_at && ( Zoomer sedan {new Date(zoomer.joined_at).toLocaleDateString('sv-SE', { month: 'long', year: 'numeric' })} )} {/* Sign out */} Logga ut ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#0A0A1B' }, center: { flex: 1, backgroundColor: '#0A0A1B', alignItems: 'center', justifyContent: 'center' }, headerSection: { alignItems: 'center', paddingTop: 64, paddingHorizontal: 24, paddingBottom: 24, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.06)', }, avatar: { width: 88, height: 88, borderRadius: 44, backgroundColor: 'rgba(99,102,241,0.3)', alignItems: 'center', justifyContent: 'center', marginBottom: 16, borderWidth: 3, }, avatarText: { color: '#fff', fontSize: 36, fontWeight: '800' }, displayName: { color: '#fff', fontSize: 22, fontWeight: '800', marginBottom: 4 }, email: { color: 'rgba(255,255,255,0.4)', fontSize: 14, marginBottom: 16 }, levelBadge: { borderRadius: 20, paddingHorizontal: 16, paddingVertical: 8, borderWidth: 1, marginBottom: 16, }, levelBadgeText: { fontWeight: '700', fontSize: 13 }, xpBarWrap: { width: '100%', gap: 6 }, xpBarBg: { height: 6, backgroundColor: 'rgba(255,255,255,0.08)', borderRadius: 3, overflow: 'hidden', }, xpBarFill: { height: '100%', borderRadius: 3 }, xpLabel: { color: 'rgba(255,255,255,0.3)', fontSize: 11, textAlign: 'center' }, statsGrid: { flexDirection: 'row', flexWrap: 'wrap', padding: 16, gap: 10, }, statCard: { width: '47%', backgroundColor: 'rgba(255,255,255,0.04)', borderRadius: 16, padding: 16, borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)', alignItems: 'center', }, statValue: { color: '#fff', fontSize: 24, fontWeight: '800', marginBottom: 4 }, statLabel: { color: 'rgba(255,255,255,0.35)', fontSize: 12 }, section: { paddingHorizontal: 16, marginTop: 8 }, sectionTitle: { color: 'rgba(255,255,255,0.35)', fontSize: 12, fontWeight: '700', letterSpacing: 1, textTransform: 'uppercase', marginBottom: 12, }, badgesGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 }, badgeCard: { width: '30%', backgroundColor: 'rgba(255,255,255,0.04)', borderRadius: 14, padding: 12, borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)', alignItems: 'center', gap: 4, }, badgeEmoji: { fontSize: 28 }, badgeLabel: { color: '#fff', fontSize: 11, fontWeight: '600', textAlign: 'center' }, badgeDate: { color: 'rgba(255,255,255,0.3)', fontSize: 9 }, emptyBadges: { backgroundColor: 'rgba(255,255,255,0.03)', borderRadius: 16, padding: 24, alignItems: 'center', gap: 8, borderWidth: 1, borderColor: 'rgba(255,255,255,0.06)', }, emptyEmoji: { fontSize: 36 }, emptyText: { color: 'rgba(255,255,255,0.4)', fontSize: 13, textAlign: 'center', lineHeight: 20 }, memberSince: { color: 'rgba(255,255,255,0.2)', fontSize: 12, textAlign: 'center', marginTop: 16, marginBottom: 8, }, signOutBtn: { borderWidth: 1, borderColor: 'rgba(239,68,68,0.35)', borderRadius: 14, paddingVertical: 14, alignItems: 'center', marginTop: 8, }, signOutText: { color: '#ef4444', fontWeight: '700', fontSize: 15 }, })