@oxyhq/services 5.21.4 → 5.21.6
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.
- package/lib/commonjs/ui/components/BottomSheetRouter.js +100 -286
- package/lib/commonjs/ui/components/BottomSheetRouter.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedItem.js +0 -3
- package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +14 -19
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +50 -30
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useWebSSO.js +0 -49
- package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -1
- package/lib/commonjs/ui/navigation/bottomSheetManager.js +43 -145
- package/lib/commonjs/ui/navigation/bottomSheetManager.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +0 -2
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/components/BottomSheetRouter.js +102 -284
- package/lib/module/ui/components/BottomSheetRouter.js.map +1 -1
- package/lib/module/ui/components/GroupedItem.js +0 -3
- package/lib/module/ui/components/GroupedItem.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +14 -19
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +50 -30
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useWebSSO.js +0 -49
- package/lib/module/ui/hooks/useWebSSO.js.map +1 -1
- package/lib/module/ui/navigation/bottomSheetManager.js +37 -135
- package/lib/module/ui/navigation/bottomSheetManager.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +0 -2
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts +2 -7
- package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/GroupedItem.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts +11 -60
- package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts +2 -7
- package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/GroupedItem.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts +11 -60
- package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/ui/components/BottomSheetRouter.tsx +97 -319
- package/src/ui/components/GroupedItem.tsx +0 -4
- package/src/ui/components/OxyProvider.tsx +13 -18
- package/src/ui/context/OxyContext.tsx +56 -33
- package/src/ui/hooks/useWebSSO.ts +0 -53
- package/src/ui/navigation/bottomSheetManager.ts +43 -150
- package/src/ui/screens/AccountSettingsScreen.tsx +0 -2
- package/lib/commonjs/ui/hooks/use-haptic-press.js +0 -21
- package/lib/commonjs/ui/hooks/use-haptic-press.js.map +0 -1
- package/lib/module/ui/hooks/use-haptic-press.js +0 -17
- package/lib/module/ui/hooks/use-haptic-press.js.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/use-haptic-press.d.ts +0 -8
- package/lib/typescript/commonjs/ui/hooks/use-haptic-press.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/use-haptic-press.d.ts +0 -8
- package/lib/typescript/module/ui/hooks/use-haptic-press.d.ts.map +0 -1
- package/src/ui/hooks/use-haptic-press.ts +0 -15
|
@@ -16,20 +16,26 @@ setupFonts();
|
|
|
16
16
|
// Detect if running on web
|
|
17
17
|
const isWeb = Platform.OS === 'web';
|
|
18
18
|
|
|
19
|
-
// Conditionally import
|
|
19
|
+
// Conditionally import components
|
|
20
20
|
let KeyboardProvider: any = ({ children }: any) => children;
|
|
21
|
-
let BottomSheetRouter: any =
|
|
21
|
+
let BottomSheetRouter: any = null;
|
|
22
22
|
|
|
23
|
+
// KeyboardProvider only on native
|
|
23
24
|
if (!isWeb) {
|
|
24
25
|
try {
|
|
25
|
-
// Only import on native platforms
|
|
26
26
|
KeyboardProvider = require('react-native-keyboard-controller').KeyboardProvider;
|
|
27
|
-
BottomSheetRouter = require('./BottomSheetRouter').default;
|
|
28
27
|
} catch {
|
|
29
|
-
//
|
|
28
|
+
// KeyboardProvider not available
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
// BottomSheetRouter works on all platforms
|
|
33
|
+
try {
|
|
34
|
+
BottomSheetRouter = require('./BottomSheetRouter').default;
|
|
35
|
+
} catch {
|
|
36
|
+
// BottomSheetRouter not available
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* OxyProvider - Universal provider for Expo apps (native + web)
|
|
35
41
|
*
|
|
@@ -199,24 +205,13 @@ const OxyProvider: FC<OxyProviderProps> = ({
|
|
|
199
205
|
>
|
|
200
206
|
{children}
|
|
201
207
|
{/* Only render bottom sheet router on native */}
|
|
202
|
-
{
|
|
208
|
+
{BottomSheetRouter && <BottomSheetRouter />}
|
|
203
209
|
<Toaster />
|
|
204
210
|
</OxyContextProvider>
|
|
205
211
|
</QueryClientProvider>
|
|
206
212
|
);
|
|
207
213
|
|
|
208
|
-
//
|
|
209
|
-
if (isWeb) {
|
|
210
|
-
return (
|
|
211
|
-
<SafeAreaProvider>
|
|
212
|
-
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
213
|
-
{coreContent}
|
|
214
|
-
</GestureHandlerRootView>
|
|
215
|
-
</SafeAreaProvider>
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// On native, full wrappers including KeyboardProvider
|
|
214
|
+
// All platforms use same wrapper (KeyboardProvider is passthrough on web)
|
|
220
215
|
return (
|
|
221
216
|
<SafeAreaProvider>
|
|
222
217
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
@@ -428,21 +428,13 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
428
428
|
// Web SSO: Automatically check for cross-domain session on web platforms
|
|
429
429
|
// Also used for popup auth - updates all state and persists session
|
|
430
430
|
const handleWebSSOSession = useCallback(async (session: any) => {
|
|
431
|
-
console.log('[OxyContext] handleWebSSOSession called:', {
|
|
432
|
-
hasSession: !!session,
|
|
433
|
-
hasUser: !!session?.user,
|
|
434
|
-
hasSessionId: !!session?.sessionId,
|
|
435
|
-
sessionIdPrefix: session?.sessionId?.substring(0, 8),
|
|
436
|
-
userId: session?.user?.id,
|
|
437
|
-
});
|
|
438
|
-
|
|
439
431
|
if (!session?.user || !session?.sessionId) {
|
|
440
|
-
|
|
432
|
+
if (__DEV__) {
|
|
433
|
+
loggerUtil.warn('handleWebSSOSession: Invalid session', { component: 'OxyContext' });
|
|
434
|
+
}
|
|
441
435
|
return;
|
|
442
436
|
}
|
|
443
437
|
|
|
444
|
-
console.log('[OxyContext] handleWebSSOSession: Processing valid session...');
|
|
445
|
-
|
|
446
438
|
// Update sessions state
|
|
447
439
|
const clientSession = {
|
|
448
440
|
sessionId: session.sessionId,
|
|
@@ -467,34 +459,12 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
467
459
|
sessionIds.push(session.sessionId);
|
|
468
460
|
await storage.setItem(storageKeys.sessionIds, JSON.stringify(sessionIds));
|
|
469
461
|
}
|
|
470
|
-
console.log('[OxyContext] handleWebSSOSession: Session persisted to storage', {
|
|
471
|
-
sessionId: session.sessionId?.substring(0, 8),
|
|
472
|
-
totalSessions: sessionIds.length,
|
|
473
|
-
});
|
|
474
|
-
} else {
|
|
475
|
-
console.warn('[OxyContext] handleWebSSOSession: No storage available, session not persisted!');
|
|
476
462
|
}
|
|
477
|
-
|
|
478
|
-
console.log('[OxyContext] handleWebSSOSession: Complete! User should now be authenticated.');
|
|
479
463
|
}, [updateSessions, setActiveSessionId, loginSuccess, onAuthStateChange, storage, storageKeys]);
|
|
480
464
|
|
|
481
465
|
// Enable web SSO only after local storage check completes and no user found
|
|
482
466
|
const shouldTryWebSSO = isWebBrowser() && tokenReady && !user && initialized;
|
|
483
467
|
|
|
484
|
-
// Debug logging for SSO conditions
|
|
485
|
-
useEffect(() => {
|
|
486
|
-
if (isWebBrowser()) {
|
|
487
|
-
console.log('[OxyContext] Web SSO conditions:', {
|
|
488
|
-
isWebBrowser: true,
|
|
489
|
-
tokenReady,
|
|
490
|
-
hasUser: !!user,
|
|
491
|
-
initialized,
|
|
492
|
-
shouldTryWebSSO,
|
|
493
|
-
hostname: typeof window !== 'undefined' ? window.location.hostname : 'unknown',
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
}, [tokenReady, user, shouldTryWebSSO]);
|
|
497
|
-
|
|
498
468
|
useWebSSO({
|
|
499
469
|
oxyServices,
|
|
500
470
|
onSessionFound: handleWebSSOSession,
|
|
@@ -506,6 +476,59 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
506
476
|
enabled: shouldTryWebSSO,
|
|
507
477
|
});
|
|
508
478
|
|
|
479
|
+
// IdP session validation via lightweight iframe check
|
|
480
|
+
// When user returns to tab, verify auth.oxy.so still has their session
|
|
481
|
+
// If session is gone (cleared/logged out), clear local session too
|
|
482
|
+
const lastIdPCheckRef = useRef<number>(0);
|
|
483
|
+
|
|
484
|
+
useEffect(() => {
|
|
485
|
+
if (!isWebBrowser() || !user || !initialized) return;
|
|
486
|
+
|
|
487
|
+
const checkIdPSession = () => {
|
|
488
|
+
// Debounce: check at most once per 30 seconds
|
|
489
|
+
const now = Date.now();
|
|
490
|
+
if (now - lastIdPCheckRef.current < 30000) return;
|
|
491
|
+
lastIdPCheckRef.current = now;
|
|
492
|
+
|
|
493
|
+
// Load hidden iframe to check IdP session via postMessage
|
|
494
|
+
const iframe = document.createElement('iframe');
|
|
495
|
+
iframe.style.cssText = 'display:none;width:0;height:0;border:0';
|
|
496
|
+
iframe.src = 'https://auth.oxy.so/api/auth/session-check';
|
|
497
|
+
|
|
498
|
+
let cleaned = false;
|
|
499
|
+
const cleanup = () => {
|
|
500
|
+
if (cleaned) return;
|
|
501
|
+
cleaned = true;
|
|
502
|
+
window.removeEventListener('message', handleMessage);
|
|
503
|
+
iframe.remove();
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const handleMessage = async (event: MessageEvent) => {
|
|
507
|
+
if (event.origin !== 'https://auth.oxy.so') return;
|
|
508
|
+
if (event.data?.type !== 'oxy-session-check') return;
|
|
509
|
+
cleanup();
|
|
510
|
+
|
|
511
|
+
if (!event.data.hasSession) {
|
|
512
|
+
toast.info('Your session has ended. Please sign in again.');
|
|
513
|
+
await clearSessionState();
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
window.addEventListener('message', handleMessage);
|
|
518
|
+
document.body.appendChild(iframe);
|
|
519
|
+
setTimeout(cleanup, 5000); // Timeout after 5s
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const handleVisibilityChange = () => {
|
|
523
|
+
if (document.visibilityState === 'visible') {
|
|
524
|
+
checkIdPSession();
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
529
|
+
return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
530
|
+
}, [user, initialized, clearSessionState]);
|
|
531
|
+
|
|
509
532
|
const activeSession = activeSessionId
|
|
510
533
|
? sessions.find((session) => session.sessionId === activeSessionId)
|
|
511
534
|
: undefined;
|
|
@@ -87,60 +87,35 @@ export function useWebSSO({
|
|
|
87
87
|
const fedCMSupported = isWebBrowser() && (oxyServices as any).isFedCMSupported?.();
|
|
88
88
|
|
|
89
89
|
const checkSSO = useCallback(async (): Promise<SessionLoginResponse | null> => {
|
|
90
|
-
console.log('[useWebSSO] checkSSO called', {
|
|
91
|
-
isWebBrowser: isWebBrowser(),
|
|
92
|
-
isChecking: isCheckingRef.current,
|
|
93
|
-
isIdP: isIdentityProvider(),
|
|
94
|
-
fedCMSupported,
|
|
95
|
-
});
|
|
96
|
-
|
|
97
90
|
if (!isWebBrowser() || isCheckingRef.current) {
|
|
98
91
|
return null;
|
|
99
92
|
}
|
|
100
93
|
|
|
101
94
|
// Don't use FedCM on the auth domain itself - it would authenticate against itself
|
|
102
95
|
if (isIdentityProvider()) {
|
|
103
|
-
console.log('[useWebSSO] Skipping - on identity provider domain');
|
|
104
96
|
onSSOUnavailable?.();
|
|
105
97
|
return null;
|
|
106
98
|
}
|
|
107
99
|
|
|
108
100
|
// FedCM is the only reliable cross-domain SSO mechanism
|
|
109
|
-
// Third-party cookies are deprecated and unreliable
|
|
110
101
|
if (!fedCMSupported) {
|
|
111
|
-
console.log('[useWebSSO] Skipping - FedCM not supported');
|
|
112
102
|
onSSOUnavailable?.();
|
|
113
103
|
return null;
|
|
114
104
|
}
|
|
115
105
|
|
|
116
106
|
isCheckingRef.current = true;
|
|
117
|
-
console.log('[useWebSSO] Starting FedCM silent sign-in...');
|
|
118
107
|
|
|
119
108
|
try {
|
|
120
|
-
// Use FedCM for cross-domain SSO
|
|
121
|
-
// This works because browser treats IdP requests as first-party
|
|
122
109
|
const session = await (oxyServices as any).silentSignInWithFedCM?.();
|
|
123
110
|
|
|
124
|
-
console.log('[useWebSSO] FedCM result:', {
|
|
125
|
-
hasSession: !!session,
|
|
126
|
-
hasUser: !!session?.user,
|
|
127
|
-
hasSessionId: !!session?.sessionId,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
111
|
if (session) {
|
|
131
|
-
console.log('[useWebSSO] Session found, calling onSessionFound...');
|
|
132
112
|
await onSessionFound(session);
|
|
133
|
-
console.log('[useWebSSO] onSessionFound completed');
|
|
134
113
|
return session;
|
|
135
114
|
}
|
|
136
115
|
|
|
137
|
-
// No session found - user needs to sign in
|
|
138
|
-
console.log('[useWebSSO] No session returned from FedCM');
|
|
139
116
|
onSSOUnavailable?.();
|
|
140
117
|
return null;
|
|
141
118
|
} catch (error) {
|
|
142
|
-
// FedCM failed - could be network error, user not signed in, etc.
|
|
143
|
-
console.error('[useWebSSO] FedCM error:', error);
|
|
144
119
|
onSSOUnavailable?.();
|
|
145
120
|
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
146
121
|
return null;
|
|
@@ -155,41 +130,27 @@ export function useWebSSO({
|
|
|
155
130
|
* Use this when silent mediation fails (user hasn't previously consented).
|
|
156
131
|
*/
|
|
157
132
|
const signInWithFedCM = useCallback(async (): Promise<SessionLoginResponse | null> => {
|
|
158
|
-
console.log('[useWebSSO] signInWithFedCM called');
|
|
159
|
-
|
|
160
133
|
if (!isWebBrowser() || isCheckingRef.current) {
|
|
161
134
|
return null;
|
|
162
135
|
}
|
|
163
136
|
|
|
164
137
|
if (!fedCMSupported) {
|
|
165
|
-
console.log('[useWebSSO] FedCM not supported for interactive sign-in');
|
|
166
138
|
onError?.(new Error('FedCM is not supported in this browser'));
|
|
167
139
|
return null;
|
|
168
140
|
}
|
|
169
141
|
|
|
170
142
|
isCheckingRef.current = true;
|
|
171
|
-
console.log('[useWebSSO] Starting interactive FedCM sign-in...');
|
|
172
143
|
|
|
173
144
|
try {
|
|
174
|
-
// Use interactive sign-in (shows browser UI)
|
|
175
145
|
const session = await (oxyServices as any).signInWithFedCM?.();
|
|
176
146
|
|
|
177
|
-
console.log('[useWebSSO] Interactive FedCM result:', {
|
|
178
|
-
hasSession: !!session,
|
|
179
|
-
hasUser: !!session?.user,
|
|
180
|
-
hasSessionId: !!session?.sessionId,
|
|
181
|
-
});
|
|
182
|
-
|
|
183
147
|
if (session) {
|
|
184
|
-
console.log('[useWebSSO] Interactive session found, calling onSessionFound...');
|
|
185
148
|
await onSessionFound(session);
|
|
186
|
-
console.log('[useWebSSO] onSessionFound completed');
|
|
187
149
|
return session;
|
|
188
150
|
}
|
|
189
151
|
|
|
190
152
|
return null;
|
|
191
153
|
} catch (error) {
|
|
192
|
-
console.error('[useWebSSO] Interactive FedCM error:', error);
|
|
193
154
|
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
194
155
|
return null;
|
|
195
156
|
} finally {
|
|
@@ -199,19 +160,7 @@ export function useWebSSO({
|
|
|
199
160
|
|
|
200
161
|
// Auto-check SSO on mount (web only, FedCM only, not on auth domain)
|
|
201
162
|
useEffect(() => {
|
|
202
|
-
console.log('[useWebSSO] Effect running:', {
|
|
203
|
-
enabled,
|
|
204
|
-
isWeb: isWebBrowser(),
|
|
205
|
-
hasChecked: hasCheckedRef.current,
|
|
206
|
-
isIdP: isIdentityProvider(),
|
|
207
|
-
fedCMSupported,
|
|
208
|
-
hostname: typeof window !== 'undefined' ? window.location.hostname : 'unknown',
|
|
209
|
-
});
|
|
210
|
-
|
|
211
163
|
if (!enabled || !isWebBrowser() || hasCheckedRef.current || isIdentityProvider()) {
|
|
212
|
-
console.log('[useWebSSO] Skipping SSO check:', {
|
|
213
|
-
reason: !enabled ? 'not enabled' : !isWebBrowser() ? 'not web' : hasCheckedRef.current ? 'already checked' : 'is IdP',
|
|
214
|
-
});
|
|
215
164
|
if (isIdentityProvider()) {
|
|
216
165
|
onSSOUnavailable?.();
|
|
217
166
|
}
|
|
@@ -223,8 +172,6 @@ export function useWebSSO({
|
|
|
223
172
|
if (fedCMSupported) {
|
|
224
173
|
checkSSO();
|
|
225
174
|
} else {
|
|
226
|
-
// Browser doesn't support FedCM - notify caller
|
|
227
|
-
console.log('[useWebSSO] FedCM not supported');
|
|
228
175
|
onSSOUnavailable?.();
|
|
229
176
|
}
|
|
230
177
|
}, [enabled, checkSSO, fedCMSupported, onSSOUnavailable]);
|
|
@@ -3,189 +3,82 @@ import { isValidRoute } from './routes';
|
|
|
3
3
|
import { createStore } from 'zustand/vanilla';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Bottom Sheet Manager
|
|
7
|
-
*
|
|
8
|
-
* Uses Zustand (vanilla) for robust state management without React dependencies.
|
|
9
|
-
* This ensures the manager can be imported safely anywhere.
|
|
6
|
+
* Bottom Sheet State Manager
|
|
10
7
|
*/
|
|
11
8
|
|
|
12
|
-
export interface
|
|
13
|
-
screen: RouteName;
|
|
14
|
-
props: Record<string, unknown>;
|
|
15
|
-
step?: number; // For step-based screens
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface BottomSheetRouterState {
|
|
9
|
+
export interface BottomSheetState {
|
|
19
10
|
currentScreen: RouteName | null;
|
|
20
11
|
screenProps: Record<string, unknown>;
|
|
21
|
-
currentStep?: number;
|
|
22
|
-
|
|
12
|
+
currentStep?: number;
|
|
13
|
+
history: Array<{ screen: RouteName; props: Record<string, unknown>; step?: number }>;
|
|
23
14
|
isOpen: boolean;
|
|
24
15
|
}
|
|
25
16
|
|
|
26
|
-
|
|
27
|
-
const initialState: BottomSheetRouterState = {
|
|
17
|
+
const initialState: BottomSheetState = {
|
|
28
18
|
currentScreen: null,
|
|
29
19
|
screenProps: {},
|
|
30
20
|
currentStep: undefined,
|
|
31
|
-
|
|
21
|
+
history: [],
|
|
32
22
|
isOpen: false,
|
|
33
23
|
};
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
export const bottomSheetStore = createStore<BottomSheetRouterState>(() => initialState);
|
|
25
|
+
export const bottomSheetStore = createStore<BottomSheetState>(() => initialState);
|
|
37
26
|
|
|
38
|
-
|
|
39
|
-
type BottomSheetRefObject = { current: { present: () => void; dismiss: () => void } | null } | null;
|
|
40
|
-
let bottomSheetRef: BottomSheetRefObject = null;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Set the bottom sheet ref so showBottomSheet can control it
|
|
44
|
-
*/
|
|
45
|
-
export const setBottomSheetRef = (ref: BottomSheetRefObject) => {
|
|
46
|
-
bottomSheetRef = ref;
|
|
47
|
-
};
|
|
27
|
+
export const getState = () => bottomSheetStore.getState();
|
|
48
28
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
bottomSheetStore.setState((state) => ({ ...state, ...updates }));
|
|
55
|
-
};
|
|
29
|
+
export const showBottomSheet = (
|
|
30
|
+
screenOrConfig: RouteName | { screen: RouteName; props?: Record<string, unknown> },
|
|
31
|
+
): void => {
|
|
32
|
+
const screen = typeof screenOrConfig === 'string' ? screenOrConfig : screenOrConfig.screen;
|
|
33
|
+
const props = typeof screenOrConfig === 'string' ? {} : (screenOrConfig.props || {});
|
|
56
34
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
export const subscribeToBottomSheetState = (listener: (state: BottomSheetRouterState) => void) => {
|
|
62
|
-
return bottomSheetStore.subscribe(listener);
|
|
63
|
-
};
|
|
35
|
+
if (!isValidRoute(screen)) {
|
|
36
|
+
if (__DEV__) console.warn(`[BottomSheet] Invalid route: ${screen}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
64
39
|
|
|
65
|
-
|
|
66
|
-
* Get the current bottom sheet state
|
|
67
|
-
* (Wrapper around store.getState for backward compatibility)
|
|
68
|
-
*/
|
|
69
|
-
export const getBottomSheetState = (): BottomSheetRouterState => {
|
|
70
|
-
return bottomSheetStore.getState();
|
|
71
|
-
};
|
|
40
|
+
const state = bottomSheetStore.getState();
|
|
72
41
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const addToHistory = options?.addToHistory !== false; // Default to true
|
|
83
|
-
|
|
84
|
-
// If adding to history and there's a current screen, push it to history
|
|
85
|
-
if (addToHistory && currentState.currentScreen) {
|
|
86
|
-
const historyEntry: NavigationHistoryEntry = {
|
|
87
|
-
screen: currentState.currentScreen,
|
|
88
|
-
props: { ...currentState.screenProps },
|
|
89
|
-
step: currentState.currentStep,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// We need to create a new array for immutability
|
|
93
|
-
const newHistory = [...currentState.navigationHistory, historyEntry];
|
|
94
|
-
bottomSheetStore.setState({ navigationHistory: newHistory });
|
|
42
|
+
// Push current screen to history if navigating to different screen
|
|
43
|
+
if (state.currentScreen && state.currentScreen !== screen) {
|
|
44
|
+
bottomSheetStore.setState({
|
|
45
|
+
history: [...state.history, {
|
|
46
|
+
screen: state.currentScreen,
|
|
47
|
+
props: state.screenProps,
|
|
48
|
+
step: state.currentStep,
|
|
49
|
+
}],
|
|
50
|
+
});
|
|
95
51
|
}
|
|
96
|
-
|
|
97
|
-
// Determine the new step
|
|
98
|
-
const newStep = options?.step ??
|
|
99
|
-
(props?.initialStep !== undefined ? props.initialStep :
|
|
100
|
-
(addToHistory ? undefined : currentState.currentStep));
|
|
101
|
-
|
|
52
|
+
|
|
102
53
|
bottomSheetStore.setState({
|
|
103
54
|
currentScreen: screen,
|
|
104
|
-
screenProps: props
|
|
105
|
-
currentStep:
|
|
55
|
+
screenProps: props,
|
|
56
|
+
currentStep: typeof props.initialStep === 'number' ? props.initialStep : undefined,
|
|
106
57
|
isOpen: true,
|
|
107
58
|
});
|
|
108
|
-
|
|
109
|
-
// Present the sheet after state update
|
|
110
|
-
if (bottomSheetRef?.current) {
|
|
111
|
-
bottomSheetRef.current.present();
|
|
112
|
-
}
|
|
113
59
|
};
|
|
114
60
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
*/
|
|
118
|
-
export const managerCloseBottomSheet = (): void => {
|
|
119
|
-
bottomSheetStore.setState({
|
|
120
|
-
currentScreen: null,
|
|
121
|
-
screenProps: {},
|
|
122
|
-
currentStep: undefined,
|
|
123
|
-
navigationHistory: [],
|
|
124
|
-
isOpen: false,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
if (bottomSheetRef?.current) {
|
|
128
|
-
bottomSheetRef.current.dismiss();
|
|
129
|
-
}
|
|
61
|
+
export const closeBottomSheet = (): void => {
|
|
62
|
+
bottomSheetStore.setState(initialState);
|
|
130
63
|
};
|
|
131
64
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
* Returns true if back navigation was successful, false if history is empty
|
|
135
|
-
*/
|
|
136
|
-
export const managerGoBack = (): boolean => {
|
|
137
|
-
const currentState = bottomSheetStore.getState();
|
|
65
|
+
export const goBack = (): boolean => {
|
|
66
|
+
const { history } = bottomSheetStore.getState();
|
|
138
67
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const previous = currentState.navigationHistory[currentState.navigationHistory.length - 1];
|
|
142
|
-
const newHistory = currentState.navigationHistory.slice(0, -1);
|
|
143
|
-
|
|
68
|
+
if (history.length > 0) {
|
|
69
|
+
const prev = history[history.length - 1];
|
|
144
70
|
bottomSheetStore.setState({
|
|
145
|
-
currentScreen:
|
|
146
|
-
screenProps:
|
|
147
|
-
currentStep:
|
|
148
|
-
|
|
149
|
-
isOpen: true,
|
|
71
|
+
currentScreen: prev.screen,
|
|
72
|
+
screenProps: prev.props,
|
|
73
|
+
currentStep: prev.step,
|
|
74
|
+
history: history.slice(0, -1),
|
|
150
75
|
});
|
|
151
|
-
|
|
152
76
|
return true;
|
|
153
77
|
}
|
|
154
|
-
|
|
155
|
-
return false;
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Public API for showing bottom sheets
|
|
160
|
-
*/
|
|
161
|
-
export const showBottomSheet = (
|
|
162
|
-
screenOrConfig: RouteName | { screen: RouteName; props?: Record<string, unknown> },
|
|
163
|
-
): void => {
|
|
164
|
-
let screen: RouteName;
|
|
165
|
-
let props: Record<string, unknown> = {};
|
|
166
|
-
|
|
167
|
-
if (typeof screenOrConfig === 'string') {
|
|
168
|
-
screen = screenOrConfig;
|
|
169
|
-
} else {
|
|
170
|
-
screen = screenOrConfig.screen;
|
|
171
|
-
props = screenOrConfig.props || {};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (!isValidRoute(screen)) {
|
|
175
|
-
if (__DEV__) {
|
|
176
|
-
console.warn(`[BottomSheetAPI] Invalid route: ${screen}`);
|
|
177
|
-
}
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
78
|
|
|
181
|
-
|
|
79
|
+
return false;
|
|
182
80
|
};
|
|
183
81
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
*/
|
|
187
|
-
export const closeBottomSheet = (): void => {
|
|
188
|
-
managerCloseBottomSheet();
|
|
82
|
+
export const updateState = (updates: Partial<BottomSheetState>) => {
|
|
83
|
+
bottomSheetStore.setState((state) => ({ ...state, ...updates }));
|
|
189
84
|
};
|
|
190
|
-
|
|
191
|
-
|
|
@@ -29,7 +29,6 @@ import { useThemeStyles } from '../hooks/useThemeStyles';
|
|
|
29
29
|
import { useColorScheme } from '../hooks/use-color-scheme';
|
|
30
30
|
import { Colors } from '../constants/theme';
|
|
31
31
|
import { normalizeColorScheme, normalizeTheme } from '../utils/themeUtils';
|
|
32
|
-
import { useHapticPress } from '../hooks/use-haptic-press';
|
|
33
32
|
import { EditDisplayNameModal } from '../components/profile/EditDisplayNameModal';
|
|
34
33
|
import { EditUsernameModal } from '../components/profile/EditUsernameModal';
|
|
35
34
|
import { EditEmailModal } from '../components/profile/EditEmailModal';
|
|
@@ -176,7 +175,6 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
|
|
|
176
175
|
// Get theme colors using centralized hook
|
|
177
176
|
const colorScheme = useColorScheme();
|
|
178
177
|
const themeStyles = useThemeStyles(theme || 'light', colorScheme);
|
|
179
|
-
const handlePressIn = useHapticPress();
|
|
180
178
|
|
|
181
179
|
// Extract colors for convenience - ensure it's always defined
|
|
182
180
|
// useThemeStyles always returns colors, but add safety check for edge cases
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.useHapticPress = useHapticPress;
|
|
7
|
-
var _react = require("react");
|
|
8
|
-
var Haptics = _interopRequireWildcard(require("expo-haptics"));
|
|
9
|
-
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
10
|
-
/**
|
|
11
|
-
* Hook that returns a memoized callback for haptic feedback on press.
|
|
12
|
-
* Provides consistent light haptic feedback across the app.
|
|
13
|
-
*
|
|
14
|
-
* @returns A stable callback function that triggers haptic feedback
|
|
15
|
-
*/
|
|
16
|
-
function useHapticPress() {
|
|
17
|
-
return (0, _react.useCallback)(() => {
|
|
18
|
-
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
19
|
-
}, []);
|
|
20
|
-
}
|
|
21
|
-
//# sourceMappingURL=use-haptic-press.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","Haptics","_interopRequireWildcard","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","useHapticPress","useCallback","impactAsync","ImpactFeedbackStyle","Light"],"sourceRoot":"../../../../src","sources":["ui/hooks/use-haptic-press.ts"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAC,uBAAA,CAAAF,OAAA;AAAwC,SAAAE,wBAAAC,CAAA,EAAAC,CAAA,6BAAAC,OAAA,MAAAC,CAAA,OAAAD,OAAA,IAAAE,CAAA,OAAAF,OAAA,YAAAH,uBAAA,YAAAA,CAAAC,CAAA,EAAAC,CAAA,SAAAA,CAAA,IAAAD,CAAA,IAAAA,CAAA,CAAAK,UAAA,SAAAL,CAAA,MAAAM,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAC,OAAA,EAAAV,CAAA,iBAAAA,CAAA,uBAAAA,CAAA,yBAAAA,CAAA,SAAAQ,CAAA,MAAAF,CAAA,GAAAL,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAG,CAAA,CAAAK,GAAA,CAAAX,CAAA,UAAAM,CAAA,CAAAM,GAAA,CAAAZ,CAAA,GAAAM,CAAA,CAAAO,GAAA,CAAAb,CAAA,EAAAQ,CAAA,gBAAAP,CAAA,IAAAD,CAAA,gBAAAC,CAAA,OAAAa,cAAA,CAAAC,IAAA,CAAAf,CAAA,EAAAC,CAAA,OAAAM,CAAA,IAAAD,CAAA,GAAAU,MAAA,CAAAC,cAAA,KAAAD,MAAA,CAAAE,wBAAA,CAAAlB,CAAA,EAAAC,CAAA,OAAAM,CAAA,CAAAK,GAAA,IAAAL,CAAA,CAAAM,GAAA,IAAAP,CAAA,CAAAE,CAAA,EAAAP,CAAA,EAAAM,CAAA,IAAAC,CAAA,CAAAP,CAAA,IAAAD,CAAA,CAAAC,CAAA,WAAAO,CAAA,KAAAR,CAAA,EAAAC,CAAA;AAExC;AACA;AACA;AACA;AACA;AACA;AACO,SAASkB,cAAcA,CAAA,EAAG;EAC/B,OAAO,IAAAC,kBAAW,EAAC,MAAM;IACvBtB,OAAO,CAACuB,WAAW,CAACvB,OAAO,CAACwB,mBAAmB,CAACC,KAAK,CAAC;EACxD,CAAC,EAAE,EAAE,CAAC;AACR","ignoreList":[]}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { useCallback } from 'react';
|
|
4
|
-
import * as Haptics from 'expo-haptics';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Hook that returns a memoized callback for haptic feedback on press.
|
|
8
|
-
* Provides consistent light haptic feedback across the app.
|
|
9
|
-
*
|
|
10
|
-
* @returns A stable callback function that triggers haptic feedback
|
|
11
|
-
*/
|
|
12
|
-
export function useHapticPress() {
|
|
13
|
-
return useCallback(() => {
|
|
14
|
-
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
15
|
-
}, []);
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=use-haptic-press.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","Haptics","useHapticPress","impactAsync","ImpactFeedbackStyle","Light"],"sourceRoot":"../../../../src","sources":["ui/hooks/use-haptic-press.ts"],"mappings":";;AAAA,SAASA,WAAW,QAAQ,OAAO;AACnC,OAAO,KAAKC,OAAO,MAAM,cAAc;;AAEvC;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAAA,EAAG;EAC/B,OAAOF,WAAW,CAAC,MAAM;IACvBC,OAAO,CAACE,WAAW,CAACF,OAAO,CAACG,mBAAmB,CAACC,KAAK,CAAC;EACxD,CAAC,EAAE,EAAE,CAAC;AACR","ignoreList":[]}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hook that returns a memoized callback for haptic feedback on press.
|
|
3
|
-
* Provides consistent light haptic feedback across the app.
|
|
4
|
-
*
|
|
5
|
-
* @returns A stable callback function that triggers haptic feedback
|
|
6
|
-
*/
|
|
7
|
-
export declare function useHapticPress(): () => void;
|
|
8
|
-
//# sourceMappingURL=use-haptic-press.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"use-haptic-press.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/use-haptic-press.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,cAAc,eAI7B"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hook that returns a memoized callback for haptic feedback on press.
|
|
3
|
-
* Provides consistent light haptic feedback across the app.
|
|
4
|
-
*
|
|
5
|
-
* @returns A stable callback function that triggers haptic feedback
|
|
6
|
-
*/
|
|
7
|
-
export declare function useHapticPress(): () => void;
|
|
8
|
-
//# sourceMappingURL=use-haptic-press.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"use-haptic-press.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/use-haptic-press.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,cAAc,eAI7B"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
|
-
import * as Haptics from 'expo-haptics';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Hook that returns a memoized callback for haptic feedback on press.
|
|
6
|
-
* Provides consistent light haptic feedback across the app.
|
|
7
|
-
*
|
|
8
|
-
* @returns A stable callback function that triggers haptic feedback
|
|
9
|
-
*/
|
|
10
|
-
export function useHapticPress() {
|
|
11
|
-
return useCallback(() => {
|
|
12
|
-
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
13
|
-
}, []);
|
|
14
|
-
}
|
|
15
|
-
|