@djangocfg/ui-nextjs 2.1.59 → 2.1.60
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/package.json +4 -4
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useLocalStorage.ts +113 -27
- package/src/hooks/useSessionStorage.ts +132 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-nextjs",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.60",
|
|
4
4
|
"description": "Next.js UI component library with Radix UI primitives, Tailwind CSS styling, charts, and form components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"check": "tsc --noEmit"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@djangocfg/api": "^2.1.
|
|
62
|
-
"@djangocfg/ui-core": "^2.1.
|
|
61
|
+
"@djangocfg/api": "^2.1.60",
|
|
62
|
+
"@djangocfg/ui-core": "^2.1.60",
|
|
63
63
|
"@types/react": "^19.1.0",
|
|
64
64
|
"@types/react-dom": "^19.1.0",
|
|
65
65
|
"consola": "^3.4.2",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"vidstack": "next"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
109
|
+
"@djangocfg/typescript-config": "^2.1.60",
|
|
110
110
|
"@types/node": "^24.7.2",
|
|
111
111
|
"eslint": "^9.37.0",
|
|
112
112
|
"tailwindcss-animate": "1.0.7",
|
package/src/hooks/index.ts
CHANGED
|
@@ -9,7 +9,9 @@ export * from '@djangocfg/ui-core/hooks';
|
|
|
9
9
|
|
|
10
10
|
// Storage hooks (browser localStorage/sessionStorage)
|
|
11
11
|
export { useLocalStorage } from './useLocalStorage';
|
|
12
|
+
export type { UseLocalStorageOptions } from './useLocalStorage';
|
|
12
13
|
export { useSessionStorage } from './useSessionStorage';
|
|
14
|
+
export type { UseSessionStorageOptions } from './useSessionStorage';
|
|
13
15
|
|
|
14
16
|
// Theme hook (standalone, no provider required)
|
|
15
17
|
export { useResolvedTheme } from './useResolvedTheme';
|
|
@@ -3,7 +3,55 @@
|
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Storage wrapper format with metadata
|
|
7
|
+
* Used when TTL is specified
|
|
8
|
+
*/
|
|
9
|
+
interface StorageWrapper<T> {
|
|
10
|
+
_meta: {
|
|
11
|
+
createdAt: number;
|
|
12
|
+
ttl: number;
|
|
13
|
+
};
|
|
14
|
+
_value: T;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for useLocalStorage hook
|
|
19
|
+
*/
|
|
20
|
+
export interface UseLocalStorageOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Time-to-live in milliseconds.
|
|
23
|
+
* After this time, value is considered expired and initialValue is returned.
|
|
24
|
+
* Data is automatically cleaned up on next read.
|
|
25
|
+
* @example 24 * 60 * 60 * 1000 // 24 hours
|
|
26
|
+
*/
|
|
27
|
+
ttl?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if data is in new wrapped format with _meta
|
|
32
|
+
*/
|
|
33
|
+
function isWrappedFormat<T>(data: unknown): data is StorageWrapper<T> {
|
|
34
|
+
return (
|
|
35
|
+
data !== null &&
|
|
36
|
+
typeof data === 'object' &&
|
|
37
|
+
'_meta' in data &&
|
|
38
|
+
'_value' in data &&
|
|
39
|
+
typeof (data as StorageWrapper<T>)._meta === 'object' &&
|
|
40
|
+
typeof (data as StorageWrapper<T>)._meta.createdAt === 'number'
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if wrapped data is expired
|
|
46
|
+
*/
|
|
47
|
+
function isExpired<T>(wrapped: StorageWrapper<T>): boolean {
|
|
48
|
+
if (!wrapped._meta.ttl) return false;
|
|
49
|
+
const age = Date.now() - wrapped._meta.createdAt;
|
|
50
|
+
return age > wrapped._meta.ttl;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Simple localStorage hook with better error handling and optional TTL support
|
|
7
55
|
*
|
|
8
56
|
* IMPORTANT: To prevent hydration mismatch, this hook:
|
|
9
57
|
* - Always returns initialValue on first render (same as SSR)
|
|
@@ -11,9 +59,25 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
11
59
|
*
|
|
12
60
|
* @param key - Storage key
|
|
13
61
|
* @param initialValue - Default value if key doesn't exist
|
|
62
|
+
* @param options - Optional configuration (ttl for auto-expiration)
|
|
14
63
|
* @returns [value, setValue, removeValue] - Current value, setter function, and remove function
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // Without TTL (backwards compatible)
|
|
67
|
+
* const [value, setValue] = useLocalStorage('key', 'default');
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // With TTL (24 hours)
|
|
71
|
+
* const [value, setValue] = useLocalStorage('key', 'default', {
|
|
72
|
+
* ttl: 24 * 60 * 60 * 1000
|
|
73
|
+
* });
|
|
15
74
|
*/
|
|
16
|
-
export function useLocalStorage<T>(
|
|
75
|
+
export function useLocalStorage<T>(
|
|
76
|
+
key: string,
|
|
77
|
+
initialValue: T,
|
|
78
|
+
options?: UseLocalStorageOptions
|
|
79
|
+
) {
|
|
80
|
+
const ttl = options?.ttl;
|
|
17
81
|
// Always start with initialValue to match SSR
|
|
18
82
|
const [storedValue, setStoredValue] = useState<T>(initialValue);
|
|
19
83
|
const [isHydrated, setIsHydrated] = useState(false);
|
|
@@ -29,7 +93,23 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
|
|
|
29
93
|
if (item !== null) {
|
|
30
94
|
// Try to parse as JSON first, fallback to string
|
|
31
95
|
try {
|
|
32
|
-
|
|
96
|
+
const parsed = JSON.parse(item);
|
|
97
|
+
|
|
98
|
+
// Check if new format with _meta
|
|
99
|
+
if (isWrappedFormat<T>(parsed)) {
|
|
100
|
+
// Check TTL expiration
|
|
101
|
+
if (isExpired(parsed)) {
|
|
102
|
+
// Expired! Clean up and use initial value
|
|
103
|
+
window.localStorage.removeItem(key);
|
|
104
|
+
// Keep initialValue (already set)
|
|
105
|
+
} else {
|
|
106
|
+
// Not expired, extract value
|
|
107
|
+
setStoredValue(parsed._value);
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
// Old format (backwards compatible)
|
|
111
|
+
setStoredValue(parsed as T);
|
|
112
|
+
}
|
|
33
113
|
} catch {
|
|
34
114
|
// If JSON.parse fails, return as string
|
|
35
115
|
setStoredValue(item as T);
|
|
@@ -101,18 +181,37 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
|
|
|
101
181
|
}
|
|
102
182
|
};
|
|
103
183
|
|
|
184
|
+
// Prepare data for storage (with or without TTL wrapper)
|
|
185
|
+
const prepareForStorage = (value: T): string => {
|
|
186
|
+
if (ttl) {
|
|
187
|
+
// Wrap with _meta for TTL support
|
|
188
|
+
const wrapped: StorageWrapper<T> = {
|
|
189
|
+
_meta: {
|
|
190
|
+
createdAt: Date.now(),
|
|
191
|
+
ttl,
|
|
192
|
+
},
|
|
193
|
+
_value: value,
|
|
194
|
+
};
|
|
195
|
+
return JSON.stringify(wrapped);
|
|
196
|
+
}
|
|
197
|
+
// Old format (no wrapper) - for strings, store directly
|
|
198
|
+
if (typeof value === 'string') {
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
return JSON.stringify(value);
|
|
202
|
+
};
|
|
203
|
+
|
|
104
204
|
// Update localStorage when value changes
|
|
105
205
|
const setValue = (value: T | ((val: T) => T)) => {
|
|
106
206
|
try {
|
|
107
207
|
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
108
|
-
|
|
208
|
+
|
|
109
209
|
// Check data size before attempting to save
|
|
110
210
|
if (!checkDataSize(valueToStore)) {
|
|
111
211
|
console.warn(`Data size too large for key "${key}", removing key`);
|
|
112
212
|
// Remove the key if data is too large
|
|
113
213
|
try {
|
|
114
214
|
window.localStorage.removeItem(key);
|
|
115
|
-
window.localStorage.removeItem(`${key}_timestamp`);
|
|
116
215
|
} catch {
|
|
117
216
|
// Ignore errors when removing
|
|
118
217
|
}
|
|
@@ -120,45 +219,32 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
|
|
|
120
219
|
setStoredValue(valueToStore);
|
|
121
220
|
return;
|
|
122
221
|
}
|
|
123
|
-
|
|
222
|
+
|
|
124
223
|
setStoredValue(valueToStore);
|
|
125
224
|
|
|
126
225
|
if (typeof window !== 'undefined') {
|
|
226
|
+
const dataToStore = prepareForStorage(valueToStore);
|
|
227
|
+
|
|
127
228
|
// Try to set the value
|
|
128
229
|
try {
|
|
129
|
-
|
|
130
|
-
if (typeof valueToStore === 'string') {
|
|
131
|
-
window.localStorage.setItem(key, valueToStore);
|
|
132
|
-
} else {
|
|
133
|
-
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
134
|
-
}
|
|
230
|
+
window.localStorage.setItem(key, dataToStore);
|
|
135
231
|
} catch (storageError: any) {
|
|
136
232
|
// If quota exceeded, clear old data and try again
|
|
137
|
-
if (storageError.name === 'QuotaExceededError' ||
|
|
138
|
-
storageError.code === 22 ||
|
|
233
|
+
if (storageError.name === 'QuotaExceededError' ||
|
|
234
|
+
storageError.code === 22 ||
|
|
139
235
|
storageError.message?.includes('quota')) {
|
|
140
236
|
console.warn('localStorage quota exceeded, clearing old data...');
|
|
141
237
|
clearOldData();
|
|
142
|
-
|
|
238
|
+
|
|
143
239
|
// Try again after clearing
|
|
144
240
|
try {
|
|
145
|
-
|
|
146
|
-
if (typeof valueToStore === 'string') {
|
|
147
|
-
window.localStorage.setItem(key, valueToStore);
|
|
148
|
-
} else {
|
|
149
|
-
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
150
|
-
}
|
|
241
|
+
window.localStorage.setItem(key, dataToStore);
|
|
151
242
|
} catch (retryError) {
|
|
152
243
|
console.error(`Failed to set localStorage key "${key}" after clearing old data:`, retryError);
|
|
153
244
|
// If still fails, force clear all and try one more time
|
|
154
245
|
try {
|
|
155
246
|
forceClearAll();
|
|
156
|
-
|
|
157
|
-
if (typeof valueToStore === 'string') {
|
|
158
|
-
window.localStorage.setItem(key, valueToStore);
|
|
159
|
-
} else {
|
|
160
|
-
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
161
|
-
}
|
|
247
|
+
window.localStorage.setItem(key, dataToStore);
|
|
162
248
|
} catch (finalError) {
|
|
163
249
|
console.error(`Failed to set localStorage key "${key}" after force clearing:`, finalError);
|
|
164
250
|
// If still fails, just update the state without localStorage
|
|
@@ -3,12 +3,78 @@
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Storage wrapper format with metadata
|
|
7
|
+
* Used when TTL is specified
|
|
8
|
+
*/
|
|
9
|
+
interface StorageWrapper<T> {
|
|
10
|
+
_meta: {
|
|
11
|
+
createdAt: number;
|
|
12
|
+
ttl: number;
|
|
13
|
+
};
|
|
14
|
+
_value: T;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for useSessionStorage hook
|
|
19
|
+
*/
|
|
20
|
+
export interface UseSessionStorageOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Time-to-live in milliseconds.
|
|
23
|
+
* After this time, value is considered expired and initialValue is returned.
|
|
24
|
+
* Data is automatically cleaned up on next read.
|
|
25
|
+
* @example 24 * 60 * 60 * 1000 // 24 hours
|
|
26
|
+
*/
|
|
27
|
+
ttl?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if data is in new wrapped format with _meta
|
|
32
|
+
*/
|
|
33
|
+
function isWrappedFormat<T>(data: unknown): data is StorageWrapper<T> {
|
|
34
|
+
return (
|
|
35
|
+
data !== null &&
|
|
36
|
+
typeof data === 'object' &&
|
|
37
|
+
'_meta' in data &&
|
|
38
|
+
'_value' in data &&
|
|
39
|
+
typeof (data as StorageWrapper<T>)._meta === 'object' &&
|
|
40
|
+
typeof (data as StorageWrapper<T>)._meta.createdAt === 'number'
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if wrapped data is expired
|
|
46
|
+
*/
|
|
47
|
+
function isExpired<T>(wrapped: StorageWrapper<T>): boolean {
|
|
48
|
+
if (!wrapped._meta.ttl) return false;
|
|
49
|
+
const age = Date.now() - wrapped._meta.createdAt;
|
|
50
|
+
return age > wrapped._meta.ttl;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Simple sessionStorage hook with better error handling and optional TTL support
|
|
55
|
+
*
|
|
7
56
|
* @param key - Storage key
|
|
8
57
|
* @param initialValue - Default value if key doesn't exist
|
|
58
|
+
* @param options - Optional configuration (ttl for auto-expiration)
|
|
9
59
|
* @returns [value, setValue, removeValue] - Current value, setter function, and remove function
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Without TTL (backwards compatible)
|
|
63
|
+
* const [value, setValue] = useSessionStorage('key', 'default');
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // With TTL (1 hour)
|
|
67
|
+
* const [value, setValue] = useSessionStorage('key', 'default', {
|
|
68
|
+
* ttl: 60 * 60 * 1000
|
|
69
|
+
* });
|
|
10
70
|
*/
|
|
11
|
-
export function useSessionStorage<T>(
|
|
71
|
+
export function useSessionStorage<T>(
|
|
72
|
+
key: string,
|
|
73
|
+
initialValue: T,
|
|
74
|
+
options?: UseSessionStorageOptions
|
|
75
|
+
) {
|
|
76
|
+
const ttl = options?.ttl;
|
|
77
|
+
|
|
12
78
|
// Get initial value from sessionStorage or use provided initialValue
|
|
13
79
|
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
14
80
|
if (typeof window === 'undefined') {
|
|
@@ -17,7 +83,29 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
|
|
|
17
83
|
|
|
18
84
|
try {
|
|
19
85
|
const item = window.sessionStorage.getItem(key);
|
|
20
|
-
|
|
86
|
+
if (item === null) return initialValue;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(item);
|
|
90
|
+
|
|
91
|
+
// Check if new format with _meta
|
|
92
|
+
if (isWrappedFormat<T>(parsed)) {
|
|
93
|
+
// Check TTL expiration
|
|
94
|
+
if (isExpired(parsed)) {
|
|
95
|
+
// Expired! Clean up and use initial value
|
|
96
|
+
window.sessionStorage.removeItem(key);
|
|
97
|
+
return initialValue;
|
|
98
|
+
}
|
|
99
|
+
// Not expired, extract value
|
|
100
|
+
return parsed._value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Old format (backwards compatible)
|
|
104
|
+
return parsed as T;
|
|
105
|
+
} catch {
|
|
106
|
+
// If JSON.parse fails, return as string
|
|
107
|
+
return item as T;
|
|
108
|
+
}
|
|
21
109
|
} catch (error) {
|
|
22
110
|
console.error(`Error reading sessionStorage key "${key}":`, error);
|
|
23
111
|
return initialValue;
|
|
@@ -30,13 +118,13 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
|
|
|
30
118
|
const jsonString = JSON.stringify(data);
|
|
31
119
|
const sizeInBytes = new Blob([jsonString]).size;
|
|
32
120
|
const sizeInKB = sizeInBytes / 1024;
|
|
33
|
-
|
|
121
|
+
|
|
34
122
|
// Limit to 1MB per item
|
|
35
123
|
if (sizeInKB > 1024) {
|
|
36
124
|
console.warn(`Data size (${sizeInKB.toFixed(2)}KB) exceeds 1MB limit for key "${key}"`);
|
|
37
125
|
return false;
|
|
38
126
|
}
|
|
39
|
-
|
|
127
|
+
|
|
40
128
|
return true;
|
|
41
129
|
} catch (error) {
|
|
42
130
|
console.error(`Error checking data size for key "${key}":`, error);
|
|
@@ -47,16 +135,15 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
|
|
|
47
135
|
// Clear old data when sessionStorage is full
|
|
48
136
|
const clearOldData = () => {
|
|
49
137
|
try {
|
|
50
|
-
const keys = Object.keys(sessionStorage).filter(
|
|
138
|
+
const keys = Object.keys(sessionStorage).filter(k => k && typeof k === 'string');
|
|
51
139
|
// Remove oldest items if we have more than 50 items
|
|
52
140
|
if (keys.length > 50) {
|
|
53
141
|
const itemsToRemove = Math.ceil(keys.length * 0.2);
|
|
54
142
|
for (let i = 0; i < itemsToRemove; i++) {
|
|
55
143
|
try {
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
sessionStorage.removeItem(
|
|
59
|
-
sessionStorage.removeItem(`${key}_timestamp`);
|
|
144
|
+
const k = keys[i];
|
|
145
|
+
if (k) {
|
|
146
|
+
sessionStorage.removeItem(k);
|
|
60
147
|
}
|
|
61
148
|
} catch {
|
|
62
149
|
// Ignore errors when removing items
|
|
@@ -72,9 +159,9 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
|
|
|
72
159
|
const forceClearAll = () => {
|
|
73
160
|
try {
|
|
74
161
|
const keys = Object.keys(sessionStorage);
|
|
75
|
-
for (const
|
|
162
|
+
for (const k of keys) {
|
|
76
163
|
try {
|
|
77
|
-
sessionStorage.removeItem(
|
|
164
|
+
sessionStorage.removeItem(k);
|
|
78
165
|
} catch {
|
|
79
166
|
// Ignore errors when removing items
|
|
80
167
|
}
|
|
@@ -84,18 +171,37 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
|
|
|
84
171
|
}
|
|
85
172
|
};
|
|
86
173
|
|
|
174
|
+
// Prepare data for storage (with or without TTL wrapper)
|
|
175
|
+
const prepareForStorage = (value: T): string => {
|
|
176
|
+
if (ttl) {
|
|
177
|
+
// Wrap with _meta for TTL support
|
|
178
|
+
const wrapped: StorageWrapper<T> = {
|
|
179
|
+
_meta: {
|
|
180
|
+
createdAt: Date.now(),
|
|
181
|
+
ttl,
|
|
182
|
+
},
|
|
183
|
+
_value: value,
|
|
184
|
+
};
|
|
185
|
+
return JSON.stringify(wrapped);
|
|
186
|
+
}
|
|
187
|
+
// Old format (no wrapper) - for strings, store directly
|
|
188
|
+
if (typeof value === 'string') {
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
return JSON.stringify(value);
|
|
192
|
+
};
|
|
193
|
+
|
|
87
194
|
// Update sessionStorage when value changes
|
|
88
195
|
const setValue = (value: T | ((val: T) => T)) => {
|
|
89
196
|
try {
|
|
90
197
|
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
91
|
-
|
|
198
|
+
|
|
92
199
|
// Check data size before attempting to save
|
|
93
200
|
if (!checkDataSize(valueToStore)) {
|
|
94
201
|
console.warn(`Data size too large for key "${key}", removing key`);
|
|
95
202
|
// Remove the key if data is too large
|
|
96
203
|
try {
|
|
97
204
|
window.sessionStorage.removeItem(key);
|
|
98
|
-
window.sessionStorage.removeItem(`${key}_timestamp`);
|
|
99
205
|
} catch {
|
|
100
206
|
// Ignore errors when removing
|
|
101
207
|
}
|
|
@@ -103,34 +209,32 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
|
|
|
103
209
|
setStoredValue(valueToStore);
|
|
104
210
|
return;
|
|
105
211
|
}
|
|
106
|
-
|
|
212
|
+
|
|
107
213
|
setStoredValue(valueToStore);
|
|
108
214
|
|
|
109
215
|
if (typeof window !== 'undefined') {
|
|
216
|
+
const dataToStore = prepareForStorage(valueToStore);
|
|
217
|
+
|
|
110
218
|
// Try to set the value
|
|
111
219
|
try {
|
|
112
|
-
window.sessionStorage.setItem(key,
|
|
113
|
-
// Add timestamp for cleanup
|
|
114
|
-
window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
220
|
+
window.sessionStorage.setItem(key, dataToStore);
|
|
115
221
|
} catch (storageError: any) {
|
|
116
222
|
// If quota exceeded, clear old data and try again
|
|
117
|
-
if (storageError.name === 'QuotaExceededError' ||
|
|
118
|
-
storageError.code === 22 ||
|
|
223
|
+
if (storageError.name === 'QuotaExceededError' ||
|
|
224
|
+
storageError.code === 22 ||
|
|
119
225
|
storageError.message?.includes('quota')) {
|
|
120
226
|
console.warn('sessionStorage quota exceeded, clearing old data...');
|
|
121
227
|
clearOldData();
|
|
122
|
-
|
|
228
|
+
|
|
123
229
|
// Try again after clearing
|
|
124
230
|
try {
|
|
125
|
-
window.sessionStorage.setItem(key,
|
|
126
|
-
window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
231
|
+
window.sessionStorage.setItem(key, dataToStore);
|
|
127
232
|
} catch (retryError) {
|
|
128
233
|
console.error(`Failed to set sessionStorage key "${key}" after clearing old data:`, retryError);
|
|
129
234
|
// If still fails, force clear all and try one more time
|
|
130
235
|
try {
|
|
131
236
|
forceClearAll();
|
|
132
|
-
window.sessionStorage.setItem(key,
|
|
133
|
-
window.sessionStorage.setItem(`${key}_timestamp`, Date.now().toString());
|
|
237
|
+
window.sessionStorage.setItem(key, dataToStore);
|
|
134
238
|
} catch (finalError) {
|
|
135
239
|
console.error(`Failed to set sessionStorage key "${key}" after force clearing:`, finalError);
|
|
136
240
|
// If still fails, just update the state without sessionStorage
|
|
@@ -157,18 +261,16 @@ export function useSessionStorage<T>(key: string, initialValue: T) {
|
|
|
157
261
|
if (typeof window !== 'undefined') {
|
|
158
262
|
try {
|
|
159
263
|
window.sessionStorage.removeItem(key);
|
|
160
|
-
window.sessionStorage.removeItem(`${key}_timestamp`);
|
|
161
264
|
} catch (removeError: any) {
|
|
162
265
|
// If removal fails due to quota, try to clear some data first
|
|
163
|
-
if (removeError.name === 'QuotaExceededError' ||
|
|
164
|
-
removeError.code === 22 ||
|
|
266
|
+
if (removeError.name === 'QuotaExceededError' ||
|
|
267
|
+
removeError.code === 22 ||
|
|
165
268
|
removeError.message?.includes('quota')) {
|
|
166
269
|
console.warn('sessionStorage quota exceeded during removal, clearing old data...');
|
|
167
270
|
clearOldData();
|
|
168
|
-
|
|
271
|
+
|
|
169
272
|
try {
|
|
170
273
|
window.sessionStorage.removeItem(key);
|
|
171
|
-
window.sessionStorage.removeItem(`${key}_timestamp`);
|
|
172
274
|
} catch (retryError) {
|
|
173
275
|
console.error(`Failed to remove sessionStorage key "${key}" after clearing:`, retryError);
|
|
174
276
|
// If still fails, force clear all
|