@oxyhq/services 5.4.4 → 5.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/lib/commonjs/core/index.js +22 -3
  2. package/lib/commonjs/core/index.js.map +1 -1
  3. package/lib/commonjs/index.js +50 -1
  4. package/lib/commonjs/index.js.map +1 -1
  5. package/lib/commonjs/ui/components/FollowButton.js +79 -31
  6. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  7. package/lib/commonjs/ui/components/OxySignInButton.js +2 -2
  8. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  9. package/lib/commonjs/ui/context/OxyContext.js +11 -1
  10. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  11. package/lib/commonjs/ui/hooks/index.js +13 -0
  12. package/lib/commonjs/ui/hooks/index.js.map +1 -0
  13. package/lib/commonjs/ui/hooks/useFollow.js +184 -0
  14. package/lib/commonjs/ui/hooks/useFollow.js.map +1 -0
  15. package/lib/commonjs/ui/index.js +25 -1
  16. package/lib/commonjs/ui/index.js.map +1 -1
  17. package/lib/commonjs/ui/screens/AccountCenterScreen.js +4 -3
  18. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  19. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +7 -6
  20. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  21. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +3 -2
  22. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  23. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +3 -2
  24. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  25. package/lib/commonjs/ui/screens/AppInfoScreen.js +6 -8
  26. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  27. package/lib/commonjs/ui/screens/SignInScreen.js +1 -1
  28. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  29. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +5 -4
  30. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  31. package/lib/commonjs/ui/store/index.js +219 -4
  32. package/lib/commonjs/ui/store/index.js.map +1 -1
  33. package/lib/module/core/index.js +22 -3
  34. package/lib/module/core/index.js.map +1 -1
  35. package/lib/module/index.js +6 -2
  36. package/lib/module/index.js.map +1 -1
  37. package/lib/module/ui/components/FollowButton.js +80 -32
  38. package/lib/module/ui/components/FollowButton.js.map +1 -1
  39. package/lib/module/ui/components/OxySignInButton.js +2 -2
  40. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  41. package/lib/module/ui/context/OxyContext.js +11 -1
  42. package/lib/module/ui/context/OxyContext.js.map +1 -1
  43. package/lib/module/ui/hooks/index.js +4 -0
  44. package/lib/module/ui/hooks/index.js.map +1 -0
  45. package/lib/module/ui/hooks/useFollow.js +180 -0
  46. package/lib/module/ui/hooks/useFollow.js.map +1 -0
  47. package/lib/module/ui/index.js +9 -0
  48. package/lib/module/ui/index.js.map +1 -1
  49. package/lib/module/ui/screens/AccountCenterScreen.js +4 -3
  50. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  51. package/lib/module/ui/screens/AccountOverviewScreen.js +7 -6
  52. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  53. package/lib/module/ui/screens/AccountSettingsScreen.js +3 -2
  54. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  55. package/lib/module/ui/screens/AccountSwitcherScreen.js +3 -2
  56. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  57. package/lib/module/ui/screens/AppInfoScreen.js +6 -8
  58. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  59. package/lib/module/ui/screens/SignInScreen.js +1 -1
  60. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  61. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +5 -4
  62. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  63. package/lib/module/ui/store/index.js +215 -4
  64. package/lib/module/ui/store/index.js.map +1 -1
  65. package/lib/typescript/core/index.d.ts +16 -3
  66. package/lib/typescript/core/index.d.ts.map +1 -1
  67. package/lib/typescript/index.d.ts +4 -2
  68. package/lib/typescript/index.d.ts.map +1 -1
  69. package/lib/typescript/ui/components/FollowButton.d.ts +1 -0
  70. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  71. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  72. package/lib/typescript/ui/hooks/index.d.ts +2 -0
  73. package/lib/typescript/ui/hooks/index.d.ts.map +1 -0
  74. package/lib/typescript/ui/hooks/useFollow.d.ts +43 -0
  75. package/lib/typescript/ui/hooks/useFollow.d.ts.map +1 -0
  76. package/lib/typescript/ui/index.d.ts +3 -0
  77. package/lib/typescript/ui/index.d.ts.map +1 -1
  78. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  79. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  80. package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
  81. package/lib/typescript/ui/store/index.d.ts +47 -0
  82. package/lib/typescript/ui/store/index.d.ts.map +1 -1
  83. package/package.json +1 -1
  84. package/src/core/index.ts +88 -3
  85. package/src/index.ts +19 -3
  86. package/src/ui/components/FollowButton.tsx +114 -56
  87. package/src/ui/components/OxySignInButton.tsx +2 -2
  88. package/src/ui/context/OxyContext.tsx +12 -2
  89. package/src/ui/hooks/index.ts +1 -0
  90. package/src/ui/hooks/useFollow.ts +173 -0
  91. package/src/ui/index.ts +9 -0
  92. package/src/ui/screens/AccountCenterScreen.tsx +17 -15
  93. package/src/ui/screens/AccountOverviewScreen.tsx +25 -25
  94. package/src/ui/screens/AccountSettingsScreen.tsx +30 -30
  95. package/src/ui/screens/AccountSwitcherScreen.tsx +34 -33
  96. package/src/ui/screens/AppInfoScreen.tsx +153 -155
  97. package/src/ui/screens/SignInScreen.tsx +2 -2
  98. package/src/ui/screens/karma/KarmaCenterScreen.tsx +4 -4
  99. package/src/ui/store/index.ts +197 -3
