@oxyhq/services 5.9.1 → 5.9.3
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 +1 -33
- package/lib/commonjs/core/OxyServices.js +159 -0
- package/lib/commonjs/core/OxyServices.js.map +1 -0
- package/lib/commonjs/core/OxyServicesMain.js +51 -0
- package/lib/commonjs/core/OxyServicesMain.js.map +1 -0
- package/lib/commonjs/core/analytics/AnalyticsService.js +67 -0
- package/lib/commonjs/core/analytics/AnalyticsService.js.map +1 -0
- package/lib/commonjs/core/auth/AuthService.js +526 -0
- package/lib/commonjs/core/auth/AuthService.js.map +1 -0
- package/lib/commonjs/core/devices/DeviceService.js +61 -0
- package/lib/commonjs/core/devices/DeviceService.js.map +1 -0
- package/lib/commonjs/core/files/FileService.js +176 -0
- package/lib/commonjs/core/files/FileService.js.map +1 -0
- package/lib/commonjs/core/index.js +103 -1707
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/core/karma/KarmaService.js +100 -0
- package/lib/commonjs/core/karma/KarmaService.js.map +1 -0
- package/lib/commonjs/core/locations/LocationService.js +131 -0
- package/lib/commonjs/core/locations/LocationService.js.map +1 -0
- package/lib/commonjs/core/payments/PaymentService.js +124 -0
- package/lib/commonjs/core/payments/PaymentService.js.map +1 -0
- package/lib/commonjs/core/users/UserService.js +234 -0
- package/lib/commonjs/core/users/UserService.js.map +1 -0
- package/lib/commonjs/index.js +164 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/models/session.js +2 -0
- package/lib/{module/types/middleware.js.map → commonjs/models/session.js.map} +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +28 -24
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +2 -2
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +12 -12
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +2 -2
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
- package/lib/commonjs/ui/stores/followStore.js +4 -4
- package/lib/commonjs/ui/stores/followStore.js.map +1 -1
- package/lib/commonjs/utils/apiUtils.js +93 -0
- package/lib/commonjs/utils/apiUtils.js.map +1 -0
- package/lib/commonjs/utils/asyncUtils.js +219 -0
- package/lib/commonjs/utils/asyncUtils.js.map +1 -0
- package/lib/commonjs/utils/errorUtils.js +148 -0
- package/lib/commonjs/utils/errorUtils.js.map +1 -0
- package/lib/commonjs/utils/hookUtils.js +399 -0
- package/lib/commonjs/utils/hookUtils.js.map +1 -0
- package/lib/commonjs/utils/loggerUtils.js +160 -0
- package/lib/commonjs/utils/loggerUtils.js.map +1 -0
- package/lib/commonjs/utils/validationUtils.js +174 -0
- package/lib/commonjs/utils/validationUtils.js.map +1 -0
- package/lib/module/core/OxyServices.js +153 -0
- package/lib/module/core/OxyServices.js.map +1 -0
- package/lib/module/core/OxyServicesMain.js +47 -0
- package/lib/module/core/OxyServicesMain.js.map +1 -0
- package/lib/module/core/analytics/AnalyticsService.js +62 -0
- package/lib/module/core/analytics/AnalyticsService.js.map +1 -0
- package/lib/module/core/auth/AuthService.js +521 -0
- package/lib/module/core/auth/AuthService.js.map +1 -0
- package/lib/module/core/devices/DeviceService.js +57 -0
- package/lib/module/core/devices/DeviceService.js.map +1 -0
- package/lib/module/core/files/FileService.js +171 -0
- package/lib/module/core/files/FileService.js.map +1 -0
- package/lib/module/core/index.js +25 -1694
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/core/karma/KarmaService.js +95 -0
- package/lib/module/core/karma/KarmaService.js.map +1 -0
- package/lib/module/core/locations/LocationService.js +127 -0
- package/lib/module/core/locations/LocationService.js.map +1 -0
- package/lib/module/core/payments/PaymentService.js +119 -0
- package/lib/module/core/payments/PaymentService.js.map +1 -0
- package/lib/module/core/users/UserService.js +230 -0
- package/lib/module/core/users/UserService.js.map +1 -0
- package/lib/module/index.js +8 -4
- package/lib/module/index.js.map +1 -1
- package/lib/module/models/session.js +2 -0
- package/lib/{commonjs/types/middleware.js.map → module/models/session.js.map} +1 -1
- package/lib/module/ui/context/OxyContext.js +28 -24
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +2 -2
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +12 -12
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +2 -2
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +1 -1
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +1 -1
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js +1 -1
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
- package/lib/module/ui/stores/followStore.js +4 -4
- package/lib/module/ui/stores/followStore.js.map +1 -1
- package/lib/module/utils/apiUtils.js +85 -0
- package/lib/module/utils/apiUtils.js.map +1 -0
- package/lib/module/utils/asyncUtils.js +202 -0
- package/lib/module/utils/asyncUtils.js.map +1 -0
- package/lib/module/utils/errorUtils.js +139 -0
- package/lib/module/utils/errorUtils.js.map +1 -0
- package/lib/module/utils/hookUtils.js +381 -0
- package/lib/module/utils/hookUtils.js.map +1 -0
- package/lib/module/utils/loggerUtils.js +149 -0
- package/lib/module/utils/loggerUtils.js.map +1 -0
- package/lib/module/utils/validationUtils.js +154 -0
- package/lib/module/utils/validationUtils.js.map +1 -0
- package/lib/typescript/core/OxyServices.d.ts +64 -0
- package/lib/typescript/core/OxyServices.d.ts.map +1 -0
- package/lib/typescript/core/OxyServicesMain.d.ts +33 -0
- package/lib/typescript/core/OxyServicesMain.d.ts.map +1 -0
- package/lib/typescript/core/analytics/AnalyticsService.d.ts +26 -0
- package/lib/typescript/core/analytics/AnalyticsService.d.ts.map +1 -0
- package/lib/typescript/core/auth/AuthService.d.ts +165 -0
- package/lib/typescript/core/auth/AuthService.d.ts.map +1 -0
- package/lib/typescript/core/devices/DeviceService.d.ts +20 -0
- package/lib/typescript/core/devices/DeviceService.d.ts.map +1 -0
- package/lib/typescript/core/files/FileService.d.ts +59 -0
- package/lib/typescript/core/files/FileService.d.ts.map +1 -0
- package/lib/typescript/core/index.d.ts +19 -657
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/core/karma/KarmaService.d.ts +50 -0
- package/lib/typescript/core/karma/KarmaService.d.ts.map +1 -0
- package/lib/typescript/core/locations/LocationService.d.ts +39 -0
- package/lib/typescript/core/locations/LocationService.d.ts.map +1 -0
- package/lib/typescript/core/payments/PaymentService.d.ts +50 -0
- package/lib/typescript/core/payments/PaymentService.d.ts.map +1 -0
- package/lib/typescript/core/users/UserService.d.ts +111 -0
- package/lib/typescript/core/users/UserService.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +7 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/models/{secureSession.d.ts → session.d.ts} +4 -4
- package/lib/typescript/models/session.d.ts.map +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts +2 -2
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/utils/apiUtils.d.ts +61 -0
- package/lib/typescript/utils/apiUtils.d.ts.map +1 -0
- package/lib/typescript/utils/asyncUtils.d.ts +64 -0
- package/lib/typescript/utils/asyncUtils.d.ts.map +1 -0
- package/lib/typescript/utils/errorUtils.d.ts +45 -0
- package/lib/typescript/utils/errorUtils.d.ts.map +1 -0
- package/lib/typescript/utils/hookUtils.d.ts +102 -0
- package/lib/typescript/utils/hookUtils.d.ts.map +1 -0
- package/lib/typescript/utils/loggerUtils.d.ts +49 -0
- package/lib/typescript/utils/loggerUtils.d.ts.map +1 -0
- package/lib/typescript/utils/validationUtils.d.ts +80 -0
- package/lib/typescript/utils/validationUtils.d.ts.map +1 -0
- package/package.json +2 -8
- package/src/core/OxyServices.ts +168 -0
- package/src/core/OxyServicesMain.ts +57 -0
- package/src/core/analytics/AnalyticsService.ts +64 -0
- package/src/core/auth/AuthService.ts +544 -0
- package/src/core/devices/DeviceService.ts +55 -0
- package/src/core/files/FileService.ts +194 -0
- package/src/core/index.ts +26 -1769
- package/src/core/karma/KarmaService.ts +104 -0
- package/src/core/locations/LocationService.ts +141 -0
- package/src/core/payments/PaymentService.ts +133 -0
- package/src/core/users/UserService.ts +241 -0
- package/src/index.ts +29 -8
- package/src/models/{secureSession.ts → session.ts} +5 -5
- package/src/ui/context/OxyContext.tsx +34 -30
- package/src/ui/screens/AccountSwitcherScreen.tsx +4 -4
- package/src/ui/screens/FileManagementScreen.tsx +12 -12
- package/src/ui/screens/ProfileScreen.tsx +3 -3
- package/src/ui/screens/SessionManagementScreen.tsx +2 -2
- package/src/ui/screens/SignInScreen.tsx +1 -1
- package/src/ui/screens/karma/KarmaCenterScreen.tsx +2 -2
- package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +3 -3
- package/src/ui/screens/karma/KarmaRulesScreen.tsx +3 -3
- package/src/ui/stores/followStore.ts +4 -4
- package/src/utils/apiUtils.ts +102 -0
- package/src/utils/asyncUtils.ts +265 -0
- package/src/utils/errorUtils.ts +172 -0
- package/src/utils/hookUtils.ts +397 -0
- package/src/utils/loggerUtils.ts +153 -0
- package/src/utils/validationUtils.ts +158 -0
- package/lib/commonjs/models/secureSession.js +0 -2
- package/lib/commonjs/models/secureSession.js.map +0 -1
- package/lib/commonjs/types/middleware.js +0 -6
- package/lib/module/models/secureSession.js +0 -2
- package/lib/module/models/secureSession.js.map +0 -1
- package/lib/module/types/middleware.js +0 -4
- package/lib/typescript/models/secureSession.d.ts.map +0 -1
- package/lib/typescript/types/middleware.d.ts +0 -19
- package/lib/typescript/types/middleware.d.ts.map +0 -1
- package/src/types/middleware.ts +0 -20
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hook utilities for common patterns and state management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook for managing async operations with loading, error, and data states
|
|
9
|
+
*/
|
|
10
|
+
export function useAsync<T>(
|
|
11
|
+
asyncFn: () => Promise<T>,
|
|
12
|
+
deps: any[] = []
|
|
13
|
+
) {
|
|
14
|
+
const [data, setData] = useState<T | null>(null);
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
const [error, setError] = useState<Error | null>(null);
|
|
17
|
+
|
|
18
|
+
const execute = useCallback(async () => {
|
|
19
|
+
setLoading(true);
|
|
20
|
+
setError(null);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const result = await asyncFn();
|
|
24
|
+
setData(result);
|
|
25
|
+
return result;
|
|
26
|
+
} catch (err) {
|
|
27
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
28
|
+
setError(error);
|
|
29
|
+
throw error;
|
|
30
|
+
} finally {
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}
|
|
33
|
+
}, deps);
|
|
34
|
+
|
|
35
|
+
return { data, loading, error, execute };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Hook for managing async operations that execute on mount
|
|
40
|
+
*/
|
|
41
|
+
export function useAsyncEffect<T>(
|
|
42
|
+
asyncFn: () => Promise<T>,
|
|
43
|
+
deps: any[] = []
|
|
44
|
+
) {
|
|
45
|
+
const [data, setData] = useState<T | null>(null);
|
|
46
|
+
const [loading, setLoading] = useState(true);
|
|
47
|
+
const [error, setError] = useState<Error | null>(null);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
let mounted = true;
|
|
51
|
+
|
|
52
|
+
const execute = async () => {
|
|
53
|
+
try {
|
|
54
|
+
const result = await asyncFn();
|
|
55
|
+
if (mounted) {
|
|
56
|
+
setData(result);
|
|
57
|
+
setLoading(false);
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (mounted) {
|
|
61
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
62
|
+
setError(error);
|
|
63
|
+
setLoading(false);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
execute();
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
mounted = false;
|
|
72
|
+
};
|
|
73
|
+
}, deps);
|
|
74
|
+
|
|
75
|
+
return { data, loading, error };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Hook for debounced values
|
|
80
|
+
*/
|
|
81
|
+
export function useDebounce<T>(value: T, delay: number): T {
|
|
82
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const handler = setTimeout(() => {
|
|
86
|
+
setDebouncedValue(value);
|
|
87
|
+
}, delay);
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
clearTimeout(handler);
|
|
91
|
+
};
|
|
92
|
+
}, [value, delay]);
|
|
93
|
+
|
|
94
|
+
return debouncedValue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Hook for throttled values
|
|
99
|
+
*/
|
|
100
|
+
export function useThrottle<T>(value: T, delay: number): T {
|
|
101
|
+
const [throttledValue, setThrottledValue] = useState<T>(value);
|
|
102
|
+
const lastRun = useRef(Date.now());
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
const handler = setTimeout(() => {
|
|
106
|
+
if (Date.now() - lastRun.current >= delay) {
|
|
107
|
+
setThrottledValue(value);
|
|
108
|
+
lastRun.current = Date.now();
|
|
109
|
+
}
|
|
110
|
+
}, delay - (Date.now() - lastRun.current));
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
clearTimeout(handler);
|
|
114
|
+
};
|
|
115
|
+
}, [value, delay]);
|
|
116
|
+
|
|
117
|
+
return throttledValue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Hook for previous value
|
|
122
|
+
*/
|
|
123
|
+
export function usePrevious<T>(value: T): T | undefined {
|
|
124
|
+
const ref = useRef<T>();
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
ref.current = value;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return ref.current;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Hook for boolean state with toggle
|
|
135
|
+
*/
|
|
136
|
+
export function useToggle(initialValue: boolean = false) {
|
|
137
|
+
const [value, setValue] = useState(initialValue);
|
|
138
|
+
|
|
139
|
+
const toggle = useCallback(() => setValue(v => !v), []);
|
|
140
|
+
const setTrue = useCallback(() => setValue(true), []);
|
|
141
|
+
const setFalse = useCallback(() => setValue(false), []);
|
|
142
|
+
|
|
143
|
+
return { value, toggle, setTrue, setFalse, setValue };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Hook for counter state
|
|
148
|
+
*/
|
|
149
|
+
export function useCounter(initialValue: number = 0) {
|
|
150
|
+
const [count, setCount] = useState(initialValue);
|
|
151
|
+
|
|
152
|
+
const increment = useCallback(() => setCount(c => c + 1), []);
|
|
153
|
+
const decrement = useCallback(() => setCount(c => c - 1), []);
|
|
154
|
+
const reset = useCallback(() => setCount(initialValue), [initialValue]);
|
|
155
|
+
const setValue = useCallback((value: number) => setCount(value), []);
|
|
156
|
+
|
|
157
|
+
return { count, increment, decrement, reset, setValue };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Hook for local storage
|
|
162
|
+
*/
|
|
163
|
+
export function useLocalStorage<T>(key: string, initialValue: T) {
|
|
164
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
165
|
+
try {
|
|
166
|
+
const item = window.localStorage.getItem(key);
|
|
167
|
+
return item ? JSON.parse(item) : initialValue;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error(`Error reading localStorage key "${key}":`, error);
|
|
170
|
+
return initialValue;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const setValue = useCallback((value: T | ((val: T) => T)) => {
|
|
175
|
+
try {
|
|
176
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
177
|
+
setStoredValue(valueToStore);
|
|
178
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(`Error setting localStorage key "${key}":`, error);
|
|
181
|
+
}
|
|
182
|
+
}, [key, storedValue]);
|
|
183
|
+
|
|
184
|
+
return [storedValue, setValue] as const;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Hook for session storage
|
|
189
|
+
*/
|
|
190
|
+
export function useSessionStorage<T>(key: string, initialValue: T) {
|
|
191
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
192
|
+
try {
|
|
193
|
+
const item = window.sessionStorage.getItem(key);
|
|
194
|
+
return item ? JSON.parse(item) : initialValue;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error(`Error reading sessionStorage key "${key}":`, error);
|
|
197
|
+
return initialValue;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const setValue = useCallback((value: T | ((val: T) => T)) => {
|
|
202
|
+
try {
|
|
203
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
204
|
+
setStoredValue(valueToStore);
|
|
205
|
+
window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error(`Error setting sessionStorage key "${key}":`, error);
|
|
208
|
+
}
|
|
209
|
+
}, [key, storedValue]);
|
|
210
|
+
|
|
211
|
+
return [storedValue, setValue] as const;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Hook for window size
|
|
216
|
+
*/
|
|
217
|
+
export function useWindowSize() {
|
|
218
|
+
const [windowSize, setWindowSize] = useState({
|
|
219
|
+
width: window.innerWidth,
|
|
220
|
+
height: window.innerHeight,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
const handleResize = () => {
|
|
225
|
+
setWindowSize({
|
|
226
|
+
width: window.innerWidth,
|
|
227
|
+
height: window.innerHeight,
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
window.addEventListener('resize', handleResize);
|
|
232
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
233
|
+
}, []);
|
|
234
|
+
|
|
235
|
+
return windowSize;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Hook for scroll position
|
|
240
|
+
*/
|
|
241
|
+
export function useScrollPosition() {
|
|
242
|
+
const [scrollPosition, setScrollPosition] = useState(0);
|
|
243
|
+
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
const handleScroll = () => {
|
|
246
|
+
setScrollPosition(window.pageYOffset);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
window.addEventListener('scroll', handleScroll);
|
|
250
|
+
return () => window.removeEventListener('scroll', handleScroll);
|
|
251
|
+
}, []);
|
|
252
|
+
|
|
253
|
+
return scrollPosition;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Hook for online/offline status
|
|
258
|
+
*/
|
|
259
|
+
export function useOnlineStatus() {
|
|
260
|
+
const [isOnline, setIsOnline] = useState(navigator.onLine);
|
|
261
|
+
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
const handleOnline = () => setIsOnline(true);
|
|
264
|
+
const handleOffline = () => setIsOnline(false);
|
|
265
|
+
|
|
266
|
+
window.addEventListener('online', handleOnline);
|
|
267
|
+
window.addEventListener('offline', handleOffline);
|
|
268
|
+
|
|
269
|
+
return () => {
|
|
270
|
+
window.removeEventListener('online', handleOnline);
|
|
271
|
+
window.removeEventListener('offline', handleOffline);
|
|
272
|
+
};
|
|
273
|
+
}, []);
|
|
274
|
+
|
|
275
|
+
return isOnline;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Hook for media queries
|
|
280
|
+
*/
|
|
281
|
+
export function useMediaQuery(query: string): boolean {
|
|
282
|
+
const [matches, setMatches] = useState(false);
|
|
283
|
+
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
const media = window.matchMedia(query);
|
|
286
|
+
if (media.matches !== matches) {
|
|
287
|
+
setMatches(media.matches);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const listener = () => setMatches(media.matches);
|
|
291
|
+
media.addEventListener('change', listener);
|
|
292
|
+
|
|
293
|
+
return () => media.removeEventListener('change', listener);
|
|
294
|
+
}, [matches, query]);
|
|
295
|
+
|
|
296
|
+
return matches;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Hook for keyboard events
|
|
301
|
+
*/
|
|
302
|
+
export function useKeyPress(targetKey: string): boolean {
|
|
303
|
+
const [keyPressed, setKeyPressed] = useState(false);
|
|
304
|
+
|
|
305
|
+
useEffect(() => {
|
|
306
|
+
const downHandler = ({ key }: KeyboardEvent) => {
|
|
307
|
+
if (key === targetKey) {
|
|
308
|
+
setKeyPressed(true);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const upHandler = ({ key }: KeyboardEvent) => {
|
|
313
|
+
if (key === targetKey) {
|
|
314
|
+
setKeyPressed(false);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
window.addEventListener('keydown', downHandler);
|
|
319
|
+
window.addEventListener('keyup', upHandler);
|
|
320
|
+
|
|
321
|
+
return () => {
|
|
322
|
+
window.removeEventListener('keydown', downHandler);
|
|
323
|
+
window.removeEventListener('keyup', upHandler);
|
|
324
|
+
};
|
|
325
|
+
}, [targetKey]);
|
|
326
|
+
|
|
327
|
+
return keyPressed;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Hook for click outside detection
|
|
332
|
+
*/
|
|
333
|
+
export function useClickOutside(ref: React.RefObject<HTMLElement>, handler: () => void) {
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
const listener = (event: MouseEvent | TouchEvent) => {
|
|
336
|
+
if (!ref.current || ref.current.contains(event.target as Node)) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
handler();
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
document.addEventListener('mousedown', listener);
|
|
343
|
+
document.addEventListener('touchstart', listener);
|
|
344
|
+
|
|
345
|
+
return () => {
|
|
346
|
+
document.removeEventListener('mousedown', listener);
|
|
347
|
+
document.removeEventListener('touchstart', listener);
|
|
348
|
+
};
|
|
349
|
+
}, [ref, handler]);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Hook for form validation
|
|
354
|
+
*/
|
|
355
|
+
export function useFormValidation<T extends Record<string, any>>(
|
|
356
|
+
initialValues: T,
|
|
357
|
+
validationSchema: (values: T) => Partial<Record<keyof T, string>>
|
|
358
|
+
) {
|
|
359
|
+
const [values, setValues] = useState<T>(initialValues);
|
|
360
|
+
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
|
|
361
|
+
const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({});
|
|
362
|
+
|
|
363
|
+
const validate = useCallback((valuesToValidate: T) => {
|
|
364
|
+
return validationSchema(valuesToValidate);
|
|
365
|
+
}, [validationSchema]);
|
|
366
|
+
|
|
367
|
+
const setValue = useCallback((field: keyof T, value: any) => {
|
|
368
|
+
setValues(prev => ({ ...prev, [field]: value }));
|
|
369
|
+
if (touched[field]) {
|
|
370
|
+
const newErrors = validate({ ...values, [field]: value });
|
|
371
|
+
setErrors(prev => ({ ...prev, [field]: newErrors[field] }));
|
|
372
|
+
}
|
|
373
|
+
}, [values, touched, validate]);
|
|
374
|
+
|
|
375
|
+
const setTouchedField = useCallback((field: keyof T) => {
|
|
376
|
+
setTouched(prev => ({ ...prev, [field]: true }));
|
|
377
|
+
const newErrors = validate(values);
|
|
378
|
+
setErrors(prev => ({ ...prev, [field]: newErrors[field] }));
|
|
379
|
+
}, [values, validate]);
|
|
380
|
+
|
|
381
|
+
const isValid = useMemo(() => {
|
|
382
|
+
const validationErrors = validate(values);
|
|
383
|
+
return Object.keys(validationErrors).length === 0;
|
|
384
|
+
}, [values, validate]);
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
values,
|
|
388
|
+
errors,
|
|
389
|
+
touched,
|
|
390
|
+
isValid,
|
|
391
|
+
setValue,
|
|
392
|
+
setTouchedField,
|
|
393
|
+
setValues,
|
|
394
|
+
setErrors,
|
|
395
|
+
setTouched
|
|
396
|
+
};
|
|
397
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging utilities for consistent logging across the application
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export enum LogLevel {
|
|
6
|
+
DEBUG = 0,
|
|
7
|
+
INFO = 1,
|
|
8
|
+
WARN = 2,
|
|
9
|
+
ERROR = 3,
|
|
10
|
+
NONE = 4
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface LogContext {
|
|
14
|
+
component?: string;
|
|
15
|
+
method?: string;
|
|
16
|
+
userId?: string;
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
requestId?: string;
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class Logger {
|
|
23
|
+
private level: LogLevel = LogLevel.INFO;
|
|
24
|
+
private isDevelopment: boolean = process.env.NODE_ENV === 'development';
|
|
25
|
+
|
|
26
|
+
setLevel(level: LogLevel): void {
|
|
27
|
+
this.level = level;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private shouldLog(level: LogLevel): boolean {
|
|
31
|
+
return level >= this.level;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private formatMessage(level: string, message: string, context?: LogContext): string {
|
|
35
|
+
const timestamp = new Date().toISOString();
|
|
36
|
+
const contextStr = context ? ` [${Object.entries(context).map(([k, v]) => `${k}:${v}`).join(', ')}]` : '';
|
|
37
|
+
return `[${timestamp}] ${level}${contextStr}: ${message}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
debug(message: string, context?: LogContext, ...args: any[]): void {
|
|
41
|
+
if (this.shouldLog(LogLevel.DEBUG)) {
|
|
42
|
+
const formattedMessage = this.formatMessage('DEBUG', message, context);
|
|
43
|
+
if (this.isDevelopment) {
|
|
44
|
+
console.log(formattedMessage, ...args);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
info(message: string, context?: LogContext, ...args: any[]): void {
|
|
50
|
+
if (this.shouldLog(LogLevel.INFO)) {
|
|
51
|
+
const formattedMessage = this.formatMessage('INFO', message, context);
|
|
52
|
+
console.log(formattedMessage, ...args);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
warn(message: string, context?: LogContext, ...args: any[]): void {
|
|
57
|
+
if (this.shouldLog(LogLevel.WARN)) {
|
|
58
|
+
const formattedMessage = this.formatMessage('WARN', message, context);
|
|
59
|
+
console.warn(formattedMessage, ...args);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
error(message: string, error?: any, context?: LogContext, ...args: any[]): void {
|
|
64
|
+
if (this.shouldLog(LogLevel.ERROR)) {
|
|
65
|
+
const formattedMessage = this.formatMessage('ERROR', message, context);
|
|
66
|
+
if (error) {
|
|
67
|
+
console.error(formattedMessage, error, ...args);
|
|
68
|
+
} else {
|
|
69
|
+
console.error(formattedMessage, ...args);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Specialized logging methods for common patterns
|
|
75
|
+
auth(message: string, context?: LogContext, ...args: any[]): void {
|
|
76
|
+
this.info(`🔐 ${message}`, { ...context, category: 'auth' }, ...args);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
api(message: string, context?: LogContext, ...args: any[]): void {
|
|
80
|
+
this.info(`🌐 ${message}`, { ...context, category: 'api' }, ...args);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
session(message: string, context?: LogContext, ...args: any[]): void {
|
|
84
|
+
this.info(`📱 ${message}`, { ...context, category: 'session' }, ...args);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
user(message: string, context?: LogContext, ...args: any[]): void {
|
|
88
|
+
this.info(`👤 ${message}`, { ...context, category: 'user' }, ...args);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
device(message: string, context?: LogContext, ...args: any[]): void {
|
|
92
|
+
this.info(`📱 ${message}`, { ...context, category: 'device' }, ...args);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
payment(message: string, context?: LogContext, ...args: any[]): void {
|
|
96
|
+
this.info(`💳 ${message}`, { ...context, category: 'payment' }, ...args);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Performance logging
|
|
100
|
+
performance(operation: string, duration: number, context?: LogContext): void {
|
|
101
|
+
const level = duration > 1000 ? LogLevel.WARN : LogLevel.INFO;
|
|
102
|
+
const message = `⏱️ ${operation} completed in ${duration}ms`;
|
|
103
|
+
if (level === LogLevel.WARN) {
|
|
104
|
+
this.warn(message, { ...context, category: 'performance', duration });
|
|
105
|
+
} else {
|
|
106
|
+
this.info(message, { ...context, category: 'performance', duration });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Error logging with stack trace
|
|
111
|
+
errorWithStack(message: string, error: Error, context?: LogContext): void {
|
|
112
|
+
this.error(message, error, { ...context, stack: error.stack });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Group related log messages
|
|
116
|
+
group(label: string, fn: () => void): void {
|
|
117
|
+
if (this.isDevelopment && this.shouldLog(LogLevel.DEBUG)) {
|
|
118
|
+
console.group(label);
|
|
119
|
+
fn();
|
|
120
|
+
console.groupEnd();
|
|
121
|
+
} else {
|
|
122
|
+
fn();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Create singleton instance
|
|
128
|
+
export const logger = new Logger();
|
|
129
|
+
|
|
130
|
+
// Convenience functions for common logging patterns
|
|
131
|
+
export const logAuth = (message: string, context?: LogContext, ...args: any[]) =>
|
|
132
|
+
logger.auth(message, context, ...args);
|
|
133
|
+
|
|
134
|
+
export const logApi = (message: string, context?: LogContext, ...args: any[]) =>
|
|
135
|
+
logger.api(message, context, ...args);
|
|
136
|
+
|
|
137
|
+
export const logSession = (message: string, context?: LogContext, ...args: any[]) =>
|
|
138
|
+
logger.session(message, context, ...args);
|
|
139
|
+
|
|
140
|
+
export const logUser = (message: string, context?: LogContext, ...args: any[]) =>
|
|
141
|
+
logger.user(message, context, ...args);
|
|
142
|
+
|
|
143
|
+
export const logDevice = (message: string, context?: LogContext, ...args: any[]) =>
|
|
144
|
+
logger.device(message, context, ...args);
|
|
145
|
+
|
|
146
|
+
export const logPayment = (message: string, context?: LogContext, ...args: any[]) =>
|
|
147
|
+
logger.payment(message, context, ...args);
|
|
148
|
+
|
|
149
|
+
export const logError = (message: string, error?: any, context?: LogContext, ...args: any[]) =>
|
|
150
|
+
logger.error(message, error, context, ...args);
|
|
151
|
+
|
|
152
|
+
export const logPerformance = (operation: string, duration: number, context?: LogContext) =>
|
|
153
|
+
logger.performance(operation, duration, context);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation utilities for common data validation patterns
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Email validation regex
|
|
7
|
+
*/
|
|
8
|
+
export const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Username validation regex (alphanumeric, underscore, dash, 3-30 chars)
|
|
12
|
+
*/
|
|
13
|
+
export const USERNAME_REGEX = /^[a-zA-Z0-9_-]{3,30}$/;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Password validation regex (at least 8 chars, 1 uppercase, 1 lowercase, 1 number)
|
|
17
|
+
*/
|
|
18
|
+
export const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validate email format
|
|
22
|
+
*/
|
|
23
|
+
export function isValidEmail(email: string): boolean {
|
|
24
|
+
return EMAIL_REGEX.test(email);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validate username format
|
|
29
|
+
*/
|
|
30
|
+
export function isValidUsername(username: string): boolean {
|
|
31
|
+
return USERNAME_REGEX.test(username);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validate password strength
|
|
36
|
+
*/
|
|
37
|
+
export function isValidPassword(password: string): boolean {
|
|
38
|
+
return PASSWORD_REGEX.test(password);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validate required string
|
|
43
|
+
*/
|
|
44
|
+
export function isRequiredString(value: any): boolean {
|
|
45
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate required number
|
|
50
|
+
*/
|
|
51
|
+
export function isRequiredNumber(value: any): boolean {
|
|
52
|
+
return typeof value === 'number' && !isNaN(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validate required boolean
|
|
57
|
+
*/
|
|
58
|
+
export function isRequiredBoolean(value: any): boolean {
|
|
59
|
+
return typeof value === 'boolean';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validate array
|
|
64
|
+
*/
|
|
65
|
+
export function isValidArray(value: any): boolean {
|
|
66
|
+
return Array.isArray(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validate object
|
|
71
|
+
*/
|
|
72
|
+
export function isValidObject(value: any): boolean {
|
|
73
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validate UUID format
|
|
78
|
+
*/
|
|
79
|
+
export function isValidUUID(uuid: string): boolean {
|
|
80
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
81
|
+
return UUID_REGEX.test(uuid);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validate URL format
|
|
86
|
+
*/
|
|
87
|
+
export function isValidURL(url: string): boolean {
|
|
88
|
+
try {
|
|
89
|
+
new URL(url);
|
|
90
|
+
return true;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Validate date string
|
|
98
|
+
*/
|
|
99
|
+
export function isValidDate(dateString: string): boolean {
|
|
100
|
+
const date = new Date(dateString);
|
|
101
|
+
return !isNaN(date.getTime());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Validate file size (in bytes)
|
|
106
|
+
*/
|
|
107
|
+
export function isValidFileSize(size: number, maxSize: number): boolean {
|
|
108
|
+
return size > 0 && size <= maxSize;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Validate file type
|
|
113
|
+
*/
|
|
114
|
+
export function isValidFileType(filename: string, allowedTypes: string[]): boolean {
|
|
115
|
+
const extension = filename.split('.').pop()?.toLowerCase();
|
|
116
|
+
return extension ? allowedTypes.includes(extension) : false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Sanitize string input
|
|
121
|
+
*/
|
|
122
|
+
export function sanitizeString(input: string): string {
|
|
123
|
+
return input.trim().replace(/[<>]/g, '');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Sanitize HTML input
|
|
128
|
+
*/
|
|
129
|
+
export function sanitizeHTML(input: string): string {
|
|
130
|
+
return input
|
|
131
|
+
.replace(/&/g, '&')
|
|
132
|
+
.replace(/</g, '<')
|
|
133
|
+
.replace(/>/g, '>')
|
|
134
|
+
.replace(/"/g, '"')
|
|
135
|
+
.replace(/'/g, ''');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Validate and sanitize user input
|
|
140
|
+
*/
|
|
141
|
+
export function validateAndSanitizeUserInput(input: any, type: 'string' | 'email' | 'username'): string | null {
|
|
142
|
+
if (typeof input !== 'string') {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const sanitized = sanitizeString(input);
|
|
147
|
+
|
|
148
|
+
switch (type) {
|
|
149
|
+
case 'email':
|
|
150
|
+
return isValidEmail(sanitized) ? sanitized : null;
|
|
151
|
+
case 'username':
|
|
152
|
+
return isValidUsername(sanitized) ? sanitized : null;
|
|
153
|
+
case 'string':
|
|
154
|
+
return isRequiredString(sanitized) ? sanitized : null;
|
|
155
|
+
default:
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["models/secureSession.ts"],"mappings":"","ignoreList":[]}
|