@oxyhq/services 5.13.3 → 5.13.10

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 (156) hide show
  1. package/lib/commonjs/core/HttpClient.js +1 -1
  2. package/lib/commonjs/core/HttpClient.js.map +1 -1
  3. package/lib/commonjs/core/OxyServices.js +82 -8
  4. package/lib/commonjs/core/OxyServices.js.map +1 -1
  5. package/lib/commonjs/i18n/locales/en-US.json +222 -6
  6. package/lib/commonjs/lib/sonner.js.map +1 -1
  7. package/lib/commonjs/ui/components/GroupedItem.js +24 -22
  8. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  9. package/lib/commonjs/ui/components/OxyProvider.js +35 -14
  10. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  11. package/lib/commonjs/ui/navigation/routes.js +36 -1
  12. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  13. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +150 -5
  14. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  15. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +475 -319
  16. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  17. package/lib/commonjs/ui/screens/AccountVerificationScreen.js +217 -0
  18. package/lib/commonjs/ui/screens/AccountVerificationScreen.js.map +1 -0
  19. package/lib/commonjs/ui/screens/FileManagementScreen.js +911 -213
  20. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  21. package/lib/commonjs/ui/screens/HelpSupportScreen.js +131 -0
  22. package/lib/commonjs/ui/screens/HelpSupportScreen.js.map +1 -0
  23. package/lib/commonjs/ui/screens/HistoryViewScreen.js +258 -0
  24. package/lib/commonjs/ui/screens/HistoryViewScreen.js.map +1 -0
  25. package/lib/commonjs/ui/screens/LegalDocumentsScreen.js +211 -0
  26. package/lib/commonjs/ui/screens/LegalDocumentsScreen.js.map +1 -0
  27. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +0 -1
  28. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  29. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +307 -0
  30. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -0
  31. package/lib/commonjs/ui/screens/ProfileScreen.js +1 -7
  32. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  33. package/lib/commonjs/ui/screens/SavesCollectionsScreen.js +205 -0
  34. package/lib/commonjs/ui/screens/SavesCollectionsScreen.js.map +1 -0
  35. package/lib/commonjs/ui/screens/SearchSettingsScreen.js +239 -0
  36. package/lib/commonjs/ui/screens/SearchSettingsScreen.js.map +1 -0
  37. package/lib/commonjs/ui/screens/SignInScreen.js +14 -29
  38. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  39. package/lib/commonjs/utils/asyncUtils.js +1 -0
  40. package/lib/commonjs/utils/asyncUtils.js.map +1 -1
  41. package/lib/commonjs/utils/cache.js +4 -4
  42. package/lib/commonjs/utils/cache.js.map +1 -1
  43. package/lib/commonjs/utils/index.js +0 -6
  44. package/lib/commonjs/utils/index.js.map +1 -1
  45. package/lib/module/core/HttpClient.js +1 -1
  46. package/lib/module/core/HttpClient.js.map +1 -1
  47. package/lib/module/core/OxyServices.js +82 -8
  48. package/lib/module/core/OxyServices.js.map +1 -1
  49. package/lib/module/i18n/locales/en-US.json +222 -6
  50. package/lib/module/lib/sonner.js.map +1 -1
  51. package/lib/module/ui/components/GroupedItem.js +24 -22
  52. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  53. package/lib/module/ui/components/OxyProvider.js +40 -17
  54. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  55. package/lib/module/ui/navigation/routes.js +36 -1
  56. package/lib/module/ui/navigation/routes.js.map +1 -1
  57. package/lib/module/ui/screens/AccountOverviewScreen.js +151 -6
  58. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  59. package/lib/module/ui/screens/AccountSettingsScreen.js +475 -319
  60. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  61. package/lib/module/ui/screens/AccountVerificationScreen.js +212 -0
  62. package/lib/module/ui/screens/AccountVerificationScreen.js.map +1 -0
  63. package/lib/module/ui/screens/FileManagementScreen.js +913 -212
  64. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  65. package/lib/module/ui/screens/HelpSupportScreen.js +126 -0
  66. package/lib/module/ui/screens/HelpSupportScreen.js.map +1 -0
  67. package/lib/module/ui/screens/HistoryViewScreen.js +253 -0
  68. package/lib/module/ui/screens/HistoryViewScreen.js.map +1 -0
  69. package/lib/module/ui/screens/LegalDocumentsScreen.js +206 -0
  70. package/lib/module/ui/screens/LegalDocumentsScreen.js.map +1 -0
  71. package/lib/module/ui/screens/PremiumSubscriptionScreen.js +0 -1
  72. package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  73. package/lib/module/ui/screens/PrivacySettingsScreen.js +302 -0
  74. package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -0
  75. package/lib/module/ui/screens/ProfileScreen.js +1 -7
  76. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  77. package/lib/module/ui/screens/SavesCollectionsScreen.js +200 -0
  78. package/lib/module/ui/screens/SavesCollectionsScreen.js.map +1 -0
  79. package/lib/module/ui/screens/SearchSettingsScreen.js +234 -0
  80. package/lib/module/ui/screens/SearchSettingsScreen.js.map +1 -0
  81. package/lib/module/ui/screens/SignInScreen.js +14 -29
  82. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  83. package/lib/module/utils/asyncUtils.js +1 -0
  84. package/lib/module/utils/asyncUtils.js.map +1 -1
  85. package/lib/module/utils/cache.js +3 -3
  86. package/lib/module/utils/cache.js.map +1 -1
  87. package/lib/module/utils/index.js +1 -1
  88. package/lib/module/utils/index.js.map +1 -1
  89. package/lib/typescript/core/OxyServices.d.ts +30 -6
  90. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  91. package/lib/typescript/lib/sonner.d.ts +1 -0
  92. package/lib/typescript/lib/sonner.d.ts.map +1 -1
  93. package/lib/typescript/types/expo-document-picker.d.ts +36 -0
  94. package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
  95. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  96. package/lib/typescript/ui/navigation/routes.d.ts +1 -1
  97. package/lib/typescript/ui/navigation/routes.d.ts.map +1 -1
  98. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  99. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  100. package/lib/typescript/ui/screens/AccountVerificationScreen.d.ts +5 -0
  101. package/lib/typescript/ui/screens/AccountVerificationScreen.d.ts.map +1 -0
  102. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  103. package/lib/typescript/ui/screens/HelpSupportScreen.d.ts +5 -0
  104. package/lib/typescript/ui/screens/HelpSupportScreen.d.ts.map +1 -0
  105. package/lib/typescript/ui/screens/HistoryViewScreen.d.ts +5 -0
  106. package/lib/typescript/ui/screens/HistoryViewScreen.d.ts.map +1 -0
  107. package/lib/typescript/ui/screens/LegalDocumentsScreen.d.ts +5 -0
  108. package/lib/typescript/ui/screens/LegalDocumentsScreen.d.ts.map +1 -0
  109. package/lib/typescript/ui/screens/PremiumSubscriptionScreen.d.ts.map +1 -1
  110. package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts +5 -0
  111. package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts.map +1 -0
  112. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
  113. package/lib/typescript/ui/screens/SavesCollectionsScreen.d.ts +5 -0
  114. package/lib/typescript/ui/screens/SavesCollectionsScreen.d.ts.map +1 -0
  115. package/lib/typescript/ui/screens/SearchSettingsScreen.d.ts +5 -0
  116. package/lib/typescript/ui/screens/SearchSettingsScreen.d.ts.map +1 -0
  117. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  118. package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
  119. package/lib/typescript/utils/cache.d.ts +3 -3
  120. package/lib/typescript/utils/cache.d.ts.map +1 -1
  121. package/lib/typescript/utils/index.d.ts +1 -1
  122. package/lib/typescript/utils/index.d.ts.map +1 -1
  123. package/package.json +1 -1
  124. package/src/core/HttpClient.ts +1 -1
  125. package/src/core/OxyServices.ts +80 -8
  126. package/src/i18n/locales/en-US.json +222 -6
  127. package/src/lib/sonner.ts +1 -0
  128. package/src/types/expo-document-picker.d.ts +36 -0
  129. package/src/ui/components/GroupedItem.tsx +23 -21
  130. package/src/ui/components/OxyProvider.tsx +33 -11
  131. package/src/ui/navigation/routes.ts +42 -0
  132. package/src/ui/screens/AccountOverviewScreen.tsx +175 -5
  133. package/src/ui/screens/AccountSettingsScreen.tsx +521 -360
  134. package/src/ui/screens/AccountVerificationScreen.tsx +235 -0
  135. package/src/ui/screens/FileManagementScreen.tsx +934 -208
  136. package/src/ui/screens/HelpSupportScreen.tsx +143 -0
  137. package/src/ui/screens/HistoryViewScreen.tsx +280 -0
  138. package/src/ui/screens/LegalDocumentsScreen.tsx +220 -0
  139. package/src/ui/screens/PremiumSubscriptionScreen.tsx +0 -1
  140. package/src/ui/screens/PrivacySettingsScreen.tsx +332 -0
  141. package/src/ui/screens/ProfileScreen.tsx +1 -8
  142. package/src/ui/screens/SavesCollectionsScreen.tsx +222 -0
  143. package/src/ui/screens/SearchSettingsScreen.tsx +219 -0
  144. package/src/ui/screens/SignInScreen.tsx +19 -35
  145. package/src/utils/asyncUtils.ts +1 -0
  146. package/src/utils/cache.ts +3 -3
  147. package/src/utils/index.ts +1 -1
  148. package/lib/commonjs/ui/components/StepBasedScreen.README.md +0 -337
  149. package/lib/commonjs/ui/components/internal/TextField.md +0 -436
  150. package/lib/commonjs/ui/styles/FONTS.md +0 -126
  151. package/lib/module/ui/components/StepBasedScreen.README.md +0 -337
  152. package/lib/module/ui/components/internal/TextField.md +0 -436
  153. package/lib/module/ui/styles/FONTS.md +0 -126
  154. package/src/ui/components/StepBasedScreen.README.md +0 -337
  155. package/src/ui/components/internal/TextField.md +0 -436
  156. package/src/ui/styles/FONTS.md +0 -126
