@multiplayer-app/session-recorder-react-native 0.0.1-alpha.1 → 0.0.1-alpha.3

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 (166) hide show
  1. package/dist/config/constants.d.ts +19 -0
  2. package/dist/config/constants.js +1 -0
  3. package/dist/config/constants.js.map +1 -0
  4. package/dist/config/defaults.d.ts +4 -0
  5. package/dist/config/defaults.js +1 -0
  6. package/dist/config/defaults.js.map +1 -0
  7. package/dist/config/index.d.ts +5 -0
  8. package/dist/config/index.js +1 -0
  9. package/dist/config/index.js.map +1 -0
  10. package/dist/config/masking.d.ts +2 -30
  11. package/dist/config/masking.js +1 -1
  12. package/dist/config/masking.js.map +1 -1
  13. package/dist/config/session-recorder.d.ts +2 -0
  14. package/dist/config/session-recorder.js +1 -0
  15. package/dist/config/session-recorder.js.map +1 -0
  16. package/dist/config/validators.d.ts +10 -0
  17. package/dist/config/validators.js +1 -0
  18. package/dist/config/validators.js.map +1 -0
  19. package/dist/expo.d.ts +2 -2
  20. package/dist/expo.js +1 -1
  21. package/dist/expo.js.map +1 -1
  22. package/dist/exporters.d.ts +3 -0
  23. package/dist/exporters.js +1 -0
  24. package/dist/exporters.js.map +1 -0
  25. package/dist/index.d.ts +3 -9
  26. package/dist/index.js +1 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/otel/helpers.d.ts +45 -3
  29. package/dist/otel/helpers.js +1 -1
  30. package/dist/otel/helpers.js.map +1 -1
  31. package/dist/otel/index.d.ts +9 -5
  32. package/dist/otel/index.js +1 -1
  33. package/dist/otel/index.js.map +1 -1
  34. package/dist/otel/instrumentations/index.js +1 -1
  35. package/dist/otel/instrumentations/index.js.map +1 -1
  36. package/dist/patch/index.d.ts +1 -0
  37. package/dist/patch/index.js +1 -0
  38. package/dist/patch/index.js.map +1 -0
  39. package/dist/patch/xhr.d.ts +2 -0
  40. package/dist/patch/xhr.js +1 -0
  41. package/dist/patch/xhr.js.map +1 -0
  42. package/dist/recorder/gestureRecorder.js +1 -1
  43. package/dist/recorder/gestureRecorder.js.map +1 -1
  44. package/dist/recorder/navigationTracker.js +1 -1
  45. package/dist/recorder/navigationTracker.js.map +1 -1
  46. package/dist/recorder/screenRecorder.d.ts +1 -0
  47. package/dist/recorder/screenRecorder.js +1 -1
  48. package/dist/recorder/screenRecorder.js.map +1 -1
  49. package/dist/services/api.service.d.ts +62 -10
  50. package/dist/services/api.service.js +1 -1
  51. package/dist/services/api.service.js.map +1 -1
  52. package/dist/services/storage.service.d.ts +23 -16
  53. package/dist/services/storage.service.js +1 -1
  54. package/dist/services/storage.service.js.map +1 -1
  55. package/dist/session-recorder.d.ts +131 -0
  56. package/dist/session-recorder.js +1 -0
  57. package/dist/session-recorder.js.map +1 -0
  58. package/dist/sessionRecorder.d.ts +113 -34
  59. package/dist/sessionRecorder.js +1 -1
  60. package/dist/sessionRecorder.js.map +1 -1
  61. package/dist/types/index.d.ts +2 -81
  62. package/dist/types/index.js +1 -1
  63. package/dist/types/index.js.map +1 -1
  64. package/dist/types/session-recorder.d.ts +366 -0
  65. package/dist/types/session-recorder.js +1 -0
  66. package/dist/types/session-recorder.js.map +1 -0
  67. package/dist/types/session.d.ts +59 -0
  68. package/dist/types/session.js +1 -0
  69. package/dist/types/session.js.map +1 -0
  70. package/dist/utils/index.d.ts +5 -0
  71. package/dist/utils/index.js +1 -0
  72. package/dist/utils/index.js.map +1 -0
  73. package/dist/utils/platform.d.ts +5 -0
  74. package/dist/utils/platform.js +1 -1
  75. package/dist/utils/platform.js.map +1 -1
  76. package/dist/utils/request-utils.d.ts +21 -0
  77. package/dist/utils/request-utils.js +1 -0
  78. package/dist/utils/request-utils.js.map +1 -0
  79. package/dist/utils/session.d.ts +5 -0
  80. package/dist/utils/session.js +1 -0
  81. package/dist/utils/session.js.map +1 -0
  82. package/dist/utils/time.d.ts +4 -0
  83. package/dist/utils/time.js +1 -0
  84. package/dist/utils/time.js.map +1 -0
  85. package/dist/utils/type-utils.d.ts +16 -0
  86. package/dist/utils/type-utils.js +1 -0
  87. package/dist/utils/type-utils.js.map +1 -0
  88. package/dist/version.d.ts +1 -1
  89. package/dist/version.js +1 -1
  90. package/package.json +3 -3
  91. package/src/config/constants.ts +60 -0
  92. package/src/config/defaults.ts +82 -0
  93. package/src/config/index.ts +6 -0
  94. package/src/config/masking.ts +10 -61
  95. package/src/config/session-recorder.ts +55 -0
  96. package/src/config/validators.ts +31 -0
  97. package/src/expo.ts +2 -22
  98. package/src/index.ts +3 -15
  99. package/src/otel/helpers.ts +247 -11
  100. package/src/otel/index.ts +109 -76
  101. package/src/otel/instrumentations/index.ts +8 -8
  102. package/src/patch/index.ts +1 -0
  103. package/src/patch/xhr.ts +142 -0
  104. package/src/recorder/gestureRecorder.ts +12 -12
  105. package/src/recorder/navigationTracker.ts +10 -10
  106. package/src/recorder/screenRecorder.ts +26 -26
  107. package/src/services/api.service.ts +169 -37
  108. package/src/services/storage.service.ts +101 -74
  109. package/src/session-recorder.ts +570 -0
  110. package/src/types/index.ts +2 -88
  111. package/src/types/session-recorder.ts +423 -0
  112. package/src/types/session.ts +65 -0
  113. package/src/utils/index.ts +6 -0
  114. package/src/utils/platform.ts +13 -0
  115. package/src/utils/request-utils.ts +61 -0
  116. package/src/utils/session.ts +18 -0
  117. package/src/utils/time.ts +17 -0
  118. package/src/utils/type-utils.ts +75 -0
  119. package/src/version.ts +1 -1
  120. package/examples/sample-expo-app/README.md +0 -142
  121. package/examples/sample-expo-app/app/(tabs)/_layout.tsx +0 -60
  122. package/examples/sample-expo-app/app/(tabs)/explore.tsx +0 -110
  123. package/examples/sample-expo-app/app/(tabs)/index.tsx +0 -125
  124. package/examples/sample-expo-app/app/(tabs)/posts.tsx +0 -96
  125. package/examples/sample-expo-app/app/(tabs)/users.tsx +0 -131
  126. package/examples/sample-expo-app/app/+not-found.tsx +0 -32
  127. package/examples/sample-expo-app/app/_layout.tsx +0 -53
  128. package/examples/sample-expo-app/app/post/[id].tsx +0 -199
  129. package/examples/sample-expo-app/app/user/[id].tsx +0 -270
  130. package/examples/sample-expo-app/app.json +0 -42
  131. package/examples/sample-expo-app/assets/fonts/SpaceMono-Regular.ttf +0 -0
  132. package/examples/sample-expo-app/assets/images/adaptive-icon.png +0 -0
  133. package/examples/sample-expo-app/assets/images/favicon.png +0 -0
  134. package/examples/sample-expo-app/assets/images/icon.png +0 -0
  135. package/examples/sample-expo-app/assets/images/partial-react-logo.png +0 -0
  136. package/examples/sample-expo-app/assets/images/react-logo.png +0 -0
  137. package/examples/sample-expo-app/assets/images/react-logo@2x.png +0 -0
  138. package/examples/sample-expo-app/assets/images/react-logo@3x.png +0 -0
  139. package/examples/sample-expo-app/assets/images/splash-icon.png +0 -0
  140. package/examples/sample-expo-app/components/Collapsible.tsx +0 -45
  141. package/examples/sample-expo-app/components/ErrorView.tsx +0 -52
  142. package/examples/sample-expo-app/components/ExternalLink.tsx +0 -24
  143. package/examples/sample-expo-app/components/HapticTab.tsx +0 -18
  144. package/examples/sample-expo-app/components/HelloWave.tsx +0 -40
  145. package/examples/sample-expo-app/components/LoadingSpinner.tsx +0 -34
  146. package/examples/sample-expo-app/components/ParallaxScrollView.tsx +0 -82
  147. package/examples/sample-expo-app/components/ThemedText.tsx +0 -60
  148. package/examples/sample-expo-app/components/ThemedView.tsx +0 -14
  149. package/examples/sample-expo-app/components/ui/IconSymbol.ios.tsx +0 -32
  150. package/examples/sample-expo-app/components/ui/IconSymbol.tsx +0 -41
  151. package/examples/sample-expo-app/components/ui/TabBarBackground.ios.tsx +0 -19
  152. package/examples/sample-expo-app/components/ui/TabBarBackground.tsx +0 -6
  153. package/examples/sample-expo-app/constants/Colors.ts +0 -26
  154. package/examples/sample-expo-app/eslint.config.js +0 -10
  155. package/examples/sample-expo-app/hooks/useApi.ts +0 -41
  156. package/examples/sample-expo-app/hooks/useColorScheme.ts +0 -1
  157. package/examples/sample-expo-app/hooks/useColorScheme.web.ts +0 -21
  158. package/examples/sample-expo-app/hooks/useThemeColor.ts +0 -21
  159. package/examples/sample-expo-app/metro.config.js +0 -26
  160. package/examples/sample-expo-app/package-lock.json +0 -26296
  161. package/examples/sample-expo-app/package.json +0 -59
  162. package/examples/sample-expo-app/scripts/reset-project.js +0 -112
  163. package/examples/sample-expo-app/services/api.ts +0 -98
  164. package/examples/sample-expo-app/tsconfig.json +0 -17
  165. package/examples/sample-expo-app/utils/navigation.ts +0 -19
  166. package/src/sessionRecorder.ts +0 -367
