@oxyhq/services 22.5.0 → 22.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/index.js +1 -1
- package/lib/commonjs/ui/client.js +1 -1
- package/lib/commonjs/ui/components/FontLoader.js +18 -145
- package/lib/commonjs/ui/components/FontLoader.js.map +1 -1
- package/lib/commonjs/ui/components/FontLoader.native.js +88 -0
- package/lib/commonjs/ui/components/FontLoader.native.js.map +1 -0
- package/lib/commonjs/ui/components/OxyProvider.js +1 -1
- package/lib/commonjs/ui/components/fileManagement/FileDetailsModal.js +34 -41
- package/lib/commonjs/ui/components/fileManagement/FileDetailsModal.js.map +1 -1
- package/lib/commonjs/ui/components/fileManagement/UploadPreview.js.map +1 -1
- package/lib/commonjs/ui/components/fileManagement/styles.js +1 -225
- package/lib/commonjs/ui/components/fileManagement/styles.js.map +1 -1
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +0 -2
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +5 -2
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/context/useOxyAccountGraph.js +7 -0
- package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +10 -4
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/userCache.js +33 -9
- package/lib/commonjs/ui/hooks/queries/userCache.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAvatarPicker.js +23 -8
- package/lib/commonjs/ui/hooks/useAvatarPicker.js.map +1 -1
- package/lib/commonjs/ui/hooks/useFileDownloadUrl.js +7 -21
- package/lib/commonjs/ui/hooks/useFileDownloadUrl.js.map +1 -1
- package/lib/commonjs/ui/hooks/useResolvedFileUrls.js +163 -0
- package/lib/commonjs/ui/hooks/useResolvedFileUrls.js.map +1 -0
- package/lib/commonjs/ui/index.js +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +51 -22
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +227 -204
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js +26 -9
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/photoGridLayout.js +44 -0
- package/lib/commonjs/ui/screens/fileManagement/photoGridLayout.js.map +1 -0
- package/lib/commonjs/ui/session/tokenTransport.js +21 -10
- package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
- package/lib/commonjs/ui/utils/fileManagement.js +3 -43
- package/lib/commonjs/ui/utils/fileManagement.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/client.js +1 -1
- package/lib/module/ui/client.js.map +1 -1
- package/lib/module/ui/components/FontLoader.js +18 -146
- package/lib/module/ui/components/FontLoader.js.map +1 -1
- package/lib/module/ui/components/FontLoader.native.js +83 -0
- package/lib/module/ui/components/FontLoader.native.js.map +1 -0
- package/lib/module/ui/components/OxyProvider.js +1 -1
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/fileManagement/FileDetailsModal.js +34 -41
- package/lib/module/ui/components/fileManagement/FileDetailsModal.js.map +1 -1
- package/lib/module/ui/components/fileManagement/UploadPreview.js.map +1 -1
- package/lib/module/ui/components/fileManagement/styles.js +0 -224
- package/lib/module/ui/components/fileManagement/styles.js.map +1 -1
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +0 -2
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +5 -2
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/context/useOxyAccountGraph.js +7 -0
- package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
- package/lib/module/ui/hooks/queries/useAccountQueries.js +10 -4
- package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/module/ui/hooks/queries/userCache.js +33 -9
- package/lib/module/ui/hooks/queries/userCache.js.map +1 -1
- package/lib/module/ui/hooks/useAvatarPicker.js +23 -8
- package/lib/module/ui/hooks/useAvatarPicker.js.map +1 -1
- package/lib/module/ui/hooks/useFileDownloadUrl.js +7 -21
- package/lib/module/ui/hooks/useFileDownloadUrl.js.map +1 -1
- package/lib/module/ui/hooks/useResolvedFileUrls.js +157 -0
- package/lib/module/ui/hooks/useResolvedFileUrls.js.map +1 -0
- package/lib/module/ui/index.js +1 -1
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +52 -23
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +229 -206
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js +26 -9
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/photoGridLayout.js +39 -0
- package/lib/module/ui/screens/fileManagement/photoGridLayout.js.map +1 -0
- package/lib/module/ui/session/tokenTransport.js +21 -10
- package/lib/module/ui/session/tokenTransport.js.map +1 -1
- package/lib/module/ui/utils/fileManagement.js +3 -42
- package/lib/module/ui/utils/fileManagement.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/FontLoader.d.ts +14 -5
- package/lib/typescript/commonjs/ui/components/FontLoader.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/FontLoader.native.d.ts +13 -0
- package/lib/typescript/commonjs/ui/components/FontLoader.native.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/fileManagement/FileDetailsModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/fileManagement/styles.d.ts +0 -218
- package/lib/typescript/commonjs/ui/components/fileManagement/styles.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +2 -2
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts +9 -0
- package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/userCache.d.ts +3 -0
- package/lib/typescript/commonjs/ui/hooks/queries/userCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useFileDownloadUrl.d.ts +3 -2
- package/lib/typescript/commonjs/ui/hooks/useFileDownloadUrl.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useResolvedFileUrls.d.ts +48 -0
- package/lib/typescript/commonjs/ui/hooks/useResolvedFileUrls.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/photoGridLayout.d.ts +37 -0
- package/lib/typescript/commonjs/ui/screens/fileManagement/photoGridLayout.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +15 -8
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts +4 -11
- package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/client.d.ts +1 -1
- package/lib/typescript/module/ui/client.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/FontLoader.d.ts +14 -5
- package/lib/typescript/module/ui/components/FontLoader.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/FontLoader.native.d.ts +13 -0
- package/lib/typescript/module/ui/components/FontLoader.native.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/fileManagement/FileDetailsModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/fileManagement/styles.d.ts +0 -218
- package/lib/typescript/module/ui/components/fileManagement/styles.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +2 -2
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts +9 -0
- package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/userCache.d.ts +3 -0
- package/lib/typescript/module/ui/hooks/queries/userCache.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useFileDownloadUrl.d.ts +3 -2
- package/lib/typescript/module/ui/hooks/useFileDownloadUrl.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useResolvedFileUrls.d.ts +48 -0
- package/lib/typescript/module/ui/hooks/useResolvedFileUrls.d.ts.map +1 -0
- package/lib/typescript/module/ui/index.d.ts +1 -1
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/photoGridLayout.d.ts +37 -0
- package/lib/typescript/module/ui/screens/fileManagement/photoGridLayout.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/tokenTransport.d.ts +15 -8
- package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/fileManagement.d.ts +4 -11
- package/lib/typescript/module/ui/utils/fileManagement.d.ts.map +1 -1
- package/package.json +5 -10
- package/src/ui/components/FontLoader.native.tsx +88 -0
- package/src/ui/components/FontLoader.tsx +16 -151
- package/src/ui/components/fileManagement/FileDetailsModal.tsx +29 -28
- package/src/ui/components/fileManagement/UploadPreview.tsx +1 -1
- package/src/ui/components/fileManagement/styles.ts +0 -225
- package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +0 -1
- package/src/ui/context/hooks/useAuthOperations.ts +5 -2
- package/src/ui/context/useOxyAccountGraph.ts +5 -0
- package/src/ui/hooks/queries/__tests__/userCache.test.ts +29 -0
- package/src/ui/hooks/queries/useAccountQueries.ts +10 -4
- package/src/ui/hooks/queries/userCache.ts +31 -8
- package/src/ui/hooks/useAvatarPicker.ts +27 -7
- package/src/ui/hooks/useFileDownloadUrl.ts +7 -23
- package/src/ui/hooks/useResolvedFileUrls.ts +170 -0
- package/src/ui/screens/FileManagementScreen.tsx +55 -27
- package/src/ui/screens/__tests__/photoGridLayout.test.ts +57 -0
- package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +229 -215
- package/src/ui/screens/fileManagement/hooks/useFileUploadState.ts +27 -3
- package/src/ui/screens/fileManagement/photoGridLayout.ts +45 -0
- package/src/ui/session/__tests__/tokenTransport.test.ts +25 -10
- package/src/ui/session/tokenTransport.ts +21 -10
- package/src/ui/utils/fileManagement.ts +8 -49
- package/lib/typescript/types/expo-vector-icons.d.ts +0 -22
- package/src/types/expo-vector-icons.d.ts +0 -22
|
@@ -382,43 +382,6 @@ export const fileManagementStyles = StyleSheet.create({
|
|
|
382
382
|
flex: 1,
|
|
383
383
|
padding: 16,
|
|
384
384
|
},
|
|
385
|
-
fileDetailCard: {
|
|
386
|
-
padding: 18,
|
|
387
|
-
borderRadius: 14,
|
|
388
|
-
borderWidth: 1,
|
|
389
|
-
alignItems: 'center',
|
|
390
|
-
},
|
|
391
|
-
fileDetailIcon: {
|
|
392
|
-
marginBottom: 16,
|
|
393
|
-
},
|
|
394
|
-
fileDetailName: {
|
|
395
|
-
fontSize: 20,
|
|
396
|
-
fontWeight: 'bold',
|
|
397
|
-
textAlign: 'center',
|
|
398
|
-
marginBottom: 24,
|
|
399
|
-
},
|
|
400
|
-
fileDetailInfo: {
|
|
401
|
-
width: '100%',
|
|
402
|
-
marginBottom: 32,
|
|
403
|
-
},
|
|
404
|
-
detailRow: {
|
|
405
|
-
flexDirection: 'row',
|
|
406
|
-
justifyContent: 'space-between',
|
|
407
|
-
alignItems: 'flex-start',
|
|
408
|
-
marginBottom: 12,
|
|
409
|
-
flexWrap: 'wrap',
|
|
410
|
-
},
|
|
411
|
-
detailLabel: {
|
|
412
|
-
fontSize: 16,
|
|
413
|
-
fontWeight: '500',
|
|
414
|
-
flex: 1,
|
|
415
|
-
minWidth: 100,
|
|
416
|
-
},
|
|
417
|
-
detailValue: {
|
|
418
|
-
fontSize: 16,
|
|
419
|
-
flex: 2,
|
|
420
|
-
textAlign: 'right',
|
|
421
|
-
},
|
|
422
385
|
modalActions: {
|
|
423
386
|
flexDirection: 'row',
|
|
424
387
|
gap: 12,
|
|
@@ -858,191 +821,3 @@ export const fileManagementStyles = StyleSheet.create({
|
|
|
858
821
|
borderRadius: 8,
|
|
859
822
|
},
|
|
860
823
|
});
|
|
861
|
-
|
|
862
|
-
/**
|
|
863
|
-
* Styles for the dedicated photo picker view used when `selectMode + image-only`
|
|
864
|
-
* is active (e.g. the avatar picker context). This view is intentionally
|
|
865
|
-
* flagship-grade: black backdrop, translucent header, edge-to-edge grid.
|
|
866
|
-
*
|
|
867
|
-
* Kept in a separate StyleSheet so the rest of `fileManagementStyles` does not
|
|
868
|
-
* carry a black-background bias when used elsewhere.
|
|
869
|
-
*/
|
|
870
|
-
export const photoPickerStyles = StyleSheet.create({
|
|
871
|
-
root: {
|
|
872
|
-
flex: 1,
|
|
873
|
-
backgroundColor: '#000000',
|
|
874
|
-
},
|
|
875
|
-
headerSpacer: {
|
|
876
|
-
// Reserved for the absolute header so content does not sit underneath it.
|
|
877
|
-
backgroundColor: '#000000',
|
|
878
|
-
},
|
|
879
|
-
header: {
|
|
880
|
-
position: 'absolute',
|
|
881
|
-
top: 0,
|
|
882
|
-
left: 0,
|
|
883
|
-
right: 0,
|
|
884
|
-
backgroundColor: 'rgba(0,0,0,0.92)',
|
|
885
|
-
flexDirection: 'row',
|
|
886
|
-
alignItems: 'center',
|
|
887
|
-
justifyContent: 'space-between',
|
|
888
|
-
paddingHorizontal: 12,
|
|
889
|
-
zIndex: 30,
|
|
890
|
-
},
|
|
891
|
-
headerRow: {
|
|
892
|
-
flexDirection: 'row',
|
|
893
|
-
alignItems: 'center',
|
|
894
|
-
justifyContent: 'space-between',
|
|
895
|
-
width: '100%',
|
|
896
|
-
// Material 3 top app bar height — leaves a generous tap target for
|
|
897
|
-
// the Cancel / Upload pills without crowding the title.
|
|
898
|
-
height: 56,
|
|
899
|
-
},
|
|
900
|
-
headerSide: {
|
|
901
|
-
flexBasis: 0,
|
|
902
|
-
flexGrow: 1,
|
|
903
|
-
flexDirection: 'row',
|
|
904
|
-
alignItems: 'center',
|
|
905
|
-
},
|
|
906
|
-
headerSideRight: {
|
|
907
|
-
justifyContent: 'flex-end',
|
|
908
|
-
},
|
|
909
|
-
headerSideLeft: {
|
|
910
|
-
justifyContent: 'flex-start',
|
|
911
|
-
},
|
|
912
|
-
headerTitle: {
|
|
913
|
-
fontSize: 17,
|
|
914
|
-
fontWeight: '600',
|
|
915
|
-
color: '#FFFFFF',
|
|
916
|
-
letterSpacing: -0.2,
|
|
917
|
-
textAlign: 'center',
|
|
918
|
-
},
|
|
919
|
-
headerCancel: {
|
|
920
|
-
paddingHorizontal: 6,
|
|
921
|
-
paddingVertical: 8,
|
|
922
|
-
minHeight: 36,
|
|
923
|
-
minWidth: 44,
|
|
924
|
-
justifyContent: 'center',
|
|
925
|
-
},
|
|
926
|
-
headerCancelText: {
|
|
927
|
-
fontSize: 17,
|
|
928
|
-
fontWeight: '500',
|
|
929
|
-
color: '#FFFFFF',
|
|
930
|
-
},
|
|
931
|
-
headerPrimaryPill: {
|
|
932
|
-
flexDirection: 'row',
|
|
933
|
-
alignItems: 'center',
|
|
934
|
-
gap: 6,
|
|
935
|
-
paddingHorizontal: 14,
|
|
936
|
-
paddingVertical: 8,
|
|
937
|
-
borderRadius: 999,
|
|
938
|
-
minHeight: 36,
|
|
939
|
-
},
|
|
940
|
-
headerPrimaryPillIconOnly: {
|
|
941
|
-
paddingHorizontal: 8,
|
|
942
|
-
width: 36,
|
|
943
|
-
justifyContent: 'center',
|
|
944
|
-
},
|
|
945
|
-
headerPrimaryText: {
|
|
946
|
-
color: '#FFFFFF',
|
|
947
|
-
fontSize: 15,
|
|
948
|
-
fontWeight: '600',
|
|
949
|
-
},
|
|
950
|
-
headerProgressBarTrack: {
|
|
951
|
-
position: 'absolute',
|
|
952
|
-
left: 0,
|
|
953
|
-
right: 0,
|
|
954
|
-
bottom: 0,
|
|
955
|
-
height: 2,
|
|
956
|
-
backgroundColor: 'rgba(255,255,255,0.12)',
|
|
957
|
-
},
|
|
958
|
-
headerProgressBarFill: {
|
|
959
|
-
height: '100%',
|
|
960
|
-
},
|
|
961
|
-
grid: {
|
|
962
|
-
flex: 1,
|
|
963
|
-
},
|
|
964
|
-
gridContent: {
|
|
965
|
-
// Top inset is set dynamically (header height + safe area).
|
|
966
|
-
paddingBottom: 24,
|
|
967
|
-
},
|
|
968
|
-
cellWrapper: {
|
|
969
|
-
position: 'relative',
|
|
970
|
-
// width/height set dynamically per device
|
|
971
|
-
},
|
|
972
|
-
cellInner: {
|
|
973
|
-
flex: 1,
|
|
974
|
-
borderRadius: 8,
|
|
975
|
-
overflow: 'hidden',
|
|
976
|
-
backgroundColor: '#111111',
|
|
977
|
-
},
|
|
978
|
-
cellImage: {
|
|
979
|
-
width: '100%',
|
|
980
|
-
height: '100%',
|
|
981
|
-
},
|
|
982
|
-
cellRing: {
|
|
983
|
-
position: 'absolute',
|
|
984
|
-
top: 0,
|
|
985
|
-
left: 0,
|
|
986
|
-
right: 0,
|
|
987
|
-
bottom: 0,
|
|
988
|
-
borderRadius: 8,
|
|
989
|
-
borderWidth: 3,
|
|
990
|
-
},
|
|
991
|
-
cellDim: {
|
|
992
|
-
opacity: 0.6,
|
|
993
|
-
},
|
|
994
|
-
cellBadge: {
|
|
995
|
-
position: 'absolute',
|
|
996
|
-
top: 6,
|
|
997
|
-
right: 6,
|
|
998
|
-
minWidth: 22,
|
|
999
|
-
height: 22,
|
|
1000
|
-
paddingHorizontal: 6,
|
|
1001
|
-
borderRadius: 11,
|
|
1002
|
-
alignItems: 'center',
|
|
1003
|
-
justifyContent: 'center',
|
|
1004
|
-
},
|
|
1005
|
-
cellBadgeText: {
|
|
1006
|
-
color: '#FFFFFF',
|
|
1007
|
-
fontSize: 12,
|
|
1008
|
-
fontWeight: '700',
|
|
1009
|
-
lineHeight: 14,
|
|
1010
|
-
},
|
|
1011
|
-
empty: {
|
|
1012
|
-
flex: 1,
|
|
1013
|
-
alignItems: 'center',
|
|
1014
|
-
justifyContent: 'center',
|
|
1015
|
-
paddingHorizontal: 32,
|
|
1016
|
-
},
|
|
1017
|
-
emptyIconWrap: {
|
|
1018
|
-
opacity: 0.3,
|
|
1019
|
-
marginBottom: 16,
|
|
1020
|
-
},
|
|
1021
|
-
emptyTitle: {
|
|
1022
|
-
fontSize: 17,
|
|
1023
|
-
fontWeight: '600',
|
|
1024
|
-
textAlign: 'center',
|
|
1025
|
-
marginBottom: 6,
|
|
1026
|
-
},
|
|
1027
|
-
emptySubtitle: {
|
|
1028
|
-
fontSize: 14,
|
|
1029
|
-
fontWeight: '400',
|
|
1030
|
-
textAlign: 'center',
|
|
1031
|
-
opacity: 0.7,
|
|
1032
|
-
marginBottom: 24,
|
|
1033
|
-
},
|
|
1034
|
-
emptyCta: {
|
|
1035
|
-
flexDirection: 'row',
|
|
1036
|
-
alignItems: 'center',
|
|
1037
|
-
gap: 8,
|
|
1038
|
-
paddingHorizontal: 22,
|
|
1039
|
-
paddingVertical: 12,
|
|
1040
|
-
borderRadius: 999,
|
|
1041
|
-
},
|
|
1042
|
-
emptyCtaText: {
|
|
1043
|
-
color: '#FFFFFF',
|
|
1044
|
-
fontSize: 15,
|
|
1045
|
-
fontWeight: '600',
|
|
1046
|
-
},
|
|
1047
|
-
});
|
|
1048
|
-
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
|
2
2
|
import { View, Text, type LayoutChangeEvent } from 'react-native';
|
|
3
3
|
import type { FileMetadata } from '@oxyhq/core';
|
|
4
|
-
// Using plain React Native styles (nativewind not installed in this repo)
|
|
5
4
|
|
|
6
5
|
export interface JustifiedPhotoGridProps {
|
|
7
6
|
photos: FileMetadata[];
|
|
@@ -170,8 +170,11 @@ export const useAuthOperations = ({
|
|
|
170
170
|
logger?.('Failed to register sign-in into device session set', registrationError);
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
//
|
|
174
|
-
|
|
173
|
+
// Hydrate the full user from the bearer (`GET /users/me`) — `verifyChallenge`
|
|
174
|
+
// has already planted the access token for this session, so the bearer IS
|
|
175
|
+
// the session identity. Avoids a second, session-id-keyed source of truth
|
|
176
|
+
// (`GET /session/user/:sessionId`) that can disagree with the bearer.
|
|
177
|
+
fullUser = await oxyServices.getCurrentUser();
|
|
175
178
|
|
|
176
179
|
// Fetch device sessions
|
|
177
180
|
let allDeviceSessions: ClientSession[] = [];
|
|
@@ -5,6 +5,7 @@ import { logger as loggerUtil } from '@oxyhq/core';
|
|
|
5
5
|
import { useAuthStore } from '../stores/authStore';
|
|
6
6
|
import { isUnauthorizedStatus } from './oxyContextHelpers';
|
|
7
7
|
import { resetSessionScopedStores } from '../stores/resetSessionScopedStores';
|
|
8
|
+
import { ASSET_DOWNLOAD_URLS_QUERY_KEY } from '../hooks/useResolvedFileUrls';
|
|
8
9
|
import type { CommitInput } from './oxyContextTypes';
|
|
9
10
|
|
|
10
11
|
interface UseOxyAccountGraphParams {
|
|
@@ -63,6 +64,10 @@ export function useOxyAccountGraph({
|
|
|
63
64
|
|
|
64
65
|
const runPostAccountSwitchSideEffects = useCallback(async (): Promise<void> => {
|
|
65
66
|
resetSessionScopedStores();
|
|
67
|
+
// Scoped media tokens are per-viewer — drop any cached URLs immediately so
|
|
68
|
+
// `keepPreviousData`-style placeholders cannot flash the prior account's
|
|
69
|
+
// private thumbnails while the new bearer mint lands.
|
|
70
|
+
queryClient.removeQueries({ queryKey: [ASSET_DOWNLOAD_URLS_QUERY_KEY] });
|
|
66
71
|
await refreshAccounts();
|
|
67
72
|
queryClient.invalidateQueries();
|
|
68
73
|
}, [refreshAccounts, queryClient]);
|
|
@@ -268,6 +268,35 @@ describe('upsertCachedUser — keys & viewer scoping', () => {
|
|
|
268
268
|
expect(readByUsername(qc, 'alice', 'viewerB')).toBeUndefined();
|
|
269
269
|
});
|
|
270
270
|
|
|
271
|
+
it('never stores viewer-relative relationship on the viewer-independent by-id key', () => {
|
|
272
|
+
const qc = makeClient();
|
|
273
|
+
upsertCachedUser(
|
|
274
|
+
qc,
|
|
275
|
+
{ id: 'u1', username: 'alice', relationship: { isFollowing: true, followsYou: true } },
|
|
276
|
+
'viewerA',
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
expect(readById(qc, 'u1')?.relationship).toBeUndefined();
|
|
280
|
+
expect(readByUsername(qc, 'alice', 'viewerA')?.relationship).toEqual({
|
|
281
|
+
isFollowing: true,
|
|
282
|
+
followsYou: true,
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('strips a stale relationship from an existing by-id entry on merge', () => {
|
|
287
|
+
const qc = makeClient();
|
|
288
|
+
qc.setQueryData(queryKeys.users.detail('u1'), {
|
|
289
|
+
id: 'u1',
|
|
290
|
+
username: 'alice',
|
|
291
|
+
relationship: { isFollowing: true, followsYou: true },
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
upsertCachedUser(qc, { id: 'u1', username: 'alice', avatar: 'file_a' }, 'viewerA');
|
|
295
|
+
|
|
296
|
+
expect(readById(qc, 'u1')?.relationship).toBeUndefined();
|
|
297
|
+
expect(readById(qc, 'u1')?.avatar).toBe('file_a');
|
|
298
|
+
});
|
|
299
|
+
|
|
271
300
|
it('is case-insensitive on username (byUsername normalization)', () => {
|
|
272
301
|
const qc = makeClient();
|
|
273
302
|
upsertCachedUser(qc, { id: 'u1', username: 'AlIcE' }, 'v1');
|
|
@@ -50,6 +50,15 @@ export const useUserProfiles = (sessionIds: string[], options?: { enabled?: bool
|
|
|
50
50
|
/**
|
|
51
51
|
* Get current authenticated user.
|
|
52
52
|
*
|
|
53
|
+
* Hydrates from `GET /users/me` (the bearer identifies the session) rather than
|
|
54
|
+
* `GET /session/user/:activeSessionId`. Post zero-cookie cutover the bearer is
|
|
55
|
+
* the single source of session truth, so keying the CURRENT user off a session
|
|
56
|
+
* id in the URL was a second, independently-updated source that could disagree
|
|
57
|
+
* with the bearer during an account switch — the request fired under the
|
|
58
|
+
* previous account's token against the new account's session id and 404'd. The
|
|
59
|
+
* query KEY stays scoped to the active account so the cache re-scopes (and
|
|
60
|
+
* refetches) on a switch; only the URL stopped carrying a session id.
|
|
61
|
+
*
|
|
53
62
|
* The store-mirror effect must NOT overwrite the in-memory user while a
|
|
54
63
|
* write mutation is in flight — otherwise a stale background refetch
|
|
55
64
|
* landing between optimistic update and server-confirmed update would
|
|
@@ -67,10 +76,7 @@ export const useCurrentUser = (options?: { enabled?: boolean }) => {
|
|
|
67
76
|
const query = useQuery({
|
|
68
77
|
queryKey: queryKeys.accounts.current(activeSessionId),
|
|
69
78
|
queryFn: async () => {
|
|
70
|
-
|
|
71
|
-
throw new Error('No active session');
|
|
72
|
-
}
|
|
73
|
-
return await oxyServices.getUserBySession(activeSessionId);
|
|
79
|
+
return await oxyServices.getCurrentUser();
|
|
74
80
|
},
|
|
75
81
|
enabled: (options?.enabled !== false) && isAuthenticated && !!activeSessionId,
|
|
76
82
|
staleTime: 1 * 60 * 1000, // 1 minute for current user
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
* marked stale — it is already managed).
|
|
30
30
|
* - Nested objects (`name`, `_count`, `relationship`) merge field-by-field, so
|
|
31
31
|
* a partial `name`/`_count`/`relationship` never replaces a fuller one.
|
|
32
|
+
* - `relationship` is written ONLY under the viewer-scoped by-username key —
|
|
33
|
+
* the viewer-independent by-id key never stores it (prevents one viewer's
|
|
34
|
+
* follow state from leaking into every other viewer's identity cache).
|
|
32
35
|
* - Anti-degradation: a good `username` / `name.displayName` / `avatar` is
|
|
33
36
|
* never overwritten by a degraded/empty one (empty username, the
|
|
34
37
|
* `'Unknown user'` ghost-author sentinel, `null` avatar).
|
|
@@ -184,8 +187,17 @@ function mergeRelationship(
|
|
|
184
187
|
/**
|
|
185
188
|
* Merge a (normalized) incoming user over an existing cache entry: keep every
|
|
186
189
|
* existing field, override only with the meaningful fields of `incoming`.
|
|
190
|
+
*
|
|
191
|
+
* When `includeRelationship` is false (the viewer-independent by-id key), the
|
|
192
|
+
* viewer-relative `relationship` field is never read, written, or preserved —
|
|
193
|
+
* only the by-username key carries it (`useUserByUsername`).
|
|
187
194
|
*/
|
|
188
|
-
function mergeUsers(
|
|
195
|
+
function mergeUsers(
|
|
196
|
+
existing: CachedUser,
|
|
197
|
+
incoming: CachedUser,
|
|
198
|
+
options?: { includeRelationship?: boolean },
|
|
199
|
+
): CachedUser {
|
|
200
|
+
const includeRelationship = options?.includeRelationship ?? true;
|
|
189
201
|
const merged: CachedUser = { ...existing };
|
|
190
202
|
for (const [key, value] of Object.entries(incoming)) {
|
|
191
203
|
if (key === 'name' || key === '_count' || key === 'relationship') continue;
|
|
@@ -199,8 +211,12 @@ function mergeUsers(existing: CachedUser, incoming: CachedUser): CachedUser {
|
|
|
199
211
|
if (name !== undefined) merged.name = name;
|
|
200
212
|
const count = mergeCount(existing._count, incoming._count);
|
|
201
213
|
if (count !== undefined) merged._count = count;
|
|
202
|
-
|
|
203
|
-
|
|
214
|
+
if (includeRelationship) {
|
|
215
|
+
const relationship = mergeRelationship(existing.relationship, incoming.relationship);
|
|
216
|
+
if (relationship !== undefined) merged.relationship = relationship;
|
|
217
|
+
} else {
|
|
218
|
+
delete merged.relationship;
|
|
219
|
+
}
|
|
204
220
|
return merged;
|
|
205
221
|
}
|
|
206
222
|
|
|
@@ -209,17 +225,20 @@ function upsertOneKey(
|
|
|
209
225
|
queryClient: QueryClient,
|
|
210
226
|
key: readonly unknown[],
|
|
211
227
|
incoming: CachedUser,
|
|
228
|
+
options: { includeRelationship: boolean },
|
|
212
229
|
): void {
|
|
230
|
+
const mergeOpts = { includeRelationship: options.includeRelationship };
|
|
213
231
|
const existing = queryClient.getQueryData<CacheableUser>(key);
|
|
214
232
|
if (existing === undefined) {
|
|
215
233
|
// Cold slot: seed the full incoming object, STALE, so react-query refetches
|
|
216
234
|
// the full authoritative profile (relationship, counts, createdAt, …).
|
|
217
|
-
|
|
235
|
+
const seeded = mergeUsers({ id: incoming.id }, incoming, mergeOpts);
|
|
236
|
+
queryClient.setQueryData<CachedUser>(key, seeded, { updatedAt: 0 });
|
|
218
237
|
return;
|
|
219
238
|
}
|
|
220
239
|
// Existing entry: merge and leave its freshness lifecycle untouched. The
|
|
221
240
|
// existing entry is keyed by `incoming.id`, so use it as the fallback id.
|
|
222
|
-
const merged = mergeUsers(toCachedUser(existing, incoming.id), incoming);
|
|
241
|
+
const merged = mergeUsers(toCachedUser(existing, incoming.id), incoming, mergeOpts);
|
|
223
242
|
const dataUpdatedAt = queryClient.getQueryState(key)?.dataUpdatedAt ?? 0;
|
|
224
243
|
queryClient.setQueryData<CachedUser>(key, merged, { updatedAt: dataUpdatedAt });
|
|
225
244
|
}
|
|
@@ -253,8 +272,12 @@ export function upsertCachedUser(
|
|
|
253
272
|
const incoming = normalizeIncoming(user);
|
|
254
273
|
if (!incoming) return;
|
|
255
274
|
|
|
256
|
-
// By-id identity entry (read by `useUserById`). Not viewer-scoped
|
|
257
|
-
|
|
275
|
+
// By-id identity entry (read by `useUserById`). Not viewer-scoped — never store
|
|
276
|
+
// the viewer-relative `relationship` here or one viewer's follow state leaks
|
|
277
|
+
// into every other viewer's by-id cache entry.
|
|
278
|
+
upsertOneKey(queryClient, queryKeys.users.detail(incoming.id), incoming, {
|
|
279
|
+
includeRelationship: false,
|
|
280
|
+
});
|
|
258
281
|
|
|
259
282
|
const username = incoming.username;
|
|
260
283
|
if (typeof username === 'string' && username.trim() !== '') {
|
|
@@ -263,7 +286,7 @@ export function upsertCachedUser(
|
|
|
263
286
|
// the key through the SAME helper the hook uses so username normalization
|
|
264
287
|
// (`trim().toLowerCase()`) matches byte-for-byte.
|
|
265
288
|
const key = queryKeys.users.byUsername(username, resolveViewerId(viewerId));
|
|
266
|
-
upsertOneKey(queryClient, key, incoming);
|
|
289
|
+
upsertOneKey(queryClient, key, incoming, { includeRelationship: true });
|
|
267
290
|
}
|
|
268
291
|
}
|
|
269
292
|
|
|
@@ -100,11 +100,19 @@ export function useAvatarPicker({
|
|
|
100
100
|
);
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
|
-
* After the user picks a file in the FileManagement sheet,
|
|
104
|
-
*
|
|
103
|
+
* After the user picks a file in the FileManagement sheet, resolve a working
|
|
104
|
+
* source URL and route to the crop screen.
|
|
105
|
+
*
|
|
106
|
+
* The picked file is usually PRIVATE, so the synchronous
|
|
107
|
+
* `getFileDownloadUrl` (public CDN origin) would 404 and the crop screen's
|
|
108
|
+
* `Image.getSize` would fail silently. We resolve the authenticated URL via
|
|
109
|
+
* `assetGetUrl` (which throws on failure — unlike `getFileDownloadUrlAsync`,
|
|
110
|
+
* which swallows errors and falls back to the broken public URL) so a
|
|
111
|
+
* failure surfaces a real user-facing error instead of a blank crop canvas.
|
|
112
|
+
* No variant is requested — cropping needs the original.
|
|
105
113
|
*/
|
|
106
114
|
const handleFilePicked = useCallback(
|
|
107
|
-
(file: FileMetadata) => {
|
|
115
|
+
async (file: FileMetadata) => {
|
|
108
116
|
if (!file.contentType?.startsWith('image/')) {
|
|
109
117
|
toast.error(
|
|
110
118
|
translate(currentLanguage ?? undefined, 'editProfile.toasts.selectImage') ||
|
|
@@ -113,10 +121,22 @@ export function useAvatarPicker({
|
|
|
113
121
|
return;
|
|
114
122
|
}
|
|
115
123
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
let sourceUri: string;
|
|
125
|
+
try {
|
|
126
|
+
const resolved = await oxyServices.assetGetUrl(file.id);
|
|
127
|
+
if (!resolved?.url) {
|
|
128
|
+
throw new Error('No download URL returned for the selected image');
|
|
129
|
+
}
|
|
130
|
+
sourceUri = resolved.url;
|
|
131
|
+
} catch (e: unknown) {
|
|
132
|
+
const message = e instanceof Error ? e.message : undefined;
|
|
133
|
+
toast.error(
|
|
134
|
+
message ||
|
|
135
|
+
translate(currentLanguage ?? undefined, 'editProfile.toasts.cropMeasureFailed') ||
|
|
136
|
+
'Could not load the selected image',
|
|
137
|
+
);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
120
140
|
|
|
121
141
|
showBottomSheet({
|
|
122
142
|
screen: 'AvatarCrop',
|
|
@@ -15,8 +15,9 @@ export interface UseFileDownloadUrlResult {
|
|
|
15
15
|
/**
|
|
16
16
|
* Hook to resolve a file's download URL asynchronously.
|
|
17
17
|
*
|
|
18
|
-
* Uses `getFileDownloadUrlAsync
|
|
19
|
-
*
|
|
18
|
+
* Uses `getFileDownloadUrlAsync`, which returns a scoped media-token stream URL
|
|
19
|
+
* for private assets. There is no sync CDN fallback — the synchronous
|
|
20
|
+
* `getFileDownloadUrl` is public-CDN-only and 404s for private uploads.
|
|
20
21
|
*/
|
|
21
22
|
export const useFileDownloadUrl = (
|
|
22
23
|
oxyServices: OxyServices | null | undefined,
|
|
@@ -54,35 +55,18 @@ export const useFileDownloadUrl = (
|
|
|
54
55
|
setError(null);
|
|
55
56
|
|
|
56
57
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (typeof instance.getFileDownloadUrlAsync === 'function') {
|
|
60
|
-
resolvedUrl = await instance.getFileDownloadUrlAsync(targetFileId, variant, expiresIn);
|
|
58
|
+
if (typeof instance.getFileDownloadUrlAsync !== 'function') {
|
|
59
|
+
throw new Error('getFileDownloadUrlAsync is not available on this OxyServices instance');
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
resolvedUrl = instance.getFileDownloadUrl(targetFileId, variant, expiresIn);
|
|
65
|
-
}
|
|
62
|
+
const resolvedUrl = await instance.getFileDownloadUrlAsync(targetFileId, variant, expiresIn);
|
|
66
63
|
|
|
67
64
|
if (!cancelled) {
|
|
68
65
|
setUrl(resolvedUrl || null);
|
|
69
66
|
}
|
|
70
67
|
} catch (err: unknown) {
|
|
71
|
-
// Fallback to sync URL on error where possible
|
|
72
|
-
try {
|
|
73
|
-
if (typeof instance.getFileDownloadUrl === 'function') {
|
|
74
|
-
const fallbackUrl = instance.getFileDownloadUrl(targetFileId, variant, expiresIn);
|
|
75
|
-
if (!cancelled) {
|
|
76
|
-
setUrl(fallbackUrl || null);
|
|
77
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
78
|
-
}
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
// Secondary failure: surface the original error below.
|
|
83
|
-
}
|
|
84
|
-
|
|
85
68
|
if (!cancelled) {
|
|
69
|
+
setUrl(null);
|
|
86
70
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
87
71
|
}
|
|
88
72
|
} finally {
|