@@ -0,0 +1,143 @@
1
+ import React, { useMemo } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ StyleSheet,
7
+ ScrollView,
8
+ Linking,
9
+ } from 'react-native';
10
+ import type { BaseScreenProps } from '../navigation/types';
11
+ import { toast } from '../../lib/sonner';
12
+ import { Header, Section, GroupedSection } from '../components';
13
+ import { useI18n } from '../hooks/useI18n';
14
+
15
+ const HelpSupportScreen: React.FC<BaseScreenProps> = ({
16
+ onClose,
17
+ theme,
18
+ goBack,
19
+ }) => {
20
+ const { t } = useI18n();
21
+
22
+ const handleContactSupport = useMemo(() => () => {
23
+ // In a real implementation, this would open a contact form or email
24
+ Linking.openURL('mailto:support@oxy.so?subject=Support Request').catch(() => {
25
+ toast.error(t('help.contactError') || 'Failed to open email client');
26
+ });
27
+ }, [t]);
28
+
29
+ const handleFAQ = useMemo(() => () => {
30
+ // In a real implementation, this would navigate to FAQ screen
31
+ toast.info(t('help.faqComing') || 'FAQ coming soon');
32
+ }, [t]);
33
+
34
+ const handleReportBug = useMemo(() => () => {
35
+ // In a real implementation, this would open a bug report form
36
+ Linking.openURL('mailto:bugs@oxy.so?subject=Bug Report').catch(() => {
37
+ toast.error(t('help.reportError') || 'Failed to open email client');
38
+ });
39
+ }, [t]);
40
+
41
+ const themeStyles = useMemo(() => {
42
+ const isDarkTheme = theme === 'dark';
43
+ return {
44
+ textColor: isDarkTheme ? '#FFFFFF' : '#000000',
45
+ backgroundColor: isDarkTheme ? '#121212' : '#FFFFFF',
46
+ secondaryBackgroundColor: isDarkTheme ? '#222222' : '#F5F5F5',
47
+ borderColor: isDarkTheme ? '#444444' : '#E0E0E0',
48
+ };
49
+ }, [theme]);
50
+
51
+ return (
52
+ <View style={[styles.container, { backgroundColor: themeStyles.backgroundColor }]}>
53
+ <Header
54
+ title={t('help.title') || 'Help & Support'}
55
+ theme={theme}
56
+ onBack={goBack || onClose}
57
+ variant="minimal"
58
+ elevation="subtle"
59
+ />
60
+
61
+ <ScrollView style={styles.content}>
62
+ {/* Help Options */}
63
+ <Section title={t('help.options') || 'Get Help'} theme={theme} isFirst={true}>
64
+ <GroupedSection
65
+ items={[
66
+ {
67
+ id: 'faq',
68
+ icon: 'help-circle',
69
+ iconColor: '#007AFF',
70
+ title: t('help.faq.title') || 'Frequently Asked Questions',
71
+ subtitle: t('help.faq.subtitle') || 'Find answers to common questions',
72
+ onPress: handleFAQ,
73
+ },
74
+ {
75
+ id: 'contact',
76
+ icon: 'mail',
77
+ iconColor: '#32D74B',
78
+ title: t('help.contact.title') || 'Contact Support',
79
+ subtitle: t('help.contact.subtitle') || 'Get help from our support team',
80
+ onPress: handleContactSupport,
81
+ },
82
+ {
83
+ id: 'report-bug',
84
+ icon: 'bug',
85
+ iconColor: '#FF9500',
86
+ title: t('help.reportBug.title') || 'Report a Bug',
87
+ subtitle: t('help.reportBug.subtitle') || 'Help us improve by reporting issues',
88
+ onPress: handleReportBug,
89
+ },
90
+ ]}
91
+ theme={theme}
92
+ />
93
+ </Section>
94
+
95
+ {/* Resources */}
96
+ <Section title={t('help.resources') || 'Resources'} theme={theme}>
97
+ <GroupedSection
98
+ items={[
99
+ {
100
+ id: 'documentation',
101
+ icon: 'document-text',
102
+ iconColor: '#8E8E93',
103
+ title: t('help.documentation.title') || 'Documentation',
104
+ subtitle: t('help.documentation.subtitle') || 'User guides and tutorials',
105
+ onPress: () => {
106
+ Linking.openURL('https://docs.oxy.so').catch(() => {
107
+ toast.error(t('help.linkError') || 'Failed to open link');
108
+ });
109
+ },
110
+ },
111
+ {
112
+ id: 'community',
113
+ icon: 'people',
114
+ iconColor: '#5856D6',
115
+ title: t('help.community.title') || 'Community',
116
+ subtitle: t('help.community.subtitle') || 'Join our community',
117
+ onPress: () => {
118
+ Linking.openURL('https://community.oxy.so').catch(() => {
119
+ toast.error(t('help.linkError') || 'Failed to open link');
120
+ });
121
+ },
122
+ },
123
+ ]}
124
+ theme={theme}
125
+ />
126
+ </Section>
127
+ </ScrollView>
128
+ </View>
129
+ );
130
+ };
131
+
132
+ const styles = StyleSheet.create({
133
+ container: {
134
+ flex: 1,
135
+ },
136
+ content: {
137
+ flex: 1,
138
+ padding: 16,
139
+ },
140
+ });
141
+
142
+ export default React.memo(HelpSupportScreen);
143
+
@@ -0,0 +1,280 @@
1
+ import React, { useState, useCallback, useMemo } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ StyleSheet,
7
+ ScrollView,
8
+ ActivityIndicator,
9
+ } from 'react-native';
10
+ import type { BaseScreenProps } from '../navigation/types';
11
+ import { useOxy } from '../context/OxyContext';
12
+ import { toast } from '../../lib/sonner';
13
+ import { confirmAction } from '../utils/confirmAction';
14
+ import { Header, Section, GroupedSection } from '../components';
15
+ import { useI18n } from '../hooks/useI18n';
16
+
17
+ interface HistoryItem {
18
+ id: string;
19
+ query: string;
20
+ type: 'search' | 'browse';
21
+ timestamp: Date;
22
+ }
23
+
24
+ const HistoryViewScreen: React.FC<BaseScreenProps> = ({
25
+ onClose,
26
+ theme,
27
+ goBack,
28
+ }) => {
29
+ const { user } = useOxy();
30
+ const { t } = useI18n();
31
+ const [history, setHistory] = useState<HistoryItem[]>([]);
32
+ const [isLoading, setIsLoading] = useState(true);
33
+ const [isDeleting, setIsDeleting] = useState(false);
34
+
35
+ // Helper to get storage
36
+ const getStorage = async () => {
37
+ const isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
38
+
39
+ if (isReactNative) {
40
+ try {
41
+ const asyncStorageModule = await import('@react-native-async-storage/async-storage');
42
+ const storage = (asyncStorageModule.default as unknown) as any;
43
+ return {
44
+ getItem: storage.getItem.bind(storage),
45
+ setItem: storage.setItem.bind(storage),
46
+ removeItem: storage.removeItem.bind(storage),
47
+ };
48
+ } catch (error) {
49
+ console.error('AsyncStorage not available:', error);
50
+ throw new Error('AsyncStorage is required in React Native environment');
51
+ }
52
+ } else {
53
+ // Use localStorage for web
54
+ return {
55
+ getItem: async (key: string) => {
56
+ if (typeof window !== 'undefined' && window.localStorage) {
57
+ return window.localStorage.getItem(key);
58
+ }
59
+ return null;
60
+ },
61
+ setItem: async (key: string, value: string) => {
62
+ if (typeof window !== 'undefined' && window.localStorage) {
63
+ window.localStorage.setItem(key, value);
64
+ }
65
+ },
66
+ removeItem: async (key: string) => {
67
+ if (typeof window !== 'undefined' && window.localStorage) {
68
+ window.localStorage.removeItem(key);
69
+ }
70
+ }
71
+ };
72
+ }
73
+ };
74
+
75
+ // Load history from storage
76
+ React.useEffect(() => {
77
+ const loadHistory = async () => {
78
+ try {
79
+ setIsLoading(true);
80
+ // In a real implementation, this would fetch from API or local storage
81
+ // For now, we'll use a mock implementation
82
+ const storage = await getStorage();
83
+ const historyKey = `history_${user?.id || 'guest'}`;
84
+ const stored = await storage.getItem(historyKey);
85
+
86
+ if (stored) {
87
+ const parsed = JSON.parse(stored);
88
+ setHistory(parsed.map((item: any) => ({
89
+ ...item,
90
+ timestamp: new Date(item.timestamp),
91
+ })));
92
+ } else {
93
+ setHistory([]);
94
+ }
95
+ } catch (error) {
96
+ setHistory([]);
97
+ } finally {
98
+ setIsLoading(false);
99
+ }
100
+ };
101
+
102
+ loadHistory();
103
+ }, [user?.id]);
104
+
105
+ const handleDeleteLast15Minutes = useCallback(async () => {
106
+ confirmAction(
107
+ t('history.deleteLast15Minutes.confirm') || 'Delete last 15 minutes of history?',
108
+ async () => {
109
+ try {
110
+ setIsDeleting(true);
111
+ const fifteenMinutesAgo = new Date(Date.now() - 15 * 60 * 1000);
112
+
113
+ const filtered = history.filter(item => item.timestamp < fifteenMinutesAgo);
114
+ setHistory(filtered);
115
+
116
+ // Save to storage
117
+ const storage = await getStorage();
118
+ const historyKey = `history_${user?.id || 'guest'}`;
119
+ await storage.setItem(historyKey, JSON.stringify(filtered));
120
+
121
+ toast.success(t('history.deleteLast15Minutes.success') || 'Last 15 minutes deleted');
122
+ } catch (error) {
123
+ console.error('Failed to delete history:', error);
124
+ toast.error(t('history.deleteLast15Minutes.error') || 'Failed to delete history');
125
+ } finally {
126
+ setIsDeleting(false);
127
+ }
128
+ }
129
+ );
130
+ }, [history, user?.id, t]);
131
+
132
+ const handleClearAll = useCallback(async () => {
133
+ confirmAction(
134
+ t('history.clearAll.confirm') || 'Clear all history? This cannot be undone.',
135
+ async () => {
136
+ try {
137
+ setIsDeleting(true);
138
+ setHistory([]);
139
+
140
+ // Clear from storage
141
+ const storage = await getStorage();
142
+ const historyKey = `history_${user?.id || 'guest'}`;
143
+ await storage.removeItem(historyKey);
144
+
145
+ toast.success(t('history.clearAll.success') || 'History cleared');
146
+ } catch (error) {
147
+ console.error('Failed to clear history:', error);
148
+ toast.error(t('history.clearAll.error') || 'Failed to clear history');
149
+ } finally {
150
+ setIsDeleting(false);
151
+ }
152
+ }
153
+ );
154
+ }, [user?.id, t]);
155
+
156
+ const themeStyles = useMemo(() => {
157
+ const isDarkTheme = theme === 'dark';
158
+ return {
159
+ textColor: isDarkTheme ? '#FFFFFF' : '#000000',
160
+ backgroundColor: isDarkTheme ? '#121212' : '#FFFFFF',
161
+ secondaryBackgroundColor: isDarkTheme ? '#222222' : '#F5F5F5',
162
+ borderColor: isDarkTheme ? '#444444' : '#E0E0E0',
163
+ };
164
+ }, [theme]);
165
+
166
+ const formatTime = (date: Date) => {
167
+ const now = new Date();
168
+ const diff = now.getTime() - date.getTime();
169
+ const minutes = Math.floor(diff / 60000);
170
+ const hours = Math.floor(minutes / 60);
171
+ const days = Math.floor(hours / 24);
172
+
173
+ if (minutes < 1) return t('history.justNow') || 'Just now';
174
+ if (minutes < 60) return `${minutes} ${t('history.minutesAgo') || 'minutes ago'}`;
175
+ if (hours < 24) return `${hours} ${t('history.hoursAgo') || 'hours ago'}`;
176
+ if (days < 7) return `${days} ${t('history.daysAgo') || 'days ago'}`;
177
+ return date.toLocaleDateString();
178
+ };
179
+
180
+ return (
181
+ <View style={[styles.container, { backgroundColor: themeStyles.backgroundColor }]}>
182
+ <Header
183
+ title={t('history.title') || 'History'}
184
+ theme={theme}
185
+ onBack={goBack || onClose}
186
+ variant="minimal"
187
+ elevation="subtle"
188
+ />
189
+
190
+ <ScrollView style={styles.content}>
191
+ {/* Actions */}
192
+ <Section title={t('history.actions') || 'Actions'} theme={theme} isFirst={true}>
193
+ <GroupedSection
194
+ items={[
195
+ {
196
+ id: 'delete-last-15',
197
+ icon: 'time-outline',
198
+ iconColor: '#FF9500',
199
+ title: t('history.deleteLast15Minutes.title') || 'Delete Last 15 Minutes',
200
+ subtitle: t('history.deleteLast15Minutes.subtitle') || 'Remove recent history entries',
201
+ onPress: handleDeleteLast15Minutes,
202
+ disabled: isDeleting || history.length === 0,
203
+ },
204
+ {
205
+ id: 'clear-all',
206
+ icon: 'trash-outline',
207
+ iconColor: '#FF3B30',
208
+ title: t('history.clearAll.title') || 'Clear All History',
209
+ subtitle: t('history.clearAll.subtitle') || 'Remove all history entries',
210
+ onPress: handleClearAll,
211
+ disabled: isDeleting || history.length === 0,
212
+ },
213
+ ]}
214
+ theme={theme}
215
+ />
216
+ </Section>
217
+
218
+ {/* History List */}
219
+ <Section title={t('history.recent') || 'Recent History'} theme={theme}>
220
+ {isLoading ? (
221
+ <View style={styles.loadingContainer}>
222
+ <ActivityIndicator size="large" color={themeStyles.textColor} />
223
+ <Text style={[styles.loadingText, { color: themeStyles.textColor }]}>
224
+ {t('history.loading') || 'Loading history...'}
225
+ </Text>
226
+ </View>
227
+ ) : history.length === 0 ? (
228
+ <View style={styles.emptyContainer}>
229
+ <Text style={[styles.emptyText, { color: themeStyles.textColor }]}>
230
+ {t('history.empty') || 'No history yet'}
231
+ </Text>
232
+ </View>
233
+ ) : (
234
+ <GroupedSection
235
+ items={history.map((item) => ({
236
+ id: item.id,
237
+ icon: item.type === 'search' ? 'search' : 'globe',
238
+ iconColor: item.type === 'search' ? '#007AFF' : '#32D74B',
239
+ title: item.query,
240
+ subtitle: formatTime(item.timestamp),
241
+ }))}
242
+ theme={theme}
243
+ />
244
+ )}
245
+ </Section>
246
+ </ScrollView>
247
+ </View>
248
+ );
249
+ };
250
+
251
+ const styles = StyleSheet.create({
252
+ container: {
253
+ flex: 1,
254
+ },
255
+ content: {
256
+ flex: 1,
257
+ padding: 16,
258
+ },
259
+ loadingContainer: {
260
+ padding: 40,
261
+ alignItems: 'center',
262
+ justifyContent: 'center',
263
+ },
264
+ loadingText: {
265
+ marginTop: 12,
266
+ fontSize: 16,
267
+ },
268
+ emptyContainer: {
269
+ padding: 40,
270
+ alignItems: 'center',
271
+ justifyContent: 'center',
272
+ },
273
+ emptyText: {
274
+ fontSize: 16,
275
+ textAlign: 'center',
276
+ },
277
+ });
278
+
279
+ export default React.memo(HistoryViewScreen);
280
+
@@ -0,0 +1,220 @@
1
+ import React, { useMemo, useState, useCallback } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ StyleSheet,
7
+ ScrollView,
8
+ ActivityIndicator,
9
+ Linking,
10
+ } from 'react-native';
11
+ import type { BaseScreenProps } from '../navigation/types';
12
+ import { toast } from '../../lib/sonner';
13
+ import { Header, Section, GroupedSection } from '../components';
14
+ import { useI18n } from '../hooks/useI18n';
15
+
16
+ const LegalDocumentsScreen: React.FC<BaseScreenProps> = ({
17
+ onClose,
18
+ theme,
19
+ goBack,
20
+ initialStep,
21
+ }) => {
22
+ const { t } = useI18n();
23
+ const [loading, setLoading] = useState(false);
24
+
25
+ // Policy URLs from Oxy Transparency Center
26
+ const POLICY_URLS = {
27
+ privacy: 'https://oxy.so/company/transparency/policies/privacy',
28
+ terms: 'https://oxy.so/company/transparency/policies/terms-of-service',
29
+ community: 'https://oxy.so/company/transparency/policies/community-guidelines',
30
+ dataRetention: 'https://oxy.so/company/transparency/policies/data-retention',
31
+ contentModeration: 'https://oxy.so/company/transparency/policies/content-moderation',
32
+ childSafety: 'https://oxy.so/company/transparency/policies/child-safety',
33
+ cookie: 'https://oxy.so/company/transparency/policies/cookies',
34
+ };
35
+
36
+ // Determine which document to show based on initialStep
37
+ const documentType = initialStep === 1 ? 'privacy'
38
+ : initialStep === 2 ? 'terms'
39
+ : initialStep === 3 ? 'community'
40
+ : initialStep === 4 ? 'dataRetention'
41
+ : initialStep === 5 ? 'contentModeration'
42
+ : initialStep === 6 ? 'childSafety'
43
+ : initialStep === 7 ? 'cookie'
44
+ : null;
45
+
46
+ // Generic handler to open any policy URL
47
+ const handleOpenPolicy = useCallback((policyKey: keyof typeof POLICY_URLS) => {
48
+ return async () => {
49
+ try {
50
+ setLoading(true);
51
+ const url = POLICY_URLS[policyKey];
52
+ const canOpen = await Linking.canOpenURL(url);
53
+ if (canOpen) {
54
+ await Linking.openURL(url);
55
+ } else {
56
+ toast.error(t('legal.openError') || 'Failed to open document');
57
+ }
58
+ } catch (error) {
59
+ console.error(`Failed to open ${policyKey} policy:`, error);
60
+ toast.error(t('legal.openError') || 'Failed to open document');
61
+ } finally {
62
+ setLoading(false);
63
+ }
64
+ };
65
+ }, [t]);
66
+
67
+ const themeStyles = useMemo(() => {
68
+ const isDarkTheme = theme === 'dark';
69
+ return {
70
+ textColor: isDarkTheme ? '#FFFFFF' : '#000000',
71
+ backgroundColor: isDarkTheme ? '#121212' : '#FFFFFF',
72
+ secondaryBackgroundColor: isDarkTheme ? '#222222' : '#F5F5F5',
73
+ borderColor: isDarkTheme ? '#444444' : '#E0E0E0',
74
+ };
75
+ }, [theme]);
76
+
77
+ // If a specific document type is requested, open it directly
78
+ React.useEffect(() => {
79
+ if (documentType) {
80
+ handleOpenPolicy(documentType)();
81
+ }
82
+ }, [documentType, handleOpenPolicy]);
83
+
84
+ // Get policy title for display
85
+ const getPolicyTitle = (key: string) => {
86
+ const titles: Record<string, string> = {
87
+ privacy: t('legal.privacyPolicy.title') || 'Privacy Policy',
88
+ terms: t('legal.termsOfService.title') || 'Terms of Service',
89
+ community: t('legal.communityGuidelines.title') || 'Community Guidelines',
90
+ dataRetention: t('legal.dataRetention.title') || 'Data Retention Policy',
91
+ contentModeration: t('legal.contentModeration.title') || 'Content Moderation Policy',
92
+ childSafety: t('legal.childSafety.title') || 'Child Safety Policy',
93
+ cookie: t('legal.cookiePolicy.title') || 'Cookie Policy',
94
+ };
95
+ return titles[key] || 'Document';
96
+ };
97
+
98
+ // If a specific document type is requested, show loading state while opening
99
+ if (documentType) {
100
+ return (
101
+ <View style={[styles.container, { backgroundColor: themeStyles.backgroundColor }]}>
102
+ <Header
103
+ title={getPolicyTitle(documentType)}
104
+ theme={theme}
105
+ onBack={goBack || onClose}
106
+ variant="minimal"
107
+ elevation="subtle"
108
+ />
109
+ <View style={styles.loadingContainer}>
110
+ <ActivityIndicator size="large" color={themeStyles.textColor} />
111
+ <Text style={[styles.loadingText, { color: themeStyles.textColor }]}>
112
+ {t('legal.opening') || 'Opening document...'}
113
+ </Text>
114
+ </View>
115
+ </View>
116
+ );
117
+ }
118
+
119
+ // Default: show both options
120
+ return (
121
+ <View style={[styles.container, { backgroundColor: themeStyles.backgroundColor }]}>
122
+ <Header
123
+ title={t('legal.title') || 'Legal Documents'}
124
+ theme={theme}
125
+ onBack={goBack || onClose}
126
+ variant="minimal"
127
+ elevation="subtle"
128
+ />
129
+
130
+ <ScrollView style={styles.content}>
131
+ <Section title={t('legal.policies') || 'Policies & Guidelines'} theme={theme} isFirst={true}>
132
+ <GroupedSection
133
+ items={[
134
+ {
135
+ id: 'privacy-policy',
136
+ icon: 'shield-checkmark',
137
+ iconColor: '#30D158',
138
+ title: t('legal.privacyPolicy.title') || 'Privacy Policy',
139
+ subtitle: t('legal.privacyPolicy.subtitle') || 'How we handle your data',
140
+ onPress: handleOpenPolicy('privacy'),
141
+ },
142
+ {
143
+ id: 'terms-of-service',
144
+ icon: 'document-text',
145
+ iconColor: '#007AFF',
146
+ title: t('legal.termsOfService.title') || 'Terms of Service',
147
+ subtitle: t('legal.termsOfService.subtitle') || 'Terms and conditions of use',
148
+ onPress: handleOpenPolicy('terms'),
149
+ },
150
+ {
151
+ id: 'community-guidelines',
152
+ icon: 'people',
153
+ iconColor: '#5856D6',
154
+ title: t('legal.communityGuidelines.title') || 'Community Guidelines',
155
+ subtitle: t('legal.communityGuidelines.subtitle') || 'Rules and expectations for our community',
156
+ onPress: handleOpenPolicy('community'),
157
+ },
158
+ {
159
+ id: 'data-retention',
160
+ icon: 'time',
161
+ iconColor: '#FF9500',
162
+ title: t('legal.dataRetention.title') || 'Data Retention Policy',
163
+ subtitle: t('legal.dataRetention.subtitle') || 'How long we keep your data',
164
+ onPress: handleOpenPolicy('dataRetention'),
165
+ },
166
+ {
167
+ id: 'content-moderation',
168
+ icon: 'eye',
169
+ iconColor: '#FF3B30',
170
+ title: t('legal.contentModeration.title') || 'Content Moderation Policy',
171
+ subtitle: t('legal.contentModeration.subtitle') || 'How we moderate content',
172
+ onPress: handleOpenPolicy('contentModeration'),
173
+ },
174
+ {
175
+ id: 'child-safety',
176
+ icon: 'heart',
177
+ iconColor: '#FF2D55',
178
+ title: t('legal.childSafety.title') || 'Child Safety Policy',
179
+ subtitle: t('legal.childSafety.subtitle') || 'Protecting minors on our platform',
180
+ onPress: handleOpenPolicy('childSafety'),
181
+ },
182
+ {
183
+ id: 'cookie-policy',
184
+ icon: 'cookie',
185
+ iconColor: '#8E8E93',
186
+ title: t('legal.cookiePolicy.title') || 'Cookie Policy',
187
+ subtitle: t('legal.cookiePolicy.subtitle') || 'How we use cookies and similar technologies',
188
+ onPress: handleOpenPolicy('cookie'),
189
+ },
190
+ ]}
191
+ theme={theme}
192
+ />
193
+ </Section>
194
+ </ScrollView>
195
+ </View>
196
+ );
197
+ };
198
+
199
+ const styles = StyleSheet.create({
200
+ container: {
201
+ flex: 1,
202
+ },
203
+ content: {
204
+ flex: 1,
205
+ padding: 16,
206
+ },
207
+ loadingContainer: {
208
+ flex: 1,
209
+ alignItems: 'center',
210
+ justifyContent: 'center',
211
+ gap: 16,
212
+ },
213
+ loadingText: {
214
+ fontSize: 16,
215
+ marginTop: 12,
216
+ },
217
+ });
218
+
219
+ export default React.memo(LegalDocumentsScreen);
220
+
@@ -1045,7 +1045,6 @@ const PremiumSubscriptionScreen: React.FC<BaseScreenProps> = ({
1045
1045
  ]}
1046
1046
  onPress={() => {
1047
1047
  setCurrentAppPackage(app);
1048
- console.log('Switched to app context:', app);
1049
1048
  }}
1050
1049
  >
1051
1050
  <Text style={[