mobile: add invitations, network creation, and settings (parity phase 1)
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
This commit is contained in:
@@ -1,10 +1,47 @@
|
||||
import { Pressable, Text, View } from 'react-native';
|
||||
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">
|
||||
@@ -17,11 +54,55 @@ export function SettingsScreen({
|
||||
<View className="w-8" />
|
||||
</View>
|
||||
|
||||
<View className="flex-1 items-center justify-center px-6">
|
||||
<Text className="text-muted-foreground text-center">
|
||||
Theme, notifications, and account preferences land here later.
|
||||
</Text>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user