@optifye/dashboard-core 6.11.21 → 6.11.22
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/dist/index.js +171 -29
- package/dist/index.mjs +171 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2232,6 +2232,61 @@ var clearAuthSnapshot = () => {
|
|
|
2232
2232
|
safeStorageRemoveItem(AUTH_SNAPSHOT_STORAGE_KEY);
|
|
2233
2233
|
};
|
|
2234
2234
|
|
|
2235
|
+
// src/lib/auth/authAuditLog.ts
|
|
2236
|
+
var TAB_ID = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID().slice(0, 8) : Math.random().toString(36).slice(2, 10);
|
|
2237
|
+
var FLUSH_INTERVAL_MS = 3e4;
|
|
2238
|
+
var MAX_QUEUE_SIZE = 50;
|
|
2239
|
+
var queue = [];
|
|
2240
|
+
var flushTimer = null;
|
|
2241
|
+
var apiBaseUrl = "";
|
|
2242
|
+
var getAccessToken = null;
|
|
2243
|
+
var initAuthAuditLog = (baseUrl, tokenGetter) => {
|
|
2244
|
+
apiBaseUrl = baseUrl;
|
|
2245
|
+
getAccessToken = tokenGetter;
|
|
2246
|
+
if (typeof window === "undefined") return;
|
|
2247
|
+
if (!flushTimer) {
|
|
2248
|
+
flushTimer = setInterval(flushAuthAuditLog, FLUSH_INTERVAL_MS);
|
|
2249
|
+
window.addEventListener("visibilitychange", () => {
|
|
2250
|
+
if (document.visibilityState === "hidden") {
|
|
2251
|
+
void flushAuthAuditLog();
|
|
2252
|
+
}
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
};
|
|
2256
|
+
var logAuthEvent = (eventType, details = {}) => {
|
|
2257
|
+
queue.push({
|
|
2258
|
+
event_type: eventType,
|
|
2259
|
+
tab_id: TAB_ID,
|
|
2260
|
+
details: {
|
|
2261
|
+
...details,
|
|
2262
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
2263
|
+
}
|
|
2264
|
+
});
|
|
2265
|
+
if (queue.length >= MAX_QUEUE_SIZE) {
|
|
2266
|
+
void flushAuthAuditLog();
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
var flushAuthAuditLog = async () => {
|
|
2270
|
+
if (queue.length === 0 || !apiBaseUrl || !getAccessToken) return;
|
|
2271
|
+
const events = queue.splice(0, MAX_QUEUE_SIZE);
|
|
2272
|
+
try {
|
|
2273
|
+
const token = await getAccessToken();
|
|
2274
|
+
if (!token) return;
|
|
2275
|
+
const body = JSON.stringify({ events });
|
|
2276
|
+
const url = `${apiBaseUrl}/api/auth/audit`;
|
|
2277
|
+
await fetch(url, {
|
|
2278
|
+
method: "POST",
|
|
2279
|
+
headers: {
|
|
2280
|
+
"Content-Type": "application/json",
|
|
2281
|
+
Authorization: `Bearer ${token}`
|
|
2282
|
+
},
|
|
2283
|
+
body,
|
|
2284
|
+
keepalive: document.visibilityState === "hidden"
|
|
2285
|
+
});
|
|
2286
|
+
} catch {
|
|
2287
|
+
}
|
|
2288
|
+
};
|
|
2289
|
+
|
|
2235
2290
|
// src/lib/auth/session.ts
|
|
2236
2291
|
var DEFAULT_MIN_VALIDITY_MS = 6e4;
|
|
2237
2292
|
var refreshPromises = /* @__PURE__ */ new WeakMap();
|
|
@@ -2241,6 +2296,24 @@ var isInvalidRefreshTokenError = (error) => {
|
|
|
2241
2296
|
if (!message.includes("refresh token")) return false;
|
|
2242
2297
|
return message.includes("invalid") || message.includes("not found") || message.includes("revoked");
|
|
2243
2298
|
};
|
|
2299
|
+
var getSessionFromStorage = () => {
|
|
2300
|
+
if (typeof window === "undefined") return null;
|
|
2301
|
+
try {
|
|
2302
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
2303
|
+
const key = localStorage.key(i);
|
|
2304
|
+
if (key?.startsWith("sb-") && key?.endsWith("-auth-token")) {
|
|
2305
|
+
const raw = localStorage.getItem(key);
|
|
2306
|
+
if (!raw) continue;
|
|
2307
|
+
const parsed = JSON.parse(raw);
|
|
2308
|
+
if (parsed?.access_token && parsed?.refresh_token && parsed?.expires_at) {
|
|
2309
|
+
return parsed;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
} catch {
|
|
2314
|
+
}
|
|
2315
|
+
return null;
|
|
2316
|
+
};
|
|
2244
2317
|
var isSessionValid = (session, minValidityMs = 0) => {
|
|
2245
2318
|
if (!session) return false;
|
|
2246
2319
|
if (!session.expires_at) return true;
|
|
@@ -2297,6 +2370,18 @@ var refreshSessionSingleFlight = async (supabase) => {
|
|
|
2297
2370
|
}
|
|
2298
2371
|
const invalidRefreshToken = isInvalidRefreshTokenError(refreshError);
|
|
2299
2372
|
if (invalidRefreshToken) {
|
|
2373
|
+
const storedSession = getSessionFromStorage();
|
|
2374
|
+
if (storedSession && isSessionValid(storedSession, 0)) {
|
|
2375
|
+
console.log("[Auth] Recovered session from localStorage after invalid refresh token (another tab refreshed)");
|
|
2376
|
+
try {
|
|
2377
|
+
await supabase.auth.setSession({
|
|
2378
|
+
access_token: storedSession.access_token,
|
|
2379
|
+
refresh_token: storedSession.refresh_token
|
|
2380
|
+
});
|
|
2381
|
+
} catch {
|
|
2382
|
+
}
|
|
2383
|
+
return { session: storedSession, error: null, invalidRefreshToken: false };
|
|
2384
|
+
}
|
|
2300
2385
|
try {
|
|
2301
2386
|
await supabase.auth.signOut({ scope: "local" });
|
|
2302
2387
|
} catch (error) {
|
|
@@ -10379,6 +10464,15 @@ var AuthProvider = ({ children }) => {
|
|
|
10379
10464
|
const [error, setError] = React142.useState(null);
|
|
10380
10465
|
const [authStatus, setAuthStatus] = React142.useState("loading");
|
|
10381
10466
|
const [showOnboarding, setShowOnboarding] = React142.useState(false);
|
|
10467
|
+
const auditInitRef = React142.useRef(false);
|
|
10468
|
+
if (!auditInitRef.current && typeof window !== "undefined") {
|
|
10469
|
+
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8000";
|
|
10470
|
+
initAuthAuditLog(backendUrl, async () => {
|
|
10471
|
+
const { data: { session: s } } = await supabase.auth.getSession();
|
|
10472
|
+
return s?.access_token ?? null;
|
|
10473
|
+
});
|
|
10474
|
+
auditInitRef.current = true;
|
|
10475
|
+
}
|
|
10382
10476
|
const isFetchingRef = React142.useRef(false);
|
|
10383
10477
|
const lastProcessedSessionRef = React142.useRef(null);
|
|
10384
10478
|
const hasAuthenticatedRef = React142.useRef(false);
|
|
@@ -10542,6 +10636,7 @@ var AuthProvider = ({ children }) => {
|
|
|
10542
10636
|
resetRecoveryState();
|
|
10543
10637
|
if (refreshResult.invalidRefreshToken) {
|
|
10544
10638
|
console.warn("[AuthContext] Refresh token invalid, redirecting to login");
|
|
10639
|
+
logAuthEvent("forced_logout", { reason: "invalid_refresh_token", trigger: "fetchSession" });
|
|
10545
10640
|
setAuthStatus("failed");
|
|
10546
10641
|
clearSessionState();
|
|
10547
10642
|
await handleAuthRequired(supabase, "session_expired");
|
|
@@ -10641,6 +10736,7 @@ var AuthProvider = ({ children }) => {
|
|
|
10641
10736
|
const signOut = React142.useCallback(async () => {
|
|
10642
10737
|
try {
|
|
10643
10738
|
console.log("[AuthContext] Signing out");
|
|
10739
|
+
logAuthEvent("sign_out_initiated", { trigger: "user_action" });
|
|
10644
10740
|
setAuthStatus("loading");
|
|
10645
10741
|
resetRecoveryState();
|
|
10646
10742
|
clearAuthSnapshot();
|
|
@@ -10665,6 +10761,22 @@ var AuthProvider = ({ children }) => {
|
|
|
10665
10761
|
return;
|
|
10666
10762
|
}
|
|
10667
10763
|
const monitorTokenExpiry = async () => {
|
|
10764
|
+
const storedSession = getSessionFromStorage();
|
|
10765
|
+
if (storedSession && isSessionValid(storedSession, 5 * 60 * 1e3)) {
|
|
10766
|
+
if (storedSession.access_token !== session.access_token) {
|
|
10767
|
+
console.log("[AuthContext] Adopting session refreshed by another tab");
|
|
10768
|
+
logAuthEvent("cross_tab_refresh_adopted", { source: "monitorTokenExpiry" });
|
|
10769
|
+
setTrackedSession(storedSession);
|
|
10770
|
+
try {
|
|
10771
|
+
await supabase.auth.setSession({
|
|
10772
|
+
access_token: storedSession.access_token,
|
|
10773
|
+
refresh_token: storedSession.refresh_token
|
|
10774
|
+
});
|
|
10775
|
+
} catch {
|
|
10776
|
+
}
|
|
10777
|
+
}
|
|
10778
|
+
return;
|
|
10779
|
+
}
|
|
10668
10780
|
const expiresAt = session.expires_at;
|
|
10669
10781
|
if (!expiresAt) {
|
|
10670
10782
|
console.warn("[AuthContext] Session has no expiry time");
|
|
@@ -10677,13 +10789,16 @@ var AuthProvider = ({ children }) => {
|
|
|
10677
10789
|
console.log(`[AuthContext] Token expires in ${minutesUntilExpiry} minutes`);
|
|
10678
10790
|
if (minutesUntilExpiry < 5 && timeUntilExpiry > 0) {
|
|
10679
10791
|
console.warn("[AuthContext] Token expiring soon, attempting refresh...");
|
|
10792
|
+
logAuthEvent("token_refresh_started", { reason: "expiring_soon", minutesUntilExpiry });
|
|
10680
10793
|
const refreshResult = await refreshSessionSingleFlight(supabase);
|
|
10681
10794
|
if (isSessionValid(refreshResult.session, 0)) {
|
|
10682
10795
|
setTrackedSession(refreshResult.session);
|
|
10796
|
+
logAuthEvent("token_refresh_succeeded", { expiresAt: refreshResult.session?.expires_at });
|
|
10683
10797
|
console.log("[AuthContext] Token refreshed successfully");
|
|
10684
10798
|
return;
|
|
10685
10799
|
}
|
|
10686
10800
|
if (refreshResult.invalidRefreshToken) {
|
|
10801
|
+
logAuthEvent("token_refresh_failed", { reason: "invalid_refresh_token", trigger: "proactive" });
|
|
10687
10802
|
clearAuthSnapshot();
|
|
10688
10803
|
resetRecoveryState();
|
|
10689
10804
|
console.error("[AuthContext] Refresh token invalid during proactive refresh");
|
|
@@ -10692,13 +10807,16 @@ var AuthProvider = ({ children }) => {
|
|
|
10692
10807
|
}
|
|
10693
10808
|
if (timeUntilExpiry <= 0) {
|
|
10694
10809
|
console.warn("[AuthContext] Token has expired, attempting refresh...");
|
|
10810
|
+
logAuthEvent("token_refresh_started", { reason: "expired", minutesUntilExpiry });
|
|
10695
10811
|
const refreshResult = await refreshSessionSingleFlight(supabase);
|
|
10696
10812
|
if (isSessionValid(refreshResult.session, 0)) {
|
|
10697
10813
|
setTrackedSession(refreshResult.session);
|
|
10814
|
+
logAuthEvent("token_refresh_succeeded", { expiresAt: refreshResult.session?.expires_at });
|
|
10698
10815
|
console.log("[AuthContext] Token refreshed after expiry");
|
|
10699
10816
|
return;
|
|
10700
10817
|
}
|
|
10701
10818
|
if (refreshResult.invalidRefreshToken) {
|
|
10819
|
+
logAuthEvent("token_refresh_failed", { reason: "invalid_refresh_token", trigger: "expired" });
|
|
10702
10820
|
clearAuthSnapshot();
|
|
10703
10821
|
resetRecoveryState();
|
|
10704
10822
|
console.error("[AuthContext] Refresh token invalid after expiry");
|
|
@@ -10750,6 +10868,29 @@ var AuthProvider = ({ children }) => {
|
|
|
10750
10868
|
window.addEventListener("rbac:refresh-scope", handleScopeRefresh);
|
|
10751
10869
|
return () => window.removeEventListener("rbac:refresh-scope", handleScopeRefresh);
|
|
10752
10870
|
}, [fetchSession, session]);
|
|
10871
|
+
React142.useEffect(() => {
|
|
10872
|
+
if (typeof window === "undefined") return;
|
|
10873
|
+
const handleStorageChange = (e) => {
|
|
10874
|
+
if (!e.key?.startsWith("sb-") || !e.key?.endsWith("-auth-token")) return;
|
|
10875
|
+
if (!e.newValue || !session) return;
|
|
10876
|
+
try {
|
|
10877
|
+
const stored = JSON.parse(e.newValue);
|
|
10878
|
+
if (stored?.access_token && stored?.refresh_token && stored?.expires_at && stored.access_token !== session.access_token) {
|
|
10879
|
+
console.log("[AuthContext] Cross-tab token update detected, syncing session");
|
|
10880
|
+
logAuthEvent("cross_tab_refresh_adopted", { source: "storage_event" });
|
|
10881
|
+
setTrackedSession(stored);
|
|
10882
|
+
supabase.auth.setSession({
|
|
10883
|
+
access_token: stored.access_token,
|
|
10884
|
+
refresh_token: stored.refresh_token
|
|
10885
|
+
}).catch(() => {
|
|
10886
|
+
});
|
|
10887
|
+
}
|
|
10888
|
+
} catch {
|
|
10889
|
+
}
|
|
10890
|
+
};
|
|
10891
|
+
window.addEventListener("storage", handleStorageChange);
|
|
10892
|
+
return () => window.removeEventListener("storage", handleStorageChange);
|
|
10893
|
+
}, [session, setTrackedSession, supabase]);
|
|
10753
10894
|
React142.useEffect(() => {
|
|
10754
10895
|
if (typeof window === "undefined" || authStatus !== "recovering") {
|
|
10755
10896
|
return;
|
|
@@ -10841,6 +10982,7 @@ var AuthProvider = ({ children }) => {
|
|
|
10841
10982
|
}
|
|
10842
10983
|
if (event === "SIGNED_OUT") {
|
|
10843
10984
|
console.log("[AuthContext] User signed out");
|
|
10985
|
+
logAuthEvent("signed_out", { trigger: "auth_state_change" });
|
|
10844
10986
|
resetRecoveryState();
|
|
10845
10987
|
clearAuthSnapshot();
|
|
10846
10988
|
clearSessionState();
|
|
@@ -11117,7 +11259,7 @@ function useSessionTracking(options = {}) {
|
|
|
11117
11259
|
const trackerRef = React142.useRef(null);
|
|
11118
11260
|
const isTrackingRef = React142.useRef(false);
|
|
11119
11261
|
const initRef = React142.useRef(false);
|
|
11120
|
-
const
|
|
11262
|
+
const getAccessToken2 = React142.useCallback(async () => {
|
|
11121
11263
|
return session?.access_token || null;
|
|
11122
11264
|
}, [session?.access_token]);
|
|
11123
11265
|
React142.useEffect(() => {
|
|
@@ -11125,14 +11267,14 @@ function useSessionTracking(options = {}) {
|
|
|
11125
11267
|
return;
|
|
11126
11268
|
}
|
|
11127
11269
|
initRef.current = true;
|
|
11128
|
-
const
|
|
11129
|
-
if (!
|
|
11270
|
+
const apiBaseUrl2 = config?.apiBaseUrl || process.env.NEXT_PUBLIC_API_BASE_URL || "";
|
|
11271
|
+
if (!apiBaseUrl2) {
|
|
11130
11272
|
console.warn("[useSessionTracking] No API base URL configured, session tracking disabled");
|
|
11131
11273
|
return;
|
|
11132
11274
|
}
|
|
11133
11275
|
const tracker = createSessionTracker({
|
|
11134
|
-
apiBaseUrl,
|
|
11135
|
-
getAccessToken,
|
|
11276
|
+
apiBaseUrl: apiBaseUrl2,
|
|
11277
|
+
getAccessToken: getAccessToken2,
|
|
11136
11278
|
onSessionStart: (sessionId) => {
|
|
11137
11279
|
isTrackingRef.current = true;
|
|
11138
11280
|
onSessionStart?.(sessionId);
|
|
@@ -11153,7 +11295,7 @@ function useSessionTracking(options = {}) {
|
|
|
11153
11295
|
initRef.current = false;
|
|
11154
11296
|
isTrackingRef.current = false;
|
|
11155
11297
|
};
|
|
11156
|
-
}, [enabled, isAuthenticated, user?.id, session?.access_token, config?.apiBaseUrl,
|
|
11298
|
+
}, [enabled, isAuthenticated, user?.id, session?.access_token, config?.apiBaseUrl, getAccessToken2, onSessionStart, onSessionEnd, user]);
|
|
11157
11299
|
React142.useEffect(() => {
|
|
11158
11300
|
if (!isAuthenticated && trackerRef.current && isTrackingRef.current) {
|
|
11159
11301
|
trackerRef.current.endSession("logout");
|
|
@@ -19734,9 +19876,9 @@ function useUserUsage(userId, options = {}) {
|
|
|
19734
19876
|
const [data, setData] = React142.useState(null);
|
|
19735
19877
|
const [isLoading, setIsLoading] = React142.useState(true);
|
|
19736
19878
|
const [error, setError] = React142.useState(null);
|
|
19737
|
-
const
|
|
19879
|
+
const apiBaseUrl2 = config?.apiBaseUrl || process.env.NEXT_PUBLIC_API_BASE_URL || "";
|
|
19738
19880
|
const fetchData = React142.useCallback(async () => {
|
|
19739
|
-
if (!userId || !
|
|
19881
|
+
if (!userId || !apiBaseUrl2 || !session?.access_token) {
|
|
19740
19882
|
setIsLoading(false);
|
|
19741
19883
|
return;
|
|
19742
19884
|
}
|
|
@@ -19746,7 +19888,7 @@ function useUserUsage(userId, options = {}) {
|
|
|
19746
19888
|
const params = new URLSearchParams();
|
|
19747
19889
|
if (startDate) params.append("start_date", startDate);
|
|
19748
19890
|
if (endDate) params.append("end_date", endDate);
|
|
19749
|
-
const url = `${
|
|
19891
|
+
const url = `${apiBaseUrl2}/api/usage/user/${userId}${params.toString() ? `?${params}` : ""}`;
|
|
19750
19892
|
const response = await fetch(url, {
|
|
19751
19893
|
headers: {
|
|
19752
19894
|
"Authorization": `Bearer ${session.access_token}`
|
|
@@ -19764,7 +19906,7 @@ function useUserUsage(userId, options = {}) {
|
|
|
19764
19906
|
} finally {
|
|
19765
19907
|
setIsLoading(false);
|
|
19766
19908
|
}
|
|
19767
|
-
}, [userId,
|
|
19909
|
+
}, [userId, apiBaseUrl2, session?.access_token, startDate, endDate]);
|
|
19768
19910
|
React142.useEffect(() => {
|
|
19769
19911
|
if (enabled) {
|
|
19770
19912
|
fetchData();
|
|
@@ -19786,12 +19928,12 @@ function useCompanyUsersUsage(companyId, options = {}) {
|
|
|
19786
19928
|
const [isLoading, setIsLoading] = React142.useState(true);
|
|
19787
19929
|
const [isTodayLoading, setIsTodayLoading] = React142.useState(true);
|
|
19788
19930
|
const [error, setError] = React142.useState(null);
|
|
19789
|
-
const
|
|
19931
|
+
const apiBaseUrl2 = config?.apiBaseUrl || process.env.NEXT_PUBLIC_BACKEND_URL || "";
|
|
19790
19932
|
const userRole = user?.role || user?.role_level;
|
|
19791
19933
|
const canAccess = userRole === "owner" || userRole === "optifye";
|
|
19792
|
-
const isEnabled = enabled && canAccess && !!
|
|
19934
|
+
const isEnabled = enabled && canAccess && !!apiBaseUrl2;
|
|
19793
19935
|
const fetchOwnerReport = React142.useCallback(async () => {
|
|
19794
|
-
if (!
|
|
19936
|
+
if (!apiBaseUrl2 || !session?.access_token || !canAccess || !isEnabled) {
|
|
19795
19937
|
setIsLoading(false);
|
|
19796
19938
|
return;
|
|
19797
19939
|
}
|
|
@@ -19806,7 +19948,7 @@ function useCompanyUsersUsage(companyId, options = {}) {
|
|
|
19806
19948
|
if (startDate) params.append("start_date", startDate);
|
|
19807
19949
|
if (endDate) params.append("end_date", endDate);
|
|
19808
19950
|
if (roleFilter) params.append("role_filter", roleFilter);
|
|
19809
|
-
const url = `${
|
|
19951
|
+
const url = `${apiBaseUrl2}/api/usage/owner-report${params.toString() ? `?${params}` : ""}`;
|
|
19810
19952
|
const response = await fetch(url, {
|
|
19811
19953
|
headers: {
|
|
19812
19954
|
"Authorization": `Bearer ${session.access_token}`
|
|
@@ -19824,15 +19966,15 @@ function useCompanyUsersUsage(companyId, options = {}) {
|
|
|
19824
19966
|
} finally {
|
|
19825
19967
|
setIsLoading(false);
|
|
19826
19968
|
}
|
|
19827
|
-
}, [
|
|
19969
|
+
}, [apiBaseUrl2, session?.access_token, canAccess, isEnabled, startDate, endDate, roleFilter]);
|
|
19828
19970
|
const fetchTodayUsage = React142.useCallback(async () => {
|
|
19829
|
-
if (!
|
|
19971
|
+
if (!apiBaseUrl2 || !session?.access_token || !canAccess || !isEnabled) {
|
|
19830
19972
|
setIsTodayLoading(false);
|
|
19831
19973
|
return;
|
|
19832
19974
|
}
|
|
19833
19975
|
setIsTodayLoading(true);
|
|
19834
19976
|
try {
|
|
19835
|
-
const url = `${
|
|
19977
|
+
const url = `${apiBaseUrl2}/api/usage/today`;
|
|
19836
19978
|
const response = await fetch(url, {
|
|
19837
19979
|
headers: {
|
|
19838
19980
|
"Authorization": `Bearer ${session.access_token}`
|
|
@@ -19848,7 +19990,7 @@ function useCompanyUsersUsage(companyId, options = {}) {
|
|
|
19848
19990
|
} finally {
|
|
19849
19991
|
setIsTodayLoading(false);
|
|
19850
19992
|
}
|
|
19851
|
-
}, [
|
|
19993
|
+
}, [apiBaseUrl2, session?.access_token, canAccess, isEnabled]);
|
|
19852
19994
|
const fetchAll = React142.useCallback(async () => {
|
|
19853
19995
|
await Promise.all([
|
|
19854
19996
|
fetchOwnerReport(),
|
|
@@ -19918,9 +20060,9 @@ function useCompanyClipsCost() {
|
|
|
19918
20060
|
const hasFetchedOnceRef = React142.useRef(false);
|
|
19919
20061
|
const canViewClipsCost = user?.role_level === "owner" || user?.role_level === "optifye";
|
|
19920
20062
|
const companyId = user?.properties?.company_id || user?.company_id || entityConfig.companyId;
|
|
19921
|
-
const
|
|
20063
|
+
const apiBaseUrl2 = config?.apiBaseUrl || process.env.NEXT_PUBLIC_API_BASE_URL || "";
|
|
19922
20064
|
const fetchData = React142.useCallback(async () => {
|
|
19923
|
-
if (!canViewClipsCost || !companyId || !supabase || !
|
|
20065
|
+
if (!canViewClipsCost || !companyId || !supabase || !apiBaseUrl2 || !session?.access_token) {
|
|
19924
20066
|
setIsLoading(false);
|
|
19925
20067
|
setData(null);
|
|
19926
20068
|
hasFetchedOnceRef.current = false;
|
|
@@ -19932,7 +20074,7 @@ function useCompanyClipsCost() {
|
|
|
19932
20074
|
setError(null);
|
|
19933
20075
|
try {
|
|
19934
20076
|
const [statsResponse, linesResult] = await Promise.all([
|
|
19935
|
-
fetch(`${
|
|
20077
|
+
fetch(`${apiBaseUrl2}/api/classification/company-stats?company_id=${encodeURIComponent(companyId)}`, {
|
|
19936
20078
|
headers: {
|
|
19937
20079
|
"Authorization": `Bearer ${session.access_token}`
|
|
19938
20080
|
}
|
|
@@ -19967,7 +20109,7 @@ function useCompanyClipsCost() {
|
|
|
19967
20109
|
hasFetchedOnceRef.current = true;
|
|
19968
20110
|
setIsLoading(false);
|
|
19969
20111
|
}
|
|
19970
|
-
}, [canViewClipsCost, companyId, supabase,
|
|
20112
|
+
}, [canViewClipsCost, companyId, supabase, apiBaseUrl2, session?.access_token]);
|
|
19971
20113
|
React142.useEffect(() => {
|
|
19972
20114
|
fetchData();
|
|
19973
20115
|
}, [fetchData]);
|
|
@@ -23503,11 +23645,11 @@ function createRenderStep(runNextFrame) {
|
|
|
23503
23645
|
*/
|
|
23504
23646
|
schedule: (callback, keepAlive = false, immediate = false) => {
|
|
23505
23647
|
const addToCurrentFrame = immediate && isProcessing;
|
|
23506
|
-
const
|
|
23648
|
+
const queue2 = addToCurrentFrame ? thisFrame : nextFrame;
|
|
23507
23649
|
if (keepAlive)
|
|
23508
23650
|
toKeepAlive.add(callback);
|
|
23509
|
-
if (!
|
|
23510
|
-
|
|
23651
|
+
if (!queue2.has(callback))
|
|
23652
|
+
queue2.add(callback);
|
|
23511
23653
|
return callback;
|
|
23512
23654
|
},
|
|
23513
23655
|
/**
|
|
@@ -53956,7 +54098,7 @@ var AwardNotificationManager = () => {
|
|
|
53956
54098
|
const supabase = useSupabase();
|
|
53957
54099
|
const { user } = useAuth();
|
|
53958
54100
|
const router$1 = router.useRouter();
|
|
53959
|
-
const [
|
|
54101
|
+
const [queue2, setQueue] = React142.useState([]);
|
|
53960
54102
|
const [activeNotification, setActiveNotification] = React142.useState(null);
|
|
53961
54103
|
const lastUserIdRef = React142.useRef(null);
|
|
53962
54104
|
React142.useEffect(() => {
|
|
@@ -53977,10 +54119,10 @@ var AwardNotificationManager = () => {
|
|
|
53977
54119
|
loadNotifications();
|
|
53978
54120
|
}, [user, supabase]);
|
|
53979
54121
|
React142.useEffect(() => {
|
|
53980
|
-
if (!activeNotification &&
|
|
53981
|
-
setActiveNotification(
|
|
54122
|
+
if (!activeNotification && queue2.length > 0) {
|
|
54123
|
+
setActiveNotification(queue2[0]);
|
|
53982
54124
|
}
|
|
53983
|
-
}, [
|
|
54125
|
+
}, [queue2, activeNotification]);
|
|
53984
54126
|
const dismissActive = async () => {
|
|
53985
54127
|
if (!activeNotification) return;
|
|
53986
54128
|
if (!activeNotification.id.startsWith("mock-")) {
|
package/dist/index.mjs
CHANGED
|
@@ -2203,6 +2203,61 @@ var clearAuthSnapshot = () => {
|
|
|
2203
2203
|
safeStorageRemoveItem(AUTH_SNAPSHOT_STORAGE_KEY);
|
|
2204
2204
|
};
|
|
2205
2205
|
|
|
2206
|
+
// src/lib/auth/authAuditLog.ts
|
|
2207
|
+
var TAB_ID = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID().slice(0, 8) : Math.random().toString(36).slice(2, 10);
|
|
2208
|
+
var FLUSH_INTERVAL_MS = 3e4;
|
|
2209
|
+
var MAX_QUEUE_SIZE = 50;
|
|
2210
|
+
var queue = [];
|
|
2211
|
+
var flushTimer = null;
|
|
2212
|
+
var apiBaseUrl = "";
|
|
2213
|
+
var getAccessToken = null;
|
|
2214
|
+
var initAuthAuditLog = (baseUrl, tokenGetter) => {
|
|
2215
|
+
apiBaseUrl = baseUrl;
|
|
2216
|
+
getAccessToken = tokenGetter;
|
|
2217
|
+
if (typeof window === "undefined") return;
|
|
2218
|
+
if (!flushTimer) {
|
|
2219
|
+
flushTimer = setInterval(flushAuthAuditLog, FLUSH_INTERVAL_MS);
|
|
2220
|
+
window.addEventListener("visibilitychange", () => {
|
|
2221
|
+
if (document.visibilityState === "hidden") {
|
|
2222
|
+
void flushAuthAuditLog();
|
|
2223
|
+
}
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
var logAuthEvent = (eventType, details = {}) => {
|
|
2228
|
+
queue.push({
|
|
2229
|
+
event_type: eventType,
|
|
2230
|
+
tab_id: TAB_ID,
|
|
2231
|
+
details: {
|
|
2232
|
+
...details,
|
|
2233
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
2234
|
+
}
|
|
2235
|
+
});
|
|
2236
|
+
if (queue.length >= MAX_QUEUE_SIZE) {
|
|
2237
|
+
void flushAuthAuditLog();
|
|
2238
|
+
}
|
|
2239
|
+
};
|
|
2240
|
+
var flushAuthAuditLog = async () => {
|
|
2241
|
+
if (queue.length === 0 || !apiBaseUrl || !getAccessToken) return;
|
|
2242
|
+
const events = queue.splice(0, MAX_QUEUE_SIZE);
|
|
2243
|
+
try {
|
|
2244
|
+
const token = await getAccessToken();
|
|
2245
|
+
if (!token) return;
|
|
2246
|
+
const body = JSON.stringify({ events });
|
|
2247
|
+
const url = `${apiBaseUrl}/api/auth/audit`;
|
|
2248
|
+
await fetch(url, {
|
|
2249
|
+
method: "POST",
|
|
2250
|
+
headers: {
|
|
2251
|
+
"Content-Type": "application/json",
|
|
2252
|
+
Authorization: `Bearer ${token}`
|
|
2253
|
+
},
|
|
2254
|
+
body,
|
|
2255
|
+
keepalive: document.visibilityState === "hidden"
|
|
2256
|
+
});
|
|
2257
|
+
} catch {
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
|
|
2206
2261
|
// src/lib/auth/session.ts
|
|
2207
2262
|
var DEFAULT_MIN_VALIDITY_MS = 6e4;
|
|
2208
2263
|
var refreshPromises = /* @__PURE__ */ new WeakMap();
|
|
@@ -2212,6 +2267,24 @@ var isInvalidRefreshTokenError = (error) => {
|
|
|
2212
2267
|
if (!message.includes("refresh token")) return false;
|
|
2213
2268
|
return message.includes("invalid") || message.includes("not found") || message.includes("revoked");
|
|
2214
2269
|
};
|
|
2270
|
+
var getSessionFromStorage = () => {
|
|
2271
|
+
if (typeof window === "undefined") return null;
|
|
2272
|
+
try {
|
|
2273
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
2274
|
+
const key = localStorage.key(i);
|
|
2275
|
+
if (key?.startsWith("sb-") && key?.endsWith("-auth-token")) {
|
|
2276
|
+
const raw = localStorage.getItem(key);
|
|
2277
|
+
if (!raw) continue;
|
|
2278
|
+
const parsed = JSON.parse(raw);
|
|
2279
|
+
if (parsed?.access_token && parsed?.refresh_token && parsed?.expires_at) {
|
|
2280
|
+
return parsed;
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
} catch {
|
|
2285
|
+
}
|
|
2286
|
+
return null;
|
|
2287
|
+
};
|
|
2215
2288
|
var isSessionValid = (session, minValidityMs = 0) => {
|
|
2216
2289
|
if (!session) return false;
|
|
2217
2290
|
if (!session.expires_at) return true;
|
|
@@ -2268,6 +2341,18 @@ var refreshSessionSingleFlight = async (supabase) => {
|
|
|
2268
2341
|
}
|
|
2269
2342
|
const invalidRefreshToken = isInvalidRefreshTokenError(refreshError);
|
|
2270
2343
|
if (invalidRefreshToken) {
|
|
2344
|
+
const storedSession = getSessionFromStorage();
|
|
2345
|
+
if (storedSession && isSessionValid(storedSession, 0)) {
|
|
2346
|
+
console.log("[Auth] Recovered session from localStorage after invalid refresh token (another tab refreshed)");
|
|
2347
|
+
try {
|
|
2348
|
+
await supabase.auth.setSession({
|
|
2349
|
+
access_token: storedSession.access_token,
|
|
2350
|
+
refresh_token: storedSession.refresh_token
|
|
2351
|
+
});
|
|
2352
|
+
} catch {
|
|
2353
|
+
}
|
|
2354
|
+
return { session: storedSession, error: null, invalidRefreshToken: false };
|
|
2355
|
+
}
|
|
2271
2356
|
try {
|
|
2272
2357
|
await supabase.auth.signOut({ scope: "local" });
|
|
2273
2358
|
} catch (error) {
|
|
@@ -10350,6 +10435,15 @@ var AuthProvider = ({ children }) => {
|
|
|
10350
10435
|
const [error, setError] = useState(null);
|
|
10351
10436
|
const [authStatus, setAuthStatus] = useState("loading");
|
|
10352
10437
|
const [showOnboarding, setShowOnboarding] = useState(false);
|
|
10438
|
+
const auditInitRef = useRef(false);
|
|
10439
|
+
if (!auditInitRef.current && typeof window !== "undefined") {
|
|
10440
|
+
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8000";
|
|
10441
|
+
initAuthAuditLog(backendUrl, async () => {
|
|
10442
|
+
const { data: { session: s } } = await supabase.auth.getSession();
|
|
10443
|
+
return s?.access_token ?? null;
|
|
10444
|
+
});
|
|
10445
|
+
auditInitRef.current = true;
|
|
10446
|
+
}
|
|
10353
10447
|
const isFetchingRef = useRef(false);
|
|
10354
10448
|
const lastProcessedSessionRef = useRef(null);
|
|
10355
10449
|
const hasAuthenticatedRef = useRef(false);
|
|
@@ -10513,6 +10607,7 @@ var AuthProvider = ({ children }) => {
|
|
|
10513
10607
|
resetRecoveryState();
|
|
10514
10608
|
if (refreshResult.invalidRefreshToken) {
|
|
10515
10609
|
console.warn("[AuthContext] Refresh token invalid, redirecting to login");
|
|
10610
|
+
logAuthEvent("forced_logout", { reason: "invalid_refresh_token", trigger: "fetchSession" });
|
|
10516
10611
|
setAuthStatus("failed");
|
|
10517
10612
|
clearSessionState();
|
|
10518
10613
|
await handleAuthRequired(supabase, "session_expired");
|
|
@@ -10612,6 +10707,7 @@ var AuthProvider = ({ children }) => {
|
|
|
10612
10707
|
const signOut = useCallback(async () => {
|
|
10613
10708
|
try {
|
|
10614
10709
|
console.log("[AuthContext] Signing out");
|
|
10710
|
+
logAuthEvent("sign_out_initiated", { trigger: "user_action" });
|
|
10615
10711
|
setAuthStatus("loading");
|
|
10616
10712
|
resetRecoveryState();
|
|
10617
10713
|
clearAuthSnapshot();
|
|
@@ -10636,6 +10732,22 @@ var AuthProvider = ({ children }) => {
|
|
|
10636
10732
|
return;
|
|
10637
10733
|
}
|
|
10638
10734
|
const monitorTokenExpiry = async () => {
|
|
10735
|
+
const storedSession = getSessionFromStorage();
|
|
10736
|
+
if (storedSession && isSessionValid(storedSession, 5 * 60 * 1e3)) {
|
|
10737
|
+
if (storedSession.access_token !== session.access_token) {
|
|
10738
|
+
console.log("[AuthContext] Adopting session refreshed by another tab");
|
|
10739
|
+
logAuthEvent("cross_tab_refresh_adopted", { source: "monitorTokenExpiry" });
|
|
10740
|
+
setTrackedSession(storedSession);
|
|
10741
|
+
try {
|
|
10742
|
+
await supabase.auth.setSession({
|
|
10743
|
+
access_token: storedSession.access_token,
|
|
10744
|
+
refresh_token: storedSession.refresh_token
|
|
10745
|
+
});
|
|
10746
|
+
} catch {
|
|
10747
|
+
}
|
|
10748
|
+
}
|
|
10749
|
+
return;
|
|
10750
|
+
}
|
|
10639
10751
|
const expiresAt = session.expires_at;
|
|
10640
10752
|
if (!expiresAt) {
|
|
10641
10753
|
console.warn("[AuthContext] Session has no expiry time");
|
|
@@ -10648,13 +10760,16 @@ var AuthProvider = ({ children }) => {
|
|
|
10648
10760
|
console.log(`[AuthContext] Token expires in ${minutesUntilExpiry} minutes`);
|
|
10649
10761
|
if (minutesUntilExpiry < 5 && timeUntilExpiry > 0) {
|
|
10650
10762
|
console.warn("[AuthContext] Token expiring soon, attempting refresh...");
|
|
10763
|
+
logAuthEvent("token_refresh_started", { reason: "expiring_soon", minutesUntilExpiry });
|
|
10651
10764
|
const refreshResult = await refreshSessionSingleFlight(supabase);
|
|
10652
10765
|
if (isSessionValid(refreshResult.session, 0)) {
|
|
10653
10766
|
setTrackedSession(refreshResult.session);
|
|
10767
|
+
logAuthEvent("token_refresh_succeeded", { expiresAt: refreshResult.session?.expires_at });
|
|
10654
10768
|
console.log("[AuthContext] Token refreshed successfully");
|
|
10655
10769
|
return;
|
|
10656
10770
|
}
|
|
10657
10771
|
if (refreshResult.invalidRefreshToken) {
|
|
10772
|
+
logAuthEvent("token_refresh_failed", { reason: "invalid_refresh_token", trigger: "proactive" });
|
|
10658
10773
|
clearAuthSnapshot();
|
|
10659
10774
|
resetRecoveryState();
|
|
10660
10775
|
console.error("[AuthContext] Refresh token invalid during proactive refresh");
|
|
@@ -10663,13 +10778,16 @@ var AuthProvider = ({ children }) => {
|
|
|
10663
10778
|
}
|
|
10664
10779
|
if (timeUntilExpiry <= 0) {
|
|
10665
10780
|
console.warn("[AuthContext] Token has expired, attempting refresh...");
|
|
10781
|
+
logAuthEvent("token_refresh_started", { reason: "expired", minutesUntilExpiry });
|
|
10666
10782
|
const refreshResult = await refreshSessionSingleFlight(supabase);
|
|
10667
10783
|
if (isSessionValid(refreshResult.session, 0)) {
|
|
10668
10784
|
setTrackedSession(refreshResult.session);
|
|
10785
|
+
logAuthEvent("token_refresh_succeeded", { expiresAt: refreshResult.session?.expires_at });
|
|
10669
10786
|
console.log("[AuthContext] Token refreshed after expiry");
|
|
10670
10787
|
return;
|
|
10671
10788
|
}
|
|
10672
10789
|
if (refreshResult.invalidRefreshToken) {
|
|
10790
|
+
logAuthEvent("token_refresh_failed", { reason: "invalid_refresh_token", trigger: "expired" });
|
|
10673
10791
|
clearAuthSnapshot();
|
|
10674
10792
|
resetRecoveryState();
|
|
10675
10793
|
console.error("[AuthContext] Refresh token invalid after expiry");
|
|
@@ -10721,6 +10839,29 @@ var AuthProvider = ({ children }) => {
|
|
|
10721
10839
|
window.addEventListener("rbac:refresh-scope", handleScopeRefresh);
|
|
10722
10840
|
return () => window.removeEventListener("rbac:refresh-scope", handleScopeRefresh);
|
|
10723
10841
|
}, [fetchSession, session]);
|
|
10842
|
+
useEffect(() => {
|
|
10843
|
+
if (typeof window === "undefined") return;
|
|
10844
|
+
const handleStorageChange = (e) => {
|
|
10845
|
+
if (!e.key?.startsWith("sb-") || !e.key?.endsWith("-auth-token")) return;
|
|
10846
|
+
if (!e.newValue || !session) return;
|
|
10847
|
+
try {
|
|
10848
|
+
const stored = JSON.parse(e.newValue);
|
|
10849
|
+
if (stored?.access_token && stored?.refresh_token && stored?.expires_at && stored.access_token !== session.access_token) {
|
|
10850
|
+
console.log("[AuthContext] Cross-tab token update detected, syncing session");
|
|
10851
|
+
logAuthEvent("cross_tab_refresh_adopted", { source: "storage_event" });
|
|
10852
|
+
setTrackedSession(stored);
|
|
10853
|
+
supabase.auth.setSession({
|
|
10854
|
+
access_token: stored.access_token,
|
|
10855
|
+
refresh_token: stored.refresh_token
|
|
10856
|
+
}).catch(() => {
|
|
10857
|
+
});
|
|
10858
|
+
}
|
|
10859
|
+
} catch {
|
|
10860
|
+
}
|
|
10861
|
+
};
|
|
10862
|
+
window.addEventListener("storage", handleStorageChange);
|
|
10863
|
+
return () => window.removeEventListener("storage", handleStorageChange);
|
|
10864
|
+
}, [session, setTrackedSession, supabase]);
|
|
10724
10865
|
useEffect(() => {
|
|
10725
10866
|
if (typeof window === "undefined" || authStatus !== "recovering") {
|
|
10726
10867
|
return;
|
|
@@ -10812,6 +10953,7 @@ var AuthProvider = ({ children }) => {
|
|
|
10812
10953
|
}
|
|
10813
10954
|
if (event === "SIGNED_OUT") {
|
|
10814
10955
|
console.log("[AuthContext] User signed out");
|
|
10956
|
+
logAuthEvent("signed_out", { trigger: "auth_state_change" });
|
|
10815
10957
|
resetRecoveryState();
|
|
10816
10958
|
clearAuthSnapshot();
|
|
10817
10959
|
clearSessionState();
|
|
@@ -11088,7 +11230,7 @@ function useSessionTracking(options = {}) {
|
|
|
11088
11230
|
const trackerRef = useRef(null);
|
|
11089
11231
|
const isTrackingRef = useRef(false);
|
|
11090
11232
|
const initRef = useRef(false);
|
|
11091
|
-
const
|
|
11233
|
+
const getAccessToken2 = useCallback(async () => {
|
|
11092
11234
|
return session?.access_token || null;
|
|
11093
11235
|
}, [session?.access_token]);
|
|
11094
11236
|
useEffect(() => {
|
|
@@ -11096,14 +11238,14 @@ function useSessionTracking(options = {}) {
|
|
|
11096
11238
|
return;
|
|
11097
11239
|
}
|
|
11098
11240
|
initRef.current = true;
|
|
11099
|
-
const
|
|
11100
|
-
if (!
|
|
11241
|
+
const apiBaseUrl2 = config?.apiBaseUrl || process.env.NEXT_PUBLIC_API_BASE_URL || "";
|
|
11242
|
+
if (!apiBaseUrl2) {
|
|
11101
11243
|
console.warn("[useSessionTracking] No API base URL configured, session tracking disabled");
|
|
11102
11244
|
return;
|
|
11103
11245
|
}
|
|
11104
11246
|
const tracker = createSessionTracker({
|
|
11105
|
-
apiBaseUrl,
|
|
11106
|
-
getAccessToken,
|
|
11247
|
+
apiBaseUrl: apiBaseUrl2,
|
|
11248
|
+
getAccessToken: getAccessToken2,
|
|
11107
11249
|
onSessionStart: (sessionId) => {
|
|
11108
11250
|
isTrackingRef.current = true;
|
|
11109
11251
|
onSessionStart?.(sessionId);
|
|
@@ -11124,7 +11266,7 @@ function useSessionTracking(options = {}) {
|
|
|
11124
11266
|
initRef.current = false;
|
|
11125
11267
|
isTrackingRef.current = false;
|
|
11126
11268
|
};
|
|
11127
|
-
}, [enabled, isAuthenticated, user?.id, session?.access_token, config?.apiBaseUrl,
|
|
11269
|
+
}, [enabled, isAuthenticated, user?.id, session?.access_token, config?.apiBaseUrl, getAccessToken2, onSessionStart, onSessionEnd, user]);
|
|
11128
11270
|
useEffect(() => {
|
|
11129
11271
|
if (!isAuthenticated && trackerRef.current && isTrackingRef.current) {
|
|
11130
11272
|
trackerRef.current.endSession("logout");
|
|
@@ -19705,9 +19847,9 @@ function useUserUsage(userId, options = {}) {
|
|
|
19705
19847
|
const [data, setData] = useState(null);
|
|
19706
19848
|
const [isLoading, setIsLoading] = useState(true);
|
|
19707
19849
|
const [error, setError] = useState(null);
|
|
19708
|
-
const
|
|
19850
|
+
const apiBaseUrl2 = config?.apiBaseUrl || process.env.NEXT_PUBLIC_API_BASE_URL || "";
|
|
19709
19851
|
const fetchData = useCallback(async () => {
|
|
19710
|
-
if (!userId || !
|
|
19852
|
+
if (!userId || !apiBaseUrl2 || !session?.access_token) {
|
|
19711
19853
|
setIsLoading(false);
|
|
19712
19854
|
return;
|
|
19713
19855
|
}
|
|
@@ -19717,7 +19859,7 @@ function useUserUsage(userId, options = {}) {
|
|
|
19717
19859
|
const params = new URLSearchParams();
|
|
19718
19860
|
if (startDate) params.append("start_date", startDate);
|
|
19719
19861
|
if (endDate) params.append("end_date", endDate);
|
|
19720
|
-
const url = `${
|
|
19862
|
+
const url = `${apiBaseUrl2}/api/usage/user/${userId}${params.toString() ? `?${params}` : ""}`;
|
|
19721
19863
|
const response = await fetch(url, {
|
|
19722
19864
|
headers: {
|
|
19723
19865
|
"Authorization": `Bearer ${session.access_token}`
|
|
@@ -19735,7 +19877,7 @@ function useUserUsage(userId, options = {}) {
|
|
|
19735
19877
|
} finally {
|
|
19736
19878
|
setIsLoading(false);
|
|
19737
19879
|
}
|
|
19738
|
-
}, [userId,
|
|
19880
|
+
}, [userId, apiBaseUrl2, session?.access_token, startDate, endDate]);
|
|
19739
19881
|
useEffect(() => {
|
|
19740
19882
|
if (enabled) {
|
|
19741
19883
|
fetchData();
|
|
@@ -19757,12 +19899,12 @@ function useCompanyUsersUsage(companyId, options = {}) {
|
|
|
19757
19899
|
const [isLoading, setIsLoading] = useState(true);
|
|
19758
19900
|
const [isTodayLoading, setIsTodayLoading] = useState(true);
|
|
19759
19901
|
const [error, setError] = useState(null);
|
|
19760
|
-
const
|
|
19902
|
+
const apiBaseUrl2 = config?.apiBaseUrl || process.env.NEXT_PUBLIC_BACKEND_URL || "";
|
|
19761
19903
|
const userRole = user?.role || user?.role_level;
|
|
19762
19904
|
const canAccess = userRole === "owner" || userRole === "optifye";
|
|
19763
|
-
const isEnabled = enabled && canAccess && !!
|
|
19905
|
+
const isEnabled = enabled && canAccess && !!apiBaseUrl2;
|
|
19764
19906
|
const fetchOwnerReport = useCallback(async () => {
|
|
19765
|
-
if (!
|
|
19907
|
+
if (!apiBaseUrl2 || !session?.access_token || !canAccess || !isEnabled) {
|
|
19766
19908
|
setIsLoading(false);
|
|
19767
19909
|
return;
|
|
19768
19910
|
}
|
|
@@ -19777,7 +19919,7 @@ function useCompanyUsersUsage(companyId, options = {}) {
|
|
|
19777
19919
|
if (startDate) params.append("start_date", startDate);
|
|
19778
19920
|
if (endDate) params.append("end_date", endDate);
|
|
19779
19921
|
if (roleFilter) params.append("role_filter", roleFilter);
|
|
19780
|
-
const url = `${
|
|
19922
|
+
const url = `${apiBaseUrl2}/api/usage/owner-report${params.toString() ? `?${params}` : ""}`;
|
|
19781
19923
|
const response = await fetch(url, {
|
|
19782
19924
|
headers: {
|
|
19783
19925
|
"Authorization": `Bearer ${session.access_token}`
|
|
@@ -19795,15 +19937,15 @@ function useCompanyUsersUsage(companyId, options = {}) {
|
|
|
19795
19937
|
} finally {
|
|
19796
19938
|
setIsLoading(false);
|
|
19797
19939
|
}
|
|
19798
|
-
}, [
|
|
19940
|
+
}, [apiBaseUrl2, session?.access_token, canAccess, isEnabled, startDate, endDate, roleFilter]);
|
|
19799
19941
|
const fetchTodayUsage = useCallback(async () => {
|
|
19800
|
-
if (!
|
|
19942
|
+
if (!apiBaseUrl2 || !session?.access_token || !canAccess || !isEnabled) {
|
|
19801
19943
|
setIsTodayLoading(false);
|
|
19802
19944
|
return;
|
|
19803
19945
|
}
|
|
19804
19946
|
setIsTodayLoading(true);
|
|
19805
19947
|
try {
|
|
19806
|
-
const url = `${
|
|
19948
|
+
const url = `${apiBaseUrl2}/api/usage/today`;
|
|
19807
19949
|
const response = await fetch(url, {
|
|
19808
19950
|
headers: {
|
|
19809
19951
|
"Authorization": `Bearer ${session.access_token}`
|
|
@@ -19819,7 +19961,7 @@ function useCompanyUsersUsage(companyId, options = {}) {
|
|
|
19819
19961
|
} finally {
|
|
19820
19962
|
setIsTodayLoading(false);
|
|
19821
19963
|
}
|
|
19822
|
-
}, [
|
|
19964
|
+
}, [apiBaseUrl2, session?.access_token, canAccess, isEnabled]);
|
|
19823
19965
|
const fetchAll = useCallback(async () => {
|
|
19824
19966
|
await Promise.all([
|
|
19825
19967
|
fetchOwnerReport(),
|
|
@@ -19889,9 +20031,9 @@ function useCompanyClipsCost() {
|
|
|
19889
20031
|
const hasFetchedOnceRef = useRef(false);
|
|
19890
20032
|
const canViewClipsCost = user?.role_level === "owner" || user?.role_level === "optifye";
|
|
19891
20033
|
const companyId = user?.properties?.company_id || user?.company_id || entityConfig.companyId;
|
|
19892
|
-
const
|
|
20034
|
+
const apiBaseUrl2 = config?.apiBaseUrl || process.env.NEXT_PUBLIC_API_BASE_URL || "";
|
|
19893
20035
|
const fetchData = useCallback(async () => {
|
|
19894
|
-
if (!canViewClipsCost || !companyId || !supabase || !
|
|
20036
|
+
if (!canViewClipsCost || !companyId || !supabase || !apiBaseUrl2 || !session?.access_token) {
|
|
19895
20037
|
setIsLoading(false);
|
|
19896
20038
|
setData(null);
|
|
19897
20039
|
hasFetchedOnceRef.current = false;
|
|
@@ -19903,7 +20045,7 @@ function useCompanyClipsCost() {
|
|
|
19903
20045
|
setError(null);
|
|
19904
20046
|
try {
|
|
19905
20047
|
const [statsResponse, linesResult] = await Promise.all([
|
|
19906
|
-
fetch(`${
|
|
20048
|
+
fetch(`${apiBaseUrl2}/api/classification/company-stats?company_id=${encodeURIComponent(companyId)}`, {
|
|
19907
20049
|
headers: {
|
|
19908
20050
|
"Authorization": `Bearer ${session.access_token}`
|
|
19909
20051
|
}
|
|
@@ -19938,7 +20080,7 @@ function useCompanyClipsCost() {
|
|
|
19938
20080
|
hasFetchedOnceRef.current = true;
|
|
19939
20081
|
setIsLoading(false);
|
|
19940
20082
|
}
|
|
19941
|
-
}, [canViewClipsCost, companyId, supabase,
|
|
20083
|
+
}, [canViewClipsCost, companyId, supabase, apiBaseUrl2, session?.access_token]);
|
|
19942
20084
|
useEffect(() => {
|
|
19943
20085
|
fetchData();
|
|
19944
20086
|
}, [fetchData]);
|
|
@@ -23474,11 +23616,11 @@ function createRenderStep(runNextFrame) {
|
|
|
23474
23616
|
*/
|
|
23475
23617
|
schedule: (callback, keepAlive = false, immediate = false) => {
|
|
23476
23618
|
const addToCurrentFrame = immediate && isProcessing;
|
|
23477
|
-
const
|
|
23619
|
+
const queue2 = addToCurrentFrame ? thisFrame : nextFrame;
|
|
23478
23620
|
if (keepAlive)
|
|
23479
23621
|
toKeepAlive.add(callback);
|
|
23480
|
-
if (!
|
|
23481
|
-
|
|
23622
|
+
if (!queue2.has(callback))
|
|
23623
|
+
queue2.add(callback);
|
|
23482
23624
|
return callback;
|
|
23483
23625
|
},
|
|
23484
23626
|
/**
|
|
@@ -53927,7 +54069,7 @@ var AwardNotificationManager = () => {
|
|
|
53927
54069
|
const supabase = useSupabase();
|
|
53928
54070
|
const { user } = useAuth();
|
|
53929
54071
|
const router = useRouter();
|
|
53930
|
-
const [
|
|
54072
|
+
const [queue2, setQueue] = useState([]);
|
|
53931
54073
|
const [activeNotification, setActiveNotification] = useState(null);
|
|
53932
54074
|
const lastUserIdRef = useRef(null);
|
|
53933
54075
|
useEffect(() => {
|
|
@@ -53948,10 +54090,10 @@ var AwardNotificationManager = () => {
|
|
|
53948
54090
|
loadNotifications();
|
|
53949
54091
|
}, [user, supabase]);
|
|
53950
54092
|
useEffect(() => {
|
|
53951
|
-
if (!activeNotification &&
|
|
53952
|
-
setActiveNotification(
|
|
54093
|
+
if (!activeNotification && queue2.length > 0) {
|
|
54094
|
+
setActiveNotification(queue2[0]);
|
|
53953
54095
|
}
|
|
53954
|
-
}, [
|
|
54096
|
+
}, [queue2, activeNotification]);
|
|
53955
54097
|
const dismissActive = async () => {
|
|
53956
54098
|
if (!activeNotification) return;
|
|
53957
54099
|
if (!activeNotification.id.startsWith("mock-")) {
|