funuicss 3.8.2 → 3.8.4

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/ui/theme/theme.js CHANGED
@@ -225,8 +225,27 @@ var loadThemeFromCDN = function (projectId) { return __awaiter(void 0, void 0, v
225
225
  /* -------------------------------------------------------------------------- */
226
226
  /* BUCKET JSON LOADER */
227
227
  /* -------------------------------------------------------------------------- */
228
+ // Cache for JSON responses
229
+ var jsonFileCache = new Map();
230
+ var CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
231
+ // Helper function to get cache key
232
+ var getCacheKey = function (bucketSanitizedName, projectId, page) {
233
+ return "".concat(projectId, ":").concat(bucketSanitizedName, ":").concat(page);
234
+ };
235
+ // Helper function to clean expired cache entries
236
+ var cleanExpiredCache = function () {
237
+ var now = Date.now();
238
+ for (var _i = 0, _a = Array.from(jsonFileCache.keys()); _i < _a.length; _i++) {
239
+ var key = _a[_i];
240
+ var entry = jsonFileCache.get(key);
241
+ if (entry && now - entry.timestamp > CACHE_DURATION) {
242
+ jsonFileCache.delete(key);
243
+ }
244
+ }
245
+ };
246
+ // Load JSON file with caching and performance optimizations
228
247
  var loadBucketJsonFromCDN = function (bucketSanitizedName, projectId, page) { return __awaiter(void 0, void 0, void 0, function () {
229
- var pageNumber, publicUrl, response, data, error_4;
248
+ var cacheKey, cached, pageNumber, publicUrl, controller_1, timeoutId, response, data, error_4;
230
249
  return __generator(this, function (_a) {
231
250
  switch (_a.label) {
232
251
  case 0:
@@ -234,21 +253,42 @@ var loadBucketJsonFromCDN = function (bucketSanitizedName, projectId, page) { re
234
253
  console.error('❌ Missing parameters for JSON loading');
235
254
  return [2 /*return*/, null];
236
255
  }
256
+ // Clean expired cache entries periodically
257
+ if (jsonFileCache.size > 100) { // Only clean when cache gets large
258
+ cleanExpiredCache();
259
+ }
260
+ cacheKey = getCacheKey(bucketSanitizedName, projectId, page);
261
+ cached = jsonFileCache.get(cacheKey);
262
+ if (cached && (Date.now() - cached.timestamp < CACHE_DURATION)) {
263
+ console.log("\uD83D\uDCE6 Returning cached data for page ".concat(page));
264
+ return [2 /*return*/, cached.data];
265
+ }
237
266
  _a.label = 1;
238
267
  case 1:
239
268
  _a.trys.push([1, 6, , 7]);
240
269
  pageNumber = page.toString();
241
270
  publicUrl = "https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/projects%2F".concat(projectId, "%2Fbuckets%2F").concat(bucketSanitizedName, "%2F").concat(pageNumber, ".json?alt=media");
271
+ controller_1 = new AbortController();
272
+ timeoutId = setTimeout(function () { return controller_1.abort(); }, 10000) // 10 second timeout
273
+ ;
242
274
  return [4 /*yield*/, fetch(publicUrl, {
243
275
  cache: 'no-cache',
276
+ signal: controller_1.signal
244
277
  })];
245
278
  case 2:
246
279
  response = _a.sent();
280
+ clearTimeout(timeoutId);
247
281
  if (!response.ok) return [3 /*break*/, 4];
248
- return [4 /*yield*/, response.json()];
282
+ return [4 /*yield*/, response.json()
283
+ // Cache the response
284
+ ];
249
285
  case 3:
250
286
  data = _a.sent();
251
- console.log(data);
287
+ // Cache the response
288
+ jsonFileCache.set(cacheKey, {
289
+ data: data,
290
+ timestamp: Date.now()
291
+ });
252
292
  return [2 /*return*/, data];
253
293
  case 4:
254
294
  // File might not exist (e.g., page out of range)
@@ -256,14 +296,41 @@ var loadBucketJsonFromCDN = function (bucketSanitizedName, projectId, page) { re
256
296
  case 5: return [3 /*break*/, 7];
257
297
  case 6:
258
298
  error_4 = _a.sent();
259
- console.error("\u274C Error loading JSON file for page ".concat(page, ":"), error_4);
299
+ if (error_4 instanceof DOMException && error_4.name === 'AbortError') {
300
+ console.error("\u23F0 Timeout loading JSON file for page ".concat(page));
301
+ }
302
+ else {
303
+ console.error("\u274C Error loading JSON file for page ".concat(page, ":"), error_4);
304
+ }
260
305
  return [2 /*return*/, null];
261
306
  case 7: return [2 /*return*/];
262
307
  }
263
308
  });
264
309
  }); };
310
+ // Parallel loading for multiple pages
311
+ var loadMultipleJsonPages = function (bucketSanitizedName, projectId, pages) { return __awaiter(void 0, void 0, void 0, function () {
312
+ var promises, results, error_5;
313
+ return __generator(this, function (_a) {
314
+ switch (_a.label) {
315
+ case 0:
316
+ _a.trys.push([0, 2, , 3]);
317
+ promises = pages.map(function (page) {
318
+ return loadBucketJsonFromCDN(bucketSanitizedName, projectId, page);
319
+ });
320
+ return [4 /*yield*/, Promise.all(promises)];
321
+ case 1:
322
+ results = _a.sent();
323
+ return [2 /*return*/, results.filter(Boolean)];
324
+ case 2:
325
+ error_5 = _a.sent();
326
+ console.error('❌ Error loading multiple JSON pages:', error_5);
327
+ return [2 /*return*/, []];
328
+ case 3: return [2 /*return*/];
329
+ }
330
+ });
331
+ }); };
265
332
  var listBucketJsonFiles = function (bucketSanitizedName, projectId) { return __awaiter(void 0, void 0, void 0, function () {
266
- var files, page, hasMoreFiles, paddedPage, publicUrl, response, error_5;
333
+ var files, page, hasMoreFiles, batchSize, pageChecks, batchPromises, i, paddedPage, publicUrl, responses, i, currentPage, paddedPage, publicUrl, error_6;
267
334
  return __generator(this, function (_a) {
268
335
  switch (_a.label) {
269
336
  case 0:
@@ -277,35 +344,47 @@ var listBucketJsonFiles = function (bucketSanitizedName, projectId) { return __a
277
344
  files = [];
278
345
  page = 1;
279
346
  hasMoreFiles = true;
347
+ batchSize = 5;
348
+ pageChecks = [];
280
349
  _a.label = 2;
281
350
  case 2:
282
351
  if (!(hasMoreFiles && page <= 100)) return [3 /*break*/, 4];
283
- paddedPage = page.toString().padStart(3, '0');
284
- publicUrl = "https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/projects%2F".concat(projectId, "%2Fbuckets%2F").concat(bucketSanitizedName, "%2Fpage_").concat(paddedPage, ".json?alt=media");
285
- return [4 /*yield*/, fetch(publicUrl, {
352
+ batchPromises = [];
353
+ for (i = 0; i < batchSize && page <= 100; i++) {
354
+ paddedPage = page.toString().padStart(3, '0');
355
+ publicUrl = "https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/projects%2F".concat(projectId, "%2Fbuckets%2F").concat(bucketSanitizedName, "%2Fpage_").concat(paddedPage, ".json?alt=media");
356
+ batchPromises.push(fetch(publicUrl, {
286
357
  method: 'HEAD',
287
358
  cache: 'no-cache',
288
- })];
289
- case 3:
290
- response = _a.sent();
291
- if (response.ok) {
292
- files.push({
293
- name: "page_".concat(paddedPage, ".json"),
294
- fullPath: "projects/".concat(projectId, "/buckets/").concat(bucketSanitizedName, "/page_").concat(paddedPage, ".json"),
295
- url: publicUrl,
296
- size: parseInt(response.headers.get('content-length') || '0', 10),
297
- page: page
298
- });
359
+ }));
299
360
  page++;
300
361
  }
301
- else {
302
- hasMoreFiles = false;
362
+ return [4 /*yield*/, Promise.all(batchPromises)];
363
+ case 3:
364
+ responses = _a.sent();
365
+ for (i = 0; i < responses.length; i++) {
366
+ if (responses[i].ok) {
367
+ currentPage = page - batchSize + i;
368
+ paddedPage = currentPage.toString().padStart(3, '0');
369
+ publicUrl = "https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/projects%2F".concat(projectId, "%2Fbuckets%2F").concat(bucketSanitizedName, "%2Fpage_").concat(paddedPage, ".json?alt=media");
370
+ files.push({
371
+ name: "page_".concat(paddedPage, ".json"),
372
+ fullPath: "projects/".concat(projectId, "/buckets/").concat(bucketSanitizedName, "/page_").concat(paddedPage, ".json"),
373
+ url: publicUrl,
374
+ size: parseInt(responses[i].headers.get('content-length') || '0', 10),
375
+ page: currentPage
376
+ });
377
+ }
378
+ else {
379
+ hasMoreFiles = false;
380
+ break;
381
+ }
303
382
  }
304
383
  return [3 /*break*/, 2];
305
384
  case 4: return [2 /*return*/, files];
306
385
  case 5:
307
- error_5 = _a.sent();
308
- console.error('❌ Error listing JSON files:', error_5);
386
+ error_6 = _a.sent();
387
+ console.error('❌ Error listing JSON files:', error_6);
309
388
  return [2 /*return*/, []];
310
389
  case 6: return [2 /*return*/];
311
390
  }
@@ -383,7 +462,6 @@ var sanitizeBucketName = function (name) {
383
462
  var transformProjectBucket = function (bucket, projectId) {
384
463
  return {
385
464
  id: bucket.id || bucket._id || '',
386
- // projectId: bucket.projectId || projectId || '',
387
465
  name: bucket.name || '',
388
466
  displayName: bucket.displayName || bucket.name || '',
389
467
  category: bucket.category || 'uncategorized',
@@ -448,25 +526,33 @@ var ThemeProvider = function (_a) {
448
526
  return __generator(this, function (_a) {
449
527
  switch (_a.label) {
450
528
  case 0:
451
- _a.trys.push([0, 9, 10, 11]);
529
+ _a.trys.push([0, 8, 9, 10]);
452
530
  finalTheme = null;
453
531
  finalVersion = null;
454
- if (!providedProject) return [3 /*break*/, 1];
455
- console.log('✅ Using provided project data directly');
456
- finalTheme = providedProject;
457
- finalVersion = providedProject.version || 0;
458
- return [3 /*break*/, 8];
459
- case 1: return [4 /*yield*/, loadLocalTheme()];
460
- case 2:
532
+ // If project data is provided directly, use it and skip all loading
533
+ if (providedProject) {
534
+ console.log('✅ Using provided project data directly');
535
+ finalTheme = providedProject;
536
+ finalVersion = providedProject.version || 0;
537
+ // Apply theme immediately
538
+ applyThemeData(finalTheme, root);
539
+ setCurrentVersion(finalVersion);
540
+ setError(null);
541
+ setIsLoading(false);
542
+ setIsInitialLoad(false);
543
+ return [2 /*return*/]; // Skip all other loading logic
544
+ }
545
+ return [4 /*yield*/, loadLocalTheme()];
546
+ case 1:
461
547
  localTheme = _a.sent();
462
548
  localVersion = (localTheme === null || localTheme === void 0 ? void 0 : localTheme.version) || 0;
463
- if (!projectId) return [3 /*break*/, 7];
549
+ if (!projectId) return [3 /*break*/, 6];
464
550
  return [4 /*yield*/, validateOriginAccess(projectId)];
465
- case 3:
551
+ case 2:
466
552
  hasAccess = _a.sent();
467
- if (!hasAccess) return [3 /*break*/, 5];
553
+ if (!hasAccess) return [3 /*break*/, 4];
468
554
  return [4 /*yield*/, loadThemeFromCDN(projectId)];
469
- case 4:
555
+ case 3:
470
556
  cdnTheme = _a.sent();
471
557
  cdnVersion = (cdnTheme === null || cdnTheme === void 0 ? void 0 : cdnTheme.version) || 0;
472
558
  if (cdnTheme) {
@@ -489,8 +575,8 @@ var ThemeProvider = function (_a) {
489
575
  console.warn('⚠️ No theme found (CDN unavailable and no local theme)');
490
576
  setError('Theme not found');
491
577
  }
492
- return [3 /*break*/, 6];
493
- case 5:
578
+ return [3 /*break*/, 5];
579
+ case 4:
494
580
  // Origin validation failed
495
581
  if (localTheme) {
496
582
  console.log('⚠️ Origin validation failed, using local theme');
@@ -501,9 +587,9 @@ var ThemeProvider = function (_a) {
501
587
  console.error('❌ Origin validation failed and no local theme available');
502
588
  setError('Access denied and no local theme available');
503
589
  }
504
- _a.label = 6;
505
- case 6: return [3 /*break*/, 8];
506
- case 7:
590
+ _a.label = 5;
591
+ case 5: return [3 /*break*/, 7];
592
+ case 6:
507
593
  // No project ID provided - only use local theme
508
594
  console.log('ℹ️ No project ID provided, using local theme only');
509
595
  if (localTheme) {
@@ -515,8 +601,8 @@ var ThemeProvider = function (_a) {
515
601
  console.log('ℹ️ No local theme file found - using base theme only');
516
602
  // No error here - it's valid to use only base theme
517
603
  }
518
- _a.label = 8;
519
- case 8:
604
+ _a.label = 7;
605
+ case 7:
520
606
  // Apply the theme if we have one
521
607
  if (finalTheme && (!currentVersion || finalVersion !== currentVersion)) {
522
608
  applyThemeData(finalTheme, root);
@@ -526,33 +612,44 @@ var ThemeProvider = function (_a) {
526
612
  else if (finalTheme) {
527
613
  console.log('✓ Theme up to date');
528
614
  }
529
- return [3 /*break*/, 11];
530
- case 9:
615
+ return [3 /*break*/, 10];
616
+ case 8:
531
617
  err_1 = _a.sent();
532
618
  console.error('❌ Error loading theme:', err_1);
533
619
  setError('Failed to load theme');
534
- return [3 /*break*/, 11];
535
- case 10:
620
+ return [3 /*break*/, 10];
621
+ case 9:
536
622
  setIsLoading(false);
537
623
  setIsInitialLoad(false);
538
624
  return [7 /*endfinally*/];
539
- case 11: return [2 /*return*/];
625
+ case 10: return [2 /*return*/];
540
626
  }
541
627
  });
542
628
  }); };
543
- // Initial load
544
- loadTheme();
545
- // Only poll for updates if we have a project ID AND no provided project
546
- if (projectId && !providedProject) {
547
- pollTimer = setInterval(function () {
548
- loadTheme();
549
- }, 5 * 60 * 1000);
550
- }
551
- return function () {
552
- if (pollTimer) {
553
- clearInterval(pollTimer);
629
+ // Only load theme if no project is provided
630
+ if (!providedProject) {
631
+ // Initial load
632
+ loadTheme();
633
+ // Only poll for updates if we have a project ID
634
+ if (projectId) {
635
+ pollTimer = setInterval(function () {
636
+ loadTheme();
637
+ }, 5 * 60 * 1000);
554
638
  }
555
- };
639
+ return function () {
640
+ if (pollTimer) {
641
+ clearInterval(pollTimer);
642
+ }
643
+ };
644
+ }
645
+ else {
646
+ // If project is provided, skip loading and set state directly
647
+ console.log('✅ Using provided project data, skipping theme loading');
648
+ applyThemeData(providedProject, document.documentElement);
649
+ setCurrentVersion(providedProject.version || 0);
650
+ setIsLoading(false);
651
+ setIsInitialLoad(false);
652
+ }
556
653
  }, [projectId, currentVersion, theme, providedProject]); // Added providedProject to dependencies
557
654
  var applyThemeData = function (data, root) {
558
655
  var _a;
@@ -660,7 +757,7 @@ exports.useVariable = useVariable;
660
757
  var cachedAssets = [];
661
758
  var getAsset = function (name) {
662
759
  if (!cachedAssets.length) {
663
- console.warn('No assets available. Make sure ThemeProvider is mounted and assets are loaded.');
760
+ console.warn('No assets available. Make sure ThemeProvider is mounted.');
664
761
  return undefined;
665
762
  }
666
763
  var asset = cachedAssets.find(function (a) { return a.name === name; });
@@ -943,7 +1040,7 @@ var useAllJsonRecords = function (bucketIdOrName) {
943
1040
  var projectId = (0, exports.useTheme)().projectId;
944
1041
  var _d = (0, exports.useBucketJsonFiles)(bucketIdOrName), files = _d.files, filesLoading = _d.loading;
945
1042
  var loadAllRecords = (0, react_1.useCallback)(function () { return __awaiter(void 0, void 0, void 0, function () {
946
- var allRecords, _i, files_1, file, bucketSanitizedName, jsonData, err_4;
1043
+ var pageNumbers, jsonDataArray, allRecords_1, err_4;
947
1044
  return __generator(this, function (_a) {
948
1045
  switch (_a.label) {
949
1046
  case 0:
@@ -952,38 +1049,30 @@ var useAllJsonRecords = function (bucketIdOrName) {
952
1049
  }
953
1050
  _a.label = 1;
954
1051
  case 1:
955
- _a.trys.push([1, 6, 7, 8]);
1052
+ _a.trys.push([1, 3, 4, 5]);
956
1053
  setLoading(true);
957
1054
  setError(null);
958
- allRecords = [];
959
- _i = 0, files_1 = files;
960
- _a.label = 2;
1055
+ pageNumbers = files.map(function (file) { return file.page; });
1056
+ return [4 /*yield*/, loadMultipleJsonPages(sanitizeBucketName(bucket.displayName || bucket.name), projectId, pageNumbers)];
961
1057
  case 2:
962
- if (!(_i < files_1.length)) return [3 /*break*/, 5];
963
- file = files_1[_i];
964
- bucketSanitizedName = sanitizeBucketName(bucket.displayName || bucket.name);
965
- return [4 /*yield*/, loadBucketJsonFromCDN(bucketSanitizedName, projectId, file.page)];
1058
+ jsonDataArray = _a.sent();
1059
+ allRecords_1 = [];
1060
+ jsonDataArray.forEach(function (jsonData) {
1061
+ if (jsonData && jsonData.records) {
1062
+ allRecords_1.push.apply(allRecords_1, jsonData.records);
1063
+ }
1064
+ });
1065
+ setRecords(allRecords_1);
1066
+ return [3 /*break*/, 5];
966
1067
  case 3:
967
- jsonData = _a.sent();
968
- if (jsonData && jsonData.records) {
969
- allRecords.push.apply(allRecords, jsonData.records);
970
- }
971
- _a.label = 4;
972
- case 4:
973
- _i++;
974
- return [3 /*break*/, 2];
975
- case 5:
976
- setRecords(allRecords);
977
- return [3 /*break*/, 8];
978
- case 6:
979
1068
  err_4 = _a.sent();
980
1069
  console.error('Error loading all records:', err_4);
981
1070
  setError(err_4 instanceof Error ? err_4.message : 'Failed to load records');
982
- return [3 /*break*/, 8];
983
- case 7:
1071
+ return [3 /*break*/, 5];
1072
+ case 4:
984
1073
  setLoading(false);
985
1074
  return [7 /*endfinally*/];
986
- case 8: return [2 /*return*/];
1075
+ case 5: return [2 /*return*/];
987
1076
  }
988
1077
  });
989
1078
  }); }, [bucket, projectId, files, filesLoading]);
@@ -1011,12 +1100,21 @@ var useBucketCache = function () {
1011
1100
  var cacheKey = "".concat(bucket.id, "_").concat(projectId);
1012
1101
  delete cachedJsonFiles[cacheKey];
1013
1102
  delete cachedJsonData[cacheKey];
1103
+ // Also clear JSON file cache
1104
+ var bucketSanitizedName = sanitizeBucketName(bucket.displayName || bucket.name);
1105
+ for (var _i = 0, _a = Array.from(jsonFileCache.keys()); _i < _a.length; _i++) {
1106
+ var key = _a[_i];
1107
+ if (key.startsWith("".concat(projectId, ":").concat(bucketSanitizedName))) {
1108
+ jsonFileCache.delete(key);
1109
+ }
1110
+ }
1014
1111
  }
1015
1112
  }
1016
1113
  }
1017
1114
  else {
1018
1115
  cachedJsonFiles = {};
1019
1116
  cachedJsonData = {};
1117
+ jsonFileCache.clear();
1020
1118
  }
1021
1119
  }, []);
1022
1120
  var refresh = (0, react_1.useCallback)(function () {
@@ -1062,3 +1160,997 @@ var getPaginatedRecords = function (bucketIdOrName, page, pageSize) { return __a
1062
1160
  });
1063
1161
  }); };
1064
1162
  exports.getPaginatedRecords = getPaginatedRecords;
1163
+ // 'use client'
1164
+ // import React, {
1165
+ // useEffect,
1166
+ // createContext,
1167
+ // useContext,
1168
+ // useState,
1169
+ // ReactNode,
1170
+ // useMemo,
1171
+ // useCallback,
1172
+ // } from 'react'
1173
+ // import { colorVarsToDarken, themes } from './themes'
1174
+ // import { getDarkenAmount, darkenToRgba } from './darkenUtils'
1175
+ // /* -------------------------------------------------------------------------- */
1176
+ // /* TYPES */
1177
+ // /* -------------------------------------------------------------------------- */
1178
+ // export type ThemeVariant = 'standard' | 'minimal'
1179
+ // export type ThemeName =
1180
+ // | 'light'
1181
+ // | 'dark'
1182
+ // | 'dark-blue'
1183
+ // | 'light-gray'
1184
+ // | 'pastel-green'
1185
+ // | 'warm-orange'
1186
+ // | 'frosted-glass'
1187
+ // | 'midnight-purple'
1188
+ // | 'cyber-metal'
1189
+ // interface ThemeConfig {
1190
+ // [key: string]: any
1191
+ // }
1192
+ // interface Variable {
1193
+ // name: string
1194
+ // value: any
1195
+ // }
1196
+ // // Bucket Types
1197
+ // export interface BucketField {
1198
+ // name: string
1199
+ // label: string
1200
+ // type: string
1201
+ // required?: boolean
1202
+ // options?: string[]
1203
+ // min?: number
1204
+ // max?: number
1205
+ // multiple?: boolean
1206
+ // }
1207
+ // export interface Bucket {
1208
+ // id: string
1209
+ // name: string
1210
+ // displayName: string
1211
+ // category?: string
1212
+ // fields: BucketField[]
1213
+ // createdBy: string
1214
+ // createdAt: number
1215
+ // updatedAt: number
1216
+ // updatedBy: string
1217
+ // recordCount: number
1218
+ // jsonFilesCount: number
1219
+ // totalRecordsInJson: number
1220
+ // lastJsonExport?: number
1221
+ // userId?: string
1222
+ // createdAtString?: string
1223
+ // updatedAtString?: string
1224
+ // }
1225
+ // export interface BucketRecord {
1226
+ // id: string
1227
+ // values: Record<string, any>
1228
+ // createdBy: string
1229
+ // createdAt: number
1230
+ // updatedAt: number
1231
+ // updatedBy: string
1232
+ // }
1233
+ // export interface JsonFileMetadata {
1234
+ // page: number
1235
+ // totalPages: number
1236
+ // recordsInPage: number
1237
+ // totalRecords: number
1238
+ // exportedAt: string
1239
+ // recordsPerFile: number
1240
+ // projectId: string
1241
+ // bucketId: string
1242
+ // bucketName: string
1243
+ // }
1244
+ // export interface JsonFileData {
1245
+ // metadata: JsonFileMetadata
1246
+ // records: BucketRecord[]
1247
+ // }
1248
+ // export interface JsonFileInfo {
1249
+ // name: string
1250
+ // fullPath: string
1251
+ // url: string
1252
+ // size: number
1253
+ // page: number
1254
+ // }
1255
+ // interface ProjectData {
1256
+ // theme_config?: {
1257
+ // colors?: Record<string, string>
1258
+ // typography?: Record<string, string>
1259
+ // [key: string]: any
1260
+ // }
1261
+ // components?: Record<string, any>
1262
+ // default_variation?: ThemeVariant
1263
+ // variables?: Variable[]
1264
+ // assets?: Asset[]
1265
+ // buckets?: any[]
1266
+ // name?: string
1267
+ // project_id?: string
1268
+ // version?: number
1269
+ // updated_at?: string
1270
+ // trustedDomains?: Array<{
1271
+ // domain: string
1272
+ // status: string
1273
+ // isDefault?: boolean
1274
+ // }>
1275
+ // }
1276
+ // interface ThemeProviderProps {
1277
+ // theme: ThemeName
1278
+ // projectId?: string
1279
+ // funcss?: string
1280
+ // minHeight?: string
1281
+ // children: ReactNode
1282
+ // project?: ProjectData | null // New prop: directly provide project data
1283
+ // }
1284
+ // /* -------------------------------------------------------------------------- */
1285
+ // /* THEME CONTEXT */
1286
+ // /* -------------------------------------------------------------------------- */
1287
+ // interface ThemeContextType {
1288
+ // variant: ThemeVariant
1289
+ // setVariant: React.Dispatch<React.SetStateAction<ThemeVariant>>
1290
+ // themeConfig: ThemeConfig
1291
+ // projectData: ProjectData | null
1292
+ // isLoading: boolean
1293
+ // isInitialLoad: boolean
1294
+ // error: string | null
1295
+ // projectId?: string
1296
+ // }
1297
+ // const ThemeContext = createContext<ThemeContextType>({
1298
+ // variant: 'standard',
1299
+ // setVariant: () => {},
1300
+ // themeConfig: {},
1301
+ // projectData: null,
1302
+ // isLoading: true,
1303
+ // isInitialLoad: true,
1304
+ // error: null,
1305
+ // projectId: undefined,
1306
+ // })
1307
+ // export const useTheme = (): ThemeContextType => {
1308
+ // const context = useContext(ThemeContext)
1309
+ // if (!context) {
1310
+ // throw new Error('useTheme must be used within ThemeProvider')
1311
+ // }
1312
+ // return context
1313
+ // }
1314
+ // export const useVariant = () => {
1315
+ // const { variant, setVariant } = useTheme()
1316
+ // return { variant, setVariant }
1317
+ // }
1318
+ // /* -------------------------------------------------------------------------- */
1319
+ // /* ORIGIN VALIDATION */
1320
+ // /* -------------------------------------------------------------------------- */
1321
+ // const getCurrentOrigin = (): string => {
1322
+ // if (typeof window === 'undefined') return ''
1323
+ // // For local development, return localhost
1324
+ // if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
1325
+ // return 'localhost'
1326
+ // }
1327
+ // // For production, return the domain without protocol and www
1328
+ // let domain = window.location.hostname
1329
+ // domain = domain.replace(/^www\./, '')
1330
+ // return domain
1331
+ // }
1332
+ // const validateOriginAccess = async (projectId: string): Promise<boolean> => {
1333
+ // if (!projectId) {
1334
+ // console.error('❌ No project ID provided for origin validation')
1335
+ // return false
1336
+ // }
1337
+ // const currentOrigin = getCurrentOrigin()
1338
+ // console.log(`🔍 Validating origin access for: ${currentOrigin}`)
1339
+ // try {
1340
+ // // Load project data from CDN to check trusted domains
1341
+ // const projectData = await loadThemeFromCDN(projectId)
1342
+ // if (!projectData) {
1343
+ // console.error('❌ Project not found or inaccessible')
1344
+ // return false
1345
+ // }
1346
+ // const trustedDomains = projectData.trustedDomains || []
1347
+ // // Check if current origin is in trusted domains
1348
+ // const hasAccess = trustedDomains.some(domain => {
1349
+ // const trustedDomain = domain.domain.toLowerCase()
1350
+ // const currentDomain = currentOrigin.toLowerCase()
1351
+ // // Exact match or subdomain match
1352
+ // return currentDomain === trustedDomain ||
1353
+ // currentDomain.endsWith('.' + trustedDomain) ||
1354
+ // (trustedDomain === 'localhost' && currentOrigin === 'localhost')
1355
+ // })
1356
+ // if (!hasAccess) {
1357
+ // console.error(`❌ Access denied: Origin "${currentOrigin}" is not in trusted domains`)
1358
+ // console.log('📋 Trusted domains:', trustedDomains.map(d => d.domain))
1359
+ // return false
1360
+ // }
1361
+ // return true
1362
+ // } catch (error) {
1363
+ // console.error('❌ Error during origin validation:', error)
1364
+ // return false
1365
+ // }
1366
+ // }
1367
+ // /* -------------------------------------------------------------------------- */
1368
+ // /* LOCAL FILE MANAGEMENT */
1369
+ // /* -------------------------------------------------------------------------- */
1370
+ // const loadLocalTheme = async (): Promise<ProjectData | null> => {
1371
+ // try {
1372
+ // const response = await fetch('/funui.json', {
1373
+ // cache: 'no-cache',
1374
+ // })
1375
+ // if (response.ok) {
1376
+ // const data = await response.json()
1377
+ // return data
1378
+ // }
1379
+ // } catch (error) {
1380
+ // console.log('ℹ️ No local theme file found')
1381
+ // }
1382
+ // return null
1383
+ // }
1384
+ // /* -------------------------------------------------------------------------- */
1385
+ // /* CDN THEME LOADER */
1386
+ // /* -------------------------------------------------------------------------- */
1387
+ // const loadThemeFromCDN = async (projectId: string): Promise<ProjectData | null> => {
1388
+ // if (!projectId) {
1389
+ // console.error('❌ No project ID provided for CDN loading')
1390
+ // return null
1391
+ // }
1392
+ // // Try Firebase Storage public URL
1393
+ // try {
1394
+ // const publicUrl = `https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/themes%2F${projectId}.json?alt=media`
1395
+ // const response = await fetch(publicUrl, {
1396
+ // cache: 'no-cache',
1397
+ // })
1398
+ // if (response.ok) {
1399
+ // const data = await response.json()
1400
+ // return data
1401
+ // } else {
1402
+ // console.error('❌ Firebase Storage fetch failed:', response.status, response.statusText)
1403
+ // }
1404
+ // } catch (error) {
1405
+ // console.error('❌ Error loading from Firebase Storage:', error)
1406
+ // }
1407
+ // return null
1408
+ // }
1409
+ // /* -------------------------------------------------------------------------- */
1410
+ // /* BUCKET JSON LOADER */
1411
+ // /* -------------------------------------------------------------------------- */
1412
+ // const loadBucketJsonFromCDN = async (
1413
+ // bucketSanitizedName: string,
1414
+ // projectId: string,
1415
+ // page: number
1416
+ // ): Promise<JsonFileData | null> => {
1417
+ // if (!bucketSanitizedName || !projectId) {
1418
+ // console.error('❌ Missing parameters for JSON loading')
1419
+ // return null
1420
+ // }
1421
+ // try {
1422
+ // // Construct the URL for JSON files
1423
+ // const pageNumber = page.toString()
1424
+ // const publicUrl = `https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/projects%2F${projectId}%2Fbuckets%2F${bucketSanitizedName}%2F${pageNumber}.json?alt=media`
1425
+ // const response = await fetch(publicUrl, {
1426
+ // cache: 'no-cache',
1427
+ // })
1428
+ // if (response.ok) {
1429
+ // const data = await response.json()
1430
+ // console.log(data)
1431
+ // return data
1432
+ // } else {
1433
+ // // File might not exist (e.g., page out of range)
1434
+ // return null
1435
+ // }
1436
+ // } catch (error) {
1437
+ // console.error(`❌ Error loading JSON file for page ${page}:`, error)
1438
+ // return null
1439
+ // }
1440
+ // }
1441
+ // const listBucketJsonFiles = async (
1442
+ // bucketSanitizedName: string,
1443
+ // projectId: string
1444
+ // ): Promise<JsonFileInfo[]> => {
1445
+ // if (!bucketSanitizedName || !projectId) {
1446
+ // console.error('❌ Missing parameters for listing JSON files')
1447
+ // return []
1448
+ // }
1449
+ // try {
1450
+ // const files: JsonFileInfo[] = []
1451
+ // let page = 1
1452
+ // let hasMoreFiles = true
1453
+ // while (hasMoreFiles && page <= 100) {
1454
+ // const paddedPage = page.toString().padStart(3, '0')
1455
+ // const publicUrl = `https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/projects%2F${projectId}%2Fbuckets%2F${bucketSanitizedName}%2Fpage_${paddedPage}.json?alt=media`
1456
+ // const response = await fetch(publicUrl, {
1457
+ // method: 'HEAD',
1458
+ // cache: 'no-cache',
1459
+ // })
1460
+ // if (response.ok) {
1461
+ // files.push({
1462
+ // name: `page_${paddedPage}.json`,
1463
+ // fullPath: `projects/${projectId}/buckets/${bucketSanitizedName}/page_${paddedPage}.json`,
1464
+ // url: publicUrl,
1465
+ // size: parseInt(response.headers.get('content-length') || '0', 10),
1466
+ // page: page
1467
+ // })
1468
+ // page++
1469
+ // } else {
1470
+ // hasMoreFiles = false
1471
+ // }
1472
+ // }
1473
+ // return files
1474
+ // } catch (error) {
1475
+ // console.error('❌ Error listing JSON files:', error)
1476
+ // return []
1477
+ // }
1478
+ // }
1479
+ // /* -------------------------------------------------------------------------- */
1480
+ // /* CSS VARIABLE APPLIER */
1481
+ // /* -------------------------------------------------------------------------- */
1482
+ // const applyTypographyVariables = (typography: Record<string, string>, root: HTMLElement) => {
1483
+ // if (!typography) return
1484
+ // Object.entries(typography).forEach(([key, value]) => {
1485
+ // const cssVarName = `--${key.replace(/_/g, '-')}`
1486
+ // root.style.setProperty(cssVarName, value)
1487
+ // })
1488
+ // }
1489
+ // const applyColorVariables = (colors: Record<string, string>, root: HTMLElement) => {
1490
+ // if (!colors) return
1491
+ // Object.entries(colors).forEach(([key, value]) => {
1492
+ // const cssVarName = `--${key.replace(/_/g, '-')}`
1493
+ // root.style.setProperty(cssVarName, value)
1494
+ // })
1495
+ // }
1496
+ // const applyThemeConfig = (themeConfig: Record<string, any>, root: HTMLElement) => {
1497
+ // if (!themeConfig) return
1498
+ // if (themeConfig.colors) {
1499
+ // applyColorVariables(themeConfig.colors, root)
1500
+ // }
1501
+ // if (themeConfig.typography) {
1502
+ // applyTypographyVariables(themeConfig.typography, root)
1503
+ // }
1504
+ // Object.entries(themeConfig).forEach(([key, value]) => {
1505
+ // if (key !== 'colors' && key !== 'typography' && typeof value === 'string') {
1506
+ // const cssVarName = `--${key.replace(/_/g, '-')}`
1507
+ // root.style.setProperty(cssVarName, value)
1508
+ // }
1509
+ // })
1510
+ // }
1511
+ // /* -------------------------------------------------------------------------- */
1512
+ // /* VARIABLES HELPER */
1513
+ // /* -------------------------------------------------------------------------- */
1514
+ // let cachedProjectData: ProjectData | null = null
1515
+ // export const getVariable = (name: string): { name: string; value: any } | undefined => {
1516
+ // if (!cachedProjectData?.variables) {
1517
+ // console.warn('No variables available. Make sure ThemeProvider is mounted.')
1518
+ // return undefined
1519
+ // }
1520
+ // const variable = cachedProjectData.variables.find(v => v.name === name)
1521
+ // return variable
1522
+ // }
1523
+ // export const getAllVariables = (): Variable[] => {
1524
+ // return cachedProjectData?.variables || []
1525
+ // }
1526
+ // /* -------------------------------------------------------------------------- */
1527
+ // /* BUCKET UTILITIES */
1528
+ // /* -------------------------------------------------------------------------- */
1529
+ // // Cache for buckets and JSON files
1530
+ // let cachedBuckets: Bucket[] = []
1531
+ // let cachedJsonFiles: Record<string, JsonFileInfo[]> = {}
1532
+ // let cachedJsonData: Record<string, Record<number, JsonFileData>> = {}
1533
+ // const sanitizeBucketName = (name: string): string => {
1534
+ // return name
1535
+ // .trim()
1536
+ // .replace(/\s+/g, '_')
1537
+ // .replace(/[^a-zA-Z0-9_-]/g, '')
1538
+ // .toLowerCase()
1539
+ // }
1540
+ // const transformProjectBucket = (bucket: any, projectId: string): Bucket => {
1541
+ // return {
1542
+ // id: bucket.id || bucket._id || '',
1543
+ // // projectId: bucket.projectId || projectId || '',
1544
+ // name: bucket.name || '',
1545
+ // displayName: bucket.displayName || bucket.name || '',
1546
+ // category: bucket.category || 'uncategorized',
1547
+ // fields: Array.isArray(bucket.fields) ? bucket.fields : [],
1548
+ // createdBy: bucket.createdBy || bucket.userId || '',
1549
+ // createdAt: typeof bucket.createdAt === 'number' ? bucket.createdAt : Date.now(),
1550
+ // updatedAt: typeof bucket.updatedAt === 'number' ? bucket.updatedAt : Date.now(),
1551
+ // updatedBy: bucket.updatedBy || bucket.createdBy || '',
1552
+ // recordCount: bucket.recordCount || 0,
1553
+ // jsonFilesCount: bucket.jsonFilesCount || 0,
1554
+ // totalRecordsInJson: bucket.totalRecordsInJson || 0,
1555
+ // lastJsonExport: bucket.lastJsonExport,
1556
+ // userId: bucket.userId,
1557
+ // createdAtString: bucket.createdAt,
1558
+ // updatedAtString: bucket.updatedAt
1559
+ // }
1560
+ // }
1561
+ // /* -------------------------------------------------------------------------- */
1562
+ // /* COMPONENT */
1563
+ // /* -------------------------------------------------------------------------- */
1564
+ // const ThemeProvider: React.FC<ThemeProviderProps> = ({
1565
+ // theme,
1566
+ // children,
1567
+ // funcss = '',
1568
+ // minHeight = '100vh',
1569
+ // projectId,
1570
+ // project: providedProject, // New prop
1571
+ // }) => {
1572
+ // const [variant, setVariant] = useState<ThemeVariant>('standard')
1573
+ // const [themeConfig, setThemeConfig] = useState<ThemeConfig>({})
1574
+ // const [projectData, setProjectData] = useState<ProjectData | null>(null)
1575
+ // const [isLoading, setIsLoading] = useState(true)
1576
+ // const [isInitialLoad, setIsInitialLoad] = useState(true)
1577
+ // const [error, setError] = useState<string | null>(null)
1578
+ // const [currentVersion, setCurrentVersion] = useState<number | null>(null)
1579
+ // /* -------------------------- Apply base theme --------------------------- */
1580
+ // useEffect(() => {
1581
+ // const root = document.documentElement
1582
+ // const selectedTheme = themes[theme] || themes.light
1583
+ // Object.entries(selectedTheme).forEach(([key, value]) => {
1584
+ // root.style.setProperty(key, value)
1585
+ // })
1586
+ // if (
1587
+ // ['dark', 'dark-blue', 'midnight-purple', 'cyber-metal'].includes(theme)
1588
+ // ) {
1589
+ // colorVarsToDarken.forEach((varName) => {
1590
+ // const original = getComputedStyle(root)
1591
+ // .getPropertyValue(varName)
1592
+ // .trim()
1593
+ // if (original) {
1594
+ // const darkAmount = getDarkenAmount(varName)
1595
+ // const rgba = darkenToRgba(original, darkAmount, 0.9)
1596
+ // root.style.setProperty(varName, rgba)
1597
+ // }
1598
+ // })
1599
+ // }
1600
+ // }, [theme])
1601
+ // /* ---------------------- Theme Loading Logic ----------------------- */
1602
+ // useEffect(() => {
1603
+ // if (typeof window === 'undefined') {
1604
+ // setIsLoading(false)
1605
+ // setIsInitialLoad(false)
1606
+ // return
1607
+ // }
1608
+ // const root = document.documentElement
1609
+ // let pollTimer: NodeJS.Timeout
1610
+ // const loadTheme = async () => {
1611
+ // try {
1612
+ // let finalTheme: ProjectData | null = null
1613
+ // let finalVersion: number | null = null
1614
+ // // If project data is provided directly, use it and skip all loading
1615
+ // if (providedProject) {
1616
+ // console.log('✅ Using provided project data directly')
1617
+ // finalTheme = providedProject
1618
+ // finalVersion = providedProject.version || 0
1619
+ // } else {
1620
+ // // Otherwise, follow the normal loading flow
1621
+ // // First, try to load local theme
1622
+ // const localTheme = await loadLocalTheme()
1623
+ // const localVersion = localTheme?.version || 0
1624
+ // if (projectId) {
1625
+ // // Validate origin access for CDN
1626
+ // const hasAccess = await validateOriginAccess(projectId)
1627
+ // if (hasAccess) {
1628
+ // // Try to load from CDN
1629
+ // const cdnTheme = await loadThemeFromCDN(projectId)
1630
+ // const cdnVersion = cdnTheme?.version || 0
1631
+ // if (cdnTheme) {
1632
+ // // CDN theme available - use it
1633
+ // finalTheme = cdnTheme
1634
+ // finalVersion = cdnVersion
1635
+ // if (cdnVersion !== localVersion) {
1636
+ // console.log(`🔄 Version mismatch: Local(${localVersion}) vs CDN(${cdnVersion})`)
1637
+ // console.log('ℹ️ Using CDN version. Please update your local funui.json file manually.')
1638
+ // }
1639
+ // } else if (localTheme) {
1640
+ // // CDN not available but we have local theme
1641
+ // console.log('⚠️ CDN unavailable, using local theme')
1642
+ // finalTheme = localTheme
1643
+ // finalVersion = localVersion
1644
+ // } else {
1645
+ // // No theme available anywhere
1646
+ // console.warn('⚠️ No theme found (CDN unavailable and no local theme)')
1647
+ // setError('Theme not found')
1648
+ // }
1649
+ // } else {
1650
+ // // Origin validation failed
1651
+ // if (localTheme) {
1652
+ // console.log('⚠️ Origin validation failed, using local theme')
1653
+ // finalTheme = localTheme
1654
+ // finalVersion = localVersion
1655
+ // } else {
1656
+ // console.error('❌ Origin validation failed and no local theme available')
1657
+ // setError('Access denied and no local theme available')
1658
+ // }
1659
+ // }
1660
+ // } else {
1661
+ // // No project ID provided - only use local theme
1662
+ // console.log('ℹ️ No project ID provided, using local theme only')
1663
+ // if (localTheme) {
1664
+ // finalTheme = localTheme
1665
+ // finalVersion = localVersion
1666
+ // console.log('✅ Theme loaded from local file')
1667
+ // } else {
1668
+ // console.log('ℹ️ No local theme file found - using base theme only')
1669
+ // // No error here - it's valid to use only base theme
1670
+ // }
1671
+ // }
1672
+ // }
1673
+ // // Apply the theme if we have one
1674
+ // if (finalTheme && (!currentVersion || finalVersion !== currentVersion)) {
1675
+ // applyThemeData(finalTheme, root)
1676
+ // setCurrentVersion(finalVersion)
1677
+ // setError(null)
1678
+ // } else if (finalTheme) {
1679
+ // console.log('✓ Theme up to date')
1680
+ // }
1681
+ // } catch (err) {
1682
+ // console.error('❌ Error loading theme:', err)
1683
+ // setError('Failed to load theme')
1684
+ // } finally {
1685
+ // setIsLoading(false)
1686
+ // setIsInitialLoad(false)
1687
+ // }
1688
+ // }
1689
+ // // Initial load
1690
+ // loadTheme()
1691
+ // // Only poll for updates if we have a project ID AND no provided project
1692
+ // if (projectId && !providedProject) {
1693
+ // pollTimer = setInterval(() => {
1694
+ // loadTheme()
1695
+ // }, 5 * 60 * 1000)
1696
+ // }
1697
+ // return () => {
1698
+ // if (pollTimer) {
1699
+ // clearInterval(pollTimer)
1700
+ // }
1701
+ // }
1702
+ // }, [projectId, currentVersion, theme, providedProject]) // Added providedProject to dependencies
1703
+ // const applyThemeData = (data: ProjectData, root: HTMLElement) => {
1704
+ // const themeConfig = data.theme_config ?? {}
1705
+ // const newVariant = data.default_variation || 'standard'
1706
+ // setVariant(newVariant)
1707
+ // setThemeConfig(themeConfig)
1708
+ // setProjectData(data)
1709
+ // // Cache for variable access
1710
+ // cachedProjectData = data
1711
+ // // Cache for asset access
1712
+ // cachedAssets = data.assets || []
1713
+ // // Cache for bucket access
1714
+ // const projectBuckets = data.buckets || []
1715
+ // cachedBuckets = projectBuckets.map(bucket =>
1716
+ // transformProjectBucket(bucket, data.project_id || '')
1717
+ // )
1718
+ // // Apply all theme config to CSS variables
1719
+ // applyThemeConfig(themeConfig, root)
1720
+ // }
1721
+ // const contextValue = useMemo(
1722
+ // () => ({
1723
+ // variant,
1724
+ // setVariant,
1725
+ // themeConfig,
1726
+ // projectData,
1727
+ // isLoading,
1728
+ // isInitialLoad,
1729
+ // error,
1730
+ // projectId, // Include projectId in context
1731
+ // }),
1732
+ // [variant, themeConfig, projectData, isLoading, isInitialLoad, error, projectId]
1733
+ // )
1734
+ // return (
1735
+ // <ThemeContext.Provider value={contextValue}>
1736
+ // <div
1737
+ // className={`theme-${theme} ${funcss}`}
1738
+ // style={{
1739
+ // backgroundColor: 'var(--page-bg)',
1740
+ // color: 'var(--text-color)',
1741
+ // minHeight: minHeight,
1742
+ // transition: isInitialLoad ? 'none' : 'background-color 0.3s ease, color 0.3s ease',
1743
+ // }}
1744
+ // >
1745
+ // {children}
1746
+ // </div>
1747
+ // </ThemeContext.Provider>
1748
+ // )
1749
+ // }
1750
+ // export default ThemeProvider
1751
+ // /* -------------------------------------------------------------------------- */
1752
+ // /* HELPER HOOKS */
1753
+ // /* -------------------------------------------------------------------------- */
1754
+ // export const useThemeValue = (key: string): string | undefined => {
1755
+ // const { themeConfig } = useTheme()
1756
+ // return themeConfig[key]
1757
+ // }
1758
+ // export const useComponentConfig = (componentName: string): any => {
1759
+ // const { projectData } = useTheme()
1760
+ // return projectData?.components?.[componentName] || {}
1761
+ // }
1762
+ // export const useColors = (): Record<string, string> => {
1763
+ // const { projectData } = useTheme()
1764
+ // return projectData?.theme_config?.colors || {}
1765
+ // }
1766
+ // export const useTypography = (): Record<string, string> => {
1767
+ // const { projectData } = useTheme()
1768
+ // return projectData?.theme_config?.typography || {}
1769
+ // }
1770
+ // export const useThemeConfig = (): Record<string, any> => {
1771
+ // const { projectData } = useTheme()
1772
+ // return projectData?.theme_config || {}
1773
+ // }
1774
+ // export const useProjectData = (): ProjectData | null => {
1775
+ // const { projectData } = useTheme()
1776
+ // return projectData
1777
+ // }
1778
+ // export const useColor = (colorName: string): string | undefined => {
1779
+ // const colors = useColors()
1780
+ // return colors[colorName]
1781
+ // }
1782
+ // export const useTypographyValue = (property: string): string | undefined => {
1783
+ // const typography = useTypography()
1784
+ // return typography[property]
1785
+ // }
1786
+ // export const useComponentVariant = (componentName: string, variantName: string = 'default'): any => {
1787
+ // const componentConfig = useComponentConfig(componentName)
1788
+ // return componentConfig[variantName] || {}
1789
+ // }
1790
+ // // Hook to access variables
1791
+ // export const useVariables = (): Variable[] => {
1792
+ // const { projectData } = useTheme()
1793
+ // return projectData?.variables || []
1794
+ // }
1795
+ // // Hook to get a specific variable
1796
+ // export const useVariable = (name: string): any => {
1797
+ // const variables = useVariables()
1798
+ // const variable = variables.find(v => v.name === name)
1799
+ // return variable?.value
1800
+ // }
1801
+ // /* -------------------------------------------------------------------------- */
1802
+ // /* ASSETS HELPER */
1803
+ // /* -------------------------------------------------------------------------- */
1804
+ // interface Asset {
1805
+ // name: string
1806
+ // url: string
1807
+ // file_type: string
1808
+ // }
1809
+ // let cachedAssets: Asset[] = []
1810
+ // export const getAsset = (name: string): Asset | undefined => {
1811
+ // if (!cachedAssets.length) {
1812
+ // console.warn('No assets available. Make sure ThemeProvider is mounted and assets are loaded.')
1813
+ // return undefined
1814
+ // }
1815
+ // const asset = cachedAssets.find(a => a.name === name)
1816
+ // return asset
1817
+ // }
1818
+ // export const getAllAssets = (): Asset[] => {
1819
+ // return cachedAssets
1820
+ // }
1821
+ // export const getAssetValue = (name: string): string | undefined => {
1822
+ // const asset = getAsset(name)
1823
+ // return asset?.url
1824
+ // }
1825
+ // export const getAssetType = (name: string): string | undefined => {
1826
+ // const asset = getAsset(name)
1827
+ // return asset?.file_type
1828
+ // }
1829
+ // export const getAssetInfo = (name: string): { value: string; type: string; name: string } | undefined => {
1830
+ // const asset = getAsset(name)
1831
+ // if (!asset) return undefined
1832
+ // return {
1833
+ // value: asset.url,
1834
+ // type: asset.file_type,
1835
+ // name: asset.name
1836
+ // }
1837
+ // }
1838
+ // // Hook to access all assets
1839
+ // export const useAssets = (): Asset[] => {
1840
+ // const { projectData } = useTheme()
1841
+ // return projectData?.assets || []
1842
+ // }
1843
+ // // Hook to get a specific asset
1844
+ // export const useAsset = (name: string): Asset | undefined => {
1845
+ // const assets = useAssets()
1846
+ // const asset = assets.find(a => a.name === name)
1847
+ // return asset
1848
+ // }
1849
+ // // Hook to get asset URL (most common use case)
1850
+ // export const useAssetValue = (name: string): string | undefined => {
1851
+ // const asset = useAsset(name)
1852
+ // return asset?.url
1853
+ // }
1854
+ // // Hook to get asset type
1855
+ // export const useAssetType = (name: string): string | undefined => {
1856
+ // const asset = useAsset(name)
1857
+ // return asset?.file_type
1858
+ // }
1859
+ // // Hook to get complete asset info
1860
+ // export const useAssetInfo = (name: string): { value: string; type: string; name: string } | undefined => {
1861
+ // const asset = useAsset(name)
1862
+ // if (!asset) return undefined
1863
+ // return {
1864
+ // value: asset.url,
1865
+ // type: asset.file_type,
1866
+ // name: asset.name
1867
+ // }
1868
+ // }
1869
+ // // Hook to filter assets by type
1870
+ // export const useAssetsByType = (type: string): Asset[] => {
1871
+ // const assets = useAssets()
1872
+ // return assets.filter(asset => asset.file_type === type)
1873
+ // }
1874
+ // // Hook to get image assets
1875
+ // export const useImageAssets = (): Asset[] => {
1876
+ // return useAssetsByType('image')
1877
+ // }
1878
+ // // Hook to get video assets
1879
+ // export const useVideoAssets = (): Asset[] => {
1880
+ // return useAssetsByType('video')
1881
+ // }
1882
+ // // Hook to get audio assets
1883
+ // export const useAudioAssets = (): Asset[] => {
1884
+ // return useAssetsByType('audio')
1885
+ // }
1886
+ // // Hook to get document assets
1887
+ // export const useDocumentAssets = (): Asset[] => {
1888
+ // return useAssetsByType('document')
1889
+ // }
1890
+ // /* -------------------------------------------------------------------------- */
1891
+ // /* BUCKET HOOKS */
1892
+ // /* -------------------------------------------------------------------------- */
1893
+ // // Helper function to get bucket
1894
+ // const getBucketFromCache = (bucketIdOrName: string): Bucket | undefined => {
1895
+ // if (!cachedBuckets.length) {
1896
+ // console.warn('No buckets available. Make sure ThemeProvider is mounted.')
1897
+ // return undefined
1898
+ // }
1899
+ // return cachedBuckets.find(b =>
1900
+ // b.id === bucketIdOrName ||
1901
+ // b.name.toLowerCase() === bucketIdOrName.toLowerCase() ||
1902
+ // b.displayName?.toLowerCase() === bucketIdOrName.toLowerCase()
1903
+ // )
1904
+ // }
1905
+ // // Hook to access all buckets
1906
+ // export const useBuckets = (): Bucket[] => {
1907
+ // const { projectData } = useTheme()
1908
+ // const [buckets, setBuckets] = useState<Bucket[]>([])
1909
+ // useEffect(() => {
1910
+ // if (projectData?.buckets) {
1911
+ // const transformedBuckets = projectData.buckets.map(bucket =>
1912
+ // transformProjectBucket(bucket, projectData.project_id || '')
1913
+ // )
1914
+ // setBuckets(transformedBuckets)
1915
+ // cachedBuckets = transformedBuckets
1916
+ // } else {
1917
+ // setBuckets([])
1918
+ // }
1919
+ // }, [projectData])
1920
+ // return buckets
1921
+ // }
1922
+ // // Hook to get a specific bucket
1923
+ // export const useBucket = (bucketIdOrName: string): Bucket | undefined => {
1924
+ // const buckets = useBuckets()
1925
+ // return buckets.find(b =>
1926
+ // b.id === bucketIdOrName ||
1927
+ // b.name.toLowerCase() === bucketIdOrName.toLowerCase() ||
1928
+ // b.displayName?.toLowerCase() === bucketIdOrName.toLowerCase()
1929
+ // )
1930
+ // }
1931
+ // // Hook to get buckets by category
1932
+ // export const useBucketsByCategory = (category: string): Bucket[] => {
1933
+ // const buckets = useBuckets()
1934
+ // if (category === 'all' || category === 'uncategorized') {
1935
+ // return buckets.filter(b => !b.category || b.category === 'uncategorized')
1936
+ // }
1937
+ // return buckets.filter(b => b.category === category)
1938
+ // }
1939
+ // // Hook to get bucket JSON files
1940
+ // export const useBucketJsonFiles = (bucketIdOrName: string): {
1941
+ // files: JsonFileInfo[]
1942
+ // loading: boolean
1943
+ // error: string | null
1944
+ // refresh: () => Promise<void>
1945
+ // } => {
1946
+ // const [files, setFiles] = useState<JsonFileInfo[]>([])
1947
+ // const [loading, setLoading] = useState(true)
1948
+ // const [error, setError] = useState<string | null>(null)
1949
+ // const bucket = useBucket(bucketIdOrName)
1950
+ // const { projectId } = useTheme()
1951
+ // const loadFiles = useCallback(async () => {
1952
+ // if (!bucket || !projectId) {
1953
+ // setFiles([])
1954
+ // setLoading(false)
1955
+ // return
1956
+ // }
1957
+ // try {
1958
+ // setLoading(true)
1959
+ // setError(null)
1960
+ // const bucketSanitizedName = sanitizeBucketName(bucket.displayName || bucket.name)
1961
+ // const jsonFiles = await listBucketJsonFiles(bucketSanitizedName, projectId)
1962
+ // setFiles(jsonFiles)
1963
+ // // Update cache
1964
+ // const cacheKey = `${bucket.id}_${projectId}`
1965
+ // cachedJsonFiles[cacheKey] = jsonFiles
1966
+ // } catch (err) {
1967
+ // console.error('Error loading JSON files:', err)
1968
+ // setError(err instanceof Error ? err.message : 'Failed to load JSON files')
1969
+ // } finally {
1970
+ // setLoading(false)
1971
+ // }
1972
+ // }, [bucket, projectId])
1973
+ // useEffect(() => {
1974
+ // loadFiles()
1975
+ // }, [loadFiles])
1976
+ // return {
1977
+ // files,
1978
+ // loading,
1979
+ // error,
1980
+ // refresh: loadFiles
1981
+ // }
1982
+ // }
1983
+ // // Hook to get paginated records (YOU ONLY NEED TO PASS PAGE NUMBER!)
1984
+ // export const usePaginatedRecords = (
1985
+ // bucketIdOrName: string,
1986
+ // page: number,
1987
+ // pageSize?: number
1988
+ // ): {
1989
+ // records: BucketRecord[]
1990
+ // metadata: JsonFileMetadata | null
1991
+ // loading: boolean
1992
+ // error: string | null
1993
+ // refresh: () => Promise<void>
1994
+ // } => {
1995
+ // const [records, setRecords] = useState<BucketRecord[]>([])
1996
+ // const [metadata, setMetadata] = useState<JsonFileMetadata | null>(null)
1997
+ // const [loading, setLoading] = useState(true)
1998
+ // const [error, setError] = useState<string | null>(null)
1999
+ // const bucket = useBucket(bucketIdOrName)
2000
+ // const { projectId } = useTheme()
2001
+ // const loadRecords = useCallback(async () => {
2002
+ // if (!bucket || !projectId) {
2003
+ // setRecords([])
2004
+ // setMetadata(null)
2005
+ // setLoading(false)
2006
+ // return
2007
+ // }
2008
+ // try {
2009
+ // setLoading(true)
2010
+ // setError(null)
2011
+ // const bucketSanitizedName = sanitizeBucketName(bucket.displayName || bucket.name)
2012
+ // const jsonData = await loadBucketJsonFromCDN(bucketSanitizedName, projectId, page)
2013
+ // if (jsonData) {
2014
+ // let recordsToSet = jsonData.records || []
2015
+ // // If pageSize is specified, limit the records
2016
+ // if (pageSize && recordsToSet.length > pageSize) {
2017
+ // recordsToSet = recordsToSet.slice(0, pageSize)
2018
+ // }
2019
+ // setRecords(recordsToSet)
2020
+ // setMetadata(jsonData.metadata)
2021
+ // // Update cache
2022
+ // const cacheKey = `${bucket.id}_${projectId}`
2023
+ // if (!cachedJsonData[cacheKey]) {
2024
+ // cachedJsonData[cacheKey] = {}
2025
+ // }
2026
+ // cachedJsonData[cacheKey][page] = jsonData
2027
+ // } else {
2028
+ // setRecords([])
2029
+ // setMetadata(null)
2030
+ // }
2031
+ // } catch (err) {
2032
+ // console.error(`Error loading records for page ${page}:`, err)
2033
+ // setError(err instanceof Error ? err.message : 'Failed to load records')
2034
+ // } finally {
2035
+ // setLoading(false)
2036
+ // }
2037
+ // }, [bucket, projectId, page, pageSize])
2038
+ // useEffect(() => {
2039
+ // loadRecords()
2040
+ // }, [loadRecords])
2041
+ // return {
2042
+ // records,
2043
+ // metadata,
2044
+ // loading,
2045
+ // error,
2046
+ // refresh: loadRecords
2047
+ // }
2048
+ // }
2049
+ // // Hook to get all records from JSON files
2050
+ // export const useAllJsonRecords = (
2051
+ // bucketIdOrName: string
2052
+ // ): {
2053
+ // records: BucketRecord[]
2054
+ // loading: boolean
2055
+ // error: string | null
2056
+ // refresh: () => Promise<void>
2057
+ // } => {
2058
+ // const [records, setRecords] = useState<BucketRecord[]>([])
2059
+ // const [loading, setLoading] = useState(true)
2060
+ // const [error, setError] = useState<string | null>(null)
2061
+ // const bucket = useBucket(bucketIdOrName)
2062
+ // const { projectId } = useTheme()
2063
+ // const { files, loading: filesLoading } = useBucketJsonFiles(bucketIdOrName)
2064
+ // const loadAllRecords = useCallback(async () => {
2065
+ // if (!bucket || !projectId || filesLoading) {
2066
+ // return
2067
+ // }
2068
+ // try {
2069
+ // setLoading(true)
2070
+ // setError(null)
2071
+ // const allRecords: BucketRecord[] = []
2072
+ // for (const file of files) {
2073
+ // const bucketSanitizedName = sanitizeBucketName(bucket.displayName || bucket.name)
2074
+ // const jsonData = await loadBucketJsonFromCDN(bucketSanitizedName, projectId, file.page)
2075
+ // if (jsonData && jsonData.records) {
2076
+ // allRecords.push(...jsonData.records)
2077
+ // }
2078
+ // }
2079
+ // setRecords(allRecords)
2080
+ // } catch (err) {
2081
+ // console.error('Error loading all records:', err)
2082
+ // setError(err instanceof Error ? err.message : 'Failed to load records')
2083
+ // } finally {
2084
+ // setLoading(false)
2085
+ // }
2086
+ // }, [bucket, projectId, files, filesLoading])
2087
+ // useEffect(() => {
2088
+ // if (!filesLoading) {
2089
+ // loadAllRecords()
2090
+ // }
2091
+ // }, [loadAllRecords, filesLoading])
2092
+ // return {
2093
+ // records,
2094
+ // loading: loading || filesLoading,
2095
+ // error,
2096
+ // refresh: loadAllRecords
2097
+ // }
2098
+ // }
2099
+ // // Hook for cache management
2100
+ // export const useBucketCache = () => {
2101
+ // const clearCache = useCallback((bucketIdOrName?: string) => {
2102
+ // if (bucketIdOrName) {
2103
+ // const bucket = getBucketFromCache(bucketIdOrName)
2104
+ // if (bucket) {
2105
+ // const { projectId } = useContext(ThemeContext)
2106
+ // if (projectId) {
2107
+ // const cacheKey = `${bucket.id}_${projectId}`
2108
+ // delete cachedJsonFiles[cacheKey]
2109
+ // delete cachedJsonData[cacheKey]
2110
+ // }
2111
+ // }
2112
+ // } else {
2113
+ // cachedJsonFiles = {}
2114
+ // cachedJsonData = {}
2115
+ // }
2116
+ // }, [])
2117
+ // const refresh = useCallback(() => {
2118
+ // // This will be handled by the ThemeProvider when project data changes
2119
+ // console.log('Buckets will refresh when project data updates')
2120
+ // }, [])
2121
+ // return {
2122
+ // clearCache,
2123
+ // refresh
2124
+ // }
2125
+ // }
2126
+ // // Helper function to get bucket records (for non-hook usage)
2127
+ // export const getPaginatedRecords = async (
2128
+ // bucketIdOrName: string,
2129
+ // page: number,
2130
+ // pageSize?: number
2131
+ // ): Promise<{
2132
+ // records: BucketRecord[]
2133
+ // metadata: JsonFileMetadata | null
2134
+ // }> => {
2135
+ // const bucket = getBucketFromCache(bucketIdOrName)
2136
+ // if (!bucket) {
2137
+ // throw new Error(`Bucket not found: ${bucketIdOrName}`)
2138
+ // }
2139
+ // const { projectId } = useContext(ThemeContext)
2140
+ // if (!projectId) {
2141
+ // throw new Error('Project ID not available')
2142
+ // }
2143
+ // const bucketSanitizedName = sanitizeBucketName(bucket.displayName || bucket.name)
2144
+ // const jsonData = await loadBucketJsonFromCDN(bucketSanitizedName, projectId, page)
2145
+ // if (!jsonData) {
2146
+ // return { records: [], metadata: null }
2147
+ // }
2148
+ // let records = jsonData.records || []
2149
+ // if (pageSize && records.length > pageSize) {
2150
+ // records = records.slice(0, pageSize)
2151
+ // }
2152
+ // return {
2153
+ // records,
2154
+ // metadata: jsonData.metadata
2155
+ // }
2156
+ // }