@oxyhq/services 5.18.2 → 5.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/lib/commonjs/core/mixins/index.js +36 -13
  2. package/lib/commonjs/core/mixins/index.js.map +1 -1
  3. package/lib/commonjs/index.js +8 -0
  4. package/lib/commonjs/index.js.map +1 -1
  5. package/lib/commonjs/ui/client.js +170 -0
  6. package/lib/commonjs/ui/client.js.map +1 -0
  7. package/lib/commonjs/ui/components/profile/EditFieldModal.js +412 -0
  8. package/lib/commonjs/ui/components/profile/EditFieldModal.js.map +1 -0
  9. package/lib/commonjs/ui/context/OxyContext.js +63 -1
  10. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  11. package/lib/commonjs/ui/hooks/useAuth.js +115 -0
  12. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -0
  13. package/lib/commonjs/ui/hooks/useSettingToggle.js +7 -1
  14. package/lib/commonjs/ui/hooks/useSettingToggle.js.map +1 -1
  15. package/lib/commonjs/ui/hooks/useWebSSO.js +75 -0
  16. package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -0
  17. package/lib/commonjs/ui/index.js +17 -2
  18. package/lib/commonjs/ui/index.js.map +1 -1
  19. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +59 -65
  20. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
  21. package/lib/commonjs/ui/screens/SearchSettingsScreen.js +38 -58
  22. package/lib/commonjs/ui/screens/SearchSettingsScreen.js.map +1 -1
  23. package/lib/commonjs/ui/server.js +105 -0
  24. package/lib/commonjs/ui/server.js.map +1 -0
  25. package/lib/commonjs/ui/utils/iconNames.js +133 -0
  26. package/lib/commonjs/ui/utils/iconNames.js.map +1 -0
  27. package/lib/commonjs/ui/utils/sessionHelpers.js +7 -0
  28. package/lib/commonjs/ui/utils/sessionHelpers.js.map +1 -1
  29. package/lib/commonjs/utils/requestUtils.js +4 -3
  30. package/lib/commonjs/utils/requestUtils.js.map +1 -1
  31. package/lib/module/core/mixins/index.js +36 -13
  32. package/lib/module/core/mixins/index.js.map +1 -1
  33. package/lib/module/index.js +2 -2
  34. package/lib/module/index.js.map +1 -1
  35. package/lib/module/ui/client.js +47 -0
  36. package/lib/module/ui/client.js.map +1 -0
  37. package/lib/module/ui/components/profile/EditFieldModal.js +406 -0
  38. package/lib/module/ui/components/profile/EditFieldModal.js.map +1 -0
  39. package/lib/module/ui/context/OxyContext.js +63 -1
  40. package/lib/module/ui/context/OxyContext.js.map +1 -1
  41. package/lib/module/ui/hooks/useAuth.js +106 -0
  42. package/lib/module/ui/hooks/useAuth.js.map +1 -0
  43. package/lib/module/ui/hooks/useSettingToggle.js +7 -1
  44. package/lib/module/ui/hooks/useSettingToggle.js.map +1 -1
  45. package/lib/module/ui/hooks/useWebSSO.js +71 -0
  46. package/lib/module/ui/hooks/useWebSSO.js.map +1 -0
  47. package/lib/module/ui/index.js +17 -3
  48. package/lib/module/ui/index.js.map +1 -1
  49. package/lib/module/ui/screens/PrivacySettingsScreen.js +59 -65
  50. package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
  51. package/lib/module/ui/screens/SearchSettingsScreen.js +39 -59
  52. package/lib/module/ui/screens/SearchSettingsScreen.js.map +1 -1
  53. package/lib/module/ui/server.js +65 -0
  54. package/lib/module/ui/server.js.map +1 -0
  55. package/lib/module/ui/utils/iconNames.js +124 -0
  56. package/lib/module/ui/utils/iconNames.js.map +1 -0
  57. package/lib/module/ui/utils/sessionHelpers.js +7 -0
  58. package/lib/module/ui/utils/sessionHelpers.js.map +1 -1
  59. package/lib/module/utils/requestUtils.js +4 -2
  60. package/lib/module/utils/requestUtils.js.map +1 -1
  61. package/lib/typescript/commonjs/core/mixins/index.d.ts +18 -1115
  62. package/lib/typescript/commonjs/core/mixins/index.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/index.d.ts +2 -0
  64. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/ui/client.d.ts +33 -0
  66. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -0
  67. package/lib/typescript/commonjs/ui/components/profile/EditFieldModal.d.ts +110 -0
  68. package/lib/typescript/commonjs/ui/components/profile/EditFieldModal.d.ts.map +1 -0
  69. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +3 -0
  70. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +3 -3
  72. package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts +6 -10
  73. package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  74. package/lib/typescript/commonjs/ui/hooks/queries/useSecurityQueries.d.ts +1 -1
  75. package/lib/typescript/commonjs/ui/hooks/queries/useSecurityQueries.d.ts.map +1 -1
  76. package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts +3 -5
  77. package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/ui/hooks/useAssets.d.ts +1 -1
  79. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +69 -0
  80. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -0
  81. package/lib/typescript/commonjs/ui/hooks/useSettingToggle.d.ts +4 -2
  82. package/lib/typescript/commonjs/ui/hooks/useSettingToggle.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +34 -0
  84. package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -0
  85. package/lib/typescript/commonjs/ui/index.d.ts +2 -2
  86. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  88. package/lib/typescript/commonjs/ui/screens/SearchSettingsScreen.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/ui/server.d.ts +43 -0
  90. package/lib/typescript/commonjs/ui/server.d.ts.map +1 -0
  91. package/lib/typescript/commonjs/ui/utils/iconNames.d.ts +112 -0
  92. package/lib/typescript/commonjs/ui/utils/iconNames.d.ts.map +1 -0
  93. package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts +8 -3
  94. package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts.map +1 -1
  95. package/lib/typescript/commonjs/utils/requestUtils.d.ts +3 -1
  96. package/lib/typescript/commonjs/utils/requestUtils.d.ts.map +1 -1
  97. package/lib/typescript/module/core/mixins/index.d.ts +18 -1115
  98. package/lib/typescript/module/core/mixins/index.d.ts.map +1 -1
  99. package/lib/typescript/module/index.d.ts +2 -0
  100. package/lib/typescript/module/index.d.ts.map +1 -1
  101. package/lib/typescript/module/ui/client.d.ts +33 -0
  102. package/lib/typescript/module/ui/client.d.ts.map +1 -0
  103. package/lib/typescript/module/ui/components/profile/EditFieldModal.d.ts +110 -0
  104. package/lib/typescript/module/ui/components/profile/EditFieldModal.d.ts.map +1 -0
  105. package/lib/typescript/module/ui/context/OxyContext.d.ts +3 -0
  106. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  107. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +3 -3
  108. package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts +6 -10
  109. package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  110. package/lib/typescript/module/ui/hooks/queries/useSecurityQueries.d.ts +1 -1
  111. package/lib/typescript/module/ui/hooks/queries/useSecurityQueries.d.ts.map +1 -1
  112. package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts +3 -5
  113. package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  114. package/lib/typescript/module/ui/hooks/useAssets.d.ts +1 -1
  115. package/lib/typescript/module/ui/hooks/useAuth.d.ts +69 -0
  116. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -0
  117. package/lib/typescript/module/ui/hooks/useSettingToggle.d.ts +4 -2
  118. package/lib/typescript/module/ui/hooks/useSettingToggle.d.ts.map +1 -1
  119. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +34 -0
  120. package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -0
  121. package/lib/typescript/module/ui/index.d.ts +2 -2
  122. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  123. package/lib/typescript/module/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  124. package/lib/typescript/module/ui/screens/SearchSettingsScreen.d.ts.map +1 -1
  125. package/lib/typescript/module/ui/server.d.ts +43 -0
  126. package/lib/typescript/module/ui/server.d.ts.map +1 -0
  127. package/lib/typescript/module/ui/utils/iconNames.d.ts +112 -0
  128. package/lib/typescript/module/ui/utils/iconNames.d.ts.map +1 -0
  129. package/lib/typescript/module/ui/utils/sessionHelpers.d.ts +8 -3
  130. package/lib/typescript/module/ui/utils/sessionHelpers.d.ts.map +1 -1
  131. package/lib/typescript/module/utils/requestUtils.d.ts +3 -1
  132. package/lib/typescript/module/utils/requestUtils.d.ts.map +1 -1
  133. package/package.json +1 -1
  134. package/src/core/mixins/index.ts +57 -43
  135. package/src/index.ts +3 -1
  136. package/src/ui/client.ts +55 -0
  137. package/src/ui/components/profile/EditFieldModal.tsx +465 -0
  138. package/src/ui/context/OxyContext.tsx +69 -0
  139. package/src/ui/hooks/useAuth.ts +159 -0
  140. package/src/ui/hooks/useSettingToggle.ts +7 -3
  141. package/src/ui/hooks/useWebSSO.ts +93 -0
  142. package/src/ui/index.ts +17 -2
  143. package/src/ui/screens/PrivacySettingsScreen.tsx +54 -63
  144. package/src/ui/screens/SearchSettingsScreen.tsx +42 -64
  145. package/src/ui/server.ts +70 -0
  146. package/src/ui/utils/iconNames.ts +136 -0
  147. package/src/ui/utils/sessionHelpers.ts +10 -3
  148. package/src/utils/requestUtils.ts +10 -7
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Unified Auth Hook
3
+ *
4
+ * Provides a clean, standard interface for authentication across all platforms.
5
+ * This is the recommended way to access auth state in Oxy apps.
6
+ *
7
+ * Usage:
8
+ * ```tsx
9
+ * import { useAuth } from '@oxyhq/services';
10
+ *
11
+ * function MyComponent() {
12
+ * const { user, isAuthenticated, isLoading, signIn, signOut } = useAuth();
13
+ *
14
+ * if (isLoading) return <Loading />;
15
+ * if (!isAuthenticated) return <SignInButton onClick={() => signIn()} />;
16
+ * return <Welcome user={user} />;
17
+ * }
18
+ * ```
19
+ */
20
+
21
+ import { useCallback } from 'react';
22
+ import { useOxy } from '../context/OxyContext';
23
+ import type { User } from '../../models/interfaces';
24
+
25
+ export interface AuthState {
26
+ /** Current authenticated user, null if not authenticated */
27
+ user: User | null;
28
+
29
+ /** Whether user is authenticated */
30
+ isAuthenticated: boolean;
31
+
32
+ /** Whether auth state is being determined (initial load) */
33
+ isLoading: boolean;
34
+
35
+ /** Whether the auth token is ready for API calls */
36
+ isReady: boolean;
37
+
38
+ /** Current error message, if any */
39
+ error: string | null;
40
+ }
41
+
42
+ export interface AuthActions {
43
+ /**
44
+ * Sign in with cryptographic identity
45
+ * On native: Uses device keychain
46
+ * On web: Opens auth popup/redirect
47
+ */
48
+ signIn: (publicKey?: string) => Promise<User>;
49
+
50
+ /**
51
+ * Sign out current session
52
+ */
53
+ signOut: () => Promise<void>;
54
+
55
+ /**
56
+ * Sign out all sessions across all devices
57
+ */
58
+ signOutAll: () => Promise<void>;
59
+
60
+ /**
61
+ * Refresh auth state (re-check session validity)
62
+ */
63
+ refresh: () => Promise<void>;
64
+ }
65
+
66
+ export interface UseAuthReturn extends AuthState, AuthActions {
67
+ /** Access to full OxyServices instance for advanced usage */
68
+ oxyServices: ReturnType<typeof useOxy>['oxyServices'];
69
+ }
70
+
71
+ /**
72
+ * Unified auth hook for all Oxy apps
73
+ *
74
+ * Features:
75
+ * - Zero config: Just wrap with OxyProvider and use
76
+ * - Cross-platform: Same API on native and web
77
+ * - Auto SSO: Web apps automatically check for cross-domain sessions
78
+ * - Type-safe: Full TypeScript support
79
+ */
80
+ export function useAuth(): UseAuthReturn {
81
+ const {
82
+ user,
83
+ isAuthenticated,
84
+ isLoading,
85
+ isTokenReady,
86
+ error,
87
+ signIn: oxySignIn,
88
+ logout,
89
+ logoutAll,
90
+ refreshSessions,
91
+ oxyServices,
92
+ hasIdentity,
93
+ getPublicKey,
94
+ showBottomSheet,
95
+ } = useOxy();
96
+
97
+ const signIn = useCallback(async (publicKey?: string): Promise<User> => {
98
+ // If public key provided, use it directly
99
+ if (publicKey) {
100
+ return oxySignIn(publicKey);
101
+ }
102
+
103
+ // Try to get existing identity
104
+ const hasExisting = await hasIdentity();
105
+
106
+ if (hasExisting) {
107
+ const existingKey = await getPublicKey();
108
+ if (existingKey) {
109
+ return oxySignIn(existingKey);
110
+ }
111
+ }
112
+
113
+ // No identity - show auth UI
114
+ // On native: shows bottom sheet for identity creation
115
+ // On web: could trigger popup auth
116
+ showBottomSheet?.('OxyAuth');
117
+
118
+ // Return a promise that resolves when auth completes
119
+ // This is a simplified version - real implementation would
120
+ // wait for the auth flow to complete
121
+ return new Promise((resolve, reject) => {
122
+ // For now, just reject - the bottom sheet handles the flow
123
+ reject(new Error('Please complete sign-in in the auth sheet'));
124
+ });
125
+ }, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet]);
126
+
127
+ const signOut = useCallback(async (): Promise<void> => {
128
+ await logout();
129
+ }, [logout]);
130
+
131
+ const signOutAll = useCallback(async (): Promise<void> => {
132
+ await logoutAll();
133
+ }, [logoutAll]);
134
+
135
+ const refresh = useCallback(async (): Promise<void> => {
136
+ await refreshSessions();
137
+ }, [refreshSessions]);
138
+
139
+ return {
140
+ // State
141
+ user,
142
+ isAuthenticated,
143
+ isLoading,
144
+ isReady: isTokenReady,
145
+ error,
146
+
147
+ // Actions
148
+ signIn,
149
+ signOut,
150
+ signOutAll,
151
+ refresh,
152
+
153
+ // Advanced
154
+ oxyServices,
155
+ };
156
+ }
157
+
158
+ // Re-export useOxy for backward compatibility and advanced usage
159
+ export { useOxy } from '../context/OxyContext';
@@ -91,7 +91,7 @@ export function useSettingToggle(options: UseSettingToggleOptions): UseSettingTo
91
91
  * Hook for managing multiple toggle settings at once.
