@oxyhq/services 5.13.4 → 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.
- package/lib/commonjs/core/HttpClient.js +1 -1
- package/lib/commonjs/core/HttpClient.js.map +1 -1
- package/lib/commonjs/core/OxyServices.js +83 -30
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/core/index.js +0 -7
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/i18n/locales/en-US.json +222 -6
- package/lib/commonjs/index.js +0 -7
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/lib/sonner.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedItem.js +24 -22
- package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +35 -14
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/navigation/routes.js +36 -1
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js +150 -5
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +475 -319
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountVerificationScreen.js +217 -0
- package/lib/commonjs/ui/screens/AccountVerificationScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/FileManagementScreen.js +911 -213
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/HelpSupportScreen.js +131 -0
- package/lib/commonjs/ui/screens/HelpSupportScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/HistoryViewScreen.js +258 -0
- package/lib/commonjs/ui/screens/HistoryViewScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/LegalDocumentsScreen.js +211 -0
- package/lib/commonjs/ui/screens/LegalDocumentsScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +0 -1
- package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +307 -0
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/ProfileScreen.js +1 -7
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SavesCollectionsScreen.js +205 -0
- package/lib/commonjs/ui/screens/SavesCollectionsScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/SearchSettingsScreen.js +239 -0
- package/lib/commonjs/ui/screens/SearchSettingsScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/SignInScreen.js +14 -29
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/utils/asyncUtils.js +1 -0
- package/lib/commonjs/utils/asyncUtils.js.map +1 -1
- package/lib/commonjs/utils/cache.js +4 -4
- package/lib/commonjs/utils/cache.js.map +1 -1
- package/lib/commonjs/utils/index.js +0 -6
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/module/core/HttpClient.js +1 -1
- package/lib/module/core/HttpClient.js.map +1 -1
- package/lib/module/core/OxyServices.js +82 -29
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/core/index.js +1 -1
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/i18n/locales/en-US.json +222 -6
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/lib/sonner.js.map +1 -1
- package/lib/module/ui/components/GroupedItem.js +24 -22
- package/lib/module/ui/components/GroupedItem.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +40 -17
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/navigation/routes.js +36 -1
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/screens/AccountOverviewScreen.js +151 -6
- package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +475 -319
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountVerificationScreen.js +212 -0
- package/lib/module/ui/screens/AccountVerificationScreen.js.map +1 -0
- package/lib/module/ui/screens/FileManagementScreen.js +913 -212
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/HelpSupportScreen.js +126 -0
- package/lib/module/ui/screens/HelpSupportScreen.js.map +1 -0
- package/lib/module/ui/screens/HistoryViewScreen.js +253 -0
- package/lib/module/ui/screens/HistoryViewScreen.js.map +1 -0
- package/lib/module/ui/screens/LegalDocumentsScreen.js +206 -0
- package/lib/module/ui/screens/LegalDocumentsScreen.js.map +1 -0
- package/lib/module/ui/screens/PremiumSubscriptionScreen.js +0 -1
- package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
- package/lib/module/ui/screens/PrivacySettingsScreen.js +302 -0
- package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -0
- package/lib/module/ui/screens/ProfileScreen.js +1 -7
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/SavesCollectionsScreen.js +200 -0
- package/lib/module/ui/screens/SavesCollectionsScreen.js.map +1 -0
- package/lib/module/ui/screens/SearchSettingsScreen.js +234 -0
- package/lib/module/ui/screens/SearchSettingsScreen.js.map +1 -0
- package/lib/module/ui/screens/SignInScreen.js +14 -29
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/utils/asyncUtils.js +1 -0
- package/lib/module/utils/asyncUtils.js.map +1 -1
- package/lib/module/utils/cache.js +3 -3
- package/lib/module/utils/cache.js.map +1 -1
- package/lib/module/utils/index.js +1 -1
- package/lib/module/utils/index.js.map +1 -1
- package/lib/typescript/core/OxyServices.d.ts +30 -24
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/core/index.d.ts +1 -1
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/lib/sonner.d.ts +1 -0
- package/lib/typescript/lib/sonner.d.ts.map +1 -1
- package/lib/typescript/types/expo-document-picker.d.ts +36 -0
- package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountVerificationScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/AccountVerificationScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/HelpSupportScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/HelpSupportScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/HistoryViewScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/HistoryViewScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/LegalDocumentsScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/LegalDocumentsScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/PremiumSubscriptionScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SavesCollectionsScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/SavesCollectionsScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/SearchSettingsScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/SearchSettingsScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
- package/lib/typescript/utils/cache.d.ts +3 -3
- package/lib/typescript/utils/cache.d.ts.map +1 -1
- package/lib/typescript/utils/index.d.ts +1 -1
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/HttpClient.ts +1 -1
- package/src/core/OxyServices.ts +80 -30
- package/src/core/index.ts +1 -1
- package/src/i18n/locales/en-US.json +222 -6
- package/src/index.ts +1 -1
- package/src/lib/sonner.ts +1 -0
- package/src/types/expo-document-picker.d.ts +36 -0
- package/src/ui/components/GroupedItem.tsx +23 -21
- package/src/ui/components/OxyProvider.tsx +33 -11
- package/src/ui/navigation/routes.ts +42 -0
- package/src/ui/screens/AccountOverviewScreen.tsx +175 -5
- package/src/ui/screens/AccountSettingsScreen.tsx +521 -360
- package/src/ui/screens/AccountVerificationScreen.tsx +235 -0
- package/src/ui/screens/FileManagementScreen.tsx +934 -208
- package/src/ui/screens/HelpSupportScreen.tsx +143 -0
- package/src/ui/screens/HistoryViewScreen.tsx +280 -0
- package/src/ui/screens/LegalDocumentsScreen.tsx +220 -0
- package/src/ui/screens/PremiumSubscriptionScreen.tsx +0 -1
- package/src/ui/screens/PrivacySettingsScreen.tsx +332 -0
- package/src/ui/screens/ProfileScreen.tsx +1 -8
- package/src/ui/screens/SavesCollectionsScreen.tsx +222 -0
- package/src/ui/screens/SearchSettingsScreen.tsx +219 -0
- package/src/ui/screens/SignInScreen.tsx +19 -35
- package/src/utils/asyncUtils.ts +1 -0
- package/src/utils/cache.ts +3 -3
- package/src/utils/index.ts +1 -1
- package/lib/commonjs/ui/components/StepBasedScreen.README.md +0 -337
- package/lib/commonjs/ui/components/internal/TextField.md +0 -436
- package/lib/commonjs/ui/styles/FONTS.md +0 -126
- package/lib/module/ui/components/StepBasedScreen.README.md +0 -337
- package/lib/module/ui/components/internal/TextField.md +0 -436
- package/lib/module/ui/styles/FONTS.md +0 -126
- package/src/ui/components/StepBasedScreen.README.md +0 -337
- package/src/ui/components/internal/TextField.md +0 -436
- 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
|
+
|