Surface backend capabilities that already existed in the mobile API client but had no UI: - NetworkListScreen now lists pending invitations with an Accept action and a header "+" to create a network; empty state offers creation instead of pointing users to desktop. - New CreateNetworkSheet and use-invitations hooks (accept invite, create network) following the existing react-query patterns. - SettingsScreen replaces its placeholder with an email-notifications toggle (optimistic, mirrors desktop), app version, and sign out. - Wire the previously-unreachable Settings row into the Drawer. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01LqGPzXQ1AA9CqYHgCgmbtV
109 lines
3.7 KiB
TypeScript
109 lines
3.7 KiB
TypeScript
import { useState } from 'react';
|
||
import { Pressable, Switch, Text, View } from 'react-native';
|
||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||
import Constants from 'expo-constants';
|
||
import { apiClient } from '@/api/client';
|
||
import { useAuthStore } from '@/stores/auth-store';
|
||
import { logError, toUserMessage } from '@/lib/errors';
|
||
import { toast } from 'sonner-native';
|
||
import type { RootStackScreenProps } from '@/navigation/types';
|
||
|
||
export function SettingsScreen({
|
||
navigation,
|
||
}: RootStackScreenProps<'Settings'>) {
|
||
const user = useAuthStore((s) => s.user);
|
||
const signOut = useAuthStore((s) => s.signOut);
|
||
const isSigningOut = useAuthStore((s) => s.isSigningOut);
|
||
const [emailNotifications, setEmailNotifications] = useState(
|
||
user?.email_notifications_enabled ?? true,
|
||
);
|
||
const version = Constants.expoConfig?.version ?? '—';
|
||
|
||
// Optimistic toggle — flip the local + auth-store state immediately, roll
|
||
// back on failure. Mirrors desktop's settings-page handler.
|
||
const handleToggleEmailNotifications = async (checked: boolean) => {
|
||
setEmailNotifications(checked);
|
||
useAuthStore.setState((state) => ({
|
||
user: state.user
|
||
? { ...state.user, email_notifications_enabled: checked }
|
||
: null,
|
||
}));
|
||
try {
|
||
await apiClient.updateSettings({ email_notifications_enabled: checked });
|
||
} catch (err) {
|
||
setEmailNotifications(!checked);
|
||
useAuthStore.setState((state) => ({
|
||
user: state.user
|
||
? { ...state.user, email_notifications_enabled: !checked }
|
||
: null,
|
||
}));
|
||
toast.error(toUserMessage(err));
|
||
logError(err, { scope: 'settings.emailNotifications' });
|
||
}
|
||
};
|
||
|
||
return (
|
||
<SafeAreaView className="flex-1 bg-background" edges={['top']}>
|
||
<View className="flex-row items-center px-3 py-3 border-b border-border">
|
||
<Pressable onPress={() => navigation.goBack()} className="px-2 py-1">
|
||
<Text className="text-foreground text-2xl">‹</Text>
|
||
</Pressable>
|
||
<Text className="flex-1 text-center text-foreground text-base font-semibold">
|
||
Settings
|
||
</Text>
|
||
<View className="w-8" />
|
||
</View>
|
||
|
||
<View className="px-4 py-4">
|
||
<SettingsGroup title="Notifications">
|
||
<View className="flex-row items-center justify-between px-4 py-3">
|
||
<Text className="text-foreground text-base">
|
||
Email notifications
|
||
</Text>
|
||
<Switch
|
||
value={emailNotifications}
|
||
onValueChange={handleToggleEmailNotifications}
|
||
/>
|
||
</View>
|
||
</SettingsGroup>
|
||
|
||
<SettingsGroup title="About">
|
||
<View className="flex-row items-center justify-between px-4 py-3">
|
||
<Text className="text-foreground text-base">Version</Text>
|
||
<Text className="text-muted-foreground text-base">{version}</Text>
|
||
</View>
|
||
</SettingsGroup>
|
||
|
||
<View className="mt-4">
|
||
<Pressable
|
||
onPress={() => void signOut()}
|
||
disabled={isSigningOut}
|
||
className="px-4 py-3 active:bg-accent rounded-xl"
|
||
>
|
||
<Text className="text-destructive text-base font-medium">
|
||
{isSigningOut ? 'Signing out…' : 'Sign out'}
|
||
</Text>
|
||
</Pressable>
|
||
</View>
|
||
</View>
|
||
</SafeAreaView>
|
||
);
|
||
}
|
||
|
||
function SettingsGroup({
|
||
title,
|
||
children,
|
||
}: {
|
||
title: string;
|
||
children: React.ReactNode;
|
||
}) {
|
||
return (
|
||
<View className="mb-2">
|
||
<Text className="text-muted-foreground text-xs uppercase tracking-wide px-4 pt-4 pb-1">
|
||
{title}
|
||
</Text>
|
||
<View className="bg-muted/40 rounded-xl overflow-hidden">{children}</View>
|
||
</View>
|
||
);
|
||
}
|