@oxyhq/services 5.1.12 → 5.1.13
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/OxyProvider.js +50 -30
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +4 -4
- package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +15 -7
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/navigation/OxyRouter.js +47 -7
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +346 -0
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/SignInScreen.js +82 -86
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignUpScreen.js +194 -103
- package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js +88 -0
- package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +364 -0
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js +202 -0
- package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +148 -0
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js +127 -0
- package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +105 -0
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +1 -0
- package/lib/commonjs/ui/styles/theme.js +1 -2
- package/lib/commonjs/ui/styles/theme.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +51 -31
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +4 -4
- package/lib/module/ui/components/OxySignInButton.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +15 -7
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/navigation/OxyRouter.js +47 -7
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +340 -0
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -0
- package/lib/module/ui/screens/SignInScreen.js +83 -87
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/SignUpScreen.js +194 -104
- package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaAboutScreen.js +83 -0
- package/lib/module/ui/screens/karma/KarmaAboutScreen.js.map +1 -0
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js +358 -0
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -0
- package/lib/module/ui/screens/karma/KarmaFAQScreen.js +197 -0
- package/lib/module/ui/screens/karma/KarmaFAQScreen.js.map +1 -0
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +142 -0
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -0
- package/lib/module/ui/screens/karma/KarmaRewardsScreen.js +122 -0
- package/lib/module/ui/screens/karma/KarmaRewardsScreen.js.map +1 -0
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js +100 -0
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -0
- package/lib/module/ui/styles/theme.js +1 -2
- package/lib/module/ui/styles/theme.js.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxySignInButton.d.ts +5 -0
- package/lib/typescript/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +4 -1
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/screens/ProfileScreen.d.ts +9 -0
- package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaAboutScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/karma/KarmaAboutScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/karma/KarmaCenterScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/karma/KarmaCenterScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/karma/KarmaLeaderboardScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/karma/KarmaRulesScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/karma/KarmaRulesScreen.d.ts.map +1 -0
- package/lib/typescript/ui/styles/theme.d.ts +0 -1
- package/lib/typescript/ui/styles/theme.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/ui/components/OxyProvider.tsx +42 -29
- package/src/ui/components/OxySignInButton.tsx +9 -3
- package/src/ui/context/OxyContext.tsx +16 -8
- package/src/ui/navigation/OxyRouter.tsx +44 -7
- package/src/ui/screens/ProfileScreen.tsx +155 -0
- package/src/ui/screens/SignInScreen.tsx +68 -73
- package/src/ui/screens/SignUpScreen.tsx +140 -81
- package/src/ui/screens/karma/KarmaAboutScreen.tsx +45 -0
- package/src/ui/screens/karma/KarmaCenterScreen.tsx +271 -0
- package/src/ui/screens/karma/KarmaFAQScreen.tsx +164 -0
- package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +79 -0
- package/src/ui/screens/karma/KarmaRewardsScreen.tsx +55 -0
- package/src/ui/screens/karma/KarmaRulesScreen.tsx +65 -0
- package/src/ui/styles/theme.ts +1 -2
- package/lib/commonjs/ui/screens/AboutKarmaScreen.js +0 -50
- package/lib/commonjs/ui/screens/AboutKarmaScreen.js.map +0 -1
- package/lib/module/ui/screens/AboutKarmaScreen.js +0 -45
- package/lib/module/ui/screens/AboutKarmaScreen.js.map +0 -1
- package/lib/typescript/ui/screens/AboutKarmaScreen.d.ts +0 -5
- package/lib/typescript/ui/screens/AboutKarmaScreen.d.ts.map +0 -1
- package/src/ui/screens/AboutKarmaScreen.tsx +0 -58
|
@@ -14,6 +14,7 @@ import { FontLoader, setupFonts } from './FontLoader';
|
|
|
14
14
|
|
|
15
15
|
// Import bottom sheet components directly - no longer a peer dependency
|
|
16
16
|
import { BottomSheetModal, BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetModalProvider, BottomSheetView } from './bottomSheet';
|
|
17
|
+
import { BottomSheetScrollView } from '@gorhom/bottom-sheet';
|
|
17
18
|
|
|
18
19
|
// Initialize fonts automatically
|
|
19
20
|
setupFonts();
|
|
@@ -95,6 +96,10 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
|
|
|
95
96
|
// Use the internal ref (which is passed as a prop from OxyProvider)
|
|
96
97
|
const modalRef = useRef<BottomSheetModal>(null);
|
|
97
98
|
|
|
99
|
+
// Track content height for dynamic sizing
|
|
100
|
+
const [contentHeight, setContentHeight] = useState<number>(0);
|
|
101
|
+
const screenHeight = Dimensions.get('window').height;
|
|
102
|
+
|
|
98
103
|
// Set up effect to sync the internal ref with our modal ref
|
|
99
104
|
useEffect(() => {
|
|
100
105
|
if (bottomSheetRef && modalRef.current) {
|
|
@@ -116,18 +121,18 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
|
|
|
116
121
|
|
|
117
122
|
// Add a method to navigate between screens
|
|
118
123
|
// @ts-ignore - Adding custom method
|
|
119
|
-
bottomSheetRef.current._navigateToScreen = (screenName: string) => {
|
|
124
|
+
bottomSheetRef.current._navigateToScreen = (screenName: string, props?: Record<string, any>) => {
|
|
120
125
|
// Access the navigation function exposed by OxyRouter
|
|
121
126
|
// Use internal mechanism to notify router about navigation
|
|
122
127
|
// We'll use a simple event-based approach
|
|
123
128
|
if (typeof document !== 'undefined') {
|
|
124
129
|
// For web - use a custom event
|
|
125
|
-
const event = new CustomEvent('oxy:navigate', { detail: screenName });
|
|
130
|
+
const event = new CustomEvent('oxy:navigate', { detail: { screen: screenName, props } });
|
|
126
131
|
document.dispatchEvent(event);
|
|
127
132
|
} else {
|
|
128
133
|
// For React Native - use the navigation prop directly if available
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
// We'll implement a mechanism in OxyRouter to listen for this
|
|
135
|
+
(global as any).oxyNavigateEvent = { screen: screenName, props };
|
|
131
136
|
}
|
|
132
137
|
};
|
|
133
138
|
}
|
|
@@ -300,22 +305,37 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
|
|
|
300
305
|
const highestPoint = points[points.length - 1];
|
|
301
306
|
setSnapPoints([highestPoint, highestPoint]);
|
|
302
307
|
} else {
|
|
303
|
-
|
|
308
|
+
// If we have content height, use it as a constraint
|
|
309
|
+
if (contentHeight > 0) {
|
|
310
|
+
// Calculate content height as percentage of screen (plus some padding)
|
|
311
|
+
const contentHeightPercent = Math.min(Math.ceil((contentHeight + 40) / screenHeight * 100), 90) + '%';
|
|
312
|
+
// Use content height for first snap point if it's taller than the default
|
|
313
|
+
const firstPoint = contentHeight / screenHeight > 0.6 ? contentHeightPercent : points[0];
|
|
314
|
+
setSnapPoints([firstPoint, points[1]]);
|
|
315
|
+
} else {
|
|
316
|
+
setSnapPoints(points);
|
|
317
|
+
}
|
|
304
318
|
}
|
|
305
|
-
}, [keyboardVisible]);
|
|
306
|
-
|
|
307
|
-
//
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
319
|
+
}, [keyboardVisible, contentHeight, screenHeight]);
|
|
320
|
+
|
|
321
|
+
// Handle content layout changes to measure height
|
|
322
|
+
const handleContentLayout = useCallback((event: any) => {
|
|
323
|
+
const layoutHeight = event.nativeEvent.layout.height;
|
|
324
|
+
setContentHeight(layoutHeight);
|
|
325
|
+
|
|
326
|
+
// Update snap points based on new content height
|
|
327
|
+
if (keyboardVisible) {
|
|
328
|
+
// If keyboard is visible, use the highest snap point
|
|
329
|
+
const highestPoint = snapPoints[snapPoints.length - 1];
|
|
330
|
+
setSnapPoints([highestPoint, highestPoint]);
|
|
331
|
+
} else {
|
|
332
|
+
if (layoutHeight > 0) {
|
|
333
|
+
const contentHeightPercent = Math.min(Math.ceil((layoutHeight + 40) / screenHeight * 100), 90) + '%';
|
|
334
|
+
const firstPoint = layoutHeight / screenHeight > 0.6 ? contentHeightPercent : snapPoints[0];
|
|
335
|
+
setSnapPoints([firstPoint, snapPoints[1]]);
|
|
336
|
+
}
|
|
317
337
|
}
|
|
318
|
-
}, []);
|
|
338
|
+
}, [keyboardVisible, screenHeight, snapPoints]);
|
|
319
339
|
|
|
320
340
|
// Close the bottom sheet with animation
|
|
321
341
|
const handleClose = useCallback(() => {
|
|
@@ -367,6 +387,7 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
|
|
|
367
387
|
snapPoints={snapPoints}
|
|
368
388
|
enablePanDownToClose
|
|
369
389
|
backdropComponent={renderBackdrop}
|
|
390
|
+
// Remove enableDynamicSizing as we're implementing our own solution
|
|
370
391
|
handleComponent={() => (
|
|
371
392
|
<Animated.View
|
|
372
393
|
style={{
|
|
@@ -410,16 +431,13 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
|
|
|
410
431
|
console.log(`Animating from index ${fromIndex} to ${toIndex}`);
|
|
411
432
|
}}
|
|
412
433
|
>
|
|
413
|
-
<
|
|
434
|
+
<BottomSheetScrollView
|
|
414
435
|
style={[
|
|
415
436
|
styles.contentContainer,
|
|
416
437
|
// Override padding if provided in customStyles
|
|
417
438
|
customStyles.contentPadding !== undefined && { padding: customStyles.contentPadding },
|
|
418
|
-
// Add bottom padding when keyboard is visible to ensure content is not covered
|
|
419
|
-
keyboardVisible && {
|
|
420
|
-
paddingBottom: Math.max(keyboardHeight - insets.bottom, 0)
|
|
421
|
-
},
|
|
422
439
|
]}
|
|
440
|
+
onLayout={handleContentLayout}
|
|
423
441
|
>
|
|
424
442
|
<Animated.View
|
|
425
443
|
style={[
|
|
@@ -443,23 +461,18 @@ const OxyBottomSheet: React.FC<OxyProviderProps> = ({
|
|
|
443
461
|
adjustSnapPoints={adjustSnapPoints}
|
|
444
462
|
/>
|
|
445
463
|
</Animated.View>
|
|
446
|
-
</
|
|
464
|
+
</BottomSheetScrollView>
|
|
447
465
|
</BottomSheetModal>
|
|
448
466
|
);
|
|
449
467
|
};
|
|
450
468
|
|
|
451
469
|
const styles = StyleSheet.create({
|
|
452
470
|
contentContainer: {
|
|
453
|
-
flex: 1,
|
|
454
|
-
padding: 16,
|
|
455
471
|
width: '100%',
|
|
456
|
-
height: '100%',
|
|
457
472
|
backgroundColor: 'transparent', // Make this transparent to let the bottom sheet background show through
|
|
458
473
|
},
|
|
459
474
|
animatedContent: {
|
|
460
|
-
flex: 1,
|
|
461
475
|
width: '100%',
|
|
462
|
-
height: '100%',
|
|
463
476
|
},
|
|
464
477
|
indicator: {
|
|
465
478
|
width: 40,
|
|
@@ -44,6 +44,12 @@ export interface OxySignInButtonProps {
|
|
|
44
44
|
* @default false
|
|
45
45
|
*/
|
|
46
46
|
showWhenAuthenticated?: boolean;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Which screen to open in the bottom sheet
|
|
50
|
+
* @default 'SignIn'
|
|
51
|
+
*/
|
|
52
|
+
screen?: string;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
/**
|
|
@@ -79,6 +85,7 @@ export const OxySignInButton: React.FC<OxySignInButtonProps> = ({
|
|
|
79
85
|
text = 'Sign in with Oxy',
|
|
80
86
|
disabled = false,
|
|
81
87
|
showWhenAuthenticated = false,
|
|
88
|
+
screen = 'SignIn',
|
|
82
89
|
}) => {
|
|
83
90
|
// Get all needed values from context in a single call
|
|
84
91
|
const { user, showBottomSheet } = useOxy();
|
|
@@ -93,10 +100,9 @@ export const OxySignInButton: React.FC<OxySignInButtonProps> = ({
|
|
|
93
100
|
return;
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
//
|
|
103
|
+
// Allow passing any screen name, including 'SignUp', 'AccountCenter', etc.
|
|
97
104
|
if (showBottomSheet) {
|
|
98
|
-
|
|
99
|
-
showBottomSheet('SignIn');
|
|
105
|
+
showBottomSheet(screen);
|
|
100
106
|
} else {
|
|
101
107
|
console.warn('OxySignInButton: showBottomSheet is not available. Either provide an onPress prop or ensure this component is used within an OxyProvider.');
|
|
102
108
|
}
|
|
@@ -20,7 +20,7 @@ export interface OxyContextState {
|
|
|
20
20
|
bottomSheetRef?: React.RefObject<any>;
|
|
21
21
|
|
|
22
22
|
// Methods to directly control the bottom sheet
|
|
23
|
-
showBottomSheet?: (screen?: string) => void;
|
|
23
|
+
showBottomSheet?: (screenOrConfig?: string | { screen: string; props?: Record<string, any> }) => void;
|
|
24
24
|
hideBottomSheet?: () => void;
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -292,16 +292,24 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
292
292
|
};
|
|
293
293
|
|
|
294
294
|
// Methods to control the bottom sheet
|
|
295
|
-
const showBottomSheet = useCallback((screen?: string) => {
|
|
295
|
+
const showBottomSheet = useCallback((screenOrConfig?: string | { screen: string; props?: Record<string, any> }) => {
|
|
296
296
|
if (bottomSheetRef?.current) {
|
|
297
297
|
// Expand the bottom sheet
|
|
298
298
|
bottomSheetRef.current.expand();
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
299
|
+
if (typeof screenOrConfig === 'string') {
|
|
300
|
+
// If a screen is specified, navigate to it
|
|
301
|
+
if (screenOrConfig && bottomSheetRef.current._navigateToScreen) {
|
|
302
|
+
setTimeout(() => {
|
|
303
|
+
bottomSheetRef.current._navigateToScreen(screenOrConfig);
|
|
304
|
+
}, 100);
|
|
305
|
+
}
|
|
306
|
+
} else if (screenOrConfig && typeof screenOrConfig === 'object' && screenOrConfig.screen) {
|
|
307
|
+
// If an object is passed, navigate and pass props
|
|
308
|
+
if (bottomSheetRef.current._navigateToScreen) {
|
|
309
|
+
setTimeout(() => {
|
|
310
|
+
bottomSheetRef.current._navigateToScreen(screenOrConfig.screen, screenOrConfig.props || {});
|
|
311
|
+
}, 100);
|
|
312
|
+
}
|
|
305
313
|
}
|
|
306
314
|
}
|
|
307
315
|
}, [bottomSheetRef]);
|
|
@@ -7,7 +7,13 @@ import SignInScreen from '../screens/SignInScreen';
|
|
|
7
7
|
import SignUpScreen from '../screens/SignUpScreen';
|
|
8
8
|
import AccountCenterScreen from '../screens/AccountCenterScreen';
|
|
9
9
|
import AccountOverviewScreen from '../screens/AccountOverviewScreen';
|
|
10
|
-
import
|
|
10
|
+
import KarmaCenterScreen from '../screens/karma/KarmaCenterScreen';
|
|
11
|
+
import KarmaLeaderboardScreen from '../screens/karma/KarmaLeaderboardScreen';
|
|
12
|
+
import KarmaRulesScreen from '../screens/karma/KarmaRulesScreen';
|
|
13
|
+
import KarmaAboutScreen from '../screens/karma/KarmaAboutScreen';
|
|
14
|
+
import KarmaRewardsScreen from '../screens/karma/KarmaRewardsScreen';
|
|
15
|
+
import KarmaFAQScreen from '../screens/karma/KarmaFAQScreen';
|
|
16
|
+
import ProfileScreen from '../screens/ProfileScreen';
|
|
11
17
|
|
|
12
18
|
// Import types
|
|
13
19
|
import { OxyRouterProps, RouteConfig } from './types';
|
|
@@ -16,11 +22,11 @@ import { OxyRouterProps, RouteConfig } from './types';
|
|
|
16
22
|
const routes: Record<string, RouteConfig> = {
|
|
17
23
|
SignIn: {
|
|
18
24
|
component: SignInScreen,
|
|
19
|
-
snapPoints: ['
|
|
25
|
+
snapPoints: ['10%', '80%'],
|
|
20
26
|
},
|
|
21
27
|
SignUp: {
|
|
22
28
|
component: SignUpScreen,
|
|
23
|
-
snapPoints: ['
|
|
29
|
+
snapPoints: ['10%', '90%'],
|
|
24
30
|
},
|
|
25
31
|
AccountCenter: {
|
|
26
32
|
component: AccountCenterScreen,
|
|
@@ -30,8 +36,32 @@ const routes: Record<string, RouteConfig> = {
|
|
|
30
36
|
component: AccountOverviewScreen,
|
|
31
37
|
snapPoints: ['60%', '85%'],
|
|
32
38
|
},
|
|
39
|
+
KarmaCenter: {
|
|
40
|
+
component: KarmaCenterScreen,
|
|
41
|
+
snapPoints: ['60%', '100%'],
|
|
42
|
+
},
|
|
43
|
+
KarmaLeaderboard: {
|
|
44
|
+
component: KarmaLeaderboardScreen,
|
|
45
|
+
snapPoints: ['60%', '100%'],
|
|
46
|
+
},
|
|
47
|
+
KarmaRules: {
|
|
48
|
+
component: KarmaRulesScreen,
|
|
49
|
+
snapPoints: ['60%', '90%'],
|
|
50
|
+
},
|
|
33
51
|
AboutKarma: {
|
|
34
|
-
component:
|
|
52
|
+
component: KarmaAboutScreen,
|
|
53
|
+
snapPoints: ['60%', '90%'],
|
|
54
|
+
},
|
|
55
|
+
KarmaRewards: {
|
|
56
|
+
component: KarmaRewardsScreen,
|
|
57
|
+
snapPoints: ['60%', '90%'],
|
|
58
|
+
},
|
|
59
|
+
KarmaFAQ: {
|
|
60
|
+
component: KarmaFAQScreen,
|
|
61
|
+
snapPoints: ['60%', '90%'],
|
|
62
|
+
},
|
|
63
|
+
Profile: {
|
|
64
|
+
component: ProfileScreen,
|
|
35
65
|
snapPoints: ['60%', '90%'],
|
|
36
66
|
},
|
|
37
67
|
};
|
|
@@ -69,9 +99,16 @@ const OxyRouter: React.FC<OxyRouterProps> = ({
|
|
|
69
99
|
// Set up event listener for navigation events
|
|
70
100
|
const handleNavigationEvent = (event: any) => {
|
|
71
101
|
if (event && event.detail) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
102
|
+
// Support both string and object detail
|
|
103
|
+
if (typeof event.detail === 'string') {
|
|
104
|
+
const screenName = event.detail;
|
|
105
|
+
console.log(`Navigation event received for screen: ${screenName}`);
|
|
106
|
+
navigate(screenName);
|
|
107
|
+
} else if (typeof event.detail === 'object' && event.detail.screen) {
|
|
108
|
+
const { screen, props } = event.detail;
|
|
109
|
+
console.log(`Navigation event received for screen: ${screen} with props`, props);
|
|
110
|
+
navigate(screen, props || {});
|
|
111
|
+
}
|
|
75
112
|
}
|
|
76
113
|
};
|
|
77
114
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { View, Text, StyleSheet, ActivityIndicator, ScrollView } from 'react-native';
|
|
3
|
+
import { BaseScreenProps } from '../navigation/types';
|
|
4
|
+
import { useOxy } from '../context/OxyContext';
|
|
5
|
+
import Avatar from '../components/Avatar';
|
|
6
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
7
|
+
|
|
8
|
+
interface ProfileScreenProps extends BaseScreenProps {
|
|
9
|
+
userId: string;
|
|
10
|
+
username?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme, goBack }) => {
|
|
14
|
+
const { oxyServices } = useOxy();
|
|
15
|
+
const [profile, setProfile] = useState<any>(null);
|
|
16
|
+
const [karmaTotal, setKarmaTotal] = useState<number | null>(null);
|
|
17
|
+
const [postsCount, setPostsCount] = useState<number | null>(null);
|
|
18
|
+
const [commentsCount, setCommentsCount] = useState<number | null>(null);
|
|
19
|
+
const [followersCount, setFollowersCount] = useState<number | null>(null);
|
|
20
|
+
const [followingCount, setFollowingCount] = useState<number | null>(null);
|
|
21
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
22
|
+
const [error, setError] = useState<string | null>(null);
|
|
23
|
+
|
|
24
|
+
const isDarkTheme = theme === 'dark';
|
|
25
|
+
const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF';
|
|
26
|
+
const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
|
|
27
|
+
const primaryColor = '#d169e5';
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setIsLoading(true);
|
|
31
|
+
setError(null);
|
|
32
|
+
|
|
33
|
+
// Load user profile and karma total
|
|
34
|
+
Promise.all([
|
|
35
|
+
oxyServices.getUserById(userId),
|
|
36
|
+
oxyServices.getUserKarmaTotal ? oxyServices.getUserKarmaTotal(userId) : Promise.resolve({ total: undefined })
|
|
37
|
+
])
|
|
38
|
+
.then(([profileRes, karmaRes]) => {
|
|
39
|
+
setProfile(profileRes);
|
|
40
|
+
setKarmaTotal(typeof karmaRes.total === 'number' ? karmaRes.total : null);
|
|
41
|
+
|
|
42
|
+
// Mock data for other stats
|
|
43
|
+
// In a real app, these would come from API endpoints
|
|
44
|
+
setPostsCount(Math.floor(Math.random() * 50));
|
|
45
|
+
setCommentsCount(Math.floor(Math.random() * 100));
|
|
46
|
+
setFollowersCount(Math.floor(Math.random() * 200));
|
|
47
|
+
setFollowingCount(Math.floor(Math.random() * 100));
|
|
48
|
+
})
|
|
49
|
+
.catch((err: any) => setError(err.message || 'Failed to load profile'))
|
|
50
|
+
.finally(() => setIsLoading(false));
|
|
51
|
+
}, [userId]);
|
|
52
|
+
|
|
53
|
+
if (isLoading) {
|
|
54
|
+
return (
|
|
55
|
+
<View style={[styles.container, { backgroundColor, justifyContent: 'center' }]}>
|
|
56
|
+
<ActivityIndicator size="large" color={primaryColor} />
|
|
57
|
+
</View>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (error) {
|
|
62
|
+
return (
|
|
63
|
+
<View style={[styles.container, { backgroundColor, justifyContent: 'center' }]}>
|
|
64
|
+
<Text style={{ color: '#D32F2F', textAlign: 'center' }}>{error}</Text>
|
|
65
|
+
</View>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<View style={[styles.container, { backgroundColor }]}>
|
|
71
|
+
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
|
72
|
+
{/* Banner Image */}
|
|
73
|
+
<View style={styles.bannerContainer}>
|
|
74
|
+
<View style={styles.bannerImage} />
|
|
75
|
+
</View>
|
|
76
|
+
{/* Avatar overlapping banner */}
|
|
77
|
+
<View style={styles.avatarRow}>
|
|
78
|
+
<View style={styles.avatarWrapper}>
|
|
79
|
+
<Avatar imageUrl={profile?.avatarUrl} name={profile?.username || username} size={96} theme={theme} />
|
|
80
|
+
</View>
|
|
81
|
+
{/* Edit Profile/Follow Button placeholder */}
|
|
82
|
+
<View style={styles.actionButtonWrapper}>
|
|
83
|
+
<Text style={styles.actionButton}>Edit Profile</Text>
|
|
84
|
+
</View>
|
|
85
|
+
</View>
|
|
86
|
+
{/* Profile Info */}
|
|
87
|
+
<View style={styles.header}>
|
|
88
|
+
<Text style={[styles.displayName, { color: textColor }]}>{profile?.displayName || profile?.username || username || profile?.id}</Text>
|
|
89
|
+
{profile?.username && (
|
|
90
|
+
<Text style={[styles.subText, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>@{profile.username}</Text>
|
|
91
|
+
)}
|
|
92
|
+
{/* Bio placeholder */}
|
|
93
|
+
<Text style={[styles.bio, { color: textColor }]}>{profile?.bio || 'This user has no bio yet.'}</Text>
|
|
94
|
+
{/* Email and Join Date Row */}
|
|
95
|
+
<View style={styles.infoRow}>
|
|
96
|
+
{profile?.email && (
|
|
97
|
+
<View style={styles.infoItem}>
|
|
98
|
+
<Ionicons name="mail-outline" size={16} color={isDarkTheme ? '#BBBBBB' : '#888888'} style={{ marginRight: 4 }} />
|
|
99
|
+
<Text style={[styles.infoText, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>{profile.email}</Text>
|
|
100
|
+
</View>
|
|
101
|
+
)}
|
|
102
|
+
{profile?.createdAt && (
|
|
103
|
+
<View style={styles.infoItem}>
|
|
104
|
+
<Ionicons name="calendar-outline" size={16} color={isDarkTheme ? '#BBBBBB' : '#888888'} style={{ marginRight: 4 }} />
|
|
105
|
+
<Text style={[styles.infoText, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>Joined {new Date(profile.createdAt).toLocaleDateString()}</Text>
|
|
106
|
+
</View>
|
|
107
|
+
)}
|
|
108
|
+
</View>
|
|
109
|
+
{/* Divider */}
|
|
110
|
+
<View style={styles.divider} />
|
|
111
|
+
{/* All Stats in one row */}
|
|
112
|
+
<View style={styles.statsRow}>
|
|
113
|
+
<View style={styles.statItem}>
|
|
114
|
+
<Text style={[styles.karmaAmount, { color: primaryColor }]}>{karmaTotal !== null && karmaTotal !== undefined ? karmaTotal : '--'}</Text>
|
|
115
|
+
<Text style={[styles.karmaLabel, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>Karma</Text>
|
|
116
|
+
</View>
|
|
117
|
+
<View style={styles.statItem}>
|
|
118
|
+
<Text style={[styles.karmaAmount, { color: textColor }]}>{followersCount !== null ? followersCount : '--'}</Text>
|
|
119
|
+
<Text style={[styles.karmaLabel, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>Followers</Text>
|
|
120
|
+
</View>
|
|
121
|
+
<View style={styles.statItem}>
|
|
122
|
+
<Text style={[styles.karmaAmount, { color: textColor }]}>{followingCount !== null ? followingCount : '--'}</Text>
|
|
123
|
+
<Text style={[styles.karmaLabel, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>Following</Text>
|
|
124
|
+
</View>
|
|
125
|
+
</View>
|
|
126
|
+
</View>
|
|
127
|
+
</ScrollView>
|
|
128
|
+
</View>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const styles = StyleSheet.create({
|
|
133
|
+
container: { flex: 1 },
|
|
134
|
+
scrollContainer: { alignItems: 'stretch', paddingBottom: 40 },
|
|
135
|
+
bannerContainer: { height: 160, backgroundColor: '#e1bee7', position: 'relative', overflow: 'hidden' },
|
|
136
|
+
bannerImage: { flex: 1, backgroundColor: '#d169e5' }, // Placeholder, replace with Image if available
|
|
137
|
+
avatarRow: { flexDirection: 'row', alignItems: 'flex-end', marginTop: -56, paddingHorizontal: 20, justifyContent: 'space-between', zIndex: 2 },
|
|
138
|
+
avatarWrapper: { borderWidth: 5, borderColor: '#fff', borderRadius: 64, overflow: 'hidden', backgroundColor: '#fff', },
|
|
139
|
+
actionButtonWrapper: { flex: 1, alignItems: 'flex-end', justifyContent: 'flex-end' },
|
|
140
|
+
actionButton: { backgroundColor: '#fff', color: '#d169e5', borderWidth: 1, borderColor: '#d169e5', borderRadius: 24, paddingVertical: 7, paddingHorizontal: 22, fontWeight: 'bold', fontSize: 16, marginBottom: 8, elevation: 2, shadowColor: '#d169e5', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.08, shadowRadius: 2 },
|
|
141
|
+
header: { alignItems: 'flex-start', paddingTop: 18, paddingBottom: 24, width: '100%', paddingHorizontal: 20 },
|
|
142
|
+
displayName: { fontSize: 24, fontWeight: 'bold', marginTop: 10, marginBottom: 2, letterSpacing: 0.1 },
|
|
143
|
+
subText: { fontSize: 16, marginBottom: 2, color: '#a0a0a0' },
|
|
144
|
+
bio: { fontSize: 16, marginTop: 10, marginBottom: 10, color: '#666', fontStyle: 'italic', lineHeight: 22 },
|
|
145
|
+
infoRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 10, flexWrap: 'wrap' },
|
|
146
|
+
infoItem: { flexDirection: 'row', alignItems: 'center', marginRight: 28, marginBottom: 4, minWidth: 120 },
|
|
147
|
+
infoText: { fontSize: 15 },
|
|
148
|
+
divider: { height: 1, backgroundColor: '#e0e0e0', width: '100%', marginVertical: 14 },
|
|
149
|
+
statsRow: { width: '100%', flex: 1, flexDirection: 'row', alignItems: 'center', marginTop: 6, marginBottom: 2, justifyContent: 'space-between' },
|
|
150
|
+
statItem: { flex: 1, alignItems: 'center', minWidth: 50, marginBottom: 12 },
|
|
151
|
+
karmaLabel: { fontSize: 14, marginBottom: 2, textAlign: 'center', color: '#a0a0a0' },
|
|
152
|
+
karmaAmount: { fontSize: 24, fontWeight: 'bold', textAlign: 'center', letterSpacing: 0.2 },
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export default ProfileScreen;
|
|
@@ -15,6 +15,7 @@ import { BaseScreenProps } from '../navigation/types';
|
|
|
15
15
|
import { useOxy } from '../context/OxyContext';
|
|
16
16
|
import { fontFamilies, useThemeColors, createCommonStyles } from '../styles';
|
|
17
17
|
import OxyLogo from '../components/OxyLogo';
|
|
18
|
+
import { BottomSheetScrollView } from '@gorhom/bottom-sheet';
|
|
18
19
|
|
|
19
20
|
const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
20
21
|
navigate,
|
|
@@ -77,92 +78,86 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
|
|
|
77
78
|
};
|
|
78
79
|
|
|
79
80
|
return (
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
<BottomSheetScrollView
|
|
82
|
+
contentContainerStyle={commonStyles.scrollContainer}
|
|
83
|
+
keyboardShouldPersistTaps="handled"
|
|
83
84
|
>
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
autoCapitalize="none"
|
|
114
|
-
testID="username-input"
|
|
115
|
-
/>
|
|
116
|
-
</View>
|
|
117
|
-
|
|
118
|
-
<View style={styles.inputContainer}>
|
|
119
|
-
<Text style={[styles.label, { color: colors.text }]}>Password</Text>
|
|
120
|
-
<TextInput
|
|
121
|
-
style={commonStyles.input}
|
|
122
|
-
placeholder="Enter your password"
|
|
123
|
-
placeholderTextColor={colors.placeholder}
|
|
124
|
-
value={password}
|
|
125
|
-
onChangeText={setPassword}
|
|
126
|
-
secureTextEntry
|
|
127
|
-
testID="password-input"
|
|
128
|
-
/>
|
|
129
|
-
</View>
|
|
85
|
+
<OxyLogo
|
|
86
|
+
style={{ marginBottom: 24 }}
|
|
87
|
+
width={50}
|
|
88
|
+
height={50}
|
|
89
|
+
/>
|
|
90
|
+
<Text style={[
|
|
91
|
+
styles.title,
|
|
92
|
+
{ color: colors.text }
|
|
93
|
+
]}>Sign In</Text>
|
|
94
|
+
|
|
95
|
+
{errorMessage ? (
|
|
96
|
+
<View style={commonStyles.errorContainer}>
|
|
97
|
+
<Text style={commonStyles.errorText}>{errorMessage}</Text>
|
|
98
|
+
</View>
|
|
99
|
+
) : null}
|
|
100
|
+
|
|
101
|
+
<View style={styles.formContainer}>
|
|
102
|
+
<View style={styles.inputContainer}>
|
|
103
|
+
<Text style={[styles.label, { color: colors.text }]}>Username</Text>
|
|
104
|
+
<TextInput
|
|
105
|
+
style={commonStyles.input}
|
|
106
|
+
placeholder="Enter your username"
|
|
107
|
+
placeholderTextColor={colors.placeholder}
|
|
108
|
+
value={username}
|
|
109
|
+
onChangeText={setUsername}
|
|
110
|
+
autoCapitalize="none"
|
|
111
|
+
testID="username-input"
|
|
112
|
+
/>
|
|
113
|
+
</View>
|
|
130
114
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
115
|
+
<View style={styles.inputContainer}>
|
|
116
|
+
<Text style={[styles.label, { color: colors.text }]}>Password</Text>
|
|
117
|
+
<TextInput
|
|
118
|
+
style={commonStyles.input}
|
|
119
|
+
placeholder="Enter your password"
|
|
120
|
+
placeholderTextColor={colors.placeholder}
|
|
121
|
+
value={password}
|
|
122
|
+
onChangeText={setPassword}
|
|
123
|
+
secureTextEntry
|
|
124
|
+
testID="password-input"
|
|
125
|
+
/>
|
|
126
|
+
</View>
|
|
143
127
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
128
|
+
<TouchableOpacity
|
|
129
|
+
style={[commonStyles.button, { opacity: isLoading ? 0.8 : 1 }]}
|
|
130
|
+
onPress={handleLogin}
|
|
131
|
+
disabled={isLoading}
|
|
132
|
+
testID="login-button"
|
|
133
|
+
>
|
|
134
|
+
{isLoading ? (
|
|
135
|
+
<ActivityIndicator color="#FFFFFF" size="small" />
|
|
136
|
+
) : (
|
|
137
|
+
<Text style={commonStyles.buttonText}>Sign In</Text>
|
|
138
|
+
)}
|
|
139
|
+
</TouchableOpacity>
|
|
140
|
+
|
|
141
|
+
<View style={styles.footerTextContainer}>
|
|
142
|
+
<Text style={[styles.footerText, { color: colors.text }]}>
|
|
143
|
+
Don't have an account?{' '}
|
|
144
|
+
</Text>
|
|
145
|
+
<TouchableOpacity onPress={() => navigate('SignUp')}>
|
|
146
|
+
<Text style={[styles.linkText, { color: colors.primary }]}>Sign Up</Text>
|
|
147
|
+
</TouchableOpacity>
|
|
152
148
|
</View>
|
|
153
|
-
</
|
|
154
|
-
</
|
|
149
|
+
</View>
|
|
150
|
+
</BottomSheetScrollView>
|
|
155
151
|
);
|
|
156
152
|
};
|
|
157
153
|
|
|
158
154
|
const styles = StyleSheet.create({
|
|
159
|
-
// Local screen-specific styles
|
|
160
155
|
title: {
|
|
161
156
|
fontFamily: Platform.OS === 'web'
|
|
162
157
|
? 'Phudu' // Use CSS font name directly for web
|
|
163
158
|
: 'Phudu-Bold', // Use exact font name as registered with Font.loadAsync
|
|
164
159
|
fontWeight: Platform.OS === 'web' ? 'bold' : undefined, // Only apply fontWeight on web
|
|
165
|
-
fontSize:
|
|
160
|
+
fontSize: 54,
|
|
166
161
|
marginBottom: 24,
|
|
167
162
|
},
|
|
168
163
|
formContainer: {
|