@oxyhq/services 22.11.1 → 22.12.1
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/hooks/useSurfaceHeader.js +8 -6
- package/lib/commonjs/ui/hooks/useSurfaceHeader.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaceRegistry.js +11 -37
- package/lib/commonjs/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/commonjs/ui/screens/AvatarCropScreen.js +9 -13
- package/lib/commonjs/ui/screens/AvatarCropScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +66 -18
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PaymentGatewayScreen.js +11 -0
- package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +91 -117
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +22 -54
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +38 -154
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/module/ui/hooks/useSurfaceHeader.js +8 -6
- package/lib/module/ui/hooks/useSurfaceHeader.js.map +1 -1
- package/lib/module/ui/navigation/surfaceRegistry.js +11 -37
- package/lib/module/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/module/ui/screens/AvatarCropScreen.js +9 -13
- package/lib/module/ui/screens/AvatarCropScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +66 -18
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/PaymentGatewayScreen.js +11 -0
- package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +93 -119
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +23 -54
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +38 -154
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts +7 -5
- package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts +8 -9
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts +2 -1
- package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts +0 -6
- package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts +7 -5
- package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts +8 -9
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts +1 -1
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts +2 -1
- package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts +0 -6
- package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ui/hooks/useSurfaceHeader.ts +21 -16
- package/src/ui/navigation/__tests__/getSurfaceConfig.test.ts +16 -2
- package/src/ui/navigation/surfaceRegistry.ts +14 -42
- package/src/ui/screens/AvatarCropScreen.tsx +14 -23
- package/src/ui/screens/FileManagementScreen.tsx +76 -29
- package/src/ui/screens/PaymentGatewayScreen.tsx +11 -0
- package/src/ui/screens/ProfileScreen.tsx +96 -111
- package/src/ui/screens/WelcomeNewUserScreen.tsx +23 -63
- package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +39 -171
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
3
|
import { View, StyleSheet, ActivityIndicator } from 'react-native';
|
|
4
|
+
import { useQuery } from '@tanstack/react-query';
|
|
4
5
|
import type { BaseScreenProps } from '../types/navigation';
|
|
5
6
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
6
7
|
import { Button } from '@oxyhq/bloom/button';
|
|
@@ -11,9 +12,9 @@ import FollowButton from '../components/FollowButton';
|
|
|
11
12
|
import { useFollow } from '../hooks/useFollow';
|
|
12
13
|
import { Ionicons } from '@expo/vector-icons';
|
|
13
14
|
import { useI18n } from '../hooks/useI18n';
|
|
15
|
+
import { useSurfaceHeader } from '../hooks/useSurfaceHeader';
|
|
14
16
|
import { useOxy } from '../context/OxyContext';
|
|
15
17
|
import { getAccountDisplayName, logger, normalizeProfileLinks } from '@oxyhq/core';
|
|
16
|
-
import type { User, ProfileLink } from '@oxyhq/core';
|
|
17
18
|
import { extractErrorMessage } from '../utils/errorHandlers';
|
|
18
19
|
|
|
19
20
|
interface ProfileScreenProps extends BaseScreenProps {
|
|
@@ -32,13 +33,6 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
|
|
|
32
33
|
// into), so previewing my profile while switched into an org/project/bot
|
|
33
34
|
// resolves "this is mine" against that account, not the session owner.
|
|
34
35
|
const { oxyServices, user: currentUser } = useOxy();
|
|
35
|
-
const [profile, setProfile] = useState<User | null>(null);
|
|
36
|
-
const [reputationTotal, setReputationTotal] = useState<number | null>(null);
|
|
37
|
-
const [postsCount, setPostsCount] = useState<number | null>(null);
|
|
38
|
-
const [commentsCount, setCommentsCount] = useState<number | null>(null);
|
|
39
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
40
|
-
const [error, setError] = useState<string | null>(null);
|
|
41
|
-
const [links, setLinks] = useState<ProfileLink[]>([]);
|
|
42
36
|
|
|
43
37
|
// Use the follow hook for real follower data
|
|
44
38
|
const {
|
|
@@ -59,86 +53,92 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
|
|
|
59
53
|
|
|
60
54
|
const currentUserId = normalizeId(currentUser?.id);
|
|
61
55
|
const targetUserId = normalizeId(userId);
|
|
62
|
-
const isOwnProfile = currentUserId && targetUserId && currentUserId === targetUserId;
|
|
56
|
+
const isOwnProfile = !!(currentUserId && targetUserId && currentUserId === targetUserId);
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// Normalize profile links via the shared @oxyhq/core helper.
|
|
105
|
-
setLinks(normalizeProfileLinks(profileRes.linksMetadata, profileRes.links));
|
|
58
|
+
// Profile + reputation + stats in ONE React Query (no data-fetch effect). The
|
|
59
|
+
// reputation/stats reads swallow their own failures; only `getUserById` can
|
|
60
|
+
// reject the query, and for MY OWN profile a failed fetch falls back to the
|
|
61
|
+
// active-account snapshot instead of erroring. The key includes both ids so a
|
|
62
|
+
// switch/own-profile change refetches.
|
|
63
|
+
const profileQuery = useQuery({
|
|
64
|
+
queryKey: ['profileScreen', targetUserId, currentUserId],
|
|
65
|
+
enabled: !!userId,
|
|
66
|
+
retry: false,
|
|
67
|
+
queryFn: async () => {
|
|
68
|
+
// Follower/following counts come from the `useFollow` hook; the stats
|
|
69
|
+
// read is kept for parity (it warms server-side counters) but its
|
|
70
|
+
// result is not surfaced in this view, so it is not bound.
|
|
71
|
+
const [profileRes, reputationRes] = await Promise.all([
|
|
72
|
+
oxyServices.getUserById(userId).catch((err: unknown) => {
|
|
73
|
+
if (isOwnProfile) return currentUser;
|
|
74
|
+
logger.error(
|
|
75
|
+
'Profile loading error',
|
|
76
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
77
|
+
{ component: 'ProfileScreen' },
|
|
78
|
+
);
|
|
79
|
+
throw err;
|
|
80
|
+
}),
|
|
81
|
+
oxyServices.getReputationBalance(userId)
|
|
82
|
+
.then((balance): { total: number | undefined } => ({ total: balance.total }))
|
|
83
|
+
.catch((): { total: number | undefined } => ({ total: undefined })),
|
|
84
|
+
oxyServices.getUserStats
|
|
85
|
+
? oxyServices.getUserStats(userId).catch(() => ({ postCount: 0, commentCount: 0 }))
|
|
86
|
+
: Promise.resolve({ postCount: 0, commentCount: 0 }),
|
|
87
|
+
]);
|
|
88
|
+
if (!profileRes) {
|
|
89
|
+
throw new Error('Profile data is not available');
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
profile: profileRes,
|
|
93
|
+
reputationTotal: typeof reputationRes.total === 'number' ? reputationRes.total : null,
|
|
94
|
+
links: normalizeProfileLinks(profileRes.linksMetadata, profileRes.links),
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
});
|
|
106
98
|
|
|
107
|
-
|
|
99
|
+
const profile = profileQuery.data?.profile ?? null;
|
|
100
|
+
const reputationTotal = profileQuery.data?.reputationTotal ?? null;
|
|
101
|
+
const links = profileQuery.data?.links ?? [];
|
|
102
|
+
const isLoading = !!userId && profileQuery.isLoading;
|
|
108
103
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
104
|
+
// Friendly, status-aware error copy derived from the query error (no effect).
|
|
105
|
+
const error = useMemo<string | null>(() => {
|
|
106
|
+
if (!userId) return 'No user ID provided';
|
|
107
|
+
if (!profileQuery.isError) return null;
|
|
108
|
+
const err = profileQuery.error;
|
|
109
|
+
const errorWithStatus =
|
|
110
|
+
err && typeof err === 'object' && 'status' in err
|
|
111
|
+
? (err as { status?: number; message?: string })
|
|
112
|
+
: null;
|
|
113
|
+
const errorMessageText = extractErrorMessage(err, '');
|
|
114
|
+
if (
|
|
115
|
+
errorWithStatus?.status === 404 ||
|
|
116
|
+
errorMessageText.includes('not found') ||
|
|
117
|
+
errorMessageText.includes('Resource not found')
|
|
118
|
+
) {
|
|
119
|
+
return isOwnProfile
|
|
120
|
+
? 'Unable to load your profile from the server. This may be due to a temporary service issue.'
|
|
121
|
+
: 'This user profile could not be found or may have been removed.';
|
|
122
|
+
}
|
|
123
|
+
if (errorWithStatus?.status === 403) {
|
|
124
|
+
return 'You do not have permission to view this profile.';
|
|
125
|
+
}
|
|
126
|
+
if (errorWithStatus?.status === 500) {
|
|
127
|
+
return 'Server error occurred while loading the profile. Please try again later.';
|
|
128
|
+
}
|
|
129
|
+
return errorMessageText || 'Failed to load profile';
|
|
130
|
+
}, [userId, profileQuery.isError, profileQuery.error, isOwnProfile]);
|
|
137
131
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
132
|
+
// Display name: the loaded profile's, else the passed handle. Also the nav-bar
|
|
133
|
+
// title (a stable "Profile" fallback before it resolves), so the banner +
|
|
134
|
+
// overlapping avatar scroll as content UNDER the shared gradient nav bar.
|
|
135
|
+
// `onImage` tone keeps the title + close legible over the colored banner.
|
|
136
|
+
const displayName = profile ? getAccountDisplayName(profile, locale) : username || '';
|
|
137
|
+
useSurfaceHeader({
|
|
138
|
+
title: displayName || t('profile.title'),
|
|
139
|
+
largeTitle: false,
|
|
140
|
+
tone: 'onImage',
|
|
141
|
+
});
|
|
142
142
|
|
|
143
143
|
if (isLoading) {
|
|
144
144
|
return (
|
|
@@ -149,35 +149,21 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
if (error) {
|
|
152
|
+
// The shared nav header owns back/close; the error body is just the alert.
|
|
152
153
|
return (
|
|
153
|
-
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
)}
|
|
164
|
-
<H2 style={styles.errorTitle} className="text-text">
|
|
165
|
-
{t('profile.errorTitle') || 'Profile Error'}
|
|
166
|
-
</H2>
|
|
167
|
-
</View>
|
|
168
|
-
<View style={styles.errorContent} className="px-space-32 gap-space-12 py-space-40">
|
|
169
|
-
<Ionicons name="alert-circle" size={48} color={bloomTheme.colors.error} />
|
|
170
|
-
<Text style={styles.errorText} className="text-text">{error}</Text>
|
|
171
|
-
<Text style={styles.errorSubtext} className="text-text-secondary">
|
|
172
|
-
{t('profile.errorSubtext') || "This could happen if the user doesn't exist or the profile service is unavailable."}
|
|
173
|
-
</Text>
|
|
174
|
-
</View>
|
|
175
|
-
</>
|
|
154
|
+
<View style={styles.errorContent} className="px-space-32 gap-space-12 py-space-40">
|
|
155
|
+
<Ionicons name="alert-circle" size={48} color={bloomTheme.colors.error} />
|
|
156
|
+
<H2 style={styles.errorTitle} className="text-text text-center">
|
|
157
|
+
{t('profile.errorTitle') || 'Profile Error'}
|
|
158
|
+
</H2>
|
|
159
|
+
<Text style={styles.errorText} className="text-text">{error}</Text>
|
|
160
|
+
<Text style={styles.errorSubtext} className="text-text-secondary">
|
|
161
|
+
{t('profile.errorSubtext') || "This could happen if the user doesn't exist or the profile service is unavailable."}
|
|
162
|
+
</Text>
|
|
163
|
+
</View>
|
|
176
164
|
);
|
|
177
165
|
}
|
|
178
166
|
|
|
179
|
-
const displayName = profile ? getAccountDisplayName(profile, locale) : username || '';
|
|
180
|
-
|
|
181
167
|
// The singular `location` field was removed from the User contract; derive
|
|
182
168
|
// the primary place from the `locations` list instead. `locations` is only
|
|
183
169
|
// reachable through the User index signature (typed `unknown`), so narrow it
|
|
@@ -362,7 +348,6 @@ const styles = StyleSheet.create({
|
|
|
362
348
|
statLabel: { fontSize: 14, marginBottom: 2, textAlign: 'center' },
|
|
363
349
|
statAmount: { fontSize: 24, fontWeight: 'bold', textAlign: 'center', letterSpacing: 0.2 },
|
|
364
350
|
// Error state layout
|
|
365
|
-
errorHeader: { flexDirection: 'row', alignItems: 'center', gap: 16 },
|
|
366
351
|
errorTitle: { fontSize: 20 },
|
|
367
352
|
errorContent: { justifyContent: 'center', alignItems: 'center' },
|
|
368
353
|
errorText: { fontSize: 18, fontWeight: '600', textAlign: 'center' },
|
|
@@ -1,63 +1,30 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import { useCallback, useMemo, useRef, useState
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { View, StyleSheet, Platform, Animated } from 'react-native';
|
|
4
|
-
import AnimatedReanimated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';
|
|
5
4
|
import type { BaseScreenProps } from '../types/navigation';
|
|
6
5
|
import { Avatar } from '@oxyhq/bloom/avatar';
|
|
7
6
|
import { Ionicons } from '@expo/vector-icons';
|
|
8
7
|
import { toast } from '@oxyhq/bloom';
|
|
9
|
-
import { useAuthStore } from '../stores/authStore';
|
|
10
8
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
11
9
|
import { Button } from '@oxyhq/bloom/button';
|
|
12
10
|
import { H1, Text } from '@oxyhq/bloom/typography';
|
|
13
11
|
import { TextField, TextFieldInput } from '@oxyhq/bloom/text-field';
|
|
14
12
|
import { useI18n } from '../hooks/useI18n';
|
|
13
|
+
import { useSurfaceHeader } from '../hooks/useSurfaceHeader';
|
|
15
14
|
import { useOxy } from '../context/OxyContext';
|
|
16
15
|
import { useUpdateProfile } from '../hooks/mutations/useAccountMutations';
|
|
17
|
-
import { getAccountDisplayName } from '@oxyhq/core';
|
|
16
|
+
import { getAccountDisplayName, type User } from '@oxyhq/core';
|
|
18
17
|
|
|
19
18
|
const GAP = 12;
|
|
20
19
|
const INNER_GAP = 8;
|
|
21
20
|
|
|
22
|
-
// Individual animated progress dot
|
|
23
|
-
const AnimatedProgressDot: React.FC<{
|
|
24
|
-
isActive: boolean;
|
|
25
|
-
colors: any;
|
|
26
|
-
styles: any;
|
|
27
|
-
}> = ({ isActive, colors, styles }) => {
|
|
28
|
-
const width = useSharedValue(isActive ? 12 : 6);
|
|
29
|
-
const backgroundColor = useSharedValue(isActive ? colors.primary : colors.border);
|
|
30
|
-
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
width.value = withTiming(isActive ? 12 : 6, { duration: 300 });
|
|
33
|
-
backgroundColor.value = withTiming(
|
|
34
|
-
isActive ? colors.primary : colors.border,
|
|
35
|
-
{ duration: 300 }
|
|
36
|
-
);
|
|
37
|
-
}, [isActive, colors.primary, colors.border, width, backgroundColor]);
|
|
38
|
-
|
|
39
|
-
const animatedStyle = useAnimatedStyle(() => ({
|
|
40
|
-
width: width.value,
|
|
41
|
-
backgroundColor: backgroundColor.value,
|
|
42
|
-
}));
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<AnimatedReanimated.View
|
|
46
|
-
style={[
|
|
47
|
-
styles.progressDot,
|
|
48
|
-
animatedStyle,
|
|
49
|
-
]}
|
|
50
|
-
/>
|
|
51
|
-
);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
21
|
/**
|
|
55
22
|
* Post-signup welcome & onboarding screen.
|
|
56
23
|
* - Greets the newly registered user
|
|
57
24
|
* - Lets them immediately set / change their avatar using existing FileManagement picker
|
|
58
25
|
* - Only when the user presses "Continue" do we invoke onAuthenticated to finish flow & close sheet
|
|
59
26
|
*/
|
|
60
|
-
const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?:
|
|
27
|
+
const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: User }> = ({
|
|
61
28
|
onAuthenticated,
|
|
62
29
|
theme,
|
|
63
30
|
newUser,
|
|
@@ -76,7 +43,7 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
|
|
|
76
43
|
border: bloomTheme.colors.border,
|
|
77
44
|
text: bloomTheme.colors.text,
|
|
78
45
|
};
|
|
79
|
-
const styles = useMemo(() => createStyles(
|
|
46
|
+
const styles = useMemo(() => createStyles(), []);
|
|
80
47
|
|
|
81
48
|
// Animation state
|
|
82
49
|
const fadeAnim = useRef(new Animated.Value(1)).current;
|
|
@@ -194,6 +161,22 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
|
|
|
194
161
|
openChangeAvatarSurface();
|
|
195
162
|
}, [openChangeAvatarSurface, currentStep, avatarStepIndex, animateToStepCallback]);
|
|
196
163
|
|
|
164
|
+
// Shared Dialog nav header: a stable "Welcome" title, the wizard `progress`
|
|
165
|
+
// bar (replacing the old in-content dots), and a back affordance that steps the
|
|
166
|
+
// wizard back once past the intro (the step CTAs keep their own
|
|
167
|
+
// Back/Skip/Next/Enter buttons as content; the header's close dismisses,
|
|
168
|
+
// matching the pre-existing backdrop-dismiss).
|
|
169
|
+
const progress = useMemo(
|
|
170
|
+
() => ({ step: currentStep + 1, total: totalSteps }),
|
|
171
|
+
[currentStep, totalSteps],
|
|
172
|
+
);
|
|
173
|
+
useSurfaceHeader({
|
|
174
|
+
title: t('welcomeNew.navTitle'),
|
|
175
|
+
largeTitle: false,
|
|
176
|
+
onBack: currentStep > 0 ? prevStep : undefined,
|
|
177
|
+
progress,
|
|
178
|
+
});
|
|
179
|
+
|
|
197
180
|
const step = steps[currentStep];
|
|
198
181
|
const renderActionButtons = useCallback(() => {
|
|
199
182
|
if (currentStep === totalSteps - 1) {
|
|
@@ -260,16 +243,7 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
|
|
|
260
243
|
|
|
261
244
|
return (
|
|
262
245
|
<View style={styles.container}>
|
|
263
|
-
|
|
264
|
-
{steps.map((s, i) => (
|
|
265
|
-
<AnimatedProgressDot
|
|
266
|
-
key={s.key}
|
|
267
|
-
isActive={i === currentStep}
|
|
268
|
-
colors={colors}
|
|
269
|
-
styles={styles}
|
|
270
|
-
/>
|
|
271
|
-
))}
|
|
272
|
-
</View>
|
|
246
|
+
{/* Step progress now lives in the shared Dialog header (`progress`). */}
|
|
273
247
|
<Animated.View style={{ opacity: fadeAnim, transform: [{ translateX: slideAnim }] }}>
|
|
274
248
|
<View style={[styles.scrollInner, styles.contentContainer]}>
|
|
275
249
|
<View style={[styles.header, styles.sectionSpacing]}>
|
|
@@ -337,7 +311,7 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
|
|
|
337
311
|
|
|
338
312
|
};
|
|
339
313
|
|
|
340
|
-
const createStyles = (
|
|
314
|
+
const createStyles = () => {
|
|
341
315
|
return StyleSheet.create({
|
|
342
316
|
container: {
|
|
343
317
|
width: '100%',
|
|
@@ -400,20 +374,6 @@ const createStyles = (colors: ReturnType<typeof useTheme>['colors']) => {
|
|
|
400
374
|
avatar: {
|
|
401
375
|
marginBottom: INNER_GAP,
|
|
402
376
|
},
|
|
403
|
-
progressContainer: {
|
|
404
|
-
flexDirection: 'row',
|
|
405
|
-
width: '100%',
|
|
406
|
-
justifyContent: 'center',
|
|
407
|
-
marginTop: 24, // Space for bottom sheet handle (~20px) + small buffer
|
|
408
|
-
marginBottom: 24, // Equal spacing below dots
|
|
409
|
-
},
|
|
410
|
-
progressDot: {
|
|
411
|
-
height: 6,
|
|
412
|
-
width: 6,
|
|
413
|
-
borderRadius: 3,
|
|
414
|
-
marginHorizontal: 3,
|
|
415
|
-
backgroundColor: colors.border,
|
|
416
|
-
},
|
|
417
377
|
});
|
|
418
378
|
};
|
|
419
379
|
|