@oxyhq/services 5.3.10 → 5.3.11
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/LICENSE +21 -0
- package/README.md +63 -571
- package/lib/commonjs/core/index.js +103 -34
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/lib/sonner.js +17 -0
- package/lib/commonjs/lib/sonner.js.map +1 -0
- package/lib/commonjs/lib/sonner.web.js +17 -0
- package/lib/commonjs/lib/sonner.web.js.map +1 -0
- package/lib/commonjs/ui/components/FollowButton.js +54 -4
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +22 -3
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +4 -3
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js +15 -14
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +10 -9
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +17 -16
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AppInfoScreen.js +4 -5
- package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SessionManagementScreen.js +7 -6
- package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +821 -74
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignUpScreen.js +7 -5
- package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/commonjs/ui/styles/theme.js +1 -1
- package/lib/module/core/index.js +103 -34
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/lib/sonner.js +4 -0
- package/lib/module/lib/sonner.js.map +1 -0
- package/lib/module/lib/sonner.web.js +4 -0
- package/lib/module/lib/sonner.web.js.map +1 -0
- package/lib/module/ui/components/FollowButton.js +54 -4
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +22 -3
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/screens/AccountCenterScreen.js +4 -3
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountOverviewScreen.js +15 -14
- package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +10 -9
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +17 -16
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/AppInfoScreen.js +5 -6
- package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js +7 -6
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +824 -77
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/SignUpScreen.js +7 -5
- package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/module/ui/styles/theme.js +1 -1
- package/lib/typescript/core/index.d.ts +44 -23
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/lib/sonner.d.ts +2 -0
- package/lib/typescript/lib/sonner.d.ts.map +1 -0
- package/lib/typescript/lib/sonner.web.d.ts +2 -0
- package/lib/typescript/lib/sonner.web.d.ts.map +1 -0
- package/lib/typescript/models/interfaces.d.ts +24 -0
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/ui/components/FollowButton.d.ts +32 -1
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/core/index.ts +104 -36
- package/src/lib/sonner.ts +1 -0
- package/src/lib/sonner.web.ts +1 -0
- package/src/models/interfaces.ts +29 -0
- package/src/ui/components/FollowButton.tsx +67 -3
- package/src/ui/components/OxyProvider.tsx +15 -0
- package/src/ui/screens/AccountCenterScreen.tsx +4 -3
- package/src/ui/screens/AccountOverviewScreen.tsx +15 -14
- package/src/ui/screens/AccountSettingsScreen.tsx +10 -9
- package/src/ui/screens/AccountSwitcherScreen.tsx +17 -16
- package/src/ui/screens/AppInfoScreen.tsx +4 -7
- package/src/ui/screens/SessionManagementScreen.tsx +7 -15
- package/src/ui/screens/SignInScreen.tsx +729 -52
- package/src/ui/screens/SignUpScreen.tsx +7 -5
- package/src/ui/styles/theme.ts +1 -1
- package/CHANGELOG.md +0 -97
- package/UI_COMPONENTS.md +0 -462
package/src/core/index.ts
CHANGED
|
@@ -37,7 +37,12 @@ import {
|
|
|
37
37
|
FileUploadResponse,
|
|
38
38
|
FileListResponse,
|
|
39
39
|
FileUpdateRequest,
|
|
40
|
-
FileDeleteResponse
|
|
40
|
+
FileDeleteResponse,
|
|
41
|
+
// Device session interfaces
|
|
42
|
+
DeviceSession,
|
|
43
|
+
DeviceSessionsResponse,
|
|
44
|
+
DeviceSessionLogoutResponse,
|
|
45
|
+
UpdateDeviceNameResponse
|
|
41
46
|
} from '../models/interfaces';
|
|
42
47
|
|
|
43
48
|
// Import secure session types
|
|
@@ -154,8 +159,10 @@ export class OxyServices {
|
|
|
154
159
|
|
|
155
160
|
try {
|
|
156
161
|
const decoded = jwtDecode<JwtPayload>(this.accessToken);
|
|
157
|
-
|
|
158
|
-
|
|
162
|
+
|
|
163
|
+
// Check for both userId (preferred) and id (fallback) for compatibility
|
|
164
|
+
return decoded.userId || (decoded as any).id || null;
|
|
165
|
+
} catch (error) {
|
|
159
166
|
return null;
|
|
160
167
|
}
|
|
161
168
|
}
|
|
@@ -340,6 +347,69 @@ export class OxyServices {
|
|
|
340
347
|
}
|
|
341
348
|
}
|
|
342
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Get device sessions for a specific session ID
|
|
352
|
+
* @param sessionId - The session ID to get device sessions for
|
|
353
|
+
* @param deviceId - Optional device ID filter
|
|
354
|
+
* @returns Array of device sessions
|
|
355
|
+
*/
|
|
356
|
+
async getDeviceSessions(sessionId: string, deviceId?: string): Promise<DeviceSession[]> {
|
|
357
|
+
try {
|
|
358
|
+
const params = deviceId ? { deviceId } : {};
|
|
359
|
+
const res = await this.client.get(`/secure-session/device/sessions/${sessionId}`, { params });
|
|
360
|
+
|
|
361
|
+
// Map backend response to frontend interface
|
|
362
|
+
return (res.data.sessions || []).map((session: any) => ({
|
|
363
|
+
sessionId: session.sessionId,
|
|
364
|
+
deviceId: res.data.deviceId || '',
|
|
365
|
+
deviceName: session.deviceInfo?.deviceName || 'Unknown Device',
|
|
366
|
+
isActive: true, // All returned sessions are active
|
|
367
|
+
lastActive: session.lastActive,
|
|
368
|
+
expiresAt: session.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
369
|
+
isCurrent: session.sessionId === sessionId,
|
|
370
|
+
user: session.user,
|
|
371
|
+
createdAt: session.createdAt
|
|
372
|
+
}));
|
|
373
|
+
} catch (error) {
|
|
374
|
+
throw this.handleError(error);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Logout all device sessions for a specific device
|
|
380
|
+
* @param sessionId - The session ID
|
|
381
|
+
* @param deviceId - Optional device ID (uses current session's device if not provided)
|
|
382
|
+
* @param excludeCurrent - Whether to exclude the current session from logout
|
|
383
|
+
* @returns Logout response
|
|
384
|
+
*/
|
|
385
|
+
async logoutAllDeviceSessions(sessionId: string, deviceId?: string, excludeCurrent?: boolean): Promise<DeviceSessionLogoutResponse> {
|
|
386
|
+
try {
|
|
387
|
+
const data: any = {};
|
|
388
|
+
if (deviceId) data.deviceId = deviceId;
|
|
389
|
+
if (excludeCurrent !== undefined) data.excludeCurrent = excludeCurrent;
|
|
390
|
+
|
|
391
|
+
const res = await this.client.post(`/secure-session/device/logout-all/${sessionId}`, data);
|
|
392
|
+
return res.data;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
throw this.handleError(error);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Update device name for a session
|
|
400
|
+
* @param sessionId - The session ID
|
|
401
|
+
* @param deviceName - The new device name
|
|
402
|
+
* @returns Update response
|
|
403
|
+
*/
|
|
404
|
+
async updateDeviceName(sessionId: string, deviceName: string): Promise<UpdateDeviceNameResponse> {
|
|
405
|
+
try {
|
|
406
|
+
const res = await this.client.put(`/secure-session/device/name/${sessionId}`, { deviceName });
|
|
407
|
+
return res.data;
|
|
408
|
+
} catch (error) {
|
|
409
|
+
throw this.handleError(error);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
343
413
|
/* Profile Methods */
|
|
344
414
|
|
|
345
415
|
/**
|
|
@@ -1162,34 +1232,22 @@ export class OxyServices {
|
|
|
1162
1232
|
}
|
|
1163
1233
|
|
|
1164
1234
|
/**
|
|
1165
|
-
*
|
|
1166
|
-
* @param sessionId -
|
|
1167
|
-
* @param
|
|
1168
|
-
* @returns
|
|
1235
|
+
* Validate session using x-session-id header
|
|
1236
|
+
* @param sessionId - The session ID to validate (sent as header)
|
|
1237
|
+
* @param deviceFingerprint - Optional device fingerprint for enhanced security
|
|
1238
|
+
* @returns Session validation status with user data
|
|
1169
1239
|
*/
|
|
1170
|
-
async
|
|
1240
|
+
async validateSessionFromHeader(sessionId: string, deviceFingerprint?: string): Promise<{ valid: boolean; expiresAt: string; lastActivity: string; user: User; sessionId?: string }> {
|
|
1171
1241
|
try {
|
|
1172
|
-
const
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1242
|
+
const headers: any = {
|
|
1243
|
+
'x-session-id': sessionId
|
|
1244
|
+
};
|
|
1245
|
+
|
|
1246
|
+
if (deviceFingerprint) {
|
|
1247
|
+
headers['x-device-fingerprint'] = deviceFingerprint;
|
|
1248
|
+
}
|
|
1179
1249
|
|
|
1180
|
-
|
|
1181
|
-
* Logout all sessions on a specific device
|
|
1182
|
-
* @param sessionId - Current session ID
|
|
1183
|
-
* @param deviceId - Optional device ID (uses current session's device if not provided)
|
|
1184
|
-
* @param excludeCurrent - Whether to exclude the current session from logout
|
|
1185
|
-
* @returns Success status
|
|
1186
|
-
*/
|
|
1187
|
-
async logoutAllDeviceSessions(sessionId: string, deviceId?: string, excludeCurrent: boolean = true): Promise<{ message: string; sessionsTerminated: number }> {
|
|
1188
|
-
try {
|
|
1189
|
-
const res = await this.client.post(`/secure-session/device/logout-all/${sessionId}`, {
|
|
1190
|
-
deviceId,
|
|
1191
|
-
excludeCurrent
|
|
1192
|
-
});
|
|
1250
|
+
const res = await this.client.get('/secure-session/validate-header', { headers });
|
|
1193
1251
|
return res.data;
|
|
1194
1252
|
} catch (error) {
|
|
1195
1253
|
throw this.handleError(error);
|
|
@@ -1197,16 +1255,25 @@ export class OxyServices {
|
|
|
1197
1255
|
}
|
|
1198
1256
|
|
|
1199
1257
|
/**
|
|
1200
|
-
*
|
|
1201
|
-
*
|
|
1202
|
-
* @param
|
|
1203
|
-
* @
|
|
1258
|
+
* Validate session using automatic header detection
|
|
1259
|
+
* The validateSession endpoint will automatically read from x-session-id header
|
|
1260
|
+
* @param sessionId - The session ID to validate (sent as header)
|
|
1261
|
+
* @param deviceFingerprint - Optional device fingerprint for enhanced security
|
|
1262
|
+
* @returns Session validation status with user data
|
|
1204
1263
|
*/
|
|
1205
|
-
async
|
|
1264
|
+
async validateSessionAuto(sessionId: string, deviceFingerprint?: string): Promise<{ valid: boolean; expiresAt: string; lastActivity: string; user: User; source?: string }> {
|
|
1206
1265
|
try {
|
|
1207
|
-
const
|
|
1208
|
-
|
|
1209
|
-
}
|
|
1266
|
+
const headers: any = {
|
|
1267
|
+
'x-session-id': sessionId
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
if (deviceFingerprint) {
|
|
1271
|
+
headers['x-device-fingerprint'] = deviceFingerprint;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// Call the regular validateSession endpoint which now auto-reads from headers
|
|
1275
|
+
// Use 'auto' as placeholder since the controller reads from header
|
|
1276
|
+
const res = await this.client.get('/secure-session/validate/auto', { headers });
|
|
1210
1277
|
return res.data;
|
|
1211
1278
|
} catch (error) {
|
|
1212
1279
|
throw this.handleError(error);
|
|
@@ -1275,6 +1342,7 @@ export class OxyServices {
|
|
|
1275
1342
|
|
|
1276
1343
|
// Get user ID from token
|
|
1277
1344
|
const userId = tempOxyServices.getCurrentUserId();
|
|
1345
|
+
|
|
1278
1346
|
if (!userId) {
|
|
1279
1347
|
const error = {
|
|
1280
1348
|
message: 'Invalid token payload',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'sonner-native';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'sonner';
|
package/src/models/interfaces.ts
CHANGED
|
@@ -186,4 +186,33 @@ export interface FileDeleteResponse {
|
|
|
186
186
|
success: boolean;
|
|
187
187
|
message: string;
|
|
188
188
|
fileId: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Device Session interfaces
|
|
192
|
+
export interface DeviceSession {
|
|
193
|
+
sessionId: string;
|
|
194
|
+
deviceId: string;
|
|
195
|
+
deviceName: string;
|
|
196
|
+
isActive: boolean;
|
|
197
|
+
lastActive: string;
|
|
198
|
+
expiresAt: string;
|
|
199
|
+
isCurrent: boolean;
|
|
200
|
+
user?: User;
|
|
201
|
+
createdAt?: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface DeviceSessionsResponse {
|
|
205
|
+
deviceId: string;
|
|
206
|
+
sessions: DeviceSession[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface DeviceSessionLogoutResponse {
|
|
210
|
+
message: string;
|
|
211
|
+
deviceId: string;
|
|
212
|
+
sessionsTerminated: number;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface UpdateDeviceNameResponse {
|
|
216
|
+
message: string;
|
|
217
|
+
deviceName: string;
|
|
189
218
|
}
|
|
@@ -19,6 +19,7 @@ import Animated, {
|
|
|
19
19
|
} from 'react-native-reanimated';
|
|
20
20
|
import { useOxy } from '../context/OxyContext';
|
|
21
21
|
import { fontFamilies } from '../styles/fonts';
|
|
22
|
+
import { toast } from '../../lib/sonner';
|
|
22
23
|
|
|
23
24
|
export interface FollowButtonProps {
|
|
24
25
|
/**
|
|
@@ -64,10 +65,23 @@ export interface FollowButtonProps {
|
|
|
64
65
|
* @default true
|
|
65
66
|
*/
|
|
66
67
|
showLoadingState?: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether to prevent default action and stop event propagation
|
|
71
|
+
* Useful when the button is inside links or other pressable containers
|
|
72
|
+
* @default true
|
|
73
|
+
*/
|
|
74
|
+
preventParentActions?: boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Custom onPress handler - if provided, will override default follow/unfollow behavior
|
|
78
|
+
* Event object is passed to allow for preventDefault/stopPropagation
|
|
79
|
+
*/
|
|
80
|
+
onPress?: (event: any) => void;
|
|
67
81
|
}
|
|
68
82
|
|
|
69
83
|
/**
|
|
70
|
-
* An animated follow button with interactive state changes
|
|
84
|
+
* An animated follow button with interactive state changes and preventDefault support
|
|
71
85
|
*
|
|
72
86
|
* @example
|
|
73
87
|
* ```tsx
|
|
@@ -82,6 +96,26 @@ export interface FollowButtonProps {
|
|
|
82
96
|
* style={{ borderRadius: 12 }}
|
|
83
97
|
* onFollowChange={(isFollowing) => console.log(`User is now ${isFollowing ? 'followed' : 'unfollowed'}`)}
|
|
84
98
|
* />
|
|
99
|
+
*
|
|
100
|
+
* // Inside a pressable container (prevents parent actions)
|
|
101
|
+
* <TouchableOpacity onPress={() => navigateToProfile()}>
|
|
102
|
+
* <View>
|
|
103
|
+
* <Text>User Profile</Text>
|
|
104
|
+
* <FollowButton
|
|
105
|
+
* userId="123"
|
|
106
|
+
* preventParentActions={true} // Default: true
|
|
107
|
+
* />
|
|
108
|
+
* </View>
|
|
109
|
+
* </TouchableOpacity>
|
|
110
|
+
*
|
|
111
|
+
* // Custom onPress handler
|
|
112
|
+
* <FollowButton
|
|
113
|
+
* userId="123"
|
|
114
|
+
* onPress={(event) => {
|
|
115
|
+
* event.preventDefault(); // Custom preventDefault
|
|
116
|
+
* // Custom logic here
|
|
117
|
+
* }}
|
|
118
|
+
* />
|
|
85
119
|
* ```
|
|
86
120
|
*/
|
|
87
121
|
const FollowButton: React.FC<FollowButtonProps> = ({
|
|
@@ -93,6 +127,8 @@ const FollowButton: React.FC<FollowButtonProps> = ({
|
|
|
93
127
|
textStyle,
|
|
94
128
|
disabled = false,
|
|
95
129
|
showLoadingState = true,
|
|
130
|
+
preventParentActions = true,
|
|
131
|
+
onPress,
|
|
96
132
|
}) => {
|
|
97
133
|
const { oxyServices, isAuthenticated } = useOxy();
|
|
98
134
|
const [isFollowing, setIsFollowing] = useState(initiallyFollowing);
|
|
@@ -110,8 +146,32 @@ const FollowButton: React.FC<FollowButtonProps> = ({
|
|
|
110
146
|
});
|
|
111
147
|
}, [isFollowing, animationProgress]);
|
|
112
148
|
|
|
113
|
-
// The button press handler
|
|
114
|
-
const handlePress = async () => {
|
|
149
|
+
// The button press handler with preventDefault support
|
|
150
|
+
const handlePress = async (event?: any) => {
|
|
151
|
+
// Prevent parent actions if enabled (e.g., if inside a link or pressable container)
|
|
152
|
+
if (preventParentActions && event) {
|
|
153
|
+
// For React Native Web compatibility
|
|
154
|
+
if (Platform.OS === 'web' && event.preventDefault) {
|
|
155
|
+
event.preventDefault();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Stop event propagation to prevent parent TouchableOpacity/Pressable actions
|
|
159
|
+
if (event.stopPropagation) {
|
|
160
|
+
event.stopPropagation();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// For React Native, prevent gesture bubbling
|
|
164
|
+
if (event.nativeEvent && event.nativeEvent.stopPropagation) {
|
|
165
|
+
event.nativeEvent.stopPropagation();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// If custom onPress is provided, use it instead of default behavior
|
|
170
|
+
if (onPress) {
|
|
171
|
+
onPress(event);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
115
175
|
if (disabled || isLoading || !isAuthenticated) return;
|
|
116
176
|
|
|
117
177
|
// Touch feedback animation
|
|
@@ -143,8 +203,12 @@ const FollowButton: React.FC<FollowButtonProps> = ({
|
|
|
143
203
|
if (onFollowChange) {
|
|
144
204
|
onFollowChange(newFollowingState);
|
|
145
205
|
}
|
|
206
|
+
|
|
207
|
+
// Show success toast
|
|
208
|
+
toast.success(newFollowingState ? 'Following user!' : 'Unfollowed user');
|
|
146
209
|
} catch (error) {
|
|
147
210
|
console.error('Follow action failed:', error);
|
|
211
|
+
toast.error('Failed to update follow status. Please try again.');
|
|
148
212
|
} finally {
|
|
149
213
|
setIsLoading(false);
|
|
150
214
|
}
|
|
@@ -11,6 +11,7 @@ import AccountCenterScreen from '../screens/AccountCenterScreen';
|
|
|
11
11
|
import { OxyContextProvider, useOxy } from '../context/OxyContext';
|
|
12
12
|
import OxyRouter from '../navigation/OxyRouter';
|
|
13
13
|
import { FontLoader, setupFonts } from './FontLoader';
|
|
14
|
+
import { Toaster } from '../../lib/sonner';
|
|
14
15
|
|
|
15
16
|
// Import bottom sheet components directly - no longer a peer dependency
|
|
16
17
|
import { BottomSheetModal, BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetModalProvider, BottomSheetView } from './bottomSheet';
|
|
@@ -71,6 +72,10 @@ const OxyProvider: React.FC<OxyProviderProps> = (props) => {
|
|
|
71
72
|
{children}
|
|
72
73
|
</SafeAreaProvider>
|
|
73
74
|
</BottomSheetModalProvider>
|
|
75
|
+
{/* Move Toaster outside BottomSheetModalProvider to ensure it appears above the modal backdrop */}
|
|
76
|
+
<View style={styles.toasterContainer}>
|
|
77
|
+
<Toaster position="top-center" swipeToDismissDirection="left" offset={15} />
|
|
78
|
+
</View>
|
|
74
79
|
</GestureHandlerRootView>
|
|
75
80
|
</FontLoader>
|
|
76
81
|
</OxyContextProvider>
|
|
@@ -532,6 +537,16 @@ const styles = StyleSheet.create({
|
|
|
532
537
|
}
|
|
533
538
|
})
|
|
534
539
|
},
|
|
540
|
+
toasterContainer: {
|
|
541
|
+
position: 'absolute',
|
|
542
|
+
top: 0,
|
|
543
|
+
left: 0,
|
|
544
|
+
right: 0,
|
|
545
|
+
bottom: 0,
|
|
546
|
+
zIndex: 9999,
|
|
547
|
+
elevation: 9999, // For Android
|
|
548
|
+
pointerEvents: 'box-none', // Allow touches to pass through to underlying components
|
|
549
|
+
},
|
|
535
550
|
});
|
|
536
551
|
|
|
537
552
|
export default OxyProvider;
|
|
@@ -14,6 +14,7 @@ import { BaseScreenProps } from '../navigation/types';
|
|
|
14
14
|
import { useOxy } from '../context/OxyContext';
|
|
15
15
|
import { fontFamilies } from '../styles/fonts';
|
|
16
16
|
import { packageInfo } from '../../constants/version';
|
|
17
|
+
import { toast } from '../../lib/sonner';
|
|
17
18
|
|
|
18
19
|
const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
19
20
|
onClose,
|
|
@@ -38,7 +39,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
38
39
|
}
|
|
39
40
|
} catch (error) {
|
|
40
41
|
console.error('Logout failed:', error);
|
|
41
|
-
|
|
42
|
+
toast.error('There was a problem signing you out. Please try again.');
|
|
42
43
|
}
|
|
43
44
|
};
|
|
44
45
|
|
|
@@ -138,7 +139,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
138
139
|
{Platform.OS !== 'web' && (
|
|
139
140
|
<TouchableOpacity
|
|
140
141
|
style={[styles.actionButton, { borderColor }]}
|
|
141
|
-
onPress={() =>
|
|
142
|
+
onPress={() => toast.info('Notifications feature coming soon!')}
|
|
142
143
|
>
|
|
143
144
|
<Text style={[styles.actionButtonText, { color: textColor }]}>Notifications</Text>
|
|
144
145
|
</TouchableOpacity>
|
|
@@ -146,7 +147,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
146
147
|
|
|
147
148
|
<TouchableOpacity
|
|
148
149
|
style={[styles.actionButton, { borderColor }]}
|
|
149
|
-
onPress={() =>
|
|
150
|
+
onPress={() => toast.info('Help & Support feature coming soon!')}
|
|
150
151
|
>
|
|
151
152
|
<Text style={[styles.actionButtonText, { color: textColor }]}>Help & Support</Text>
|
|
152
153
|
</TouchableOpacity>
|
|
@@ -16,6 +16,7 @@ import { useOxy } from '../context/OxyContext';
|
|
|
16
16
|
import OxyLogo from '../components/OxyLogo';
|
|
17
17
|
import Avatar from '../components/Avatar';
|
|
18
18
|
import { fontFamilies } from '../styles/fonts';
|
|
19
|
+
import { toast } from '../../lib/sonner';
|
|
19
20
|
|
|
20
21
|
const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
21
22
|
onClose,
|
|
@@ -59,7 +60,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
59
60
|
}
|
|
60
61
|
} catch (error) {
|
|
61
62
|
console.error('Logout failed:', error);
|
|
62
|
-
|
|
63
|
+
toast.error('There was a problem signing you out. Please try again.');
|
|
63
64
|
}
|
|
64
65
|
};
|
|
65
66
|
|
|
@@ -83,7 +84,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
83
84
|
};
|
|
84
85
|
|
|
85
86
|
const handleAddAccount = () => {
|
|
86
|
-
|
|
87
|
+
toast.info('Add another account feature coming soon!');
|
|
87
88
|
};
|
|
88
89
|
|
|
89
90
|
const handleSignOutAll = () => {
|
|
@@ -168,7 +169,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
168
169
|
|
|
169
170
|
<TouchableOpacity
|
|
170
171
|
style={[styles.manageAccountButton, { borderColor }]}
|
|
171
|
-
onPress={() =>
|
|
172
|
+
onPress={() => toast.info('Manage your Oxy Account feature coming soon!')}
|
|
172
173
|
>
|
|
173
174
|
<Text style={[styles.manageAccountText, { color: textColor }]}>
|
|
174
175
|
Manage your Oxy Account
|
|
@@ -195,7 +196,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
195
196
|
<TouchableOpacity
|
|
196
197
|
key={account.id}
|
|
197
198
|
style={[styles.accountItem, { borderColor }]}
|
|
198
|
-
onPress={() =>
|
|
199
|
+
onPress={() => toast.info(`Switch to ${account.username}?`)}
|
|
199
200
|
>
|
|
200
201
|
<View style={styles.accountItemLeft}>
|
|
201
202
|
{account.avatar.url ? (
|
|
@@ -260,42 +261,42 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
260
261
|
<Text style={{ fontSize: 18 }}>🕒</Text>,
|
|
261
262
|
'History',
|
|
262
263
|
'Saving',
|
|
263
|
-
() =>
|
|
264
|
+
() => toast.info('View your history feature coming soon!')
|
|
264
265
|
)}
|
|
265
266
|
|
|
266
267
|
{renderFeatureItem(
|
|
267
268
|
<Text style={{ fontSize: 18 }}>⏱️</Text>,
|
|
268
269
|
'Delete last 15 minutes',
|
|
269
270
|
null,
|
|
270
|
-
() =>
|
|
271
|
+
() => toast.info('Delete recent history feature coming soon!')
|
|
271
272
|
)}
|
|
272
273
|
|
|
273
274
|
{renderFeatureItem(
|
|
274
275
|
<Text style={{ fontSize: 18 }}>📋</Text>,
|
|
275
276
|
'Saves & Collections',
|
|
276
277
|
null,
|
|
277
|
-
() =>
|
|
278
|
+
() => toast.info('Saved items feature coming soon!')
|
|
278
279
|
)}
|
|
279
280
|
|
|
280
281
|
{renderFeatureItem(
|
|
281
282
|
<Text style={{ fontSize: 18 }}>🔍</Text>,
|
|
282
283
|
'Search personalization',
|
|
283
284
|
null,
|
|
284
|
-
() =>
|
|
285
|
+
() => toast.info('Search personalization feature coming soon!')
|
|
285
286
|
)}
|
|
286
287
|
|
|
287
288
|
{renderFeatureItem(
|
|
288
289
|
<Text style={{ fontSize: 18 }}>🛡️</Text>,
|
|
289
290
|
'SafeSearch',
|
|
290
291
|
features.safeSearch ? 'On' : 'Off',
|
|
291
|
-
() =>
|
|
292
|
+
() => toast.info('SafeSearch settings feature coming soon!')
|
|
292
293
|
)}
|
|
293
294
|
|
|
294
295
|
{renderFeatureItem(
|
|
295
296
|
<Text style={{ fontSize: 18 }}>🌐</Text>,
|
|
296
297
|
'Language',
|
|
297
298
|
features.language,
|
|
298
|
-
() =>
|
|
299
|
+
() => toast.info('Language settings feature coming soon!')
|
|
299
300
|
)}
|
|
300
301
|
</View>
|
|
301
302
|
|
|
@@ -303,7 +304,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
303
304
|
<View style={styles.footerButtonsRow}>
|
|
304
305
|
<TouchableOpacity
|
|
305
306
|
style={styles.footerButton}
|
|
306
|
-
onPress={() =>
|
|
307
|
+
onPress={() => toast.info('More settings feature coming soon!')}
|
|
307
308
|
>
|
|
308
309
|
<Text style={[styles.footerButtonText, { color: textColor }]}>
|
|
309
310
|
More settings
|
|
@@ -312,7 +313,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
312
313
|
|
|
313
314
|
<TouchableOpacity
|
|
314
315
|
style={styles.footerButton}
|
|
315
|
-
onPress={() =>
|
|
316
|
+
onPress={() => toast.info('Help & support feature coming soon!')}
|
|
316
317
|
>
|
|
317
318
|
<Text style={[styles.footerButtonText, { color: textColor }]}>
|
|
318
319
|
Help
|
|
@@ -321,11 +322,11 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
321
322
|
</View>
|
|
322
323
|
|
|
323
324
|
<View style={styles.footerLinksRow}>
|
|
324
|
-
<TouchableOpacity onPress={() =>
|
|
325
|
+
<TouchableOpacity onPress={() => toast.info('Privacy Policy feature coming soon!')}>
|
|
325
326
|
<Text style={[styles.footerLink, { color: iconColor }]}>Privacy Policy</Text>
|
|
326
327
|
</TouchableOpacity>
|
|
327
328
|
<Text style={[{ color: iconColor, marginHorizontal: 5 }]}>•</Text>
|
|
328
|
-
<TouchableOpacity onPress={() =>
|
|
329
|
+
<TouchableOpacity onPress={() => toast.info('Terms of Service feature coming soon!')}>
|
|
329
330
|
<Text style={[styles.footerLink, { color: iconColor }]}>Terms of Service</Text>
|
|
330
331
|
</TouchableOpacity>
|
|
331
332
|
</View>
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { BaseScreenProps } from '../navigation/types';
|
|
15
15
|
import { useOxy } from '../context/OxyContext';
|
|
16
16
|
import Avatar from '../components/Avatar';
|
|
17
|
+
import { toast } from '../../lib/sonner';
|
|
17
18
|
|
|
18
19
|
interface AccountSettingsScreenProps extends BaseScreenProps {
|
|
19
20
|
activeTab?: 'profile' | 'password' | 'notifications';
|
|
@@ -104,9 +105,9 @@ const AccountSettingsScreen: React.FC<AccountSettingsScreenProps> = ({
|
|
|
104
105
|
|
|
105
106
|
// Call API to update user
|
|
106
107
|
await oxyServices.updateUser(user!.id, updates);
|
|
107
|
-
|
|
108
|
+
toast.success('Profile updated successfully');
|
|
108
109
|
} catch (error: any) {
|
|
109
|
-
|
|
110
|
+
toast.error(error.message || 'Failed to update profile');
|
|
110
111
|
} finally {
|
|
111
112
|
setIsLoading(false);
|
|
112
113
|
}
|
|
@@ -115,17 +116,17 @@ const AccountSettingsScreen: React.FC<AccountSettingsScreenProps> = ({
|
|
|
115
116
|
const handleChangePassword = async () => {
|
|
116
117
|
// Validate inputs
|
|
117
118
|
if (!currentPassword || !newPassword || !confirmPassword) {
|
|
118
|
-
|
|
119
|
+
toast.error('Please fill in all password fields');
|
|
119
120
|
return;
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
if (newPassword !== confirmPassword) {
|
|
123
|
-
|
|
124
|
+
toast.error('New passwords do not match');
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
if (newPassword.length < 8) {
|
|
128
|
-
|
|
129
|
+
toast.error('Password must be at least 8 characters long');
|
|
129
130
|
return;
|
|
130
131
|
}
|
|
131
132
|
|
|
@@ -144,9 +145,9 @@ const AccountSettingsScreen: React.FC<AccountSettingsScreenProps> = ({
|
|
|
144
145
|
setCurrentPassword('');
|
|
145
146
|
setNewPassword('');
|
|
146
147
|
setConfirmPassword('');
|
|
147
|
-
|
|
148
|
+
toast.success('Password updated successfully');
|
|
148
149
|
} catch (error: any) {
|
|
149
|
-
|
|
150
|
+
toast.error(error.message || 'Failed to update password');
|
|
150
151
|
} finally {
|
|
151
152
|
setIsLoading(false);
|
|
152
153
|
}
|
|
@@ -165,9 +166,9 @@ const AccountSettingsScreen: React.FC<AccountSettingsScreenProps> = ({
|
|
|
165
166
|
push: pushNotifications,
|
|
166
167
|
},
|
|
167
168
|
});
|
|
168
|
-
|
|
169
|
+
toast.success('Notification preferences updated successfully');
|
|
169
170
|
} catch (error: any) {
|
|
170
|
-
|
|
171
|
+
toast.error(error.message || 'Failed to update notification preferences');
|
|
171
172
|
} finally {
|
|
172
173
|
setIsLoading(false);
|
|
173
174
|
}
|