239 lines
8.6 KiB
TypeScript

import { useState } from 'react';
import { View, Text, Pressable, Modal, Image } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons';
import { useRouter, type RelativePathString } from 'expo-router';
import { useTranslation } from 'react-i18next';
import { useAuthStore } from '../stores/auth';
import { useNotificationStore } from '../stores/notifications';
import { supabase } from '../lib/supabase';
import { resolveAvatar } from '../lib/resolveAvatar';
import { useMe } from '../hooks/useMe';
import { NotificationsDropdown } from './NotificationsDropdown';
type Props = {
notifCount?: number;
};
type MenuItem = {
icon: React.ComponentProps<typeof Ionicons>['name'];
label: string;
color?: string;
action: () => void;
};
export function AppHeader({ notifCount }: Props = {}) {
const insets = useSafeAreaInsets();
const router = useRouter();
const { t } = useTranslation();
const { user } = useAuthStore();
const { me } = useMe();
const storeUnread = useNotificationStore((s) => s.unread);
const badge = notifCount ?? storeUnread;
const [dropdownOpen, setDropdownOpen] = useState(false);
const [notifOpen, setNotifOpen] = useState(false);
const firstName = (user?.user_metadata?.first_name as string | undefined) ?? '';
const lastName = (user?.user_metadata?.last_name as string | undefined) ?? '';
const email = user?.email ?? '';
// Initials-Fallback: erst nickname (DB), dann firstName/email
const initials = (() => {
if (me?.nickname) return me.nickname.slice(0, 2).toUpperCase();
return ((firstName.charAt(0) + (lastName.charAt(0) || email.charAt(0))).toUpperCase() || '?');
})();
// Avatar: aus DB (`/api/auth/me` → profiles.avatar). Kann Hero-Avatar-ID
// ("spider"/"hulk"/...) ODER Custom-Photo-URL (https://... von Foto-Upload)
// sein. resolveAvatar handlet beide Fälle.
// user_metadata.avatar_id ist veraltet — wird bei Profile-Edit nicht
// aktualisiert. DB ist Single Source of Truth.
const avatarUrl = me ? resolveAvatar(me.avatar, me.nickname ?? '') : '';
const [avatarLoadFailed, setAvatarLoadFailed] = useState(false);
const showAvatarImage = !!avatarUrl && !avatarLoadFailed && !!me?.avatar;
function closeAndNavigate(path: RelativePathString) {
setDropdownOpen(false);
router.push(path);
}
async function handleSignOut() {
setDropdownOpen(false);
await supabase.auth.signOut();
router.replace('/' as RelativePathString);
}
const menuItems: MenuItem[] = [
{
icon: 'person-outline',
label: t('appHeader.editProfile'),
action: () => closeAndNavigate('/settings' as RelativePathString),
},
{
icon: 'settings-outline',
label: t('appHeader.settings'),
action: () => closeAndNavigate('/settings' as RelativePathString),
},
];
const headerHeight = insets.top + 56;
return (
<View
className="bg-white border-b border-neutral-200"
style={{ paddingTop: insets.top }}
>
<View className="h-14 flex-row items-center justify-between px-5">
<Text className="text-lg text-midnight-900 tracking-tight" style={{ fontFamily: 'Nunito_700Bold' }}>
{t('appHeader.appName')}
</Text>
<View className="flex-row items-center gap-2">
{/* Notifications dropdown trigger */}
<Pressable
onPress={() => setNotifOpen(true)}
className="w-9 h-9 rounded-full bg-white items-center justify-center"
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
>
<Ionicons name="notifications-outline" size={18} color="#737373" />
{badge > 0 && (
<View className="absolute top-0 right-0 w-4 h-4 rounded-full bg-rebreak-500 items-center justify-center">
<Text className="text-white text-[9px]" style={{ fontFamily: 'Nunito_700Bold' }}>
{badge > 9 ? '9+' : String(badge)}
</Text>
</View>
)}
</Pressable>
{/* Profil-Avatar — tap → dropdown */}
<Pressable
onPress={() => setDropdownOpen(true)}
className={`w-9 h-9 rounded-full items-center justify-center overflow-hidden ${showAvatarImage ? 'bg-neutral-100' : 'bg-rebreak-500'}`}
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
>
{showAvatarImage ? (
<Image
source={{ uri: avatarUrl }}
onError={() => setAvatarLoadFailed(true)}
style={{ width: 36, height: 36, borderRadius: 18 }}
/>
) : (
<Text className="text-white text-xs" style={{ fontFamily: 'Nunito_700Bold' }}>{initials}</Text>
)}
</Pressable>
</View>
</View>
{/* Dropdown modal */}
<Modal
visible={dropdownOpen}
transparent
animationType="fade"
statusBarTranslucent
onRequestClose={() => setDropdownOpen(false)}
>
<Pressable
onPress={() => setDropdownOpen(false)}
style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.18)' }}
>
<View
onStartShouldSetResponder={() => true}
style={{
position: 'absolute',
top: headerHeight + 6,
right: 12,
backgroundColor: '#ffffff',
borderRadius: 18,
shadowColor: '#000',
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.18,
shadowRadius: 20,
elevation: 12,
minWidth: 260,
overflow: 'hidden',
}}
>
{/* SOS prominent oben — Pressable mit innerem Row-View */}
<Pressable onPress={() => closeAndNavigate('/urge' as RelativePathString)}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 18,
paddingVertical: 16,
}}
>
<View
style={{
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#fee2e2',
alignItems: 'center',
justifyContent: 'center',
marginRight: 14,
}}
>
<Ionicons name="heart" size={18} color="#dc2626" />
</View>
<View style={{ flex: 1 }}>
<Text style={{ fontSize: 15, fontFamily: 'Nunito_700Bold', color: '#dc2626' }}>
{t('appHeader.sosLabel')}
</Text>
<Text style={{ fontSize: 12, fontFamily: 'Nunito_400Regular', color: '#a3a3a3', marginTop: 1 }}>
{t('appHeader.sosSubtitle')}
</Text>
</View>
<Ionicons name="chevron-forward" size={16} color="#d4d4d8" />
</View>
</Pressable>
<View style={{ height: 1, backgroundColor: '#f0f0f0' }} />
{menuItems.map((item) => (
<Pressable key={item.label} onPress={item.action}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 18,
paddingVertical: 14,
}}
>
<Ionicons name={item.icon} size={18} color="#737373" style={{ marginRight: 14 }} />
<Text style={{ fontSize: 14, fontFamily: 'Nunito_600SemiBold', color: '#0a0a0a' }}>
{item.label}
</Text>
</View>
</Pressable>
))}
<View style={{ height: 1, backgroundColor: '#f0f0f0' }} />
<Pressable onPress={handleSignOut}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 18,
paddingVertical: 14,
}}
>
<Ionicons name="log-out-outline" size={18} color="#dc2626" style={{ marginRight: 14 }} />
<Text style={{ fontSize: 14, fontFamily: 'Nunito_600SemiBold', color: '#dc2626' }}>
{t('appHeader.signOut')}
</Text>
</View>
</Pressable>
</View>
</Pressable>
</Modal>
<NotificationsDropdown
visible={notifOpen}
onClose={() => setNotifOpen(false)}
topOffset={headerHeight}
/>
</View>
);
}