@oxyhq/auth 1.0.0
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/README.md +56 -0
- package/dist/cjs/WebOxyProvider.js +287 -0
- package/dist/cjs/hooks/mutations/index.js +23 -0
- package/dist/cjs/hooks/mutations/mutationFactory.js +126 -0
- package/dist/cjs/hooks/mutations/useAccountMutations.js +275 -0
- package/dist/cjs/hooks/mutations/useServicesMutations.js +149 -0
- package/dist/cjs/hooks/queries/index.js +35 -0
- package/dist/cjs/hooks/queries/queryKeys.js +82 -0
- package/dist/cjs/hooks/queries/useAccountQueries.js +141 -0
- package/dist/cjs/hooks/queries/useSecurityQueries.js +45 -0
- package/dist/cjs/hooks/queries/useServicesQueries.js +113 -0
- package/dist/cjs/hooks/queryClient.js +110 -0
- package/dist/cjs/hooks/useAssets.js +225 -0
- package/dist/cjs/hooks/useFileDownloadUrl.js +91 -0
- package/dist/cjs/hooks/useFileFiltering.js +81 -0
- package/dist/cjs/hooks/useFollow.js +159 -0
- package/dist/cjs/hooks/useFollow.types.js +4 -0
- package/dist/cjs/hooks/useQueryClient.js +16 -0
- package/dist/cjs/hooks/useSessionSocket.js +215 -0
- package/dist/cjs/hooks/useWebSSO.js +146 -0
- package/dist/cjs/index.js +115 -0
- package/dist/cjs/stores/accountStore.js +226 -0
- package/dist/cjs/stores/assetStore.js +192 -0
- package/dist/cjs/stores/authStore.js +47 -0
- package/dist/cjs/stores/followStore.js +154 -0
- package/dist/cjs/utils/authHelpers.js +154 -0
- package/dist/cjs/utils/avatarUtils.js +77 -0
- package/dist/cjs/utils/errorHandlers.js +128 -0
- package/dist/cjs/utils/sessionHelpers.js +90 -0
- package/dist/cjs/utils/storageHelpers.js +147 -0
- package/dist/esm/WebOxyProvider.js +282 -0
- package/dist/esm/hooks/mutations/index.js +10 -0
- package/dist/esm/hooks/mutations/mutationFactory.js +122 -0
- package/dist/esm/hooks/mutations/useAccountMutations.js +267 -0
- package/dist/esm/hooks/mutations/useServicesMutations.js +141 -0
- package/dist/esm/hooks/queries/index.js +14 -0
- package/dist/esm/hooks/queries/queryKeys.js +76 -0
- package/dist/esm/hooks/queries/useAccountQueries.js +131 -0
- package/dist/esm/hooks/queries/useSecurityQueries.js +40 -0
- package/dist/esm/hooks/queries/useServicesQueries.js +105 -0
- package/dist/esm/hooks/queryClient.js +104 -0
- package/dist/esm/hooks/useAssets.js +220 -0
- package/dist/esm/hooks/useFileDownloadUrl.js +86 -0
- package/dist/esm/hooks/useFileFiltering.js +78 -0
- package/dist/esm/hooks/useFollow.js +154 -0
- package/dist/esm/hooks/useFollow.types.js +3 -0
- package/dist/esm/hooks/useQueryClient.js +12 -0
- package/dist/esm/hooks/useSessionSocket.js +209 -0
- package/dist/esm/hooks/useWebSSO.js +143 -0
- package/dist/esm/index.js +48 -0
- package/dist/esm/stores/accountStore.js +219 -0
- package/dist/esm/stores/assetStore.js +180 -0
- package/dist/esm/stores/authStore.js +44 -0
- package/dist/esm/stores/followStore.js +151 -0
- package/dist/esm/utils/authHelpers.js +145 -0
- package/dist/esm/utils/avatarUtils.js +72 -0
- package/dist/esm/utils/errorHandlers.js +121 -0
- package/dist/esm/utils/sessionHelpers.js +84 -0
- package/dist/esm/utils/storageHelpers.js +108 -0
- package/dist/types/WebOxyProvider.d.ts +97 -0
- package/dist/types/hooks/mutations/index.d.ts +8 -0
- package/dist/types/hooks/mutations/mutationFactory.d.ts +75 -0
- package/dist/types/hooks/mutations/useAccountMutations.d.ts +68 -0
- package/dist/types/hooks/mutations/useServicesMutations.d.ts +22 -0
- package/dist/types/hooks/queries/index.d.ts +10 -0
- package/dist/types/hooks/queries/queryKeys.d.ts +64 -0
- package/dist/types/hooks/queries/useAccountQueries.d.ts +42 -0
- package/dist/types/hooks/queries/useSecurityQueries.d.ts +14 -0
- package/dist/types/hooks/queries/useServicesQueries.d.ts +31 -0
- package/dist/types/hooks/queryClient.d.ts +18 -0
- package/dist/types/hooks/useAssets.d.ts +34 -0
- package/dist/types/hooks/useFileDownloadUrl.d.ts +18 -0
- package/dist/types/hooks/useFileFiltering.d.ts +28 -0
- package/dist/types/hooks/useFollow.d.ts +61 -0
- package/dist/types/hooks/useFollow.types.d.ts +32 -0
- package/dist/types/hooks/useQueryClient.d.ts +6 -0
- package/dist/types/hooks/useSessionSocket.d.ts +13 -0
- package/dist/types/hooks/useWebSSO.d.ts +57 -0
- package/dist/types/index.d.ts +46 -0
- package/dist/types/stores/accountStore.d.ts +33 -0
- package/dist/types/stores/assetStore.d.ts +53 -0
- package/dist/types/stores/authStore.d.ts +16 -0
- package/dist/types/stores/followStore.d.ts +24 -0
- package/dist/types/utils/authHelpers.d.ts +98 -0
- package/dist/types/utils/avatarUtils.d.ts +33 -0
- package/dist/types/utils/errorHandlers.d.ts +34 -0
- package/dist/types/utils/sessionHelpers.d.ts +63 -0
- package/dist/types/utils/storageHelpers.d.ts +27 -0
- package/package.json +71 -0
- package/src/WebOxyProvider.tsx +372 -0
- package/src/global.d.ts +1 -0
- package/src/hooks/mutations/index.ts +25 -0
- package/src/hooks/mutations/mutationFactory.ts +215 -0
- package/src/hooks/mutations/useAccountMutations.ts +344 -0
- package/src/hooks/mutations/useServicesMutations.ts +164 -0
- package/src/hooks/queries/index.ts +36 -0
- package/src/hooks/queries/queryKeys.ts +88 -0
- package/src/hooks/queries/useAccountQueries.ts +152 -0
- package/src/hooks/queries/useSecurityQueries.ts +64 -0
- package/src/hooks/queries/useServicesQueries.ts +126 -0
- package/src/hooks/queryClient.ts +112 -0
- package/src/hooks/useAssets.ts +291 -0
- package/src/hooks/useFileDownloadUrl.ts +118 -0
- package/src/hooks/useFileFiltering.ts +115 -0
- package/src/hooks/useFollow.ts +175 -0
- package/src/hooks/useFollow.types.ts +33 -0
- package/src/hooks/useQueryClient.ts +17 -0
- package/src/hooks/useSessionSocket.ts +233 -0
- package/src/hooks/useWebSSO.ts +187 -0
- package/src/index.ts +144 -0
- package/src/stores/accountStore.ts +296 -0
- package/src/stores/assetStore.ts +281 -0
- package/src/stores/authStore.ts +63 -0
- package/src/stores/followStore.ts +181 -0
- package/src/utils/authHelpers.ts +183 -0
- package/src/utils/avatarUtils.ts +103 -0
- package/src/utils/errorHandlers.ts +194 -0
- package/src/utils/sessionHelpers.ts +151 -0
- package/src/utils/storageHelpers.ts +130 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
const initialState = {
|
|
3
|
+
accounts: {},
|
|
4
|
+
accountOrder: [],
|
|
5
|
+
accountsArray: [],
|
|
6
|
+
loading: false,
|
|
7
|
+
loadingSessionIds: new Set(),
|
|
8
|
+
error: null,
|
|
9
|
+
};
|
|
10
|
+
// Helper: Build accounts array from accounts map and order
|
|
11
|
+
const buildAccountsArray = (accounts, order) => {
|
|
12
|
+
const result = [];
|
|
13
|
+
for (const id of order) {
|
|
14
|
+
const account = accounts[id];
|
|
15
|
+
if (account)
|
|
16
|
+
result.push(account);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
};
|
|
20
|
+
// Helper: Create QuickAccount from user data
|
|
21
|
+
const createQuickAccount = (sessionId, userData, existingAccount, oxyServices) => {
|
|
22
|
+
const displayName = userData.name?.full || userData.name?.first || userData.username || 'Account';
|
|
23
|
+
const userId = userData.id || userData._id?.toString();
|
|
24
|
+
// Preserve existing avatarUrl if avatar hasn't changed (prevents image reload)
|
|
25
|
+
let avatarUrl;
|
|
26
|
+
if (existingAccount && existingAccount.avatar === userData.avatar && existingAccount.avatarUrl) {
|
|
27
|
+
avatarUrl = existingAccount.avatarUrl; // Reuse existing URL
|
|
28
|
+
}
|
|
29
|
+
else if (userData.avatar && oxyServices) {
|
|
30
|
+
avatarUrl = oxyServices.getFileDownloadUrl(userData.avatar, 'thumb');
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
sessionId,
|
|
34
|
+
userId,
|
|
35
|
+
username: userData.username || '',
|
|
36
|
+
displayName,
|
|
37
|
+
avatar: userData.avatar,
|
|
38
|
+
avatarUrl,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export const useAccountStore = create((set, get) => ({
|
|
42
|
+
...initialState,
|
|
43
|
+
setAccounts: (accounts) => set((state) => {
|
|
44
|
+
const accountMap = {};
|
|
45
|
+
const order = [];
|
|
46
|
+
const seenSessionIds = new Set();
|
|
47
|
+
for (const account of accounts) {
|
|
48
|
+
if (seenSessionIds.has(account.sessionId))
|
|
49
|
+
continue;
|
|
50
|
+
seenSessionIds.add(account.sessionId);
|
|
51
|
+
accountMap[account.sessionId] = account;
|
|
52
|
+
order.push(account.sessionId);
|
|
53
|
+
}
|
|
54
|
+
const accountsArray = buildAccountsArray(accountMap, order);
|
|
55
|
+
const sameOrder = order.length === state.accountOrder.length &&
|
|
56
|
+
order.every((id, i) => id === state.accountOrder[i]);
|
|
57
|
+
const sameAccounts = sameOrder &&
|
|
58
|
+
order.every(id => {
|
|
59
|
+
const existing = state.accounts[id];
|
|
60
|
+
const newAccount = accountMap[id];
|
|
61
|
+
return existing &&
|
|
62
|
+
existing.sessionId === newAccount.sessionId &&
|
|
63
|
+
existing.userId === newAccount.userId &&
|
|
64
|
+
existing.avatar === newAccount.avatar &&
|
|
65
|
+
existing.avatarUrl === newAccount.avatarUrl;
|
|
66
|
+
});
|
|
67
|
+
if (sameAccounts)
|
|
68
|
+
return {};
|
|
69
|
+
return { accounts: accountMap, accountOrder: order, accountsArray };
|
|
70
|
+
}),
|
|
71
|
+
addAccount: (account) => set((state) => {
|
|
72
|
+
// Check if account with same sessionId exists
|
|
73
|
+
if (state.accounts[account.sessionId]) {
|
|
74
|
+
// Update existing
|
|
75
|
+
const existing = state.accounts[account.sessionId];
|
|
76
|
+
if (existing.avatar === account.avatar && existing.avatarUrl === account.avatarUrl) {
|
|
77
|
+
return {}; // No change
|
|
78
|
+
}
|
|
79
|
+
const newAccounts = { ...state.accounts, [account.sessionId]: account };
|
|
80
|
+
return {
|
|
81
|
+
accounts: newAccounts,
|
|
82
|
+
accountsArray: buildAccountsArray(newAccounts, state.accountOrder),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const newAccounts = { ...state.accounts, [account.sessionId]: account };
|
|
86
|
+
const newOrder = [account.sessionId, ...state.accountOrder];
|
|
87
|
+
return {
|
|
88
|
+
accounts: newAccounts,
|
|
89
|
+
accountOrder: newOrder,
|
|
90
|
+
accountsArray: buildAccountsArray(newAccounts, newOrder),
|
|
91
|
+
};
|
|
92
|
+
}),
|
|
93
|
+
updateAccount: (sessionId, updates) => set((state) => {
|
|
94
|
+
const existing = state.accounts[sessionId];
|
|
95
|
+
if (!existing)
|
|
96
|
+
return {};
|
|
97
|
+
const updated = { ...existing, ...updates };
|
|
98
|
+
if (existing.avatar === updated.avatar && existing.avatarUrl === updated.avatarUrl) {
|
|
99
|
+
return {}; // No change
|
|
100
|
+
}
|
|
101
|
+
const newAccounts = { ...state.accounts, [sessionId]: updated };
|
|
102
|
+
return {
|
|
103
|
+
accounts: newAccounts,
|
|
104
|
+
accountsArray: buildAccountsArray(newAccounts, state.accountOrder),
|
|
105
|
+
};
|
|
106
|
+
}),
|
|
107
|
+
removeAccount: (sessionId) => set((state) => {
|
|
108
|
+
if (!state.accounts[sessionId])
|
|
109
|
+
return {};
|
|
110
|
+
const { [sessionId]: _removed, ...rest } = state.accounts;
|
|
111
|
+
const newOrder = state.accountOrder.filter(id => id !== sessionId);
|
|
112
|
+
return {
|
|
113
|
+
accounts: rest,
|
|
114
|
+
accountOrder: newOrder,
|
|
115
|
+
accountsArray: buildAccountsArray(rest, newOrder),
|
|
116
|
+
};
|
|
117
|
+
}),
|
|
118
|
+
moveAccountToTop: (sessionId) => set((state) => {
|
|
119
|
+
if (!state.accounts[sessionId])
|
|
120
|
+
return {};
|
|
121
|
+
const filtered = state.accountOrder.filter(id => id !== sessionId);
|
|
122
|
+
const newOrder = [sessionId, ...filtered];
|
|
123
|
+
return {
|
|
124
|
+
accountOrder: newOrder,
|
|
125
|
+
accountsArray: buildAccountsArray(state.accounts, newOrder),
|
|
126
|
+
};
|
|
127
|
+
}),
|
|
128
|
+
setLoading: (loading) => set({ loading }),
|
|
129
|
+
setLoadingSession: (sessionId, loading) => set((state) => {
|
|
130
|
+
const newSet = new Set(state.loadingSessionIds);
|
|
131
|
+
if (loading) {
|
|
132
|
+
newSet.add(sessionId);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
newSet.delete(sessionId);
|
|
136
|
+
}
|
|
137
|
+
return { loadingSessionIds: newSet };
|
|
138
|
+
}),
|
|
139
|
+
setError: (error) => set({ error }),
|
|
140
|
+
loadAccounts: async (sessionIds, oxyServices, existingAccounts = [], preserveOrder = true) => {
|
|
141
|
+
const state = get();
|
|
142
|
+
const uniqueSessionIds = Array.from(new Set(sessionIds));
|
|
143
|
+
if (uniqueSessionIds.length === 0) {
|
|
144
|
+
get().setAccounts([]);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Try to get data from TanStack Query cache first
|
|
148
|
+
try {
|
|
149
|
+
// This will be called from a component, so we need to access queryClient differently
|
|
150
|
+
// For now, we'll keep the API call but optimize it
|
|
151
|
+
const existingMap = new Map(existingAccounts.map(a => [a.sessionId, a]));
|
|
152
|
+
for (const account of Object.values(state.accounts)) {
|
|
153
|
+
existingMap.set(account.sessionId, account);
|
|
154
|
+
}
|
|
155
|
+
const missingSessionIds = uniqueSessionIds.filter(id => !existingMap.has(id));
|
|
156
|
+
if (missingSessionIds.length === 0) {
|
|
157
|
+
const ordered = uniqueSessionIds
|
|
158
|
+
.map(id => existingMap.get(id))
|
|
159
|
+
.filter((acc) => acc !== undefined);
|
|
160
|
+
get().setAccounts(ordered);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (state.loading) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
set({ loading: true, error: null });
|
|
167
|
+
try {
|
|
168
|
+
const batchResults = await oxyServices.getUsersBySessions(missingSessionIds);
|
|
169
|
+
const accountMap = new Map();
|
|
170
|
+
for (const { sessionId, user: userData } of batchResults) {
|
|
171
|
+
if (userData && !accountMap.has(sessionId)) {
|
|
172
|
+
const existing = existingMap.get(sessionId);
|
|
173
|
+
accountMap.set(sessionId, createQuickAccount(sessionId, userData, existing, oxyServices));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const [sessionId, account] of accountMap) {
|
|
177
|
+
existingMap.set(sessionId, account);
|
|
178
|
+
}
|
|
179
|
+
const orderToUse = preserveOrder ? uniqueSessionIds : [...uniqueSessionIds, ...state.accountOrder];
|
|
180
|
+
const seen = new Set();
|
|
181
|
+
const ordered = [];
|
|
182
|
+
for (const sessionId of orderToUse) {
|
|
183
|
+
if (seen.has(sessionId))
|
|
184
|
+
continue;
|
|
185
|
+
seen.add(sessionId);
|
|
186
|
+
const account = existingMap.get(sessionId);
|
|
187
|
+
if (account)
|
|
188
|
+
ordered.push(account);
|
|
189
|
+
}
|
|
190
|
+
get().setAccounts(ordered);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to load accounts';
|
|
194
|
+
if (__DEV__) {
|
|
195
|
+
console.error('AccountStore: Failed to load accounts:', error);
|
|
196
|
+
}
|
|
197
|
+
set({ error: errorMessage });
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
set({ loading: false });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to load accounts';
|
|
205
|
+
if (__DEV__) {
|
|
206
|
+
console.error('AccountStore: Failed to load accounts:', error);
|
|
207
|
+
}
|
|
208
|
+
set({ error: errorMessage, loading: false });
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
reset: () => set(initialState),
|
|
212
|
+
}));
|
|
213
|
+
// Selectors for performance - return cached array to prevent infinite loops
|
|
214
|
+
export const useAccounts = () => {
|
|
215
|
+
return useAccountStore(state => state.accountsArray);
|
|
216
|
+
};
|
|
217
|
+
export const useAccountLoading = () => useAccountStore(s => s.loading);
|
|
218
|
+
export const useAccountError = () => useAccountStore(s => s.error);
|
|
219
|
+
export const useAccountLoadingSession = (sessionId) => useAccountStore(s => s.loadingSessionIds.has(sessionId));
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
const initialState = {
|
|
3
|
+
assets: {},
|
|
4
|
+
uploadProgress: {},
|
|
5
|
+
loading: {
|
|
6
|
+
uploading: false,
|
|
7
|
+
linking: false,
|
|
8
|
+
deleting: false,
|
|
9
|
+
},
|
|
10
|
+
errors: {},
|
|
11
|
+
};
|
|
12
|
+
export const useAssetStore = create((set, get) => ({
|
|
13
|
+
...initialState,
|
|
14
|
+
// Asset management
|
|
15
|
+
setAsset: (asset) => {
|
|
16
|
+
set((state) => ({
|
|
17
|
+
assets: {
|
|
18
|
+
...state.assets,
|
|
19
|
+
[asset.id]: asset,
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
},
|
|
23
|
+
setAssets: (assets) => {
|
|
24
|
+
set((state) => {
|
|
25
|
+
const assetMap = assets.reduce((acc, asset) => {
|
|
26
|
+
acc[asset.id] = asset;
|
|
27
|
+
return acc;
|
|
28
|
+
}, {});
|
|
29
|
+
return {
|
|
30
|
+
assets: {
|
|
31
|
+
...state.assets,
|
|
32
|
+
...assetMap,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
removeAsset: (assetId) => {
|
|
38
|
+
set((state) => {
|
|
39
|
+
const { [assetId]: removed, ...rest } = state.assets;
|
|
40
|
+
return { assets: rest };
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
// Upload progress
|
|
44
|
+
setUploadProgress: (fileId, progress) => {
|
|
45
|
+
set((state) => ({
|
|
46
|
+
uploadProgress: {
|
|
47
|
+
...state.uploadProgress,
|
|
48
|
+
[fileId]: progress,
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
51
|
+
},
|
|
52
|
+
removeUploadProgress: (fileId) => {
|
|
53
|
+
set((state) => {
|
|
54
|
+
const { [fileId]: removed, ...rest } = state.uploadProgress;
|
|
55
|
+
return { uploadProgress: rest };
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
// Link management
|
|
59
|
+
addLink: (assetId, link) => {
|
|
60
|
+
set((state) => {
|
|
61
|
+
const asset = state.assets[assetId];
|
|
62
|
+
if (!asset)
|
|
63
|
+
return state;
|
|
64
|
+
// Check if link already exists
|
|
65
|
+
const existingLink = asset.links.find((l) => l.app === link.app &&
|
|
66
|
+
l.entityType === link.entityType &&
|
|
67
|
+
l.entityId === link.entityId);
|
|
68
|
+
if (existingLink)
|
|
69
|
+
return state;
|
|
70
|
+
const updatedAsset = {
|
|
71
|
+
...asset,
|
|
72
|
+
links: [...asset.links, link],
|
|
73
|
+
usageCount: asset.links.length + 1,
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
assets: {
|
|
77
|
+
...state.assets,
|
|
78
|
+
[assetId]: updatedAsset,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
removeLink: (assetId, app, entityType, entityId) => {
|
|
84
|
+
set((state) => {
|
|
85
|
+
const asset = state.assets[assetId];
|
|
86
|
+
if (!asset)
|
|
87
|
+
return state;
|
|
88
|
+
const filteredLinks = asset.links.filter((link) => !(link.app === app &&
|
|
89
|
+
link.entityType === entityType &&
|
|
90
|
+
link.entityId === entityId));
|
|
91
|
+
const updatedAsset = {
|
|
92
|
+
...asset,
|
|
93
|
+
links: filteredLinks,
|
|
94
|
+
usageCount: filteredLinks.length,
|
|
95
|
+
status: filteredLinks.length === 0 ? 'trash' : asset.status,
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
assets: {
|
|
99
|
+
...state.assets,
|
|
100
|
+
[assetId]: updatedAsset,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
// Loading states
|
|
106
|
+
setUploading: (uploading) => {
|
|
107
|
+
set((state) => ({
|
|
108
|
+
loading: { ...state.loading, uploading },
|
|
109
|
+
}));
|
|
110
|
+
},
|
|
111
|
+
setLinking: (linking) => {
|
|
112
|
+
set((state) => ({
|
|
113
|
+
loading: { ...state.loading, linking },
|
|
114
|
+
}));
|
|
115
|
+
},
|
|
116
|
+
setDeleting: (deleting) => {
|
|
117
|
+
set((state) => ({
|
|
118
|
+
loading: { ...state.loading, deleting },
|
|
119
|
+
}));
|
|
120
|
+
},
|
|
121
|
+
// Error management
|
|
122
|
+
setUploadError: (error) => {
|
|
123
|
+
set((state) => ({
|
|
124
|
+
errors: { ...state.errors, upload: error },
|
|
125
|
+
}));
|
|
126
|
+
},
|
|
127
|
+
setLinkError: (error) => {
|
|
128
|
+
set((state) => ({
|
|
129
|
+
errors: { ...state.errors, link: error },
|
|
130
|
+
}));
|
|
131
|
+
},
|
|
132
|
+
setDeleteError: (error) => {
|
|
133
|
+
set((state) => ({
|
|
134
|
+
errors: { ...state.errors, delete: error },
|
|
135
|
+
}));
|
|
136
|
+
},
|
|
137
|
+
clearErrors: () => {
|
|
138
|
+
set({ errors: {} });
|
|
139
|
+
},
|
|
140
|
+
// Utility methods
|
|
141
|
+
getAssetsByApp: (app) => {
|
|
142
|
+
const { assets } = get();
|
|
143
|
+
return Object.values(assets).filter((asset) => asset.links.some((link) => link.app === app));
|
|
144
|
+
},
|
|
145
|
+
getAssetsByEntity: (app, entityType, entityId) => {
|
|
146
|
+
const { assets } = get();
|
|
147
|
+
return Object.values(assets).filter((asset) => asset.links.some((link) => link.app === app &&
|
|
148
|
+
link.entityType === entityType &&
|
|
149
|
+
link.entityId === entityId));
|
|
150
|
+
},
|
|
151
|
+
getAssetUsageCount: (assetId) => {
|
|
152
|
+
const { assets } = get();
|
|
153
|
+
const asset = assets[assetId];
|
|
154
|
+
return asset ? asset.usageCount : 0;
|
|
155
|
+
},
|
|
156
|
+
isAssetLinked: (assetId, app, entityType, entityId) => {
|
|
157
|
+
const { assets } = get();
|
|
158
|
+
const asset = assets[assetId];
|
|
159
|
+
if (!asset)
|
|
160
|
+
return false;
|
|
161
|
+
return asset.links.some((link) => link.app === app &&
|
|
162
|
+
link.entityType === entityType &&
|
|
163
|
+
link.entityId === entityId);
|
|
164
|
+
},
|
|
165
|
+
// Reset store
|
|
166
|
+
reset: () => {
|
|
167
|
+
set(initialState);
|
|
168
|
+
},
|
|
169
|
+
}));
|
|
170
|
+
// Selector hooks for convenience
|
|
171
|
+
export const useAssets = () => useAssetStore((state) => Object.values(state.assets));
|
|
172
|
+
export const useAsset = (assetId) => useAssetStore((state) => state.assets[assetId]);
|
|
173
|
+
export const useUploadProgress = () => useAssetStore((state) => state.uploadProgress);
|
|
174
|
+
export const useAssetLoading = () => useAssetStore((state) => state.loading);
|
|
175
|
+
export const useAssetErrors = () => useAssetStore((state) => state.errors);
|
|
176
|
+
// Typed selectors for specific use cases
|
|
177
|
+
export const useAssetsByApp = (app) => useAssetStore((state) => state.getAssetsByApp(app));
|
|
178
|
+
export const useAssetsByEntity = (app, entityType, entityId) => useAssetStore((state) => state.getAssetsByEntity(app, entityType, entityId));
|
|
179
|
+
export const useAssetUsageCount = (assetId) => useAssetStore((state) => state.getAssetUsageCount(assetId));
|
|
180
|
+
export const useIsAssetLinked = (assetId, app, entityType, entityId) => useAssetStore((state) => state.isAssetLinked(assetId, app, entityType, entityId));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { createDebugLogger } from '@oxyhq/core';
|
|
3
|
+
const debug = createDebugLogger('AuthStore');
|
|
4
|
+
export const useAuthStore = create((set, get) => ({
|
|
5
|
+
user: null,
|
|
6
|
+
isAuthenticated: false,
|
|
7
|
+
isLoading: false,
|
|
8
|
+
error: null,
|
|
9
|
+
lastUserFetch: null,
|
|
10
|
+
loginSuccess: (user) => set({
|
|
11
|
+
isLoading: false,
|
|
12
|
+
isAuthenticated: true,
|
|
13
|
+
user,
|
|
14
|
+
lastUserFetch: Date.now(),
|
|
15
|
+
}),
|
|
16
|
+
loginFailure: (error) => set({ isLoading: false, error }),
|
|
17
|
+
logout: () => set({
|
|
18
|
+
user: null,
|
|
19
|
+
isAuthenticated: false,
|
|
20
|
+
lastUserFetch: null,
|
|
21
|
+
}),
|
|
22
|
+
setUser: (user) => set({ user, lastUserFetch: Date.now() }),
|
|
23
|
+
fetchUser: async (oxyServices, forceRefresh = false) => {
|
|
24
|
+
const state = get();
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const cacheAge = state.lastUserFetch ? now - state.lastUserFetch : Number.POSITIVE_INFINITY;
|
|
27
|
+
const cacheValid = cacheAge < 5 * 60 * 1000; // 5 minutes cache
|
|
28
|
+
// Use cached data if available and not forcing refresh
|
|
29
|
+
if (!forceRefresh && state.user && cacheValid) {
|
|
30
|
+
debug.log('Using cached user data (age:', cacheAge, 'ms)');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
set({ isLoading: true, error: null });
|
|
34
|
+
try {
|
|
35
|
+
const user = await oxyServices.getCurrentUser();
|
|
36
|
+
set({ user, isLoading: false, isAuthenticated: true, lastUserFetch: now });
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to fetch user';
|
|
40
|
+
debug.error('Error fetching user:', error);
|
|
41
|
+
set({ error: errorMessage, isLoading: false });
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
export const useFollowStore = create((set, get) => ({
|
|
3
|
+
followingUsers: {},
|
|
4
|
+
loadingUsers: {},
|
|
5
|
+
fetchingUsers: {},
|
|
6
|
+
errors: {},
|
|
7
|
+
followerCounts: {},
|
|
8
|
+
followingCounts: {},
|
|
9
|
+
loadingCounts: {},
|
|
10
|
+
setFollowingStatus: (userId, isFollowing) => set((state) => ({
|
|
11
|
+
followingUsers: { ...state.followingUsers, [userId]: isFollowing },
|
|
12
|
+
errors: { ...state.errors, [userId]: null },
|
|
13
|
+
})),
|
|
14
|
+
clearFollowError: (userId) => set((state) => ({
|
|
15
|
+
errors: { ...state.errors, [userId]: null },
|
|
16
|
+
})),
|
|
17
|
+
resetFollowState: () => set({
|
|
18
|
+
followingUsers: {},
|
|
19
|
+
loadingUsers: {},
|
|
20
|
+
fetchingUsers: {},
|
|
21
|
+
errors: {},
|
|
22
|
+
followerCounts: {},
|
|
23
|
+
followingCounts: {},
|
|
24
|
+
loadingCounts: {},
|
|
25
|
+
}),
|
|
26
|
+
fetchFollowStatus: async (userId, oxyServices) => {
|
|
27
|
+
set((state) => ({
|
|
28
|
+
fetchingUsers: { ...state.fetchingUsers, [userId]: true },
|
|
29
|
+
errors: { ...state.errors, [userId]: null },
|
|
30
|
+
}));
|
|
31
|
+
try {
|
|
32
|
+
const response = await oxyServices.getFollowStatus(userId);
|
|
33
|
+
set((state) => ({
|
|
34
|
+
followingUsers: { ...state.followingUsers, [userId]: response.isFollowing },
|
|
35
|
+
fetchingUsers: { ...state.fetchingUsers, [userId]: false },
|
|
36
|
+
errors: { ...state.errors, [userId]: null },
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
set((state) => ({
|
|
41
|
+
fetchingUsers: { ...state.fetchingUsers, [userId]: false },
|
|
42
|
+
errors: { ...state.errors, [userId]: error?.message || 'Failed to fetch follow status' },
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
toggleFollowUser: async (userId, oxyServices, isCurrentlyFollowing) => {
|
|
47
|
+
set((state) => ({
|
|
48
|
+
loadingUsers: { ...state.loadingUsers, [userId]: true },
|
|
49
|
+
errors: { ...state.errors, [userId]: null },
|
|
50
|
+
}));
|
|
51
|
+
try {
|
|
52
|
+
let response;
|
|
53
|
+
let newFollowState;
|
|
54
|
+
if (isCurrentlyFollowing) {
|
|
55
|
+
response = await oxyServices.unfollowUser(userId);
|
|
56
|
+
newFollowState = false;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
response = await oxyServices.followUser(userId);
|
|
60
|
+
newFollowState = true;
|
|
61
|
+
}
|
|
62
|
+
// Update follow status
|
|
63
|
+
set((state) => ({
|
|
64
|
+
followingUsers: { ...state.followingUsers, [userId]: newFollowState },
|
|
65
|
+
loadingUsers: { ...state.loadingUsers, [userId]: false },
|
|
66
|
+
errors: { ...state.errors, [userId]: null },
|
|
67
|
+
}));
|
|
68
|
+
// Update counts if the response includes them
|
|
69
|
+
// The API returns counts for both users:
|
|
70
|
+
// - followers: target user's follower count (the user being followed)
|
|
71
|
+
// - following: current user's following count (the user doing the following)
|
|
72
|
+
if (response && response.counts) {
|
|
73
|
+
const { counts } = response;
|
|
74
|
+
// Get current user ID from oxyServices
|
|
75
|
+
const currentUserId = oxyServices.getCurrentUserId();
|
|
76
|
+
set((state) => {
|
|
77
|
+
const updates = {};
|
|
78
|
+
// Update target user's follower count (the user being followed)
|
|
79
|
+
updates.followerCounts = {
|
|
80
|
+
...state.followerCounts,
|
|
81
|
+
[userId]: counts.followers
|
|
82
|
+
};
|
|
83
|
+
// Update current user's following count (the user doing the following)
|
|
84
|
+
if (currentUserId) {
|
|
85
|
+
updates.followingCounts = {
|
|
86
|
+
...state.followingCounts,
|
|
87
|
+
[currentUserId]: counts.following
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return updates;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
set((state) => ({
|
|
96
|
+
loadingUsers: { ...state.loadingUsers, [userId]: false },
|
|
97
|
+
errors: { ...state.errors, [userId]: error?.message || 'Failed to update follow status' },
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
setFollowerCount: (userId, count) => set((state) => ({
|
|
102
|
+
followerCounts: { ...state.followerCounts, [userId]: count },
|
|
103
|
+
})),
|
|
104
|
+
setFollowingCount: (userId, count) => set((state) => ({
|
|
105
|
+
followingCounts: { ...state.followingCounts, [userId]: count },
|
|
106
|
+
})),
|
|
107
|
+
updateCountsFromFollowAction: (targetUserId, action, counts, currentUserId) => {
|
|
108
|
+
set((state) => {
|
|
109
|
+
const updates = {};
|
|
110
|
+
// Update target user's follower count (the user being followed)
|
|
111
|
+
updates.followerCounts = {
|
|
112
|
+
...state.followerCounts,
|
|
113
|
+
[targetUserId]: counts.followers
|
|
114
|
+
};
|
|
115
|
+
// Update current user's following count (the user doing the following)
|
|
116
|
+
if (currentUserId) {
|
|
117
|
+
updates.followingCounts = {
|
|
118
|
+
...state.followingCounts,
|
|
119
|
+
[currentUserId]: counts.following
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return updates;
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
fetchUserCounts: async (userId, oxyServices) => {
|
|
126
|
+
set((state) => ({
|
|
127
|
+
loadingCounts: { ...state.loadingCounts, [userId]: true },
|
|
128
|
+
}));
|
|
129
|
+
try {
|
|
130
|
+
const user = await oxyServices.getUserById(userId);
|
|
131
|
+
if (user && user._count) {
|
|
132
|
+
set((state) => ({
|
|
133
|
+
followerCounts: {
|
|
134
|
+
...state.followerCounts,
|
|
135
|
+
[userId]: user._count?.followers || 0
|
|
136
|
+
},
|
|
137
|
+
followingCounts: {
|
|
138
|
+
...state.followingCounts,
|
|
139
|
+
[userId]: user._count?.following || 0
|
|
140
|
+
},
|
|
141
|
+
loadingCounts: { ...state.loadingCounts, [userId]: false },
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
set((state) => ({
|
|
147
|
+
loadingCounts: { ...state.loadingCounts, [userId]: false },
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
}));
|