92
92
  * Useful when you have several related boolean settings.
93
93
  */
94
- export function useSettingToggles<T extends Record<string, boolean>>(options: {
94
+ export function useSettingToggles<T extends { [K in keyof T]: boolean }>(options: {
95
95
  initialValues: T;
96
96
  onSave: (key: keyof T, value: boolean) => Promise<void>;
97
97
  errorMessage?: string | ((key: keyof T) => string);
@@ -100,7 +100,7 @@ export function useSettingToggles<T extends Record<string, boolean>>(options: {
100
100
  values: T;
101
101
  savingKeys: Set<keyof T>;
102
102
  toggle: (key: keyof T) => Promise<void>;
103
- setValues: (values: T) => void;
103
+ setValues: (values: Partial<T>) => void;
104
104
  } {
105
105
  const { initialValues, onSave, errorMessage = 'Failed to save setting', revertOnError = true } = options;
106
106
 
@@ -141,7 +141,11 @@ export function useSettingToggles<T extends Record<string, boolean>>(options: {
141
141
  }
142
142
  }, [values, onSave, errorMessage, revertOnError]);
143
143
 
144
- return { values, savingKeys, toggle, setValues };
144
+ const setValuesExternal = useCallback((newValues: Partial<T>) => {
145
+ setValues(prev => ({ ...prev, ...newValues }));
146
+ }, []);
147
+
148
+ return { values, savingKeys, toggle, setValues: setValuesExternal };
145
149
  }
146
150
 
147
151
  export default useSettingToggle;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Web SSO Hook
3
+ *
4
+ * Automatically handles cross-domain SSO for web apps.
5
+ * Uses the OxyServices.silentSignIn() method which loads a hidden iframe
6
+ * to check for existing session at auth.oxy.so.
7
+ *
8
+ * This is called automatically by OxyContext on web platforms.
9
+ */
10
+
11
+ import { useEffect, useRef, useCallback } from 'react';
12
+ import type { OxyServices } from '../../core/OxyServices';
13
+ import type { SessionLoginResponse } from '../../models/session';
14
+
15
+ interface UseWebSSOOptions {
16
+ oxyServices: OxyServices;
17
+ onSessionFound: (session: SessionLoginResponse) => Promise<void>;
18
+ onError?: (error: Error) => void;
19
+ enabled?: boolean;
20
+ }
21
+
22
+ interface UseWebSSOResult {
23
+ checkSSO: () => Promise<SessionLoginResponse | null>;
24
+ isChecking: boolean;
25
+ }
26
+
27
+ /**
28
+ * Check if we're running in a web browser environment (not React Native)
29
+ */
30
+ function isWebBrowser(): boolean {
31
+ // Check for browser globals and that we have a real DOM (React Native has window but not documentElement)
32
+ return typeof window !== 'undefined' &&
33
+ typeof document !== 'undefined' &&
34
+ typeof document.documentElement !== 'undefined';
35
+ }
36
+
37
+ /**
38
+ * Hook for automatic web SSO
39
+ *
40
+ * Automatically checks for existing cross-domain session on mount.
41
+ * Only runs on web platforms. Uses OxyServices.silentSignIn() internally.
42
+ */
43
+ export function useWebSSO({
44
+ oxyServices,
45
+ onSessionFound,
46
+ onError,
47
+ enabled = true,
48
+ }: UseWebSSOOptions): UseWebSSOResult {
49
+ const isCheckingRef = useRef(false);
50
+ const hasCheckedRef = useRef(false);
51
+
52
+ const checkSSO = useCallback(async (): Promise<SessionLoginResponse | null> => {
53
+ if (!isWebBrowser() || isCheckingRef.current) {
54
+ return null;
55
+ }
56
+
57
+ isCheckingRef.current = true;
58
+
59
+ try {
60
+ // Use the existing silentSignIn method from OxyServices
61
+ // which handles iframe creation, postMessage, and token storage
62
+ const session = await (oxyServices as any).silentSignIn?.();
63
+
64
+ if (session) {
65
+ await onSessionFound(session);
66
+ }
67
+
68
+ return session;
69
+ } catch (error) {
70
+ onError?.(error instanceof Error ? error : new Error(String(error)));
71
+ return null;
72
+ } finally {
73
+ isCheckingRef.current = false;
74
+ }
75
+ }, [oxyServices, onSessionFound, onError]);
76
+
77
+ // Auto-check SSO on mount (web only)
78
+ useEffect(() => {
79
+ if (!enabled || !isWebBrowser() || hasCheckedRef.current) {
80
+ return;
81
+ }
82
+
83
+ hasCheckedRef.current = true;
84
+ checkSSO();
85
+ }, [enabled, checkSSO]);
86
+
87
+ return {
88
+ checkSSO,
89
+ isChecking: isCheckingRef.current,
90
+ };
91
+ }
92
+
93
+ export { isWebBrowser };
package/src/ui/index.ts CHANGED
@@ -1,13 +1,25 @@
1
1
  /**
2
2
  * UI Component exports - Frontend Only (with backend-safe fallbacks)
3
- *
3
+ *
4
4
  * This module exports all React/React Native UI components and hooks.
5
5
  * In backend, all exports are no-ops or empty objects.
6
+ *
7
+ * NOTE: This entry point uses runtime detection which prevents tree-shaking.
8
+ * For better bundle optimization, use:
9
+ * - '@oxyhq/services/ui/client' for client bundles (tree-shakeable)
10
+ * - '@oxyhq/services/ui/server' for SSR environments (all noops)
11
+ *
12
+ * @example
13
+ * // Client bundle (tree-shakeable)
14
+ * import { OxyProvider, useOxy } from '@oxyhq/services/ui/client';
15
+ *
16
+ * // SSR (noops)
17
+ * import { OxyProvider, useOxy } from '@oxyhq/services/ui/server';
6
18
  */
7
19
  import isFrontend from './isFrontend';
8
20
 
9
21
  // UI exports
10
- let OxyProvider, OxySignInButton, OxyLogo, Avatar, FollowButton, OxyPayButton, FontLoader, setupFonts, OxyIcon, useOxy, useFollow, ProfileScreen, useAuthStore, useAccountStore, fontFamilies, fontStyles, toast, useStorage;
22
+ let OxyProvider, OxySignInButton, OxyLogo, Avatar, FollowButton, OxyPayButton, FontLoader, setupFonts, OxyIcon, useOxy, useAuth, useFollow, ProfileScreen, useAuthStore, useAccountStore, fontFamilies, fontStyles, toast, useStorage;
11
23
 
12
24
  if (isFrontend) {
13
25
  OxyProvider = require('./components/OxyProvider').default;
@@ -20,6 +32,7 @@ if (isFrontend) {
20
32
  setupFonts = require('./components/FontLoader').setupFonts;
21
33
  OxyIcon = require('./components/icon').OxyIcon;
22
34
  useOxy = require('./context/OxyContext').useOxy;
35
+ useAuth = require('./hooks/useAuth').useAuth;
23
36
  useFollow = require('./hooks').useFollow;
24
37
  ProfileScreen = require('./screens/ProfileScreen').default;
25
38
  useAuthStore = require('./stores/authStore').useAuthStore;
@@ -44,6 +57,7 @@ if (isFrontend) {
44
57
  setupFonts = () => {};
45
58
  OxyIcon = noopComponent;
46
59
  useOxy = noopHook;
60
+ useAuth = noopHook;
47
61
  useFollow = noopHook;
48
62
  ProfileScreen = noopComponent;
49
63
  useAuthStore = noopHook;
@@ -65,6 +79,7 @@ export {
65
79
  setupFonts,
66
80
  OxyIcon,
67
81
  useOxy,
82
+ useAuth,
68
83
  useFollow,
69
84
  ProfileScreen,
70
85
  useAuthStore,
@@ -4,7 +4,6 @@ import {
4
4
  Text,
5
5
  StyleSheet,
6
6
  ScrollView,
7
- ActivityIndicator,
8
7
  TouchableOpacity,
9
8
  } from 'react-native';
10
9
  import type { BaseScreenProps } from '../types/navigation';
@@ -12,6 +11,7 @@ import { toast } from '../../lib/sonner';
12
11
  import { Header, Section, Avatar, SettingRow, LoadingState, EmptyState, GroupedSection } from '../components';
13
12
  import { useI18n } from '../hooks/useI18n';
14
13
  import { useThemeStyles } from '../hooks/useThemeStyles';
14
+ import { useSettingToggles } from '../hooks/useSettingToggle';
15
15
  import { normalizeTheme } from '../utils/themeUtils';
16
16
  import type { BlockedUser, RestrictedUser } from '../../models/interfaces';
17
17
  import { useOxy } from '../context/OxyContext';
@@ -38,42 +38,53 @@ interface PrivacySettings {
38
38
  muteKeywords: boolean;
39
39
  }
40
40
 
41
+ const DEFAULT_PRIVACY_SETTINGS: PrivacySettings = {
42
+ isPrivateAccount: false,
43
+ hideOnlineStatus: false,
44
+ hideLastSeen: false,
45
+ profileVisibility: true,
46
+ loginAlerts: true,
47
+ blockScreenshots: false,
48
+ login: true,
49
+ biometricLogin: false,
50
+ showActivity: true,
51
+ allowTagging: true,
52
+ allowMentions: true,
53
+ hideReadReceipts: false,
54
+ allowDirectMessages: true,
55
+ dataSharing: true,
56
+ locationSharing: false,
57
+ analyticsSharing: true,
58
+ sensitiveContent: false,
59
+ autoFilter: true,
60
+ muteKeywords: false,
61
+ };
62
+
41
63
  const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
42
64
  onClose,
43
65
  theme,
44
66
  goBack,
45
67
  }) => {
46
- // Use useOxy() hook for OxyContext values
47
68
  const { oxyServices, user } = useOxy();
48
69
  const { t } = useI18n();
49
- const [settings, setSettings] = useState<PrivacySettings>({
50
- isPrivateAccount: false,
51
- hideOnlineStatus: false,
52
- hideLastSeen: false,
53
- profileVisibility: true,
54
- loginAlerts: true,
55
- blockScreenshots: false,
56
- login: true,
57
- biometricLogin: false,
58
- showActivity: true,
59
- allowTagging: true,
60
- allowMentions: true,
61
- hideReadReceipts: false,
62
- allowDirectMessages: true,
63
- dataSharing: true,
64
- locationSharing: false,
65
- analyticsSharing: true,
66
- sensitiveContent: false,
67
- autoFilter: true,
68
- muteKeywords: false,
69
- });
70
70
  const [isLoading, setIsLoading] = useState(true);
71
- const [isSaving, setIsSaving] = useState(false);
72
71
  const [blockedUsers, setBlockedUsers] = useState<BlockedUser[]>([]);
73
72
  const [restrictedUsers, setRestrictedUsers] = useState<RestrictedUser[]>([]);
74
73
  const [isLoadingUsers, setIsLoadingUsers] = useState(false);
75
74
 
76
- // Load settings and users
75
+ // Use the existing useSettingToggles hook for toggle management
76
+ const { values: settings, toggle, savingKeys, setValues } = useSettingToggles<PrivacySettings>({
77
+ initialValues: DEFAULT_PRIVACY_SETTINGS,
78
+ onSave: async (key, value) => {
79
+ if (!user?.id || !oxyServices) return;
80
+ await oxyServices.updatePrivacySettings({ [key]: value }, user.id);
81
+ },
82
+ errorMessage: t('privacySettings.updateError') || 'Failed to update privacy setting',
83
+ });
84
+
85
+ const isSaving = savingKeys.size > 0;
86
+
87
+ // Load settings
77
88
  useEffect(() => {
78
89
  const loadSettings = async () => {
79
90
  try {
@@ -81,7 +92,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
81
92
  if (user?.id && oxyServices) {
82
93
  const privacySettings = await oxyServices.getPrivacySettings(user.id);
83
94
  if (privacySettings) {
84
- setSettings(privacySettings);
95
+ setValues(privacySettings);
85
96
  }
86
97
  }
87
98
  } catch (error) {
@@ -93,7 +104,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
93
104
  };
94
105
 
95
106
  loadSettings();
96
- }, [user?.id, oxyServices, t]);
107
+ }, [user?.id, oxyServices, t, setValues]);
97
108
 
98
109
  // Load blocked and restricted users
99
110
  useEffect(() => {
@@ -117,26 +128,6 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
117
128
  loadUsers();
118
129
  }, [oxyServices]);
119
130
 
120
- const updateSetting = useCallback(async (key: keyof PrivacySettings, value: boolean) => {
121
- try {
122
- setIsSaving(true);
123
- const newSettings = { ...settings, [key]: value };
124
- setSettings(newSettings);
125
-
126
- if (user?.id && oxyServices) {
127
- await oxyServices.updatePrivacySettings({ [key]: value }, user.id);
128
- toast.success(t('privacySettings.updated') || 'Privacy settings updated');
129
- }
130
- } catch (error) {
131
- console.error(`Failed to update ${key}:`, error);
132
- toast.error(t('privacySettings.updateError') || 'Failed to update privacy setting');
133
- // Revert on error
134
- setSettings(settings);
135
- } finally {
136
- setIsSaving(false);
137
- }
138
- }, [settings, user?.id, oxyServices, t]);
139
-
140
131
  const handleUnblock = useCallback(async (userId: string) => {
141
132
  if (!oxyServices) return;
142
133
  try {
@@ -295,7 +286,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
295
286
  title={t('privacySettings.isPrivateAccount') || 'Private Account'}
296
287
  description={t('privacySettings.isPrivateAccountDesc') || 'Only approved followers can see your posts'}
297
288
  value={settings.isPrivateAccount}
298
- onValueChange={(value) => updateSetting('isPrivateAccount', value)}
289
+ onValueChange={() => toggle('isPrivateAccount')}
299
290
  disabled={isSaving}
300
291
  textColor={themeStyles.textColor}
301
292
  mutedTextColor={themeStyles.mutedTextColor}
@@ -305,7 +296,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
305
296
  title={t('privacySettings.profileVisibility') || 'Profile Visibility'}
306
297
  description={t('privacySettings.profileVisibilityDesc') || 'Control who can view your profile'}
307
298
  value={settings.profileVisibility}
308
- onValueChange={(value) => updateSetting('profileVisibility', value)}
299
+ onValueChange={() => toggle('profileVisibility')}
309
300
  disabled={isSaving}
310
301
  textColor={themeStyles.textColor}
311
302
  mutedTextColor={themeStyles.mutedTextColor}
@@ -315,7 +306,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
315
306
  title={t('privacySettings.hideOnlineStatus') || 'Hide Online Status'}
316
307
  description={t('privacySettings.hideOnlineStatusDesc') || 'Don\'t show when you\'re online'}
317
308
  value={settings.hideOnlineStatus}
318
- onValueChange={(value) => updateSetting('hideOnlineStatus', value)}
309
+ onValueChange={() => toggle('hideOnlineStatus')}
319
310
  disabled={isSaving}
320
311
  textColor={themeStyles.textColor}
321
312
  mutedTextColor={themeStyles.mutedTextColor}
@@ -325,7 +316,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
325
316
  title={t('privacySettings.hideLastSeen') || 'Hide Last Seen'}
326
317
  description={t('privacySettings.hideLastSeenDesc') || 'Don\'t show when you were last active'}
327
318
  value={settings.hideLastSeen}
328
- onValueChange={(value) => updateSetting('hideLastSeen', value)}
319
+ onValueChange={() => toggle('hideLastSeen')}
329
320
  disabled={isSaving}
330
321
  textColor={themeStyles.textColor}
331
322
  mutedTextColor={themeStyles.mutedTextColor}
@@ -339,7 +330,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
339
330
  title={t('privacySettings.allowTagging') || 'Allow Tagging'}
340
331
  description={t('privacySettings.allowTaggingDesc') || 'Let others tag you in posts'}
341
332
  value={settings.allowTagging}
342
- onValueChange={(value) => updateSetting('allowTagging', value)}
333
+ onValueChange={() => toggle('allowTagging')}
343
334
  disabled={isSaving}
344
335
  textColor={themeStyles.textColor}
345
336
  mutedTextColor={themeStyles.mutedTextColor}
@@ -349,7 +340,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
349
340
  title={t('privacySettings.allowMentions') || 'Allow Mentions'}
350
341
  description={t('privacySettings.allowMentionsDesc') || 'Let others mention you'}
351
342
  value={settings.allowMentions}
352
- onValueChange={(value) => updateSetting('allowMentions', value)}
343
+ onValueChange={() => toggle('allowMentions')}
353
344
  disabled={isSaving}
354
345
  textColor={themeStyles.textColor}
355
346
  mutedTextColor={themeStyles.mutedTextColor}
@@ -359,7 +350,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
359
350
  title={t('privacySettings.allowDirectMessages') || 'Allow Direct Messages'}
360
351
  description={t('privacySettings.allowDirectMessagesDesc') || 'Let others send you direct messages'}
361
352
  value={settings.allowDirectMessages}
362
- onValueChange={(value) => updateSetting('allowDirectMessages', value)}
353
+ onValueChange={() => toggle('allowDirectMessages')}
363
354
  disabled={isSaving}
364
355
  textColor={themeStyles.textColor}
365
356
  mutedTextColor={themeStyles.mutedTextColor}
@@ -369,7 +360,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
369
360
  title={t('privacySettings.hideReadReceipts') || 'Hide Read Receipts'}
370
361
  description={t('privacySettings.hideReadReceiptsDesc') || 'Don\'t show read receipts in messages'}
371
362
  value={settings.hideReadReceipts}
372
- onValueChange={(value) => updateSetting('hideReadReceipts', value)}
363
+ onValueChange={() => toggle('hideReadReceipts')}
373
364
  disabled={isSaving}
374
365
  textColor={themeStyles.textColor}
375
366
  mutedTextColor={themeStyles.mutedTextColor}
@@ -383,7 +374,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
383
374
  title={t('privacySettings.showActivity') || 'Show Activity Status'}
384
375
  description={t('privacySettings.showActivityDesc') || 'Display your activity on your profile'}
385
376
  value={settings.showActivity}
386
- onValueChange={(value) => updateSetting('showActivity', value)}
377
+ onValueChange={() => toggle('showActivity')}
387
378
  disabled={isSaving}
388
379
  textColor={themeStyles.textColor}
389
380
  mutedTextColor={themeStyles.mutedTextColor}
@@ -393,7 +384,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
393
384
  title={t('privacySettings.dataSharing') || 'Data Sharing'}
394
385
  description={t('privacySettings.dataSharingDesc') || 'Allow sharing data for personalization'}
395
386
  value={settings.dataSharing}
396
- onValueChange={(value) => updateSetting('dataSharing', value)}
387
+ onValueChange={() => toggle('dataSharing')}
397
388
  disabled={isSaving}
398
389
  textColor={themeStyles.textColor}
399
390
  mutedTextColor={themeStyles.mutedTextColor}
@@ -403,7 +394,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
403
394
  title={t('privacySettings.locationSharing') || 'Location Sharing'}
404
395
  description={t('privacySettings.locationSharingDesc') || 'Share your location'}
405
396
  value={settings.locationSharing}
406
- onValueChange={(value) => updateSetting('locationSharing', value)}
397
+ onValueChange={() => toggle('locationSharing')}
407
398
  disabled={isSaving}
408
399
  textColor={themeStyles.textColor}
409
400
  mutedTextColor={themeStyles.mutedTextColor}
@@ -413,7 +404,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
413
404
  title={t('privacySettings.analyticsSharing') || 'Analytics Sharing'}
414
405
  description={t('privacySettings.analyticsSharingDesc') || 'Allow analytics data collection'}
415
406
  value={settings.analyticsSharing}
416
- onValueChange={(value) => updateSetting('analyticsSharing', value)}
407
+ onValueChange={() => toggle('analyticsSharing')}
417
408
  disabled={isSaving}
418
409
  textColor={themeStyles.textColor}
419
410
  mutedTextColor={themeStyles.mutedTextColor}
@@ -427,7 +418,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
427
418
  title={t('privacySettings.sensitiveContent') || 'Show Sensitive Content'}
428
419
  description={t('privacySettings.sensitiveContentDesc') || 'Allow sensitive or explicit content'}
429
420
  value={settings.sensitiveContent}
430
- onValueChange={(value) => updateSetting('sensitiveContent', value)}
421
+ onValueChange={() => toggle('sensitiveContent')}
431
422
  disabled={isSaving}
432
423
  textColor={themeStyles.textColor}
433
424
  mutedTextColor={themeStyles.mutedTextColor}
@@ -437,7 +428,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
437
428
  title={t('privacySettings.autoFilter') || 'Auto Filter'}
438
429
  description={t('privacySettings.autoFilterDesc') || 'Automatically filter inappropriate content'}
439
430
  value={settings.autoFilter}
440
- onValueChange={(value) => updateSetting('autoFilter', value)}
431
+ onValueChange={() => toggle('autoFilter')}
441
432
  disabled={isSaving}
442
433
  textColor={themeStyles.textColor}
443
434
  mutedTextColor={themeStyles.mutedTextColor}
@@ -447,7 +438,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
447
438
  title={t('privacySettings.muteKeywords') || 'Mute Keywords'}
448
439
  description={t('privacySettings.muteKeywordsDesc') || 'Hide posts containing muted keywords'}
449
440
  value={settings.muteKeywords}
450
- onValueChange={(value) => updateSetting('muteKeywords', value)}
441
+ onValueChange={() => toggle('muteKeywords')}
451
442
  disabled={isSaving}
452
443
  textColor={themeStyles.textColor}
453
444
  mutedTextColor={themeStyles.mutedTextColor}
@@ -457,7 +448,7 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
457
448
  title={t('privacySettings.blockScreenshots') || 'Block Screenshots'}
458
449
  description={t('privacySettings.blockScreenshotsDesc') || 'Prevent screenshots of your content'}
459
450
  value={settings.blockScreenshots}
460
- onValueChange={(value) => updateSetting('blockScreenshots', value)}
451
+ onValueChange={() => toggle('blockScreenshots')}
461
452
  disabled={isSaving}
462
453
  textColor={themeStyles.textColor}
463
454
  mutedTextColor={themeStyles.mutedTextColor}