diff --git a/apps/mobile/REFACTORING_TASKS.md b/apps/mobile/REFACTORING_TASKS.md index f8fb447..a92bb36 100644 --- a/apps/mobile/REFACTORING_TASKS.md +++ b/apps/mobile/REFACTORING_TASKS.md @@ -142,9 +142,10 @@ Track progress by checking off completed items. - [x] Extract `ProfileHeader.tsx` - [x] Extract `CoupleStatus.tsx` - [x] Extract `SettingsSection.tsx` -- [ ] Extract `NotificationSettings.tsx` -- [x] Extract `PrivacySettings.tsx` -- [ ] Extract `AppearanceSettings.tsx` +- [x] Extract `NotificationSettings.tsx` +- [x] Extract `PrivacySettings.tsx` +- [x] Extract `AppearanceSettings.tsx` + - [x] Extract `DangerZone.tsx` - [x] Extract `SwitchItem.tsx` - [x] Extract `MenuItem.tsx` diff --git a/apps/mobile/src/features/profile/ProfileScreen.tsx b/apps/mobile/src/features/profile/ProfileScreen.tsx index ace4f96..c8de2e4 100644 --- a/apps/mobile/src/features/profile/ProfileScreen.tsx +++ b/apps/mobile/src/features/profile/ProfileScreen.tsx @@ -17,7 +17,7 @@ import { supabase } from '../../lib/supabase'; // Needed for debug reset import { useProfileSettings, useCoupleManagement } from './hooks'; // Components -import { ProfileHeader, CoupleStatus, PrivacySettings, DangerZone, SettingsSection, MenuItem } from './components'; +import { AppearanceSettings, CoupleStatus, NotificationSettings, PrivacySettings, DangerZone, SettingsSection, MenuItem } from './components'; const MAX_CONTENT_WIDTH = 500; const ACCENT_GRADIENT = featureColors.profile.gradient as [string, string]; @@ -81,7 +81,7 @@ export function ProfileScreen() { {/* Profile Header */} - + {/* Notifications */} + + {/* Preferences */} = (props) => { + return ; +}; diff --git a/apps/mobile/src/features/profile/components/NotificationSettings.tsx b/apps/mobile/src/features/profile/components/NotificationSettings.tsx new file mode 100644 index 0000000..5eab3fb --- /dev/null +++ b/apps/mobile/src/features/profile/components/NotificationSettings.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import { SettingsSection } from './SettingsSection'; +import { SwitchItem } from './SwitchItem'; + +export interface NotificationSettingsProps { + pushEnabled: boolean; + isUpdatingPush: boolean; + onPushToggle: (value: boolean) => void; + delay?: number; +} + +export const NotificationSettings: React.FC = ({ + pushEnabled, + isUpdatingPush, + onPushToggle, + delay = 360, +}) => { + return ( + + + + ); +}; diff --git a/apps/mobile/src/features/profile/components/ProfileHeader.tsx b/apps/mobile/src/features/profile/components/ProfileHeader.tsx index 284ab28..7b4ab02 100644 --- a/apps/mobile/src/features/profile/components/ProfileHeader.tsx +++ b/apps/mobile/src/features/profile/components/ProfileHeader.tsx @@ -10,7 +10,7 @@ import { Profile } from '../../../types'; const ACCENT_GRADIENT = featureColors.profile.gradient as [string, string]; -interface ProfileHeaderProps { +export interface ProfileHeaderProps { user: Profile | null; isUploadingAvatar: boolean; isEditingName: boolean; diff --git a/apps/mobile/src/features/profile/components/index.ts b/apps/mobile/src/features/profile/components/index.ts index 7631fd1..1f1c484 100644 --- a/apps/mobile/src/features/profile/components/index.ts +++ b/apps/mobile/src/features/profile/components/index.ts @@ -1,5 +1,10 @@ // Profile feature components export { ProfileHeader } from './ProfileHeader'; +export type { ProfileHeaderProps } from './ProfileHeader'; +export { AppearanceSettings } from './AppearanceSettings'; +export type { AppearanceSettingsProps } from './AppearanceSettings'; +export { NotificationSettings } from './NotificationSettings'; +export type { NotificationSettingsProps } from './NotificationSettings'; export { CoupleStatus } from './CoupleStatus'; export { PrivacySettings } from './PrivacySettings'; export { DangerZone } from './DangerZone'; diff --git a/apps/mobile/src/features/profile/hooks/useProfileSettings.ts b/apps/mobile/src/features/profile/hooks/useProfileSettings.ts index 3a6beee..1d90152 100644 --- a/apps/mobile/src/features/profile/hooks/useProfileSettings.ts +++ b/apps/mobile/src/features/profile/hooks/useProfileSettings.ts @@ -6,6 +6,11 @@ import { decode } from 'base64-arraybuffer'; import { useAuthStore, useSubscriptionStore, usePacksStore } from '../../../store'; import { supabase } from '../../../lib/supabase'; import { Events } from '../../../lib/analytics'; +import { + registerForPushNotificationsAsync, + savePushToken, + clearPushToken, +} from '../../../lib/notifications'; import { isBiometricAvailable, isBiometricEnabled, @@ -30,6 +35,10 @@ export function useProfileSettings() { const [showExplicit, setShowExplicit] = useState(user?.show_explicit_content ?? true); const [isUpdatingExplicit, setIsUpdatingExplicit] = useState(false); + // Notifications + const [pushEnabled, setPushEnabled] = useState(!!user?.push_token); + const [isUpdatingPush, setIsUpdatingPush] = useState(false); + // Biometrics const [biometricAvailable, setBiometricAvailable] = useState(false); const [biometricEnabledState, setBiometricEnabledState] = useState(false); @@ -47,6 +56,7 @@ export function useProfileSettings() { if (user?.show_explicit_content !== undefined) { setShowExplicit(user.show_explicit_content); } + setPushEnabled(!!user?.push_token); }, [user]); // Check biometric availability @@ -276,6 +286,48 @@ export function useProfileSettings() { } }; + const handlePushToggle = async (value: boolean) => { + if (!user?.id) return; + + if (Platform.OS === 'web') { + setPushEnabled(false); + Alert.alert("Not Supported", "Push notifications aren't available on web."); + return; + } + + setPushEnabled(value); + setIsUpdatingPush(true); + + try { + if (value) { + const token = await registerForPushNotificationsAsync(); + if (!token) { + setPushEnabled(false); + Alert.alert( + "Enable Notifications", + "Allow notifications in system settings to receive alerts.", + [ + { text: "Not Now", style: "cancel" }, + { text: "Open Settings", onPress: () => Linking.openSettings() }, + ] + ); + return; + } + + await savePushToken(user.id, token); + } else { + await clearPushToken(user.id); + } + + await fetchUser(); + } catch (error) { + setPushEnabled(!value); + Alert.alert("Error", "Failed to update notification settings. Please try again."); + } finally { + setIsUpdatingPush(false); + } + }; + const handleBiometricToggle = async (value: boolean) => { setIsUpdatingBiometric(true); @@ -330,6 +382,8 @@ export function useProfileSettings() { isUploadingAvatar, showExplicit, isUpdatingExplicit, + pushEnabled, + isUpdatingPush, biometricAvailable, biometricEnabled: biometricEnabledState, biometricType, @@ -343,6 +397,7 @@ export function useProfileSettings() { handleCancelEditName, handleAvatarPress, handleExplicitToggle, + handlePushToggle, handleBiometricToggle, handleRestorePurchases, handleManageSubscription,