@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.
Files changed (68) hide show
  1. package/lib/commonjs/ui/hooks/useSurfaceHeader.js +8 -6
  2. package/lib/commonjs/ui/hooks/useSurfaceHeader.js.map +1 -1
  3. package/lib/commonjs/ui/navigation/surfaceRegistry.js +11 -37
  4. package/lib/commonjs/ui/navigation/surfaceRegistry.js.map +1 -1
  5. package/lib/commonjs/ui/screens/AvatarCropScreen.js +9 -13
  6. package/lib/commonjs/ui/screens/AvatarCropScreen.js.map +1 -1
  7. package/lib/commonjs/ui/screens/FileManagementScreen.js +66 -18
  8. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  9. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js +11 -0
  10. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
  11. package/lib/commonjs/ui/screens/ProfileScreen.js +91 -117
  12. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  13. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +22 -54
  14. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  15. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +38 -154
  16. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  17. package/lib/module/ui/hooks/useSurfaceHeader.js +8 -6
  18. package/lib/module/ui/hooks/useSurfaceHeader.js.map +1 -1
  19. package/lib/module/ui/navigation/surfaceRegistry.js +11 -37
  20. package/lib/module/ui/navigation/surfaceRegistry.js.map +1 -1
  21. package/lib/module/ui/screens/AvatarCropScreen.js +9 -13
  22. package/lib/module/ui/screens/AvatarCropScreen.js.map +1 -1
  23. package/lib/module/ui/screens/FileManagementScreen.js +66 -18
  24. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  25. package/lib/module/ui/screens/PaymentGatewayScreen.js +11 -0
  26. package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
  27. package/lib/module/ui/screens/ProfileScreen.js +93 -119
  28. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  29. package/lib/module/ui/screens/WelcomeNewUserScreen.js +23 -54
  30. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  31. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +38 -154
  32. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  33. package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts +7 -5
  34. package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts +8 -9
  36. package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts +1 -1
  39. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts +2 -1
  43. package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts +0 -6
  45. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  46. package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts +7 -5
  47. package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
  48. package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts +8 -9
  49. package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts.map +1 -1
  50. package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts.map +1 -1
  51. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts +1 -1
  52. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
  53. package/lib/typescript/module/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
  54. package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
  55. package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts +2 -1
  56. package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  57. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts +0 -6
  58. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  59. package/package.json +4 -4
  60. package/src/ui/hooks/useSurfaceHeader.ts +21 -16
  61. package/src/ui/navigation/__tests__/getSurfaceConfig.test.ts +16 -2
  62. package/src/ui/navigation/surfaceRegistry.ts +14 -42
  63. package/src/ui/screens/AvatarCropScreen.tsx +14 -23
  64. package/src/ui/screens/FileManagementScreen.tsx +76 -29
  65. package/src/ui/screens/PaymentGatewayScreen.tsx +11 -0
  66. package/src/ui/screens/ProfileScreen.tsx +96 -111
  67. package/src/ui/screens/WelcomeNewUserScreen.tsx +23 -63
  68. package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +39 -171
@@ -1,6 +1,7 @@
1
1
  import type React from 'react';
2
- import { useEffect, useState } from 'react';
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
- useEffect(() => {
65
- if (!userId) {
66
- setError('No user ID provided');
67
- setIsLoading(false);
68
- return;
69
- }
70
-
71
- setIsLoading(true);
72
- setError(null);
73
-
74
- // Load user profile, reputation total, and stats
75
- Promise.all([
76
- oxyServices.getUserById(userId).catch((err: unknown) => {
77
- // If this is the current user and the API call fails, use current user data as fallback
78
- const normalizedCurrentId = normalizeId(currentUser?.id);
79
- const normalizedTargetId = normalizeId(userId);
80
- if (normalizedCurrentId && normalizedTargetId && normalizedCurrentId === normalizedTargetId) {
81
- return currentUser;
82
- }
83
- throw err;
84
- }),
85
- oxyServices.getReputationBalance(userId)
86
- .then((balance): { total: number | undefined } => ({ total: balance.total }))
87
- .catch((): { total: number | undefined } => ({ total: undefined })),
88
- oxyServices.getUserStats ?
89
- oxyServices.getUserStats(userId).catch(() => {
90
- return { postCount: 0, commentCount: 0 };
91
- }) :
92
- Promise.resolve({ postCount: 0, commentCount: 0 })
93
- ])
94
- .then(([profileRes, reputationRes, statsRes]) => {
95
- if (!profileRes) {
96
- setError('Profile data is not available');
97
- setIsLoading(false);
98
- return;
99
- }
100
-
101
- setProfile(profileRes);
102
- setReputationTotal(typeof reputationRes.total === 'number' ? reputationRes.total : null);
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
- // Follower/following counts are managed by the `useFollow` hook.
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
- // User stats from API
110
- setPostsCount(statsRes?.postCount ?? 0);
111
- setCommentsCount(statsRes?.commentCount ?? 0);
112
- })
113
- .catch((err: unknown) => {
114
- logger.error('Profile loading error', err instanceof Error ? err : new Error(String(err)), { component: 'ProfileScreen' });
115
- // Provide user-friendly error messages based on the error type
116
- let errorMessage = 'Failed to load profile';
117
-
118
- // Type guard for error with status property
119
- const errorWithStatus = err && typeof err === 'object' && 'status' in err ? err as { status?: number; message?: string } : null;
120
- const errorMessageText = extractErrorMessage(err, '');
121
-
122
- if (errorWithStatus?.status === 404 || errorMessageText.includes('not found') || errorMessageText.includes('Resource not found')) {
123
- const normalizedCurrentId = normalizeId(currentUser?.id);
124
- const normalizedTargetId = normalizeId(userId);
125
- if (normalizedCurrentId && normalizedTargetId && normalizedCurrentId === normalizedTargetId) {
126
- errorMessage = 'Unable to load your profile from the server. This may be due to a temporary service issue.';
127
- } else {
128
- errorMessage = 'This user profile could not be found or may have been removed.';
129
- }
130
- } else if (errorWithStatus?.status === 403) {
131
- errorMessage = 'You do not have permission to view this profile.';
132
- } else if (errorWithStatus?.status === 500) {
133
- errorMessage = 'Server error occurred while loading the profile. Please try again later.';
134
- } else if (errorMessageText) {
135
- errorMessage = errorMessageText;
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
- setError(errorMessage);
139
- })
140
- .finally(() => setIsLoading(false));
141
- }, [userId, currentUser?.id, oxyServices]);
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
- <View style={styles.errorHeader} className="px-screen-margin py-space-12 border-b border-border">
155
- {goBack && (
156
- <Button
157
- variant="icon"
158
- size="icon"
159
- onPress={goBack}
160
- accessibilityLabel={t('common.back') || 'Back'}
161
- icon={<Ionicons name="arrow-back" size={22} color={bloomTheme.colors.text} />}
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, useEffect } from 'react';
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?: any }> = ({
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(bloomTheme.colors), [bloomTheme.colors]);
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
- <View style={styles.progressContainer}>
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 = (colors: ReturnType<typeof useTheme>['colors']) => {
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