@@ -1,4 +1,4 @@
1
- import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';
1
+ import { configureStore, createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit';
2
2
  import type { User } from '../../models/interfaces';
3
3
 
4
4
  interface AuthState {
@@ -8,16 +8,142 @@ interface AuthState {
8
8
  error: string | null;
9
9
  }
10
10
 
11
- const initialState: AuthState = {
11
+ interface FollowState {
12
+ // Track follow status for each user ID
13
+ followingUsers: Record<string, boolean>;
14
+ // Track loading state for each user ID
15
+ loadingUsers: Record<string, boolean>;
16
+ // Track which user IDs are currently being fetched (to prevent duplicate requests)
17
+ fetchingUsers: Record<string, boolean>;
18
+ // Track any follow/unfollow errors
19
+ errors: Record<string, string | null>;
20
+ }
21
+
22
+ const initialAuthState: AuthState = {
12
23
  user: null,
13
24
  isAuthenticated: false,
14
25
  isLoading: false,
15
26
  error: null,
16
27
  };
17
28
 
29
+ const initialFollowState: FollowState = {
30
+ followingUsers: {},
31
+ loadingUsers: {},
32
+ fetchingUsers: {},
33
+ errors: {},
34
+ };
35
+
36
+ // Async thunk for fetching follow status from backend with deduplication
37
+ export const fetchFollowStatus = createAsyncThunk(
38
+ 'follow/fetchFollowStatus',
39
+ async ({ userId, oxyServices }: { userId: string; oxyServices: any }, { rejectWithValue }) => {
40
+ try {
41
+ // Use the proper core service method
42
+ const response = await oxyServices.getFollowStatus(userId);
43
+ return { userId, isFollowing: response.isFollowing };
44
+ } catch (error: any) {
45
+ // Ignore authentication errors when user isn't signed in - don't update state
46
+ if (error?.status === 401 || error?.message?.includes('Authentication')) {
47
+ return rejectWithValue('Not authenticated');
48
+ }
49
+ // Log other failures and reject to not update state
50
+ console.warn(`Failed to fetch follow status for user ${userId}:`, error);
51
+ return rejectWithValue(error?.message || 'Failed to fetch follow status');
52
+ }
53
+ },
54
+ {
55
+ // Prevent duplicate requests for the same user ID
56
+ condition: ({ userId }, { getState }) => {
57
+ const state = getState() as RootState;
58
+ const isAlreadyFetching = state.follow.fetchingUsers[userId];
59
+
60
+ if (isAlreadyFetching) {
61
+ console.log(`⚡ Deduplicating fetch request for user ${userId} - already in progress`);
62
+ return false; // Cancel this request
63
+ }
64
+
65
+ return true; // Allow this request
66
+ }
67
+ }
68
+ );
69
+
70
+ // Async thunk for following/unfollowing users using core services
71
+ export const toggleFollowUser = createAsyncThunk(
72
+ 'follow/toggleFollowUser',
73
+ async ({ userId, oxyServices, isCurrentlyFollowing }: {
74
+ userId: string;
75
+ oxyServices: any;
76
+ isCurrentlyFollowing: boolean;
77
+ }, { rejectWithValue, dispatch }) => {
78
+ try {
79
+ let response: { success?: boolean; message?: string; action?: string };
80
+ let newFollowState: boolean;
81
+
82
+ if (isCurrentlyFollowing) {
83
+ // Use the core service to unfollow user
84
+ response = await oxyServices.unfollowUser(userId);
85
+ newFollowState = false;
86
+ } else {
87
+ // Use the core service to follow user
88
+ response = await oxyServices.followUser(userId);
89
+ newFollowState = true;
90
+ }
91
+
92
+ // Check if the response indicates success (different APIs might return different formats)
93
+ const isSuccess = response.success !== false && response.action !== 'error';
94
+
95
+ if (isSuccess) {
96
+ return {
97
+ userId,
98
+ isFollowing: newFollowState,
99
+ message: response.message || `Successfully ${newFollowState ? 'followed' : 'unfollowed'} user`
100
+ };
101
+ } else {
102
+ return rejectWithValue(response.message || `Failed to ${newFollowState ? 'follow' : 'unfollow'} user`);
103
+ }
104
+ } catch (error: any) {
105
+ // Enhanced error handling with state mismatch detection
106
+ let errorMessage = 'Network error occurred';
107
+
108
+ if (error?.message) {
109
+ errorMessage = error.message;
110
+ } else if (error?.response?.data?.message) {
111
+ errorMessage = error.response.data.message;
112
+ } else if (error?.data?.message) {
113
+ errorMessage = error.data.message;
114
+ }
115
+
116
+ // Handle state mismatch errors by syncing with backend
117
+ if (errorMessage.includes('Not following this user') && isCurrentlyFollowing) {
118
+ console.warn(`State mismatch detected for user ${userId}: Frontend thinks following, backend says not following. Syncing state...`);
119
+ // Auto-sync with backend state
120
+ try {
121
+ const actualStatus = await oxyServices.getFollowStatus(userId);
122
+ dispatch({ type: 'follow/setFollowingStatus', payload: { userId, isFollowing: actualStatus.isFollowing } });
123
+ return rejectWithValue('State synced with backend. Please try again.');
124
+ } catch (syncError) {
125
+ console.error('Failed to sync state with backend:', syncError);
126
+ }
127
+ } else if (errorMessage.includes('Already following this user') && !isCurrentlyFollowing) {
128
+ console.warn(`State mismatch detected for user ${userId}: Frontend thinks not following, backend says following. Syncing state...`);
129
+ // Auto-sync with backend state
130
+ try {
131
+ const actualStatus = await oxyServices.getFollowStatus(userId);
132
+ dispatch({ type: 'follow/setFollowingStatus', payload: { userId, isFollowing: actualStatus.isFollowing } });
133
+ return rejectWithValue('State synced with backend. Please try again.');
134
+ } catch (syncError) {
135
+ console.error('Failed to sync state with backend:', syncError);
136
+ }
137
+ }
138
+
139
+ return rejectWithValue(errorMessage);
140
+ }
141
+ }
142
+ );
143
+
18
144
  const authSlice = createSlice({
19
145
  name: 'auth',
20
- initialState,
146
+ initialState: initialAuthState,
21
147
  reducers: {
22
148
  loginStart(state: AuthState) {
23
149
  state.isLoading = true;
@@ -39,11 +165,79 @@ const authSlice = createSlice({
39
165
  },
40
166
  });
41
167
 
168
+ const followSlice = createSlice({
169
+ name: 'follow',
170
+ initialState: initialFollowState,
171
+ reducers: {
172
+ setFollowingStatus(state: FollowState, action: PayloadAction<{ userId: string; isFollowing: boolean }>) {
173
+ const { userId, isFollowing } = action.payload;
174
+ state.followingUsers[userId] = isFollowing;
175
+ state.errors[userId] = null;
176
+ },
177
+ clearFollowError(state: FollowState, action: PayloadAction<string>) {
178
+ const userId = action.payload;
179
+ state.errors[userId] = null;
180
+ },
181
+ resetFollowState(state: FollowState) {
182
+ state.followingUsers = {};
183
+ state.loadingUsers = {};
184
+ state.fetchingUsers = {};
185
+ state.errors = {};
186
+ },
187
+ },
188
+ extraReducers: (builder) => {
189
+ builder
190
+ // Handle fetchFollowStatus
191
+ .addCase(fetchFollowStatus.pending, (state, action) => {
192
+ const { userId } = action.meta.arg;
193
+ state.fetchingUsers[userId] = true;
194
+ state.errors[userId] = null;
195
+ })
196
+ .addCase(fetchFollowStatus.fulfilled, (state, action) => {
197
+ const { userId, isFollowing } = action.payload;
198
+ state.followingUsers[userId] = isFollowing;
199
+ state.fetchingUsers[userId] = false;
200
+ state.errors[userId] = null;
201
+ })
202
+ .addCase(fetchFollowStatus.rejected, (state, action) => {
203
+ const { userId } = action.meta.arg;
204
+ state.fetchingUsers[userId] = false;
205
+ // Don't update follow state on fetch errors - preserve existing/initial state
206
+ if (action.payload !== 'Not authenticated') {
207
+ console.warn(`Failed to fetch follow status for user ${userId}:`, action.payload);
208
+ }
209
+ })
210
+ // Handle toggleFollowUser
211
+ .addCase(toggleFollowUser.pending, (state, action) => {
212
+ const { userId } = action.meta.arg;
213
+ state.loadingUsers[userId] = true;
214
+ state.errors[userId] = null;
215
+ })
216
+ .addCase(toggleFollowUser.fulfilled, (state, action) => {
217
+ const { userId, isFollowing } = action.payload;
218
+ state.followingUsers[userId] = isFollowing;
219
+ state.loadingUsers[userId] = false;
220
+ state.errors[userId] = null;
221
+ })
222
+ .addCase(toggleFollowUser.rejected, (state, action) => {
223
+ const { userId } = action.meta.arg;
224
+ state.loadingUsers[userId] = false;
225
+ state.errors[userId] = action.error.message || 'Failed to update follow status';
226
+ });
227
+ },
228
+ });
229
+
42
230
  export const { loginStart, loginSuccess, loginFailure, logout } = authSlice.actions;
231
+ export const { setFollowingStatus, clearFollowError, resetFollowState } = followSlice.actions;
232
+
233
+ // Selectors for follow state
234
+ export const selectIsUserBeingFetched = (state: RootState, userId: string) =>
235
+ state.follow.fetchingUsers[userId] ?? false;
43
236
 
44
237
  export const store = configureStore({
45
238
  reducer: {
46
239
  auth: authSlice.reducer,
240
+ follow: followSlice.reducer,
47
241
  },
48
242
  });
49
243