Add profile appearance and notifications settings

Co-authored-by: Claude Code <claude@anthropic.com>
This commit is contained in:
Jamie BruntonandClaude Code committed 2026-01-02 10:47:24 +00:00
1 parent b082da436b
commit 1452519c76
7 files changed
+115 -6

No files matched your search

+4 -3
View File
@@ -142,9 +142,10 @@ Track progress by checking off completed items.
- [x] Extract `ProfileHeader.tsx` - [x] Extract `ProfileHeader.tsx`
- [x] Extract `CoupleStatus.tsx` - [x] Extract `CoupleStatus.tsx`
- [x] Extract `SettingsSection.tsx` - [x] Extract `SettingsSection.tsx`
- [ ] Extract `NotificationSettings.tsx` - [x] Extract `NotificationSettings.tsx`
- [x] Extract `PrivacySettings.tsx` - [x] Extract `PrivacySettings.tsx`
- [ ] Extract `AppearanceSettings.tsx` - [x] Extract `AppearanceSettings.tsx`
- [x] Extract `DangerZone.tsx` - [x] Extract `DangerZone.tsx`
- [x] Extract `SwitchItem.tsx` - [x] Extract `SwitchItem.tsx`
- [x] Extract `MenuItem.tsx` - [x] Extract `MenuItem.tsx`
@@ -17,7 +17,7 @@ import { supabase } from '../../lib/supabase'; // Needed for debug reset
import { useProfileSettings, useCoupleManagement } from './hooks'; import { useProfileSettings, useCoupleManagement } from './hooks';
// Components // 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 MAX_CONTENT_WIDTH = 500;
const ACCENT_GRADIENT = featureColors.profile.gradient as [string, string]; const ACCENT_GRADIENT = featureColors.profile.gradient as [string, string];
@@ -81,7 +81,7 @@ export function ProfileScreen() {
</Animated.View> </Animated.View>
{/* Profile Header */} {/* Profile Header */}
<ProfileHeader <AppearanceSettings
user={user} user={user}
isUploadingAvatar={settings.isUploadingAvatar} isUploadingAvatar={settings.isUploadingAvatar}
isEditingName={settings.isEditingName} isEditingName={settings.isEditingName}
@@ -175,6 +175,13 @@ export function ProfileScreen() {
)} )}
</SettingsSection> </SettingsSection>
{/* Notifications */}
<NotificationSettings
pushEnabled={settings.pushEnabled}
isUpdatingPush={settings.isUpdatingPush}
onPushToggle={settings.handlePushToggle}
/>
{/* Preferences */} {/* Preferences */}
<PrivacySettings <PrivacySettings
showExplicit={settings.showExplicit} showExplicit={settings.showExplicit}
@@ -0,0 +1,10 @@
import React from 'react';
import { ProfileHeader } from './ProfileHeader';
import type { ProfileHeaderProps } from './ProfileHeader';
export type AppearanceSettingsProps = ProfileHeaderProps;
export const AppearanceSettings: React.FC<AppearanceSettingsProps> = (props) => {
return <ProfileHeader {...props} />;
};
@@ -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<NotificationSettingsProps> = ({
pushEnabled,
isUpdatingPush,
onPushToggle,
delay = 360,
}) => {
return (
<SettingsSection title="Notifications" delay={delay}>
<SwitchItem
icon="notifications-outline"
label="Push Notifications"
description="Get alerts for matches and messages"
value={pushEnabled}
onValueChange={onPushToggle}
disabled={isUpdatingPush}
/>
</SettingsSection>
);
};
@@ -10,7 +10,7 @@ import { Profile } from '../../../types';
const ACCENT_GRADIENT = featureColors.profile.gradient as [string, string]; const ACCENT_GRADIENT = featureColors.profile.gradient as [string, string];
interface ProfileHeaderProps { export interface ProfileHeaderProps {
user: Profile | null; user: Profile | null;
isUploadingAvatar: boolean; isUploadingAvatar: boolean;
isEditingName: boolean; isEditingName: boolean;
@@ -1,5 +1,10 @@
// Profile feature components // Profile feature components
export { ProfileHeader } from './ProfileHeader'; 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 { CoupleStatus } from './CoupleStatus';
export { PrivacySettings } from './PrivacySettings'; export { PrivacySettings } from './PrivacySettings';
export { DangerZone } from './DangerZone'; export { DangerZone } from './DangerZone';
@@ -6,6 +6,11 @@ import { decode } from 'base64-arraybuffer';
import { useAuthStore, useSubscriptionStore, usePacksStore } from '../../../store'; import { useAuthStore, useSubscriptionStore, usePacksStore } from '../../../store';
import { supabase } from '../../../lib/supabase'; import { supabase } from '../../../lib/supabase';
import { Events } from '../../../lib/analytics'; import { Events } from '../../../lib/analytics';
import {
registerForPushNotificationsAsync,
savePushToken,
clearPushToken,
} from '../../../lib/notifications';
import { import {
isBiometricAvailable, isBiometricAvailable,
isBiometricEnabled, isBiometricEnabled,
@@ -30,6 +35,10 @@ export function useProfileSettings() {
const [showExplicit, setShowExplicit] = useState(user?.show_explicit_content ?? true); const [showExplicit, setShowExplicit] = useState(user?.show_explicit_content ?? true);
const [isUpdatingExplicit, setIsUpdatingExplicit] = useState(false); const [isUpdatingExplicit, setIsUpdatingExplicit] = useState(false);
// Notifications
const [pushEnabled, setPushEnabled] = useState(!!user?.push_token);
const [isUpdatingPush, setIsUpdatingPush] = useState(false);
// Biometrics // Biometrics
const [biometricAvailable, setBiometricAvailable] = useState(false); const [biometricAvailable, setBiometricAvailable] = useState(false);
const [biometricEnabledState, setBiometricEnabledState] = useState(false); const [biometricEnabledState, setBiometricEnabledState] = useState(false);
@@ -47,6 +56,7 @@ export function useProfileSettings() {
if (user?.show_explicit_content !== undefined) { if (user?.show_explicit_content !== undefined) {
setShowExplicit(user.show_explicit_content); setShowExplicit(user.show_explicit_content);
} }
setPushEnabled(!!user?.push_token);
}, [user]); }, [user]);
// Check biometric availability // 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) => { const handleBiometricToggle = async (value: boolean) => {
setIsUpdatingBiometric(true); setIsUpdatingBiometric(true);
@@ -330,6 +382,8 @@ export function useProfileSettings() {
isUploadingAvatar, isUploadingAvatar,
showExplicit, showExplicit,
isUpdatingExplicit, isUpdatingExplicit,
pushEnabled,
isUpdatingPush,
biometricAvailable, biometricAvailable,
biometricEnabled: biometricEnabledState, biometricEnabled: biometricEnabledState,
biometricType, biometricType,
@@ -343,6 +397,7 @@ export function useProfileSettings() {
handleCancelEditName, handleCancelEditName,
handleAvatarPress, handleAvatarPress,
handleExplicitToggle, handleExplicitToggle,
handlePushToggle,
handleBiometricToggle, handleBiometricToggle,
handleRestorePurchases, handleRestorePurchases,
handleManageSubscription, handleManageSubscription,