@@ -1,53 +0,0 @@
1
- import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'
2
- import { useFonts } from 'expo-font'
3
- import { Stack } from 'expo-router'
4
- import { StatusBar } from 'expo-status-bar'
5
- import 'react-native-reanimated'
6
-
7
- import { useColorScheme } from '@/hooks/useColorScheme'
8
- import SessionRecorder from '@multiplayer-app/session-recorder-react-native'
9
-
10
- SessionRecorder.init({
11
- version: '0.0.1',
12
- application: 'multiplayer-web-app',
13
- environment: 'development',
14
- apiKey:
15
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnRlZ3JhdGlvbiI6IjY4NGZlMDljMjA0NmYwYjM0ZjU5ZDNjYyIsIndvcmtzcGFjZSI6IjY4NGMzYmYwYjQ2MGUzMmY3YWJmZjRlMSIsInByb2plY3QiOiI2ODRjM2M0MmI0NjBlMzJmN2FiZmY1YzgiLCJ0eXBlIjoiT1RFTCIsImlhdCI6MTc1MDA2NTMwOH0.F15dW5RUHtq4-e2FUZD_vK0FJ5USs8SRFbnPYO_0XVk',
16
- apiBaseUrl: 'http://localhost',
17
- exporterEndpoint: 'http://localhost/v1/traces',
18
- showWidget: true,
19
- ignoreUrls: [
20
- /posthog\.com.*/,
21
- /https:\/\/bam\.nr-data\.net\/.*/,
22
- /https:\/\/cdn\.jsdelivr\.net\/.*/,
23
- /https:\/\/pixel\.source\.app\/.*/
24
- ]
25
- // propagateTraceHeaderCorsUrls: new RegExp(
26
- // `${process.env.REACT_APP_API_BASE_URL}\.*`,
27
- // "i"
28
- // ),
29
- })
30
-
31
- export default function RootLayout() {
32
- const colorScheme = useColorScheme()
33
- const [loaded] = useFonts({
34
- SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf')
35
- })
36
-
37
- if (!loaded) {
38
- // Async font loading only occurs in development.
39
- return null
40
- }
41
-
42
- return (
43
- <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
44
- <Stack>
45
- <Stack.Screen name='(tabs)' options={{ headerShown: false }} />
46
- <Stack.Screen name='post/[id]' options={{ headerShown: false }} />
47
- <Stack.Screen name='user/[id]' options={{ headerShown: false }} />
48
- <Stack.Screen name='+not-found' />
49
- </Stack>
50
- <StatusBar style='auto' />
51
- </ThemeProvider>
52
- )
53
- }
@@ -1,199 +0,0 @@
1
- import React from 'react'
2
- import { ScrollView, StyleSheet, TouchableOpacity, FlatList } from 'react-native'
3
- import { useLocalSearchParams, router } from 'expo-router'
4
- import { ThemedView } from '@/components/ThemedView'
5
- import { ThemedText } from '@/components/ThemedText'
6
- import { LoadingSpinner } from '@/components/LoadingSpinner'
7
- import { ErrorView } from '@/components/ErrorView'
8
- import { IconSymbol } from '@/components/ui/IconSymbol'
9
- import { useApi } from '@/hooks/useApi'
10
- import { apiService, Comment } from '@/services/api'
11
- import { Colors } from '@/constants/Colors'
12
- import { useColorScheme } from '@/hooks/useColorScheme'
13
-
14
- export default function PostDetailScreen() {
15
- const { id } = useLocalSearchParams<{ id: string }>()
16
- const postId = parseInt(id!)
17
- const colorScheme = useColorScheme()
18
-
19
- const {
20
- data: post,
21
- loading: postLoading,
22
- error: postError,
23
- refetch: refetchPost
24
- } = useApi(() => apiService.getPost(postId), [postId])
25
-
26
- const {
27
- data: comments,
28
- loading: commentsLoading,
29
- error: commentsError,
30
- refetch: refetchComments
31
- } = useApi(() => apiService.getPostComments(postId), [postId])
32
-
33
- const handleUserPress = (userId: number) => {
34
- router.push(`/user/${userId}`)
35
- }
36
-
37
- const renderComment = ({ item }: { item: Comment }) => (
38
- <ThemedView style={styles.commentCard}>
39
- <ThemedView style={styles.commentHeader}>
40
- <ThemedText type='defaultSemiBold' style={styles.commentName}>
41
- {item.name}
42
- </ThemedText>
43
- <ThemedText style={styles.commentEmail}>{item.email}</ThemedText>
44
- </ThemedView>
45
- <ThemedText style={styles.commentBody}>{item.body}</ThemedText>
46
- </ThemedView>
47
- )
48
-
49
- if (postLoading) {
50
- return <LoadingSpinner message='Loading post...' />
51
- }
52
-
53
- if (postError) {
54
- return <ErrorView message={postError} onRetry={refetchPost} />
55
- }
56
-
57
- if (!post) {
58
- return <ErrorView message='Post not found' />
59
- }
60
-
61
- return (
62
- <ThemedView style={styles.container}>
63
- {/* Header */}
64
- <ThemedView style={styles.header}>
65
- <TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
66
- <IconSymbol name='chevron.left' size={24} color={Colors[colorScheme ?? 'light'].tint} />
67
- </TouchableOpacity>
68
- <ThemedText type='title' style={styles.headerTitle}>
69
- Post Details
70
- </ThemedText>
71
- </ThemedView>
72
-
73
- <ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
74
- {/* Post Content */}
75
- <ThemedView style={styles.postCard}>
76
- <ThemedText type='title' style={styles.postTitle}>
77
- {post.title}
78
- </ThemedText>
79
- <ThemedText style={styles.postBody}>{post.body}</ThemedText>
80
-
81
- <TouchableOpacity style={styles.userButton} onPress={() => handleUserPress(post.userId)}>
82
- <IconSymbol name='person.fill' size={16} color={Colors[colorScheme ?? 'light'].tint} />
83
- <ThemedText style={styles.userButtonText}>View User {post.userId}</ThemedText>
84
- <IconSymbol name='chevron.right' size={16} color={Colors[colorScheme ?? 'light'].tint} />
85
- </TouchableOpacity>
86
- </ThemedView>
87
-
88
- {/* Comments Section */}
89
- <ThemedView style={styles.commentsSection}>
90
- <ThemedText type='subtitle' style={styles.commentsTitle}>
91
- Comments ({comments?.length || 0})
92
- </ThemedText>
93
-
94
- {commentsLoading ? (
95
- <LoadingSpinner message='Loading comments...' />
96
- ) : commentsError ? (
97
- <ErrorView message={commentsError} onRetry={refetchComments} />
98
- ) : comments && comments.length > 0 ? (
99
- <FlatList
100
- data={comments}
101
- renderItem={renderComment}
102
- keyExtractor={(item) => item.id.toString()}
103
- scrollEnabled={false}
104
- showsVerticalScrollIndicator={false}
105
- />
106
- ) : (
107
- <ThemedText style={styles.noComments}>No comments yet</ThemedText>
108
- )}
109
- </ThemedView>
110
- </ScrollView>
111
- </ThemedView>
112
- )
113
- }
114
-
115
- const styles = StyleSheet.create({
116
- container: {
117
- flex: 1
118
- },
119
- header: {
120
- flexDirection: 'row',
121
- alignItems: 'center',
122
- padding: 20,
123
- paddingTop: 60,
124
- paddingBottom: 10
125
- },
126
- backButton: {
127
- marginRight: 16
128
- },
129
- headerTitle: {
130
- flex: 1
131
- },
132
- content: {
133
- flex: 1,
134
- padding: 20
135
- },
136
- postCard: {
137
- padding: 20,
138
- marginBottom: 20,
139
- borderRadius: 12,
140
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
141
- borderWidth: 1,
142
- borderColor: 'rgba(255, 255, 255, 0.2)'
143
- },
144
- postTitle: {
145
- marginBottom: 16,
146
- lineHeight: 28
147
- },
148
- postBody: {
149
- marginBottom: 20,
150
- lineHeight: 22
151
- },
152
- userButton: {
153
- flexDirection: 'row',
154
- alignItems: 'center',
155
- padding: 12,
156
- borderRadius: 8,
157
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
158
- borderWidth: 1,
159
- borderColor: 'rgba(255, 255, 255, 0.2)'
160
- },
161
- userButtonText: {
162
- flex: 1,
163
- marginLeft: 8,
164
- marginRight: 8
165
- },
166
- commentsSection: {
167
- marginBottom: 20
168
- },
169
- commentsTitle: {
170
- marginBottom: 16
171
- },
172
- commentCard: {
173
- padding: 16,
174
- marginBottom: 12,
175
- borderRadius: 8,
176
- backgroundColor: 'rgba(255, 255, 255, 0.05)',
177
- borderWidth: 1,
178
- borderColor: 'rgba(255, 255, 255, 0.1)'
179
- },
180
- commentHeader: {
181
- marginBottom: 8
182
- },
183
- commentName: {
184
- marginBottom: 2
185
- },
186
- commentEmail: {
187
- fontSize: 12,
188
- opacity: 0.6
189
- },
190
- commentBody: {
191
- lineHeight: 18
192
- },
193
- noComments: {
194
- textAlign: 'center',
195
- opacity: 0.6,
196
- fontStyle: 'italic',
197
- padding: 20
198
- }
199
- })
@@ -1,270 +0,0 @@
1
- import React from 'react'
2
- import { ScrollView, StyleSheet, TouchableOpacity, FlatList } from 'react-native'
3
- import { useLocalSearchParams, router } from 'expo-router'
4
- import { ThemedView } from '@/components/ThemedView'
5
- import { ThemedText } from '@/components/ThemedText'
6
- import { LoadingSpinner } from '@/components/LoadingSpinner'
7
- import { ErrorView } from '@/components/ErrorView'
8
- import { IconSymbol } from '@/components/ui/IconSymbol'
9
- import { useApi } from '@/hooks/useApi'
10
- import { apiService, Post } from '@/services/api'
11
- import { Colors } from '@/constants/Colors'
12
- import { useColorScheme } from '@/hooks/useColorScheme'
13
-
14
- export default function UserDetailScreen() {
15
- const { id } = useLocalSearchParams<{ id: string }>()
16
- const userId = parseInt(id!)
17
- const colorScheme = useColorScheme()
18
-
19
- const {
20
- data: user,
21
- loading: userLoading,
22
- error: userError,
23
- refetch: refetchUser
24
- } = useApi(() => apiService.getUser(userId), [userId])
25
-
26
- const {
27
- data: posts,
28
- loading: postsLoading,
29
- error: postsError,
30
- refetch: refetchPosts
31
- } = useApi(() => apiService.getUserPosts(userId), [userId])
32
-
33
- const handlePostPress = (post: Post) => {
34
- router.push(`/post/${post.id}`)
35
- }
36
-
37
- const renderPost = ({ item }: { item: Post }) => (
38
- <TouchableOpacity style={styles.postCard} onPress={() => handlePostPress(item)} activeOpacity={0.7}>
39
- <ThemedText type='subtitle' style={styles.postTitle}>
40
- {item.title}
41
- </ThemedText>
42
- <ThemedText style={styles.postBody} numberOfLines={2}>
43
- {item.body}
44
- </ThemedText>
45
- <ThemedText style={styles.postMeta}>Post ID: {item.id}</ThemedText>
46
- </TouchableOpacity>
47
- )
48
-
49
- if (userLoading) {
50
- return <LoadingSpinner message='Loading user...' />
51
- }
52
-
53
- if (userError) {
54
- return <ErrorView message={userError} onRetry={refetchUser} />
55
- }
56
-
57
- if (!user) {
58
- return <ErrorView message='User not found' />
59
- }
60
-
61
- return (
62
- <ThemedView style={styles.container}>
63
- {/* Header */}
64
- <ThemedView style={styles.header}>
65
- <TouchableOpacity style={styles.backButton} onPress={() => router.back()}>
66
- <IconSymbol name='chevron.left' size={24} color={Colors[colorScheme ?? 'light'].tint} />
67
- </TouchableOpacity>
68
- <ThemedText type='title' style={styles.headerTitle}>
69
- User Profile
70
- </ThemedText>
71
- </ThemedView>
72
-
73
- <ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
74
- {/* User Info Card */}
75
- <ThemedView style={styles.userCard}>
76
- <ThemedView style={styles.userAvatar}>
77
- <IconSymbol name='person.fill' size={32} color={Colors[colorScheme ?? 'light'].tint} />
78
- </ThemedView>
79
-
80
- <ThemedText type='title' style={styles.userName}>
81
- {user.name}
82
- </ThemedText>
83
- <ThemedText style={styles.userUsername}>@{user.username}</ThemedText>
84
-
85
- <ThemedView style={styles.userDetails}>
86
- <ThemedView style={styles.detailRow}>
87
- <IconSymbol name='envelope.fill' size={16} color={Colors[colorScheme ?? 'light'].tint} />
88
- <ThemedText style={styles.detailText}>{user.email}</ThemedText>
89
- </ThemedView>
90
-
91
- <ThemedView style={styles.detailRow}>
92
- <IconSymbol name='phone.fill' size={16} color={Colors[colorScheme ?? 'light'].tint} />
93
- <ThemedText style={styles.detailText}>{user.phone}</ThemedText>
94
- </ThemedView>
95
-
96
- <ThemedView style={styles.detailRow}>
97
- <IconSymbol name='globe' size={16} color={Colors[colorScheme ?? 'light'].tint} />
98
- <ThemedText style={styles.detailText}>{user.website}</ThemedText>
99
- </ThemedView>
100
- </ThemedView>
101
-
102
- <ThemedView style={styles.companySection}>
103
- <ThemedText type='subtitle' style={styles.sectionTitle}>
104
- Company
105
- </ThemedText>
106
- <ThemedText style={styles.companyName}>{user.company.name}</ThemedText>
107
- <ThemedText style={styles.companyCatchPhrase}>&ldquo;{user.company.catchPhrase}&rdquo;</ThemedText>
108
- </ThemedView>
109
-
110
- <ThemedView style={styles.addressSection}>
111
- <ThemedText type='subtitle' style={styles.sectionTitle}>
112
- Address
113
- </ThemedText>
114
- <ThemedText style={styles.addressText}>
115
- {user.address.street}, {user.address.suite}
116
- </ThemedText>
117
- <ThemedText style={styles.addressText}>
118
- {user.address.city}, {user.address.zipcode}
119
- </ThemedText>
120
- </ThemedView>
121
- </ThemedView>
122
-
123
- {/* Posts Section */}
124
- <ThemedView style={styles.postsSection}>
125
- <ThemedText type='subtitle' style={styles.postsTitle}>
126
- Posts ({posts?.length || 0})
127
- </ThemedText>
128
-
129
- {postsLoading ? (
130
- <LoadingSpinner message='Loading posts...' />
131
- ) : postsError ? (
132
- <ErrorView message={postsError} onRetry={refetchPosts} />
133
- ) : posts && posts.length > 0 ? (
134
- <FlatList
135
- data={posts}
136
- renderItem={renderPost}
137
- keyExtractor={(item) => item.id.toString()}
138
- scrollEnabled={false}
139
- showsVerticalScrollIndicator={false}
140
- />
141
- ) : (
142
- <ThemedText style={styles.noPosts}>No posts yet</ThemedText>
143
- )}
144
- </ThemedView>
145
- </ScrollView>
146
- </ThemedView>
147
- )
148
- }
149
-
150
- const styles = StyleSheet.create({
151
- container: {
152
- flex: 1
153
- },
154
- header: {
155
- flexDirection: 'row',
156
- alignItems: 'center',
157
- padding: 20,
158
- paddingTop: 60,
159
- paddingBottom: 10
160
- },
161
- backButton: {
162
- marginRight: 16
163
- },
164
- headerTitle: {
165
- flex: 1
166
- },
167
- content: {
168
- flex: 1,
169
- padding: 20
170
- },
171
- userCard: {
172
- padding: 20,
173
- marginBottom: 20,
174
- borderRadius: 12,
175
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
176
- borderWidth: 1,
177
- borderColor: 'rgba(255, 255, 255, 0.2)',
178
- alignItems: 'center'
179
- },
180
- userAvatar: {
181
- width: 80,
182
- height: 80,
183
- borderRadius: 40,
184
- backgroundColor: 'rgba(255, 255, 255, 0.2)',
185
- justifyContent: 'center',
186
- alignItems: 'center',
187
- marginBottom: 16
188
- },
189
- userName: {
190
- marginBottom: 4,
191
- textAlign: 'center'
192
- },
193
- userUsername: {
194
- fontSize: 16,
195
- opacity: 0.7,
196
- marginBottom: 20
197
- },
198
- userDetails: {
199
- width: '100%',
200
- marginBottom: 20
201
- },
202
- detailRow: {
203
- flexDirection: 'row',
204
- alignItems: 'center',
205
- marginBottom: 8
206
- },
207
- detailText: {
208
- marginLeft: 8,
209
- flex: 1
210
- },
211
- companySection: {
212
- width: '100%',
213
- marginBottom: 16,
214
- padding: 16,
215
- borderRadius: 8,
216
- backgroundColor: 'rgba(255, 255, 255, 0.05)'
217
- },
218
- sectionTitle: {
219
- marginBottom: 8
220
- },
221
- companyName: {
222
- marginBottom: 4,
223
- fontWeight: '600'
224
- },
225
- companyCatchPhrase: {
226
- fontSize: 14,
227
- opacity: 0.8,
228
- fontStyle: 'italic'
229
- },
230
- addressSection: {
231
- width: '100%',
232
- padding: 16,
233
- borderRadius: 8,
234
- backgroundColor: 'rgba(255, 255, 255, 0.05)'
235
- },
236
- addressText: {
237
- marginBottom: 2
238
- },
239
- postsSection: {
240
- marginBottom: 20
241
- },
242
- postsTitle: {
243
- marginBottom: 16
244
- },
245
- postCard: {
246
- padding: 16,
247
- marginBottom: 12,
248
- borderRadius: 8,
249
- backgroundColor: 'rgba(255, 255, 255, 0.05)',
250
- borderWidth: 1,
251
- borderColor: 'rgba(255, 255, 255, 0.1)'
252
- },
253
- postTitle: {
254
- marginBottom: 8
255
- },
256
- postBody: {
257
- marginBottom: 8,
258
- lineHeight: 18
259
- },
260
- postMeta: {
261
- fontSize: 12,
262
- opacity: 0.6
263
- },
264
- noPosts: {
265
- textAlign: 'center',
266
- opacity: 0.6,
267
- fontStyle: 'italic',
268
- padding: 20
269
- }
270
- })
@@ -1,42 +0,0 @@
1
- {
2
- "expo": {
3
- "name": "sample-expo-app",
4
- "slug": "sample-expo-app",
5
- "version": "1.0.0",
6
- "orientation": "portrait",
7
- "icon": "./assets/images/icon.png",
8
- "scheme": "sampleexpoapp",
9
- "userInterfaceStyle": "automatic",
10
- "newArchEnabled": true,
11
- "ios": {
12
- "supportsTablet": true
13
- },
14
- "android": {
15
- "adaptiveIcon": {
16
- "foregroundImage": "./assets/images/adaptive-icon.png",
17
- "backgroundColor": "#ffffff"
18
- },
19
- "edgeToEdgeEnabled": true
20
- },
21
- "web": {
22
- "bundler": "metro",
23
- "output": "static",
24
- "favicon": "./assets/images/favicon.png"
25
- },
26
- "plugins": [
27
- "expo-router",
28
- [
29
- "expo-splash-screen",
30
- {
31
- "image": "./assets/images/splash-icon.png",
32
- "imageWidth": 200,
33
- "resizeMode": "contain",
34
- "backgroundColor": "#ffffff"
35
- }
36
- ]
37
- ],
38
- "experiments": {
39
- "typedRoutes": true
40
- }
41
- }
42
- }
@@ -1,45 +0,0 @@
1
- import { PropsWithChildren, useState } from 'react';
2
- import { StyleSheet, TouchableOpacity } from 'react-native';
3
-
4
- import { ThemedText } from '@/components/ThemedText';
5
- import { ThemedView } from '@/components/ThemedView';
6
- import { IconSymbol } from '@/components/ui/IconSymbol';
7
- import { Colors } from '@/constants/Colors';
8
- import { useColorScheme } from '@/hooks/useColorScheme';
9
-
10
- export function Collapsible({ children, title }: PropsWithChildren & { title: string }) {
11
- const [isOpen, setIsOpen] = useState(false);
12
- const theme = useColorScheme() ?? 'light';
13
-
14
- return (
15
- <ThemedView>
16
- <TouchableOpacity
17
- style={styles.heading}
18
- onPress={() => setIsOpen((value) => !value)}
19
- activeOpacity={0.8}>
20
- <IconSymbol
21
- name="chevron.right"
22
- size={18}
23
- weight="medium"
24
- color={theme === 'light' ? Colors.light.icon : Colors.dark.icon}
25
- style={{ transform: [{ rotate: isOpen ? '90deg' : '0deg' }] }}
26
- />
27
-
28
- <ThemedText type="defaultSemiBold">{title}</ThemedText>
29
- </TouchableOpacity>
30
- {isOpen && <ThemedView style={styles.content}>{children}</ThemedView>}
31
- </ThemedView>
32
- );
33
- }
34
-
35
- const styles = StyleSheet.create({
36
- heading: {
37
- flexDirection: 'row',
38
- alignItems: 'center',
39
- gap: 6,
40
- },
41
- content: {
42
- marginTop: 6,
43
- marginLeft: 24,
44
- },
45
- });
@@ -1,52 +0,0 @@
1
- import React from 'react'
2
- import { StyleSheet, TouchableOpacity } from 'react-native'
3
- import { ThemedView } from './ThemedView'
4
- import { ThemedText } from './ThemedText'
5
- import { IconSymbol } from './ui/IconSymbol'
6
- import { Colors } from '@/constants/Colors'
7
- import { useColorScheme } from '@/hooks/useColorScheme'
8
-
9
- interface ErrorViewProps {
10
- message: string
11
- onRetry?: () => void
12
- }
13
-
14
- export function ErrorView({ message, onRetry }: ErrorViewProps) {
15
- const colorScheme = useColorScheme()
16
-
17
- return (
18
- <ThemedView style={styles.container}>
19
- <IconSymbol name='exclamationmark.triangle.fill' size={48} color={Colors[colorScheme ?? 'light'].tint} />
20
- <ThemedText style={styles.message}>{message}</ThemedText>
21
- {onRetry && (
22
- <TouchableOpacity style={styles.retryButton} onPress={onRetry}>
23
- <ThemedText style={styles.retryText}>Try Again</ThemedText>
24
- </TouchableOpacity>
25
- )}
26
- </ThemedView>
27
- )
28
- }
29
-
30
- const styles = StyleSheet.create({
31
- container: {
32
- flex: 1,
33
- justifyContent: 'center',
34
- alignItems: 'center',
35
- padding: 20
36
- },
37
- message: {
38
- marginTop: 16,
39
- textAlign: 'center',
40
- marginBottom: 20
41
- },
42
- retryButton: {
43
- backgroundColor: Colors.light.tint,
44
- paddingHorizontal: 20,
45
- paddingVertical: 10,
46
- borderRadius: 8
47
- },
48
- retryText: {
49
- color: 'white',
50
- fontWeight: '600'
51
- }
52
- })