@oxyhq/services 5.21.5 → 5.21.7
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/crypto/keyManager.js +67 -22
- package/lib/commonjs/crypto/keyManager.js.map +1 -1
- package/lib/commonjs/index.js +66 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/BottomSheetRouter.js +100 -286
- package/lib/commonjs/ui/components/BottomSheetRouter.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedItem.js +0 -3
- package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +14 -19
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/fileManagement/AnimatedButton.js +57 -0
- package/lib/commonjs/ui/components/fileManagement/AnimatedButton.js.map +1 -0
- package/lib/commonjs/ui/components/profile/EditBioModal.js +24 -156
- package/lib/commonjs/ui/components/profile/EditBioModal.js.map +1 -1
- package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js +28 -178
- package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js.map +1 -1
- package/lib/commonjs/ui/components/profile/EditEmailModal.js +32 -159
- package/lib/commonjs/ui/components/profile/EditEmailModal.js.map +1 -1
- package/lib/commonjs/ui/components/profile/EditLocationModal.js +45 -227
- package/lib/commonjs/ui/components/profile/EditLocationModal.js.map +1 -1
- package/lib/commonjs/ui/components/profile/EditUsernameModal.js +30 -155
- package/lib/commonjs/ui/components/profile/EditUsernameModal.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/mutationFactory.js +177 -0
- package/lib/commonjs/ui/hooks/mutations/mutationFactory.js.map +1 -0
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +10 -123
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +2 -32
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +2 -31
- package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/useFileFiltering.js +76 -0
- package/lib/commonjs/ui/hooks/useFileFiltering.js.map +1 -0
- package/lib/commonjs/ui/navigation/bottomSheetManager.js +43 -145
- package/lib/commonjs/ui/navigation/bottomSheetManager.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +0 -2
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +2 -2
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/utils/authHelpers.js +164 -0
- package/lib/commonjs/ui/utils/authHelpers.js.map +1 -0
- package/lib/commonjs/ui/utils/avatarUtils.js +18 -61
- package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
- package/lib/module/crypto/keyManager.js +67 -22
- package/lib/module/crypto/keyManager.js.map +1 -1
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/BottomSheetRouter.js +102 -284
- package/lib/module/ui/components/BottomSheetRouter.js.map +1 -1
- package/lib/module/ui/components/GroupedItem.js +0 -3
- package/lib/module/ui/components/GroupedItem.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +14 -19
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/fileManagement/AnimatedButton.js +50 -0
- package/lib/module/ui/components/fileManagement/AnimatedButton.js.map +1 -0
- package/lib/module/ui/components/profile/EditBioModal.js +24 -156
- package/lib/module/ui/components/profile/EditBioModal.js.map +1 -1
- package/lib/module/ui/components/profile/EditDisplayNameModal.js +28 -178
- package/lib/module/ui/components/profile/EditDisplayNameModal.js.map +1 -1
- package/lib/module/ui/components/profile/EditEmailModal.js +32 -159
- package/lib/module/ui/components/profile/EditEmailModal.js.map +1 -1
- package/lib/module/ui/components/profile/EditLocationModal.js +45 -227
- package/lib/module/ui/components/profile/EditLocationModal.js.map +1 -1
- package/lib/module/ui/components/profile/EditUsernameModal.js +30 -155
- package/lib/module/ui/components/profile/EditUsernameModal.js.map +1 -1
- package/lib/module/ui/hooks/mutations/mutationFactory.js +173 -0
- package/lib/module/ui/hooks/mutations/mutationFactory.js.map +1 -0
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +10 -122
- package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/module/ui/hooks/queries/useAccountQueries.js +2 -32
- package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/module/ui/hooks/queries/useServicesQueries.js +2 -31
- package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -1
- package/lib/module/ui/hooks/useFileFiltering.js +72 -0
- package/lib/module/ui/hooks/useFileFiltering.js.map +1 -0
- package/lib/module/ui/navigation/bottomSheetManager.js +37 -135
- package/lib/module/ui/navigation/bottomSheetManager.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +0 -2
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +2 -2
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/utils/authHelpers.js +154 -0
- package/lib/module/ui/utils/authHelpers.js.map +1 -0
- package/lib/module/ui/utils/avatarUtils.js +18 -61
- package/lib/module/ui/utils/avatarUtils.js.map +1 -1
- package/lib/typescript/commonjs/crypto/keyManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +6 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts +2 -7
- package/lib/typescript/commonjs/ui/components/BottomSheetRouter.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/GroupedItem.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/fileManagement/AnimatedButton.d.ts +16 -0
- package/lib/typescript/commonjs/ui/components/fileManagement/AnimatedButton.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/profile/EditBioModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/profile/EditDisplayNameModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/profile/EditEmailModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/profile/EditLocationModal.d.ts +1 -0
- package/lib/typescript/commonjs/ui/components/profile/EditLocationModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/profile/EditUsernameModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/mutationFactory.d.ts +76 -0
- package/lib/typescript/commonjs/ui/hooks/mutations/mutationFactory.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +29 -4
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useFileFiltering.d.ts +29 -0
- package/lib/typescript/commonjs/ui/hooks/useFileFiltering.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts +11 -60
- package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/authHelpers.d.ts +99 -0
- package/lib/typescript/commonjs/ui/utils/authHelpers.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts.map +1 -1
- package/lib/typescript/module/crypto/keyManager.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +6 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts +2 -7
- package/lib/typescript/module/ui/components/BottomSheetRouter.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/GroupedItem.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/fileManagement/AnimatedButton.d.ts +16 -0
- package/lib/typescript/module/ui/components/fileManagement/AnimatedButton.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/profile/EditBioModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/profile/EditDisplayNameModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/profile/EditEmailModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/profile/EditLocationModal.d.ts +1 -0
- package/lib/typescript/module/ui/components/profile/EditLocationModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/profile/EditUsernameModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/mutationFactory.d.ts +76 -0
- package/lib/typescript/module/ui/hooks/mutations/mutationFactory.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +29 -4
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts +1 -1
- package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts +1 -1
- package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useFileFiltering.d.ts +29 -0
- package/lib/typescript/module/ui/hooks/useFileFiltering.d.ts.map +1 -0
- package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts +11 -60
- package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/authHelpers.d.ts +99 -0
- package/lib/typescript/module/ui/utils/authHelpers.d.ts.map +1 -0
- package/lib/typescript/module/ui/utils/avatarUtils.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/crypto/keyManager.ts +23 -22
- package/src/index.ts +25 -0
- package/src/ui/components/BottomSheetRouter.tsx +97 -319
- package/src/ui/components/GroupedItem.tsx +0 -4
- package/src/ui/components/OxyProvider.tsx +13 -18
- package/src/ui/components/fileManagement/AnimatedButton.tsx +56 -0
- package/src/ui/components/profile/EditBioModal.tsx +38 -176
- package/src/ui/components/profile/EditDisplayNameModal.tsx +48 -195
- package/src/ui/components/profile/EditEmailModal.tsx +49 -180
- package/src/ui/components/profile/EditLocationModal.tsx +76 -263
- package/src/ui/components/profile/EditUsernameModal.tsx +47 -175
- package/src/ui/hooks/mutations/mutationFactory.ts +215 -0
- package/src/ui/hooks/mutations/useAccountMutations.ts +48 -136
- package/src/ui/hooks/queries/useAccountQueries.ts +6 -33
- package/src/ui/hooks/queries/useServicesQueries.ts +6 -32
- package/src/ui/hooks/useFileFiltering.ts +115 -0
- package/src/ui/navigation/bottomSheetManager.ts +43 -150
- package/src/ui/screens/AccountSettingsScreen.tsx +0 -2
- package/src/ui/screens/FileManagementScreen.tsx +2 -2
- package/src/ui/utils/authHelpers.ts +183 -0
- package/src/ui/utils/avatarUtils.ts +25 -65
- package/lib/commonjs/ui/hooks/use-haptic-press.js +0 -21
- package/lib/commonjs/ui/hooks/use-haptic-press.js.map +0 -1
- package/lib/module/ui/hooks/use-haptic-press.js +0 -17
- package/lib/module/ui/hooks/use-haptic-press.js.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/use-haptic-press.d.ts +0 -8
- package/lib/typescript/commonjs/ui/hooks/use-haptic-press.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/use-haptic-press.d.ts +0 -8
- package/lib/typescript/module/ui/hooks/use-haptic-press.d.ts.map +0 -1
- package/src/ui/hooks/use-haptic-press.ts +0 -15
|
@@ -1,183 +1,55 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
View,
|
|
4
|
-
Text,
|
|
5
|
-
TextInput,
|
|
6
|
-
TouchableOpacity,
|
|
7
|
-
StyleSheet,
|
|
8
|
-
Modal,
|
|
9
|
-
Platform,
|
|
10
|
-
} from 'react-native';
|
|
11
|
-
import { Ionicons } from '@expo/vector-icons';
|
|
12
|
-
import { useThemeStyles } from '../../hooks/useThemeStyles';
|
|
13
|
-
import { useColorScheme } from '../../hooks/use-color-scheme';
|
|
14
|
-
import { useI18n } from '../../hooks/useI18n';
|
|
15
|
-
import { fontFamilies } from '../../styles/fonts';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { EditFieldModal } from './EditFieldModal';
|
|
16
3
|
import { useProfileEditing } from '../../hooks/useProfileEditing';
|
|
4
|
+
import { useI18n } from '../../hooks/useI18n';
|
|
17
5
|
|
|
18
6
|
interface EditUsernameModalProps {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
7
|
+
visible: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
initialValue?: string;
|
|
10
|
+
theme?: 'light' | 'dark';
|
|
11
|
+
onSave?: () => void;
|
|
24
12
|
}
|
|
25
13
|
|
|
26
14
|
export const EditUsernameModal: React.FC<EditUsernameModalProps> = ({
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
15
|
+
visible,
|
|
16
|
+
onClose,
|
|
17
|
+
initialValue = '',
|
|
18
|
+
theme = 'light',
|
|
19
|
+
onSave,
|
|
32
20
|
}) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
</TouchableOpacity>
|
|
68
|
-
<Text style={[styles.modalTitle, { color: colors.text }]}>
|
|
69
|
-
{t('editProfile.items.username.title') || 'Username'}
|
|
70
|
-
</Text>
|
|
71
|
-
<TouchableOpacity
|
|
72
|
-
onPress={handleSave}
|
|
73
|
-
disabled={isSaving || !username.trim()}
|
|
74
|
-
style={[styles.saveButton, { opacity: (isSaving || !username.trim()) ? 0.5 : 1 }]}
|
|
75
|
-
>
|
|
76
|
-
<Text style={[styles.saveButtonText, { color: colors.tint }]}>
|
|
77
|
-
{isSaving ? 'Saving...' : 'Save'}
|
|
78
|
-
</Text>
|
|
79
|
-
</TouchableOpacity>
|
|
80
|
-
</View>
|
|
81
|
-
|
|
82
|
-
<View style={styles.modalBody}>
|
|
83
|
-
<View style={styles.inputGroup}>
|
|
84
|
-
<Text style={[styles.label, { color: colors.text }]}>
|
|
85
|
-
{t('editProfile.items.username.label') || 'Username'}
|
|
86
|
-
</Text>
|
|
87
|
-
<TextInput
|
|
88
|
-
style={[
|
|
89
|
-
styles.input,
|
|
90
|
-
{
|
|
91
|
-
backgroundColor: colors.card,
|
|
92
|
-
color: colors.text,
|
|
93
|
-
borderColor: colors.border,
|
|
94
|
-
},
|
|
95
|
-
]}
|
|
96
|
-
value={username}
|
|
97
|
-
onChangeText={setUsername}
|
|
98
|
-
placeholder={t('editProfile.items.username.placeholder') || 'Choose a username'}
|
|
99
|
-
placeholderTextColor={colors.secondaryText}
|
|
100
|
-
autoFocus
|
|
101
|
-
autoCapitalize="none"
|
|
102
|
-
autoCorrect={false}
|
|
103
|
-
selectionColor={colors.tint}
|
|
104
|
-
/>
|
|
105
|
-
</View>
|
|
106
|
-
</View>
|
|
107
|
-
</View>
|
|
108
|
-
</View>
|
|
109
|
-
</Modal>
|
|
110
|
-
);
|
|
21
|
+
const { t } = useI18n();
|
|
22
|
+
const { updateField } = useProfileEditing();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<EditFieldModal
|
|
26
|
+
visible={visible}
|
|
27
|
+
onClose={onClose}
|
|
28
|
+
title={t('editProfile.items.username.title') || 'Username'}
|
|
29
|
+
theme={theme}
|
|
30
|
+
onSave={onSave}
|
|
31
|
+
variant="single"
|
|
32
|
+
fields={[
|
|
33
|
+
{
|
|
34
|
+
key: 'username',
|
|
35
|
+
label: t('editProfile.items.username.label') || 'Username',
|
|
36
|
+
initialValue,
|
|
37
|
+
placeholder: t('editProfile.items.username.placeholder') || 'Choose a username',
|
|
38
|
+
validation: (value) => {
|
|
39
|
+
if (!value.trim()) {
|
|
40
|
+
return t('editProfile.items.username.required') || 'Username is required';
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
},
|
|
44
|
+
inputProps: {
|
|
45
|
+
autoCapitalize: 'none',
|
|
46
|
+
autoCorrect: false,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
]}
|
|
50
|
+
onSubmit={async (data) => {
|
|
51
|
+
return await updateField('username', data.username as string);
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
111
55
|
};
|
|
112
|
-
|
|
113
|
-
const styles = StyleSheet.create({
|
|
114
|
-
modalOverlay: {
|
|
115
|
-
flex: 1,
|
|
116
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
117
|
-
justifyContent: 'flex-end',
|
|
118
|
-
},
|
|
119
|
-
modalContent: {
|
|
120
|
-
borderTopLeftRadius: 20,
|
|
121
|
-
borderTopRightRadius: 20,
|
|
122
|
-
paddingTop: Platform.OS === 'ios' ? 20 : 16,
|
|
123
|
-
maxHeight: '80%',
|
|
124
|
-
},
|
|
125
|
-
modalHeader: {
|
|
126
|
-
flexDirection: 'row',
|
|
127
|
-
alignItems: 'center',
|
|
128
|
-
justifyContent: 'space-between',
|
|
129
|
-
paddingHorizontal: 16,
|
|
130
|
-
paddingBottom: 16,
|
|
131
|
-
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
132
|
-
borderBottomColor: '#E5E5EA',
|
|
133
|
-
},
|
|
134
|
-
closeButton: {
|
|
135
|
-
width: 40,
|
|
136
|
-
height: 40,
|
|
137
|
-
alignItems: 'center',
|
|
138
|
-
justifyContent: 'center',
|
|
139
|
-
},
|
|
140
|
-
modalTitle: {
|
|
141
|
-
fontSize: 18,
|
|
142
|
-
fontWeight: '600',
|
|
143
|
-
fontFamily: fontFamilies.phuduSemiBold,
|
|
144
|
-
flex: 1,
|
|
145
|
-
textAlign: 'center',
|
|
146
|
-
},
|
|
147
|
-
saveButton: {
|
|
148
|
-
paddingHorizontal: 16,
|
|
149
|
-
paddingVertical: 8,
|
|
150
|
-
},
|
|
151
|
-
saveButtonText: {
|
|
152
|
-
fontSize: 16,
|
|
153
|
-
fontWeight: '600',
|
|
154
|
-
fontFamily: fontFamilies.phuduSemiBold,
|
|
155
|
-
},
|
|
156
|
-
modalBody: {
|
|
157
|
-
padding: 16,
|
|
158
|
-
gap: 16,
|
|
159
|
-
},
|
|
160
|
-
inputGroup: {
|
|
161
|
-
gap: 8,
|
|
162
|
-
},
|
|
163
|
-
label: {
|
|
164
|
-
fontSize: 14,
|
|
165
|
-
fontWeight: '600',
|
|
166
|
-
fontFamily: fontFamilies.phuduSemiBold,
|
|
167
|
-
},
|
|
168
|
-
input: {
|
|
169
|
-
borderWidth: StyleSheet.hairlineWidth,
|
|
170
|
-
borderRadius: 12,
|
|
171
|
-
padding: 16,
|
|
172
|
-
fontSize: 16,
|
|
173
|
-
minHeight: 52,
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutation Factory - Creates standardized mutations with optimistic updates
|
|
3
|
+
*
|
|
4
|
+
* This factory reduces boilerplate code for mutations that follow the common pattern:
|
|
5
|
+
* 1. Cancel outgoing queries
|
|
6
|
+
* 2. Snapshot previous data
|
|
7
|
+
* 3. Apply optimistic update
|
|
8
|
+
* 4. On error: rollback and show toast
|
|
9
|
+
* 5. On success: update cache, stores, and invalidate queries
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { QueryClient, UseMutationOptions } from '@tanstack/react-query';
|
|
13
|
+
import type { User } from '../../../models/interfaces';
|
|
14
|
+
import { queryKeys, invalidateAccountQueries, invalidateUserQueries } from '../queries/queryKeys';
|
|
15
|
+
import { toast } from '../../../lib/sonner';
|
|
16
|
+
import { useAuthStore } from '../../stores/authStore';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Configuration for creating a standard profile mutation
|
|
20
|
+
*/
|
|
21
|
+
export interface ProfileMutationConfig<TData, TVariables> {
|
|
22
|
+
/** The mutation function that makes the API call */
|
|
23
|
+
mutationFn: (variables: TVariables) => Promise<TData>;
|
|
24
|
+
/** Query keys to cancel before mutation */
|
|
25
|
+
cancelQueryKeys?: unknown[][];
|
|
26
|
+
/** Function to apply optimistic update to the user data */
|
|
27
|
+
optimisticUpdate?: (previousUser: User, variables: TVariables) => Partial<User>;
|
|
28
|
+
/** Error message to show on failure */
|
|
29
|
+
errorMessage?: string | ((error: Error) => string);
|
|
30
|
+
/** Success message to show (optional) */
|
|
31
|
+
successMessage?: string;
|
|
32
|
+
/** Whether to update authStore on success (default: true) */
|
|
33
|
+
updateAuthStore?: boolean;
|
|
34
|
+
/** Whether to invalidate user queries on success (default: true) */
|
|
35
|
+
invalidateUserQueries?: boolean;
|
|
36
|
+
/** Whether to invalidate account queries on success (default: true) */
|
|
37
|
+
invalidateAccountQueries?: boolean;
|
|
38
|
+
/** Custom onSuccess handler */
|
|
39
|
+
onSuccess?: (data: TData, variables: TVariables, queryClient: QueryClient) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a standard profile mutation with optimistic updates
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const updateProfile = createProfileMutation({
|
|
48
|
+
* mutationFn: (updates) => oxyServices.updateProfile(updates),
|
|
49
|
+
* optimisticUpdate: (user, updates) => updates,
|
|
50
|
+
* errorMessage: 'Failed to update profile',
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function createProfileMutation<TVariables>(
|
|
55
|
+
config: ProfileMutationConfig<User, TVariables>,
|
|
56
|
+
queryClient: QueryClient,
|
|
57
|
+
activeSessionId: string | null
|
|
58
|
+
): UseMutationOptions<User, Error, TVariables, { previousUser?: User }> {
|
|
59
|
+
const {
|
|
60
|
+
mutationFn,
|
|
61
|
+
cancelQueryKeys = [],
|
|
62
|
+
optimisticUpdate,
|
|
63
|
+
errorMessage = 'Operation failed',
|
|
64
|
+
successMessage,
|
|
65
|
+
updateAuthStore = true,
|
|
66
|
+
invalidateUserQueries: shouldInvalidateUserQueries = true,
|
|
67
|
+
invalidateAccountQueries: shouldInvalidateAccountQueries = true,
|
|
68
|
+
onSuccess: customOnSuccess,
|
|
69
|
+
} = config;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
mutationFn,
|
|
73
|
+
|
|
74
|
+
onMutate: async (variables) => {
|
|
75
|
+
// Cancel queries that might conflict
|
|
76
|
+
await queryClient.cancelQueries({ queryKey: queryKeys.accounts.current() });
|
|
77
|
+
for (const key of cancelQueryKeys) {
|
|
78
|
+
await queryClient.cancelQueries({ queryKey: key });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Snapshot previous user data
|
|
82
|
+
const previousUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
83
|
+
|
|
84
|
+
// Apply optimistic update if provided
|
|
85
|
+
if (previousUser && optimisticUpdate) {
|
|
86
|
+
const updates = optimisticUpdate(previousUser, variables);
|
|
87
|
+
const optimisticUser = { ...previousUser, ...updates };
|
|
88
|
+
|
|
89
|
+
queryClient.setQueryData<User>(queryKeys.accounts.current(), optimisticUser);
|
|
90
|
+
|
|
91
|
+
if (activeSessionId) {
|
|
92
|
+
queryClient.setQueryData<User>(queryKeys.users.profile(activeSessionId), optimisticUser);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { previousUser };
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
onError: (error, _variables, context) => {
|
|
100
|
+
// Rollback optimistic update
|
|
101
|
+
if (context?.previousUser) {
|
|
102
|
+
queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
|
|
103
|
+
if (activeSessionId) {
|
|
104
|
+
queryClient.setQueryData(queryKeys.users.profile(activeSessionId), context.previousUser);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Show error toast
|
|
109
|
+
const message = typeof errorMessage === 'function'
|
|
110
|
+
? errorMessage(error)
|
|
111
|
+
: (error instanceof Error ? error.message : errorMessage);
|
|
112
|
+
toast.error(message);
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
onSuccess: (data, variables) => {
|
|
116
|
+
// Update cache with server response
|
|
117
|
+
queryClient.setQueryData(queryKeys.accounts.current(), data);
|
|
118
|
+
if (activeSessionId) {
|
|
119
|
+
queryClient.setQueryData(queryKeys.users.profile(activeSessionId), data);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Update authStore for immediate UI updates
|
|
123
|
+
if (updateAuthStore) {
|
|
124
|
+
useAuthStore.getState().setUser(data);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Invalidate related queries
|
|
128
|
+
if (shouldInvalidateUserQueries) {
|
|
129
|
+
invalidateUserQueries(queryClient);
|
|
130
|
+
}
|
|
131
|
+
if (shouldInvalidateAccountQueries) {
|
|
132
|
+
invalidateAccountQueries(queryClient);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Show success toast if configured
|
|
136
|
+
if (successMessage) {
|
|
137
|
+
toast.success(successMessage);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Call custom onSuccess handler
|
|
141
|
+
if (customOnSuccess) {
|
|
142
|
+
customOnSuccess(data, variables, queryClient);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Configuration for creating a generic mutation (non-profile)
|
|
150
|
+
*/
|
|
151
|
+
export interface GenericMutationConfig<TData, TVariables, TContext> {
|
|
152
|
+
/** The mutation function */
|
|
153
|
+
mutationFn: (variables: TVariables) => Promise<TData>;
|
|
154
|
+
/** Query key for optimistic data */
|
|
155
|
+
queryKey: unknown[];
|
|
156
|
+
/** Function to create optimistic data */
|
|
157
|
+
optimisticData?: (previous: TData | undefined, variables: TVariables) => TData;
|
|
158
|
+
/** Error message */
|
|
159
|
+
errorMessage?: string;
|
|
160
|
+
/** Success message */
|
|
161
|
+
successMessage?: string;
|
|
162
|
+
/** Additional queries to invalidate on success */
|
|
163
|
+
invalidateQueries?: unknown[][];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Creates a generic mutation with optimistic updates
|
|
168
|
+
*/
|
|
169
|
+
export function createGenericMutation<TData, TVariables>(
|
|
170
|
+
config: GenericMutationConfig<TData, TVariables, { previous?: TData }>,
|
|
171
|
+
queryClient: QueryClient
|
|
172
|
+
): UseMutationOptions<TData, Error, TVariables, { previous?: TData }> {
|
|
173
|
+
const {
|
|
174
|
+
mutationFn,
|
|
175
|
+
queryKey,
|
|
176
|
+
optimisticData,
|
|
177
|
+
errorMessage = 'Operation failed',
|
|
178
|
+
successMessage,
|
|
179
|
+
invalidateQueries = [],
|
|
180
|
+
} = config;
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
mutationFn,
|
|
184
|
+
|
|
185
|
+
onMutate: async (variables) => {
|
|
186
|
+
await queryClient.cancelQueries({ queryKey });
|
|
187
|
+
const previous = queryClient.getQueryData<TData>(queryKey);
|
|
188
|
+
|
|
189
|
+
if (optimisticData) {
|
|
190
|
+
queryClient.setQueryData<TData>(queryKey, optimisticData(previous, variables));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return { previous };
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
onError: (error, _variables, context) => {
|
|
197
|
+
if (context?.previous !== undefined) {
|
|
198
|
+
queryClient.setQueryData(queryKey, context.previous);
|
|
199
|
+
}
|
|
200
|
+
toast.error(error instanceof Error ? error.message : errorMessage);
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
onSuccess: (data) => {
|
|
204
|
+
queryClient.setQueryData(queryKey, data);
|
|
205
|
+
|
|
206
|
+
for (const key of invalidateQueries) {
|
|
207
|
+
queryClient.invalidateQueries({ queryKey: key });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (successMessage) {
|
|
211
|
+
toast.success(successMessage);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -5,6 +5,7 @@ import { useOxy } from '../../context/OxyContext';
|
|
|
5
5
|
import { toast } from '../../../lib/sonner';
|
|
6
6
|
import { refreshAvatarInStore } from '../../utils/avatarUtils';
|
|
7
7
|
import { useAuthStore } from '../../stores/authStore';
|
|
8
|
+
import { authenticatedApiCall } from '../../utils/authHelpers';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Update user profile with optimistic updates and offline queue support
|
|
@@ -15,38 +16,11 @@ export const useUpdateProfile = () => {
|
|
|
15
16
|
|
|
16
17
|
return useMutation({
|
|
17
18
|
mutationFn: async (updates: Partial<User>) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} catch (tokenError) {
|
|
24
|
-
// If getting token fails, might be an offline session - try syncing
|
|
25
|
-
const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
|
|
26
|
-
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
27
|
-
// Session sync should be handled by the app layer
|
|
28
|
-
throw new Error('Session needs to be synced. Please try again.');
|
|
29
|
-
} else {
|
|
30
|
-
throw tokenError;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
return await oxyServices.updateProfile(updates);
|
|
37
|
-
} catch (error: any) {
|
|
38
|
-
const errorMessage = error?.message || '';
|
|
39
|
-
const status = error?.status || error?.response?.status;
|
|
40
|
-
|
|
41
|
-
// Handle authentication errors
|
|
42
|
-
if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
|
|
43
|
-
// Session sync should be handled by the app layer
|
|
44
|
-
throw new Error('Authentication failed. Please sign in again.');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// TanStack Query will automatically retry on network errors
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
19
|
+
return authenticatedApiCall<User>(
|
|
20
|
+
oxyServices,
|
|
21
|
+
activeSessionId,
|
|
22
|
+
() => oxyServices.updateProfile(updates)
|
|
23
|
+
);
|
|
50
24
|
},
|
|
51
25
|
// Optimistic update
|
|
52
26
|
onMutate: async (updates) => {
|
|
@@ -116,22 +90,7 @@ export const useUploadAvatar = () => {
|
|
|
116
90
|
|
|
117
91
|
return useMutation({
|
|
118
92
|
mutationFn: async (file: { uri: string; type?: string; name?: string; size?: number }) => {
|
|
119
|
-
|
|
120
|
-
if (!oxyServices.hasValidToken() && activeSessionId) {
|
|
121
|
-
try {
|
|
122
|
-
await oxyServices.getTokenBySession(activeSessionId);
|
|
123
|
-
} catch (tokenError) {
|
|
124
|
-
const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
|
|
125
|
-
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
126
|
-
// Session sync should be handled by the app layer
|
|
127
|
-
throw new Error('Session needs to be synced. Please try again.');
|
|
128
|
-
} else {
|
|
129
|
-
throw tokenError;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
try {
|
|
93
|
+
return authenticatedApiCall<User>(oxyServices, activeSessionId, async () => {
|
|
135
94
|
// Upload file first
|
|
136
95
|
const uploadResult = await oxyServices.assetUpload(file as any, 'public');
|
|
137
96
|
const fileId = uploadResult?.file?.id || uploadResult?.id || uploadResult;
|
|
@@ -142,19 +101,7 @@ export const useUploadAvatar = () => {
|
|
|
142
101
|
|
|
143
102
|
// Update profile with file ID
|
|
144
103
|
return await oxyServices.updateProfile({ avatar: fileId });
|
|
145
|
-
}
|
|
146
|
-
const errorMessage = error?.message || '';
|
|
147
|
-
const status = error?.status || error?.response?.status;
|
|
148
|
-
|
|
149
|
-
// Handle authentication errors
|
|
150
|
-
if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
|
|
151
|
-
// Session sync should be handled by the app layer
|
|
152
|
-
throw new Error('Authentication failed. Please sign in again.');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// TanStack Query will automatically retry on network errors
|
|
156
|
-
throw error;
|
|
157
|
-
}
|
|
104
|
+
});
|
|
158
105
|
},
|
|
159
106
|
onMutate: async (file) => {
|
|
160
107
|
await queryClient.cancelQueries({ queryKey: queryKeys.accounts.current() });
|
|
@@ -267,38 +214,11 @@ export const useUpdatePrivacySettings = () => {
|
|
|
267
214
|
throw new Error('User ID is required');
|
|
268
215
|
}
|
|
269
216
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
} catch (tokenError) {
|
|
276
|
-
// If getting token fails, might be an offline session - try syncing
|
|
277
|
-
const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
|
|
278
|
-
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
279
|
-
// Session sync should be handled by the app layer
|
|
280
|
-
throw new Error('Session needs to be synced. Please try again.');
|
|
281
|
-
} else {
|
|
282
|
-
throw tokenError;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
return await oxyServices.updatePrivacySettings(settings, targetUserId);
|
|
289
|
-
} catch (error: any) {
|
|
290
|
-
const errorMessage = error?.message || '';
|
|
291
|
-
const status = error?.status || error?.response?.status;
|
|
292
|
-
|
|
293
|
-
// Handle authentication errors
|
|
294
|
-
if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
|
|
295
|
-
// Session sync should be handled by the app layer
|
|
296
|
-
throw new Error('Authentication failed. Please sign in again.');
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// TanStack Query will automatically retry on network errors
|
|
300
|
-
throw error;
|
|
301
|
-
}
|
|
217
|
+
return authenticatedApiCall<Record<string, unknown>>(
|
|
218
|
+
oxyServices,
|
|
219
|
+
activeSessionId,
|
|
220
|
+
() => oxyServices.updatePrivacySettings(settings, targetUserId)
|
|
221
|
+
);
|
|
302
222
|
},
|
|
303
223
|
// Optimistic update
|
|
304
224
|
onMutate: async ({ settings, userId }) => {
|
|
@@ -354,12 +274,12 @@ export const useUpdatePrivacySettings = () => {
|
|
|
354
274
|
// Also update account query if it contains privacy settings
|
|
355
275
|
const currentUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
356
276
|
if (currentUser) {
|
|
357
|
-
const updatedUser = {
|
|
277
|
+
const updatedUser: User = {
|
|
358
278
|
...currentUser,
|
|
359
|
-
privacySettings: data,
|
|
279
|
+
privacySettings: data as { [key: string]: unknown },
|
|
360
280
|
};
|
|
361
281
|
queryClient.setQueryData<User>(queryKeys.accounts.current(), updatedUser);
|
|
362
|
-
|
|
282
|
+
|
|
363
283
|
// Update authStore so frontend components see the changes immediately
|
|
364
284
|
useAuthStore.getState().setUser(updatedUser);
|
|
365
285
|
}
|
|
@@ -376,6 +296,25 @@ export const useUpdatePrivacySettings = () => {
|
|
|
376
296
|
});
|
|
377
297
|
};
|
|
378
298
|
|
|
299
|
+
/** Uploaded file data structure from API */
|
|
300
|
+
interface UploadedFile {
|
|
301
|
+
id: string;
|
|
302
|
+
originalName?: string;
|
|
303
|
+
sha256?: string;
|
|
304
|
+
mime?: string;
|
|
305
|
+
size?: number;
|
|
306
|
+
createdAt?: string;
|
|
307
|
+
metadata?: Record<string, unknown>;
|
|
308
|
+
variants?: Array<{ type: string; key: string; width?: number; height?: number; readyAt?: string; metadata?: Record<string, unknown> }>;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Upload result type that supports both single file and batch responses */
|
|
312
|
+
interface UploadResult {
|
|
313
|
+
file?: UploadedFile;
|
|
314
|
+
files?: UploadedFile[];
|
|
315
|
+
id?: string;
|
|
316
|
+
}
|
|
317
|
+
|
|
379
318
|
/**
|
|
380
319
|
* Upload file with authentication handling and progress tracking
|
|
381
320
|
*/
|
|
@@ -383,49 +322,22 @@ export const useUploadFile = () => {
|
|
|
383
322
|
const { oxyServices, activeSessionId } = useOxy();
|
|
384
323
|
|
|
385
324
|
return useMutation({
|
|
386
|
-
mutationFn: async ({
|
|
387
|
-
file,
|
|
388
|
-
visibility,
|
|
389
|
-
metadata,
|
|
390
|
-
onProgress
|
|
391
|
-
}: {
|
|
392
|
-
file: File;
|
|
393
|
-
visibility?: 'private' | 'public' | 'unlisted';
|
|
325
|
+
mutationFn: async ({
|
|
326
|
+
file,
|
|
327
|
+
visibility,
|
|
328
|
+
metadata,
|
|
329
|
+
onProgress
|
|
330
|
+
}: {
|
|
331
|
+
file: File;
|
|
332
|
+
visibility?: 'private' | 'public' | 'unlisted';
|
|
394
333
|
metadata?: Record<string, any>;
|
|
395
334
|
onProgress?: (progress: number) => void;
|
|
396
335
|
}) => {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
} catch (tokenError) {
|
|
403
|
-
// If getting token fails, might be an offline session - try syncing
|
|
404
|
-
const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
|
|
405
|
-
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
406
|
-
// Session sync should be handled by the app layer
|
|
407
|
-
throw new Error('Session needs to be synced. Please try again.');
|
|
408
|
-
} else {
|
|
409
|
-
throw tokenError;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
try {
|
|
415
|
-
return await oxyServices.assetUpload(file as any, visibility, metadata, onProgress);
|
|
416
|
-
} catch (error: any) {
|
|
417
|
-
const errorMessage = error?.message || '';
|
|
418
|
-
const status = error?.status || error?.response?.status;
|
|
419
|
-
|
|
420
|
-
// Handle authentication errors
|
|
421
|
-
if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
|
|
422
|
-
// Session sync should be handled by the app layer
|
|
423
|
-
throw new Error('Authentication failed. Please sign in again.');
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// TanStack Query will automatically retry on network errors
|
|
427
|
-
throw error;
|
|
428
|
-
}
|
|
336
|
+
return authenticatedApiCall<UploadResult>(
|
|
337
|
+
oxyServices,
|
|
338
|
+
activeSessionId,
|
|
339
|
+
() => oxyServices.assetUpload(file as any, visibility, metadata, onProgress)
|
|
340
|
+
);
|
|
429
341
|
},
|
|
430
342
|
});
|
|
431
343
|
};
|