shared-features 0.1.5 → 0.1.7

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.
Files changed (44) hide show
  1. package/dist/{admin-commonFeatures-CMRCJCHS.js → admin-commonFeatures-CFhvjgp9.js} +3 -3
  2. package/dist/{admin-commonFeatures-CMRCJCHS.js.map → admin-commonFeatures-CFhvjgp9.js.map} +1 -1
  3. package/dist/{admin-commonFeatures-CoJPCUYw.cjs → admin-commonFeatures-pnaXeix_.cjs} +3 -3
  4. package/dist/{admin-commonFeatures-CoJPCUYw.cjs.map → admin-commonFeatures-pnaXeix_.cjs.map} +1 -1
  5. package/dist/{broadcasts-CHTb-Z7-.cjs → broadcasts-DZsQNd4R.cjs} +2 -2
  6. package/dist/{broadcasts-CHTb-Z7-.cjs.map → broadcasts-DZsQNd4R.cjs.map} +1 -1
  7. package/dist/{broadcasts-Dkmto2dR.js → broadcasts-Dlu51_38.js} +2 -2
  8. package/dist/{broadcasts-Dkmto2dR.js.map → broadcasts-Dlu51_38.js.map} +1 -1
  9. package/dist/{commonFeatures-DTaIBhdj.cjs → commonFeatures-BuY97_K4.cjs} +63 -27
  10. package/dist/commonFeatures-BuY97_K4.cjs.map +1 -0
  11. package/dist/{commonFeatures-D-MZcecu.js → commonFeatures-LzPnbR6z.js} +63 -27
  12. package/dist/commonFeatures-LzPnbR6z.js.map +1 -0
  13. package/dist/components/index.cjs +1 -1
  14. package/dist/components/index.js +1 -1
  15. package/dist/{featureFlags-CqKSOF7q.js → featureFlags-CSudFX4x.js} +7 -2
  16. package/dist/{featureFlags-CqKSOF7q.js.map → featureFlags-CSudFX4x.js.map} +1 -1
  17. package/dist/{featureFlags-C9iXfoJT.cjs → featureFlags-CfDmshkF.cjs} +7 -2
  18. package/dist/{featureFlags-C9iXfoJT.cjs.map → featureFlags-CfDmshkF.cjs.map} +1 -1
  19. package/dist/hooks/index.cjs +2 -2
  20. package/dist/hooks/index.js +2 -2
  21. package/dist/{index-u8rmRNdW.js → index-D2YWycum.js} +3 -3
  22. package/dist/{index-u8rmRNdW.js.map → index-D2YWycum.js.map} +1 -1
  23. package/dist/{index-z5yRtI-J.cjs → index-DxjbpnFC.cjs} +3 -3
  24. package/dist/{index-z5yRtI-J.cjs.map → index-DxjbpnFC.cjs.map} +1 -1
  25. package/dist/index.cjs +7 -7
  26. package/dist/index.js +7 -7
  27. package/dist/services/commonFeatures.d.ts.map +1 -1
  28. package/dist/services/index.cjs +4 -4
  29. package/dist/services/index.js +4 -4
  30. package/dist/types/commonFeatures.d.ts +15 -2
  31. package/dist/types/commonFeatures.d.ts.map +1 -1
  32. package/dist/types/index.cjs +1 -1
  33. package/dist/types/index.js +1 -1
  34. package/dist/{useCommonFeatures-B0H0o03A.cjs → useCommonFeatures-CWqe4EhH.cjs} +2 -2
  35. package/dist/{useCommonFeatures-B0H0o03A.cjs.map → useCommonFeatures-CWqe4EhH.cjs.map} +1 -1
  36. package/dist/{useCommonFeatures-B2x-8atT.js → useCommonFeatures-CnmI83Er.js} +2 -2
  37. package/dist/{useCommonFeatures-B2x-8atT.js.map → useCommonFeatures-CnmI83Er.js.map} +1 -1
  38. package/dist/{useFeatureFlags-Da05kQKA.cjs → useFeatureFlags-9_E7gair.cjs} +3 -3
  39. package/dist/{useFeatureFlags-Da05kQKA.cjs.map → useFeatureFlags-9_E7gair.cjs.map} +1 -1
  40. package/dist/{useFeatureFlags-CeojCWSx.js → useFeatureFlags-DhIb0HYi.js} +3 -3
  41. package/dist/{useFeatureFlags-CeojCWSx.js.map → useFeatureFlags-DhIb0HYi.js.map} +1 -1
  42. package/package.json +1 -1
  43. package/dist/commonFeatures-D-MZcecu.js.map +0 -1
  44. package/dist/commonFeatures-DTaIBhdj.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"broadcasts-CHTb-Z7-.cjs","sources":["../src/services/broadcasts.ts"],"sourcesContent":["/**\n * Broadcasts Service\n *\n * Handles fetching and displaying cross-project broadcast notifications\n * from the centralized aoneahsan.com Firebase backend.\n *\n * Broadcasts are created via the admin panel and displayed across all\n * consumer projects based on targeting rules.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n getDocs,\n getDoc,\n doc,\n query,\n where,\n orderBy,\n onSnapshot,\n addDoc,\n serverTimestamp,\n Timestamp,\n Unsubscribe,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig, getState } from '../firebase/config';\nimport type {\n BroadcastNotification,\n BroadcastStatus,\n BroadcastVariant,\n NotificationPlatform,\n FetchBroadcastsOptions,\n} from '../types/notifications';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_BROADCASTS = 'zaions_broadcasts';\nconst COLLECTION_BROADCAST_EVENTS = 'zaions_broadcast_events';\nconst LOCAL_STORAGE_KEY = 'shared_features_dismissed_broadcasts';\n\n// Cache for broadcasts\ninterface BroadcastsCache {\n data: BroadcastNotification[];\n timestamp: number;\n}\nlet broadcastsCache: BroadcastsCache | null = null;\nconst CACHE_TTL_MS = 2 * 60 * 1000; // 2 minutes (shorter than campaigns)\n\n// ============================================================================\n// LOCAL DISMISSAL MANAGEMENT\n// ============================================================================\n\n/**\n * Get dismissed broadcast IDs from local storage\n */\nasync function getDismissedBroadcasts(): Promise<Set<string>> {\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n const result = await Preferences.get({ key: LOCAL_STORAGE_KEY });\n if (result.value) {\n const data = JSON.parse(result.value);\n return new Set(data.dismissedIds || []);\n }\n } catch {\n // Fallback to localStorage\n try {\n const stored = localStorage.getItem(LOCAL_STORAGE_KEY);\n if (stored) {\n const data = JSON.parse(stored);\n return new Set(data.dismissedIds || []);\n }\n } catch {\n // Ignore\n }\n }\n return new Set();\n}\n\n/**\n * Save dismissed broadcast ID to local storage\n */\nasync function saveDismissedBroadcast(broadcastId: string): Promise<void> {\n const dismissed = await getDismissedBroadcasts();\n dismissed.add(broadcastId);\n\n const serialized = JSON.stringify({\n dismissedIds: Array.from(dismissed),\n updatedAt: Date.now(),\n });\n\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n await Preferences.set({ key: LOCAL_STORAGE_KEY, value: serialized });\n } catch {\n // Fallback to localStorage\n try {\n localStorage.setItem(LOCAL_STORAGE_KEY, serialized);\n } catch {\n // Ignore storage errors\n }\n }\n}\n\n/**\n * Check if a broadcast has been dismissed\n */\nexport async function isBroadcastDismissed(\n broadcastId: string\n): Promise<boolean> {\n const dismissed = await getDismissedBroadcasts();\n return dismissed.has(broadcastId);\n}\n\n/**\n * Dismiss a broadcast (save locally)\n */\nexport async function dismissBroadcast(broadcastId: string): Promise<void> {\n await saveDismissedBroadcast(broadcastId);\n}\n\n/**\n * Clear all dismissed broadcasts (useful for testing)\n */\nexport async function clearDismissedBroadcasts(): Promise<void> {\n try {\n const { Preferences } = await import('@capacitor/preferences');\n await Preferences.remove({ key: LOCAL_STORAGE_KEY });\n } catch {\n try {\n localStorage.removeItem(LOCAL_STORAGE_KEY);\n } catch {\n // Ignore\n }\n }\n}\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Check if cache is valid\n */\nfunction isCacheValid(): boolean {\n if (!broadcastsCache) return false;\n return Date.now() - broadcastsCache.timestamp < CACHE_TTL_MS;\n}\n\n/**\n * Convert Firestore document to BroadcastNotification\n */\nfunction docToBroadcast(\n docId: string,\n data: Record<string, unknown>\n): BroadcastNotification {\n return {\n id: docId,\n title: data.title as string,\n message: data.message as string,\n type: data.type as BroadcastNotification['type'],\n category: data.category as BroadcastNotification['category'],\n isRead: false, // Broadcasts don't have read state per-user\n isImportant: data.isImportant as boolean | undefined,\n actionUrl: data.actionUrl as string | undefined,\n actionText: data.actionText as string | undefined,\n createdAt: data.createdAt as Timestamp,\n metadata: data.metadata as Record<string, unknown> | undefined,\n targetProjects: (data.targetProjects as string[]) || [],\n targetPlatforms:\n (data.targetPlatforms as NotificationPlatform[]) || [],\n targetAudience:\n (data.targetAudience as BroadcastNotification['targetAudience']) || 'all',\n status: data.status as BroadcastStatus,\n startDate: data.startDate as Timestamp,\n endDate: data.endDate as Timestamp | null | undefined,\n priority: (data.priority as number) || 50,\n dismissible: data.dismissible !== false, // Default true\n variant: (data.variant as BroadcastVariant) || 'banner',\n impressions: (data.impressions as number) || 0,\n clicks: (data.clicks as number) || 0,\n createdBy: data.createdBy as string,\n updatedBy: data.updatedBy as string | undefined,\n };\n}\n\n// ============================================================================\n// BROADCAST FETCHING\n// ============================================================================\n\n/**\n * Fetch broadcasts with optional filters\n */\nexport async function fetchBroadcasts(\n options: FetchBroadcastsOptions = {}\n): Promise<BroadcastNotification[]> {\n const config = getConfig();\n const db = getSharedFeaturesDb();\n\n // Check cache first (only for unfiltered queries)\n if (\n !options.projectId &&\n !options.platform &&\n !options.variant &&\n !options.status &&\n isCacheValid()\n ) {\n return broadcastsCache!.data;\n }\n\n let q = query(collection(db, COLLECTION_BROADCASTS));\n\n // Apply status filter (default to active)\n const status = options.status || 'active';\n q = query(q, where('status', '==', status));\n\n // Order by priority descending\n q = query(q, orderBy('priority', 'desc'));\n\n if (options.limit) {\n const { limit: firestoreLimit } = await import('firebase/firestore');\n q = query(q, firestoreLimit(options.limit));\n }\n\n const snapshot = await getDocs(q);\n let broadcasts = snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\n\n // Client-side filtering for array fields\n const now = Timestamp.now();\n\n broadcasts = broadcasts.filter((b) => {\n // Date range check\n if (b.startDate && (b.startDate as Timestamp).toMillis() > now.toMillis()) {\n return false;\n }\n if (\n b.endDate &&\n (b.endDate as Timestamp).toMillis() < now.toMillis()\n ) {\n return false;\n }\n\n // Platform check\n const targetPlatform = options.platform || config.platform;\n if (\n b.targetPlatforms.length > 0 &&\n !b.targetPlatforms.includes(targetPlatform as NotificationPlatform)\n ) {\n return false;\n }\n\n // Project check\n const targetProject = options.projectId || config.projectId;\n if (\n b.targetProjects.length > 0 &&\n !b.targetProjects.includes(targetProject)\n ) {\n return false;\n }\n\n // Variant check\n if (options.variant && b.variant !== options.variant) {\n return false;\n }\n\n return true;\n });\n\n // Cache unfiltered results\n if (\n !options.projectId &&\n !options.platform &&\n !options.variant &&\n !options.status\n ) {\n broadcastsCache = {\n data: broadcasts,\n timestamp: Date.now(),\n };\n }\n\n return broadcasts;\n}\n\n/**\n * Fetch active broadcasts for display, excluding dismissed ones\n */\nexport async function fetchActiveBroadcasts(\n options: Omit<FetchBroadcastsOptions, 'status'> = {}\n): Promise<BroadcastNotification[]> {\n const broadcasts = await fetchBroadcasts({ ...options, status: 'active' });\n const dismissed = await getDismissedBroadcasts();\n\n return broadcasts.filter((b) => !dismissed.has(b.id));\n}\n\n/**\n * Fetch broadcasts by variant type\n */\nexport async function fetchBroadcastsByVariant(\n variant: BroadcastVariant\n): Promise<BroadcastNotification[]> {\n return fetchActiveBroadcasts({ variant });\n}\n\n/**\n * Get a single broadcast by ID\n */\nexport async function getBroadcastById(\n broadcastId: string\n): Promise<BroadcastNotification | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));\n\n if (!docSnap.exists()) return null;\n return docToBroadcast(docSnap.id, docSnap.data());\n}\n\n// ============================================================================\n// REAL-TIME SUBSCRIPTION\n// ============================================================================\n\n/**\n * Subscribe to broadcast updates in real-time\n */\nexport function subscribeToBroadcasts(\n callback: (broadcasts: BroadcastNotification[]) => void,\n options: FetchBroadcastsOptions = {}\n): Unsubscribe {\n const config = getConfig();\n const db = getSharedFeaturesDb();\n\n let q = query(\n collection(db, COLLECTION_BROADCASTS),\n where('status', '==', 'active'),\n orderBy('priority', 'desc')\n );\n\n return onSnapshot(\n q,\n async (snapshot) => {\n let broadcasts = snapshot.docs.map((d) =>\n docToBroadcast(d.id, d.data())\n );\n\n // Client-side filtering\n const now = Timestamp.now();\n const dismissed = await getDismissedBroadcasts();\n\n broadcasts = broadcasts.filter((b) => {\n // Skip dismissed\n if (dismissed.has(b.id)) return false;\n\n // Date range check\n if (\n b.startDate &&\n (b.startDate as Timestamp).toMillis() > now.toMillis()\n ) {\n return false;\n }\n if (\n b.endDate &&\n (b.endDate as Timestamp).toMillis() < now.toMillis()\n ) {\n return false;\n }\n\n // Platform check\n const targetPlatform = options.platform || config.platform;\n if (\n b.targetPlatforms.length > 0 &&\n !b.targetPlatforms.includes(targetPlatform as NotificationPlatform)\n ) {\n return false;\n }\n\n // Project check\n const targetProject = options.projectId || config.projectId;\n if (\n b.targetProjects.length > 0 &&\n !b.targetProjects.includes(targetProject)\n ) {\n return false;\n }\n\n // Variant check\n if (options.variant && b.variant !== options.variant) {\n return false;\n }\n\n return true;\n });\n\n callback(broadcasts);\n },\n (error) => {\n const debug = config.debug;\n if (debug) {\n console.error('[shared-features] Broadcast subscription error:', error);\n }\n }\n );\n}\n\n// ============================================================================\n// BROADCAST ANALYTICS\n// ============================================================================\n\n/**\n * Record a broadcast event (impression, click, dismiss)\n */\nexport async function recordBroadcastEvent(\n broadcastId: string,\n action: 'impression' | 'click' | 'dismiss'\n): Promise<void> {\n const config = getConfig();\n const state = getState();\n const db = getSharedFeaturesDb();\n\n const eventData = {\n broadcastId,\n projectId: config.projectId,\n platform: config.platform,\n deviceId: state.deviceId || 'unknown',\n action,\n timestamp: serverTimestamp(),\n };\n\n try {\n await addDoc(collection(db, COLLECTION_BROADCAST_EVENTS), eventData);\n\n if (config.debug) {\n console.log('[shared-features] Recorded broadcast event:', eventData);\n }\n } catch (error) {\n if (config.debug) {\n console.error(\n '[shared-features] Failed to record broadcast event:',\n error\n );\n }\n // Don't throw - we don't want analytics failures to break the UI\n }\n}\n\n/**\n * Track broadcast impression\n */\nexport async function trackBroadcastImpression(\n broadcastId: string\n): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'impression');\n}\n\n/**\n * Track broadcast click\n */\nexport async function trackBroadcastClick(broadcastId: string): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'click');\n}\n\n/**\n * Track broadcast dismiss\n */\nexport async function trackBroadcastDismiss(\n broadcastId: string\n): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'dismiss');\n await dismissBroadcast(broadcastId);\n}\n\n// ============================================================================\n// CACHE MANAGEMENT\n// ============================================================================\n\n/**\n * Clear the broadcasts cache\n */\nexport function clearBroadcastsCache(): void {\n broadcastsCache = null;\n}\n"],"names":["getConfig","getSharedFeaturesDb","query","collection","where","orderBy","getDocs","Timestamp","getDoc","doc","onSnapshot","getState","serverTimestamp","addDoc"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAM,wBAAwB;AAC9B,MAAM,8BAA8B;AACpC,MAAM,oBAAoB;AAO1B,IAAI,kBAA0C;AAC9C,MAAM,eAAe,IAAI,KAAK;AAS9B,eAAe,yBAA+C;AAC5D,MAAI;AAEF,UAAM,EAAE,YAAA,IAAgB,MAAM,OAAO,wBAAwB;AAC7D,UAAM,SAAS,MAAM,YAAY,IAAI,EAAE,KAAK,mBAAmB;AAC/D,QAAI,OAAO,OAAO;AAChB,YAAM,OAAO,KAAK,MAAM,OAAO,KAAK;AACpC,aAAO,IAAI,IAAI,KAAK,gBAAgB,CAAA,CAAE;AAAA,IACxC;AAAA,EACF,QAAQ;AAEN,QAAI;AACF,YAAM,SAAS,aAAa,QAAQ,iBAAiB;AACrD,UAAI,QAAQ;AACV,cAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,eAAO,IAAI,IAAI,KAAK,gBAAgB,CAAA,CAAE;AAAA,MACxC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,6BAAW,IAAA;AACb;AAKA,eAAe,uBAAuB,aAAoC;AACxE,QAAM,YAAY,MAAM,uBAAA;AACxB,YAAU,IAAI,WAAW;AAEzB,QAAM,aAAa,KAAK,UAAU;AAAA,IAChC,cAAc,MAAM,KAAK,SAAS;AAAA,IAClC,WAAW,KAAK,IAAA;AAAA,EAAI,CACrB;AAED,MAAI;AAEF,UAAM,EAAE,YAAA,IAAgB,MAAM,OAAO,wBAAwB;AAC7D,UAAM,YAAY,IAAI,EAAE,KAAK,mBAAmB,OAAO,YAAY;AAAA,EACrE,QAAQ;AAEN,QAAI;AACF,mBAAa,QAAQ,mBAAmB,UAAU;AAAA,IACpD,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAKA,eAAsB,qBACpB,aACkB;AAClB,QAAM,YAAY,MAAM,uBAAA;AACxB,SAAO,UAAU,IAAI,WAAW;AAClC;AAKA,eAAsB,iBAAiB,aAAoC;AACzE,QAAM,uBAAuB,WAAW;AAC1C;AAKA,eAAsB,2BAA0C;AAC9D,MAAI;AACF,UAAM,EAAE,YAAA,IAAgB,MAAM,OAAO,wBAAwB;AAC7D,UAAM,YAAY,OAAO,EAAE,KAAK,mBAAmB;AAAA,EACrD,QAAQ;AACN,QAAI;AACF,mBAAa,WAAW,iBAAiB;AAAA,IAC3C,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AASA,SAAS,eAAwB;AAC/B,MAAI,CAAC,gBAAiB,QAAO;AAC7B,SAAO,KAAK,IAAA,IAAQ,gBAAgB,YAAY;AAClD;AAKA,SAAS,eACP,OACA,MACuB;AACvB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,OAAO,KAAK;AAAA,IACZ,SAAS,KAAK;AAAA,IACd,MAAM,KAAK;AAAA,IACX,UAAU,KAAK;AAAA,IACf,QAAQ;AAAA;AAAA,IACR,aAAa,KAAK;AAAA,IAClB,WAAW,KAAK;AAAA,IAChB,YAAY,KAAK;AAAA,IACjB,WAAW,KAAK;AAAA,IAChB,UAAU,KAAK;AAAA,IACf,gBAAiB,KAAK,kBAA+B,CAAA;AAAA,IACrD,iBACG,KAAK,mBAA8C,CAAA;AAAA,IACtD,gBACG,KAAK,kBAA8D;AAAA,IACtE,QAAQ,KAAK;AAAA,IACb,WAAW,KAAK;AAAA,IAChB,SAAS,KAAK;AAAA,IACd,UAAW,KAAK,YAAuB;AAAA,IACvC,aAAa,KAAK,gBAAgB;AAAA;AAAA,IAClC,SAAU,KAAK,WAAgC;AAAA,IAC/C,aAAc,KAAK,eAA0B;AAAA,IAC7C,QAAS,KAAK,UAAqB;AAAA,IACnC,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK;AAAA,EAAA;AAEpB;AASA,eAAsB,gBACpB,UAAkC,IACA;AAClC,QAAM,SAASA,eAAAA,UAAA;AACf,QAAM,KAAKC,eAAAA,oBAAA;AAGX,MACE,CAAC,QAAQ,aACT,CAAC,QAAQ,YACT,CAAC,QAAQ,WACT,CAAC,QAAQ,UACT,gBACA;AACA,WAAO,gBAAiB;AAAA,EAC1B;AAEA,MAAI,IAAIC,UAAAA,MAAMC,UAAAA,WAAW,IAAI,qBAAqB,CAAC;AAGnD,QAAM,SAAS,QAAQ,UAAU;AACjC,MAAID,UAAAA,MAAM,GAAGE,UAAAA,MAAM,UAAU,MAAM,MAAM,CAAC;AAG1C,MAAIF,UAAAA,MAAM,GAAGG,UAAAA,QAAQ,YAAY,MAAM,CAAC;AAExC,MAAI,QAAQ,OAAO;AACjB,UAAM,EAAE,OAAO,mBAAmB,MAAM,OAAO,oBAAoB;AACnE,QAAIH,UAAAA,MAAM,GAAG,eAAe,QAAQ,KAAK,CAAC;AAAA,EAC5C;AAEA,QAAM,WAAW,MAAMI,UAAAA,QAAQ,CAAC;AAChC,MAAI,aAAa,SAAS,KAAK,IAAI,CAAC,MAAM,eAAe,EAAE,IAAI,EAAE,KAAA,CAAM,CAAC;AAGxE,QAAM,MAAMC,UAAAA,UAAU,IAAA;AAEtB,eAAa,WAAW,OAAO,CAAC,MAAM;AAEpC,QAAI,EAAE,aAAc,EAAE,UAAwB,aAAa,IAAI,YAAY;AACzE,aAAO;AAAA,IACT;AACA,QACE,EAAE,WACD,EAAE,QAAsB,aAAa,IAAI,YAC1C;AACA,aAAO;AAAA,IACT;AAGA,UAAM,iBAAiB,QAAQ,YAAY,OAAO;AAClD,QACE,EAAE,gBAAgB,SAAS,KAC3B,CAAC,EAAE,gBAAgB,SAAS,cAAsC,GAClE;AACA,aAAO;AAAA,IACT;AAGA,UAAM,gBAAgB,QAAQ,aAAa,OAAO;AAClD,QACE,EAAE,eAAe,SAAS,KAC1B,CAAC,EAAE,eAAe,SAAS,aAAa,GACxC;AACA,aAAO;AAAA,IACT;AAGA,QAAI,QAAQ,WAAW,EAAE,YAAY,QAAQ,SAAS;AACpD,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT,CAAC;AAGD,MACE,CAAC,QAAQ,aACT,CAAC,QAAQ,YACT,CAAC,QAAQ,WACT,CAAC,QAAQ,QACT;AACA,sBAAkB;AAAA,MAChB,MAAM;AAAA,MACN,WAAW,KAAK,IAAA;AAAA,IAAI;AAAA,EAExB;AAEA,SAAO;AACT;AAKA,eAAsB,sBACpB,UAAkD,IAChB;AAClC,QAAM,aAAa,MAAM,gBAAgB,EAAE,GAAG,SAAS,QAAQ,UAAU;AACzE,QAAM,YAAY,MAAM,uBAAA;AAExB,SAAO,WAAW,OAAO,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;AACtD;AAKA,eAAsB,yBACpB,SACkC;AAClC,SAAO,sBAAsB,EAAE,SAAS;AAC1C;AAKA,eAAsB,iBACpB,aACuC;AACvC,QAAM,KAAKN,eAAAA,oBAAA;AACX,QAAM,UAAU,MAAMO,iBAAOC,UAAAA,IAAI,IAAI,uBAAuB,WAAW,CAAC;AAExE,MAAI,CAAC,QAAQ,OAAA,EAAU,QAAO;AAC9B,SAAO,eAAe,QAAQ,IAAI,QAAQ,MAAM;AAClD;AASO,SAAS,sBACd,UACA,UAAkC,IACrB;AACb,QAAM,SAAST,eAAAA,UAAA;AACf,QAAM,KAAKC,eAAAA,oBAAA;AAEX,MAAI,IAAIC,UAAAA;AAAAA,IACNC,UAAAA,WAAW,IAAI,qBAAqB;AAAA,IACpCC,gBAAM,UAAU,MAAM,QAAQ;AAAA,IAC9BC,UAAAA,QAAQ,YAAY,MAAM;AAAA,EAAA;AAG5B,SAAOK,UAAAA;AAAAA,IACL;AAAA,IACA,OAAO,aAAa;AAClB,UAAI,aAAa,SAAS,KAAK;AAAA,QAAI,CAAC,MAClC,eAAe,EAAE,IAAI,EAAE,MAAM;AAAA,MAAA;AAI/B,YAAM,MAAMH,UAAAA,UAAU,IAAA;AACtB,YAAM,YAAY,MAAM,uBAAA;AAExB,mBAAa,WAAW,OAAO,CAAC,MAAM;AAEpC,YAAI,UAAU,IAAI,EAAE,EAAE,EAAG,QAAO;AAGhC,YACE,EAAE,aACD,EAAE,UAAwB,aAAa,IAAI,YAC5C;AACA,iBAAO;AAAA,QACT;AACA,YACE,EAAE,WACD,EAAE,QAAsB,aAAa,IAAI,YAC1C;AACA,iBAAO;AAAA,QACT;AAGA,cAAM,iBAAiB,QAAQ,YAAY,OAAO;AAClD,YACE,EAAE,gBAAgB,SAAS,KAC3B,CAAC,EAAE,gBAAgB,SAAS,cAAsC,GAClE;AACA,iBAAO;AAAA,QACT;AAGA,cAAM,gBAAgB,QAAQ,aAAa,OAAO;AAClD,YACE,EAAE,eAAe,SAAS,KAC1B,CAAC,EAAE,eAAe,SAAS,aAAa,GACxC;AACA,iBAAO;AAAA,QACT;AAGA,YAAI,QAAQ,WAAW,EAAE,YAAY,QAAQ,SAAS;AACpD,iBAAO;AAAA,QACT;AAEA,eAAO;AAAA,MACT,CAAC;AAED,eAAS,UAAU;AAAA,IACrB;AAAA,IACA,CAAC,UAAU;AACT,YAAM,QAAQ,OAAO;AACrB,UAAI,OAAO;AACT,gBAAQ,MAAM,mDAAmD,KAAK;AAAA,MACxE;AAAA,IACF;AAAA,EAAA;AAEJ;AASA,eAAsB,qBACpB,aACA,QACe;AACf,QAAM,SAASP,eAAAA,UAAA;AACf,QAAM,QAAQW,eAAAA,SAAA;AACd,QAAM,KAAKV,eAAAA,oBAAA;AAEX,QAAM,YAAY;AAAA,IAChB;AAAA,IACA,WAAW,OAAO;AAAA,IAClB,UAAU,OAAO;AAAA,IACjB,UAAU,MAAM,YAAY;AAAA,IAC5B;AAAA,IACA,WAAWW,UAAAA,gBAAA;AAAA,EAAgB;AAG7B,MAAI;AACF,UAAMC,UAAAA,OAAOV,UAAAA,WAAW,IAAI,2BAA2B,GAAG,SAAS;AAEnE,QAAI,OAAO,OAAO;AAChB,cAAQ,IAAI,+CAA+C,SAAS;AAAA,IACtE;AAAA,EACF,SAAS,OAAO;AACd,QAAI,OAAO,OAAO;AAChB,cAAQ;AAAA,QACN;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EAEF;AACF;AAKA,eAAsB,yBACpB,aACe;AACf,QAAM,qBAAqB,aAAa,YAAY;AACtD;AAKA,eAAsB,oBAAoB,aAAoC;AAC5E,QAAM,qBAAqB,aAAa,OAAO;AACjD;AAKA,eAAsB,sBACpB,aACe;AACf,QAAM,qBAAqB,aAAa,SAAS;AACjD,QAAM,iBAAiB,WAAW;AACpC;AASO,SAAS,uBAA6B;AAC3C,oBAAkB;AACpB;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"broadcasts-DZsQNd4R.cjs","sources":["../src/services/broadcasts.ts"],"sourcesContent":["/**\n * Broadcasts Service\n *\n * Handles fetching and displaying cross-project broadcast notifications\n * from the centralized aoneahsan.com Firebase backend.\n *\n * Broadcasts are created via the admin panel and displayed across all\n * consumer projects based on targeting rules.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n getDocs,\n getDoc,\n doc,\n query,\n where,\n orderBy,\n onSnapshot,\n addDoc,\n serverTimestamp,\n Timestamp,\n Unsubscribe,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig, getState } from '../firebase/config';\nimport type {\n BroadcastNotification,\n BroadcastStatus,\n BroadcastVariant,\n NotificationPlatform,\n FetchBroadcastsOptions,\n} from '../types/notifications';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_BROADCASTS = 'zaions_broadcasts';\nconst COLLECTION_BROADCAST_EVENTS = 'zaions_broadcast_events';\nconst LOCAL_STORAGE_KEY = 'shared_features_dismissed_broadcasts';\n\n// Cache for broadcasts\ninterface BroadcastsCache {\n data: BroadcastNotification[];\n timestamp: number;\n}\nlet broadcastsCache: BroadcastsCache | null = null;\nconst CACHE_TTL_MS = 2 * 60 * 1000; // 2 minutes (shorter than campaigns)\n\n// ============================================================================\n// LOCAL DISMISSAL MANAGEMENT\n// ============================================================================\n\n/**\n * Get dismissed broadcast IDs from local storage\n */\nasync function getDismissedBroadcasts(): Promise<Set<string>> {\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n const result = await Preferences.get({ key: LOCAL_STORAGE_KEY });\n if (result.value) {\n const data = JSON.parse(result.value);\n return new Set(data.dismissedIds || []);\n }\n } catch {\n // Fallback to localStorage\n try {\n const stored = localStorage.getItem(LOCAL_STORAGE_KEY);\n if (stored) {\n const data = JSON.parse(stored);\n return new Set(data.dismissedIds || []);\n }\n } catch {\n // Ignore\n }\n }\n return new Set();\n}\n\n/**\n * Save dismissed broadcast ID to local storage\n */\nasync function saveDismissedBroadcast(broadcastId: string): Promise<void> {\n const dismissed = await getDismissedBroadcasts();\n dismissed.add(broadcastId);\n\n const serialized = JSON.stringify({\n dismissedIds: Array.from(dismissed),\n updatedAt: Date.now(),\n });\n\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n await Preferences.set({ key: LOCAL_STORAGE_KEY, value: serialized });\n } catch {\n // Fallback to localStorage\n try {\n localStorage.setItem(LOCAL_STORAGE_KEY, serialized);\n } catch {\n // Ignore storage errors\n }\n }\n}\n\n/**\n * Check if a broadcast has been dismissed\n */\nexport async function isBroadcastDismissed(\n broadcastId: string\n): Promise<boolean> {\n const dismissed = await getDismissedBroadcasts();\n return dismissed.has(broadcastId);\n}\n\n/**\n * Dismiss a broadcast (save locally)\n */\nexport async function dismissBroadcast(broadcastId: string): Promise<void> {\n await saveDismissedBroadcast(broadcastId);\n}\n\n/**\n * Clear all dismissed broadcasts (useful for testing)\n */\nexport async function clearDismissedBroadcasts(): Promise<void> {\n try {\n const { Preferences } = await import('@capacitor/preferences');\n await Preferences.remove({ key: LOCAL_STORAGE_KEY });\n } catch {\n try {\n localStorage.removeItem(LOCAL_STORAGE_KEY);\n } catch {\n // Ignore\n }\n }\n}\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Check if cache is valid\n */\nfunction isCacheValid(): boolean {\n if (!broadcastsCache) return false;\n return Date.now() - broadcastsCache.timestamp < CACHE_TTL_MS;\n}\n\n/**\n * Convert Firestore document to BroadcastNotification\n */\nfunction docToBroadcast(\n docId: string,\n data: Record<string, unknown>\n): BroadcastNotification {\n return {\n id: docId,\n title: data.title as string,\n message: data.message as string,\n type: data.type as BroadcastNotification['type'],\n category: data.category as BroadcastNotification['category'],\n isRead: false, // Broadcasts don't have read state per-user\n isImportant: data.isImportant as boolean | undefined,\n actionUrl: data.actionUrl as string | undefined,\n actionText: data.actionText as string | undefined,\n createdAt: data.createdAt as Timestamp,\n metadata: data.metadata as Record<string, unknown> | undefined,\n targetProjects: (data.targetProjects as string[]) || [],\n targetPlatforms:\n (data.targetPlatforms as NotificationPlatform[]) || [],\n targetAudience:\n (data.targetAudience as BroadcastNotification['targetAudience']) || 'all',\n status: data.status as BroadcastStatus,\n startDate: data.startDate as Timestamp,\n endDate: data.endDate as Timestamp | null | undefined,\n priority: (data.priority as number) || 50,\n dismissible: data.dismissible !== false, // Default true\n variant: (data.variant as BroadcastVariant) || 'banner',\n impressions: (data.impressions as number) || 0,\n clicks: (data.clicks as number) || 0,\n createdBy: data.createdBy as string,\n updatedBy: data.updatedBy as string | undefined,\n };\n}\n\n// ============================================================================\n// BROADCAST FETCHING\n// ============================================================================\n\n/**\n * Fetch broadcasts with optional filters\n */\nexport async function fetchBroadcasts(\n options: FetchBroadcastsOptions = {}\n): Promise<BroadcastNotification[]> {\n const config = getConfig();\n const db = getSharedFeaturesDb();\n\n // Check cache first (only for unfiltered queries)\n if (\n !options.projectId &&\n !options.platform &&\n !options.variant &&\n !options.status &&\n isCacheValid()\n ) {\n return broadcastsCache!.data;\n }\n\n let q = query(collection(db, COLLECTION_BROADCASTS));\n\n // Apply status filter (default to active)\n const status = options.status || 'active';\n q = query(q, where('status', '==', status));\n\n // Order by priority descending\n q = query(q, orderBy('priority', 'desc'));\n\n if (options.limit) {\n const { limit: firestoreLimit } = await import('firebase/firestore');\n q = query(q, firestoreLimit(options.limit));\n }\n\n const snapshot = await getDocs(q);\n let broadcasts = snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\n\n // Client-side filtering for array fields\n const now = Timestamp.now();\n\n broadcasts = broadcasts.filter((b) => {\n // Date range check\n if (b.startDate && (b.startDate as Timestamp).toMillis() > now.toMillis()) {\n return false;\n }\n if (\n b.endDate &&\n (b.endDate as Timestamp).toMillis() < now.toMillis()\n ) {\n return false;\n }\n\n // Platform check\n const targetPlatform = options.platform || config.platform;\n if (\n b.targetPlatforms.length > 0 &&\n !b.targetPlatforms.includes(targetPlatform as NotificationPlatform)\n ) {\n return false;\n }\n\n // Project check\n const targetProject = options.projectId || config.projectId;\n if (\n b.targetProjects.length > 0 &&\n !b.targetProjects.includes(targetProject)\n ) {\n return false;\n }\n\n // Variant check\n if (options.variant && b.variant !== options.variant) {\n return false;\n }\n\n return true;\n });\n\n // Cache unfiltered results\n if (\n !options.projectId &&\n !options.platform &&\n !options.variant &&\n !options.status\n ) {\n broadcastsCache = {\n data: broadcasts,\n timestamp: Date.now(),\n };\n }\n\n return broadcasts;\n}\n\n/**\n * Fetch active broadcasts for display, excluding dismissed ones\n */\nexport async function fetchActiveBroadcasts(\n options: Omit<FetchBroadcastsOptions, 'status'> = {}\n): Promise<BroadcastNotification[]> {\n const broadcasts = await fetchBroadcasts({ ...options, status: 'active' });\n const dismissed = await getDismissedBroadcasts();\n\n return broadcasts.filter((b) => !dismissed.has(b.id));\n}\n\n/**\n * Fetch broadcasts by variant type\n */\nexport async function fetchBroadcastsByVariant(\n variant: BroadcastVariant\n): Promise<BroadcastNotification[]> {\n return fetchActiveBroadcasts({ variant });\n}\n\n/**\n * Get a single broadcast by ID\n */\nexport async function getBroadcastById(\n broadcastId: string\n): Promise<BroadcastNotification | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));\n\n if (!docSnap.exists()) return null;\n return docToBroadcast(docSnap.id, docSnap.data());\n}\n\n// ============================================================================\n// REAL-TIME SUBSCRIPTION\n// ============================================================================\n\n/**\n * Subscribe to broadcast updates in real-time\n */\nexport function subscribeToBroadcasts(\n callback: (broadcasts: BroadcastNotification[]) => void,\n options: FetchBroadcastsOptions = {}\n): Unsubscribe {\n const config = getConfig();\n const db = getSharedFeaturesDb();\n\n let q = query(\n collection(db, COLLECTION_BROADCASTS),\n where('status', '==', 'active'),\n orderBy('priority', 'desc')\n );\n\n return onSnapshot(\n q,\n async (snapshot) => {\n let broadcasts = snapshot.docs.map((d) =>\n docToBroadcast(d.id, d.data())\n );\n\n // Client-side filtering\n const now = Timestamp.now();\n const dismissed = await getDismissedBroadcasts();\n\n broadcasts = broadcasts.filter((b) => {\n // Skip dismissed\n if (dismissed.has(b.id)) return false;\n\n // Date range check\n if (\n b.startDate &&\n (b.startDate as Timestamp).toMillis() > now.toMillis()\n ) {\n return false;\n }\n if (\n b.endDate &&\n (b.endDate as Timestamp).toMillis() < now.toMillis()\n ) {\n return false;\n }\n\n // Platform check\n const targetPlatform = options.platform || config.platform;\n if (\n b.targetPlatforms.length > 0 &&\n !b.targetPlatforms.includes(targetPlatform as NotificationPlatform)\n ) {\n return false;\n }\n\n // Project check\n const targetProject = options.projectId || config.projectId;\n if (\n b.targetProjects.length > 0 &&\n !b.targetProjects.includes(targetProject)\n ) {\n return false;\n }\n\n // Variant check\n if (options.variant && b.variant !== options.variant) {\n return false;\n }\n\n return true;\n });\n\n callback(broadcasts);\n },\n (error) => {\n const debug = config.debug;\n if (debug) {\n console.error('[shared-features] Broadcast subscription error:', error);\n }\n }\n );\n}\n\n// ============================================================================\n// BROADCAST ANALYTICS\n// ============================================================================\n\n/**\n * Record a broadcast event (impression, click, dismiss)\n */\nexport async function recordBroadcastEvent(\n broadcastId: string,\n action: 'impression' | 'click' | 'dismiss'\n): Promise<void> {\n const config = getConfig();\n const state = getState();\n const db = getSharedFeaturesDb();\n\n const eventData = {\n broadcastId,\n projectId: config.projectId,\n platform: config.platform,\n deviceId: state.deviceId || 'unknown',\n action,\n timestamp: serverTimestamp(),\n };\n\n try {\n await addDoc(collection(db, COLLECTION_BROADCAST_EVENTS), eventData);\n\n if (config.debug) {\n console.log('[shared-features] Recorded broadcast event:', eventData);\n }\n } catch (error) {\n if (config.debug) {\n console.error(\n '[shared-features] Failed to record broadcast event:',\n error\n );\n }\n // Don't throw - we don't want analytics failures to break the UI\n }\n}\n\n/**\n * Track broadcast impression\n */\nexport async function trackBroadcastImpression(\n broadcastId: string\n): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'impression');\n}\n\n/**\n * Track broadcast click\n */\nexport async function trackBroadcastClick(broadcastId: string): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'click');\n}\n\n/**\n * Track broadcast dismiss\n */\nexport async function trackBroadcastDismiss(\n broadcastId: string\n): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'dismiss');\n await dismissBroadcast(broadcastId);\n}\n\n// ============================================================================\n// CACHE MANAGEMENT\n// ============================================================================\n\n/**\n * Clear the broadcasts cache\n */\nexport function clearBroadcastsCache(): void {\n broadcastsCache = null;\n}\n"],"names":["getConfig","getSharedFeaturesDb","query","collection","where","orderBy","getDocs","Timestamp","getDoc","doc","onSnapshot","getState","serverTimestamp","addDoc"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { query, collection, where, orderBy, getDocs, Timestamp, getDoc, doc, serverTimestamp, addDoc, onSnapshot } from "firebase/firestore";
2
- import { E as getConfig, L as getSharedFeaturesDb, $ as getState } from "./commonFeatures-D-MZcecu.js";
2
+ import { E as getConfig, L as getSharedFeaturesDb, $ as getState } from "./commonFeatures-LzPnbR6z.js";
3
3
  const COLLECTION_BROADCASTS = "zaions_broadcasts";
4
4
  const COLLECTION_BROADCAST_EVENTS = "zaions_broadcast_events";
5
5
  const LOCAL_STORAGE_KEY = "shared_features_dismissed_broadcasts";
@@ -254,4 +254,4 @@ export {
254
254
  subscribeToBroadcasts as s,
255
255
  trackBroadcastClick as t
256
256
  };
257
- //# sourceMappingURL=broadcasts-Dkmto2dR.js.map
257
+ //# sourceMappingURL=broadcasts-Dlu51_38.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"broadcasts-Dkmto2dR.js","sources":["../src/services/broadcasts.ts"],"sourcesContent":["/**\n * Broadcasts Service\n *\n * Handles fetching and displaying cross-project broadcast notifications\n * from the centralized aoneahsan.com Firebase backend.\n *\n * Broadcasts are created via the admin panel and displayed across all\n * consumer projects based on targeting rules.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n getDocs,\n getDoc,\n doc,\n query,\n where,\n orderBy,\n onSnapshot,\n addDoc,\n serverTimestamp,\n Timestamp,\n Unsubscribe,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig, getState } from '../firebase/config';\nimport type {\n BroadcastNotification,\n BroadcastStatus,\n BroadcastVariant,\n NotificationPlatform,\n FetchBroadcastsOptions,\n} from '../types/notifications';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_BROADCASTS = 'zaions_broadcasts';\nconst COLLECTION_BROADCAST_EVENTS = 'zaions_broadcast_events';\nconst LOCAL_STORAGE_KEY = 'shared_features_dismissed_broadcasts';\n\n// Cache for broadcasts\ninterface BroadcastsCache {\n data: BroadcastNotification[];\n timestamp: number;\n}\nlet broadcastsCache: BroadcastsCache | null = null;\nconst CACHE_TTL_MS = 2 * 60 * 1000; // 2 minutes (shorter than campaigns)\n\n// ============================================================================\n// LOCAL DISMISSAL MANAGEMENT\n// ============================================================================\n\n/**\n * Get dismissed broadcast IDs from local storage\n */\nasync function getDismissedBroadcasts(): Promise<Set<string>> {\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n const result = await Preferences.get({ key: LOCAL_STORAGE_KEY });\n if (result.value) {\n const data = JSON.parse(result.value);\n return new Set(data.dismissedIds || []);\n }\n } catch {\n // Fallback to localStorage\n try {\n const stored = localStorage.getItem(LOCAL_STORAGE_KEY);\n if (stored) {\n const data = JSON.parse(stored);\n return new Set(data.dismissedIds || []);\n }\n } catch {\n // Ignore\n }\n }\n return new Set();\n}\n\n/**\n * Save dismissed broadcast ID to local storage\n */\nasync function saveDismissedBroadcast(broadcastId: string): Promise<void> {\n const dismissed = await getDismissedBroadcasts();\n dismissed.add(broadcastId);\n\n const serialized = JSON.stringify({\n dismissedIds: Array.from(dismissed),\n updatedAt: Date.now(),\n });\n\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n await Preferences.set({ key: LOCAL_STORAGE_KEY, value: serialized });\n } catch {\n // Fallback to localStorage\n try {\n localStorage.setItem(LOCAL_STORAGE_KEY, serialized);\n } catch {\n // Ignore storage errors\n }\n }\n}\n\n/**\n * Check if a broadcast has been dismissed\n */\nexport async function isBroadcastDismissed(\n broadcastId: string\n): Promise<boolean> {\n const dismissed = await getDismissedBroadcasts();\n return dismissed.has(broadcastId);\n}\n\n/**\n * Dismiss a broadcast (save locally)\n */\nexport async function dismissBroadcast(broadcastId: string): Promise<void> {\n await saveDismissedBroadcast(broadcastId);\n}\n\n/**\n * Clear all dismissed broadcasts (useful for testing)\n */\nexport async function clearDismissedBroadcasts(): Promise<void> {\n try {\n const { Preferences } = await import('@capacitor/preferences');\n await Preferences.remove({ key: LOCAL_STORAGE_KEY });\n } catch {\n try {\n localStorage.removeItem(LOCAL_STORAGE_KEY);\n } catch {\n // Ignore\n }\n }\n}\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Check if cache is valid\n */\nfunction isCacheValid(): boolean {\n if (!broadcastsCache) return false;\n return Date.now() - broadcastsCache.timestamp < CACHE_TTL_MS;\n}\n\n/**\n * Convert Firestore document to BroadcastNotification\n */\nfunction docToBroadcast(\n docId: string,\n data: Record<string, unknown>\n): BroadcastNotification {\n return {\n id: docId,\n title: data.title as string,\n message: data.message as string,\n type: data.type as BroadcastNotification['type'],\n category: data.category as BroadcastNotification['category'],\n isRead: false, // Broadcasts don't have read state per-user\n isImportant: data.isImportant as boolean | undefined,\n actionUrl: data.actionUrl as string | undefined,\n actionText: data.actionText as string | undefined,\n createdAt: data.createdAt as Timestamp,\n metadata: data.metadata as Record<string, unknown> | undefined,\n targetProjects: (data.targetProjects as string[]) || [],\n targetPlatforms:\n (data.targetPlatforms as NotificationPlatform[]) || [],\n targetAudience:\n (data.targetAudience as BroadcastNotification['targetAudience']) || 'all',\n status: data.status as BroadcastStatus,\n startDate: data.startDate as Timestamp,\n endDate: data.endDate as Timestamp | null | undefined,\n priority: (data.priority as number) || 50,\n dismissible: data.dismissible !== false, // Default true\n variant: (data.variant as BroadcastVariant) || 'banner',\n impressions: (data.impressions as number) || 0,\n clicks: (data.clicks as number) || 0,\n createdBy: data.createdBy as string,\n updatedBy: data.updatedBy as string | undefined,\n };\n}\n\n// ============================================================================\n// BROADCAST FETCHING\n// ============================================================================\n\n/**\n * Fetch broadcasts with optional filters\n */\nexport async function fetchBroadcasts(\n options: FetchBroadcastsOptions = {}\n): Promise<BroadcastNotification[]> {\n const config = getConfig();\n const db = getSharedFeaturesDb();\n\n // Check cache first (only for unfiltered queries)\n if (\n !options.projectId &&\n !options.platform &&\n !options.variant &&\n !options.status &&\n isCacheValid()\n ) {\n return broadcastsCache!.data;\n }\n\n let q = query(collection(db, COLLECTION_BROADCASTS));\n\n // Apply status filter (default to active)\n const status = options.status || 'active';\n q = query(q, where('status', '==', status));\n\n // Order by priority descending\n q = query(q, orderBy('priority', 'desc'));\n\n if (options.limit) {\n const { limit: firestoreLimit } = await import('firebase/firestore');\n q = query(q, firestoreLimit(options.limit));\n }\n\n const snapshot = await getDocs(q);\n let broadcasts = snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\n\n // Client-side filtering for array fields\n const now = Timestamp.now();\n\n broadcasts = broadcasts.filter((b) => {\n // Date range check\n if (b.startDate && (b.startDate as Timestamp).toMillis() > now.toMillis()) {\n return false;\n }\n if (\n b.endDate &&\n (b.endDate as Timestamp).toMillis() < now.toMillis()\n ) {\n return false;\n }\n\n // Platform check\n const targetPlatform = options.platform || config.platform;\n if (\n b.targetPlatforms.length > 0 &&\n !b.targetPlatforms.includes(targetPlatform as NotificationPlatform)\n ) {\n return false;\n }\n\n // Project check\n const targetProject = options.projectId || config.projectId;\n if (\n b.targetProjects.length > 0 &&\n !b.targetProjects.includes(targetProject)\n ) {\n return false;\n }\n\n // Variant check\n if (options.variant && b.variant !== options.variant) {\n return false;\n }\n\n return true;\n });\n\n // Cache unfiltered results\n if (\n !options.projectId &&\n !options.platform &&\n !options.variant &&\n !options.status\n ) {\n broadcastsCache = {\n data: broadcasts,\n timestamp: Date.now(),\n };\n }\n\n return broadcasts;\n}\n\n/**\n * Fetch active broadcasts for display, excluding dismissed ones\n */\nexport async function fetchActiveBroadcasts(\n options: Omit<FetchBroadcastsOptions, 'status'> = {}\n): Promise<BroadcastNotification[]> {\n const broadcasts = await fetchBroadcasts({ ...options, status: 'active' });\n const dismissed = await getDismissedBroadcasts();\n\n return broadcasts.filter((b) => !dismissed.has(b.id));\n}\n\n/**\n * Fetch broadcasts by variant type\n */\nexport async function fetchBroadcastsByVariant(\n variant: BroadcastVariant\n): Promise<BroadcastNotification[]> {\n return fetchActiveBroadcasts({ variant });\n}\n\n/**\n * Get a single broadcast by ID\n */\nexport async function getBroadcastById(\n broadcastId: string\n): Promise<BroadcastNotification | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));\n\n if (!docSnap.exists()) return null;\n return docToBroadcast(docSnap.id, docSnap.data());\n}\n\n// ============================================================================\n// REAL-TIME SUBSCRIPTION\n// ============================================================================\n\n/**\n * Subscribe to broadcast updates in real-time\n */\nexport function subscribeToBroadcasts(\n callback: (broadcasts: BroadcastNotification[]) => void,\n options: FetchBroadcastsOptions = {}\n): Unsubscribe {\n const config = getConfig();\n const db = getSharedFeaturesDb();\n\n let q = query(\n collection(db, COLLECTION_BROADCASTS),\n where('status', '==', 'active'),\n orderBy('priority', 'desc')\n );\n\n return onSnapshot(\n q,\n async (snapshot) => {\n let broadcasts = snapshot.docs.map((d) =>\n docToBroadcast(d.id, d.data())\n );\n\n // Client-side filtering\n const now = Timestamp.now();\n const dismissed = await getDismissedBroadcasts();\n\n broadcasts = broadcasts.filter((b) => {\n // Skip dismissed\n if (dismissed.has(b.id)) return false;\n\n // Date range check\n if (\n b.startDate &&\n (b.startDate as Timestamp).toMillis() > now.toMillis()\n ) {\n return false;\n }\n if (\n b.endDate &&\n (b.endDate as Timestamp).toMillis() < now.toMillis()\n ) {\n return false;\n }\n\n // Platform check\n const targetPlatform = options.platform || config.platform;\n if (\n b.targetPlatforms.length > 0 &&\n !b.targetPlatforms.includes(targetPlatform as NotificationPlatform)\n ) {\n return false;\n }\n\n // Project check\n const targetProject = options.projectId || config.projectId;\n if (\n b.targetProjects.length > 0 &&\n !b.targetProjects.includes(targetProject)\n ) {\n return false;\n }\n\n // Variant check\n if (options.variant && b.variant !== options.variant) {\n return false;\n }\n\n return true;\n });\n\n callback(broadcasts);\n },\n (error) => {\n const debug = config.debug;\n if (debug) {\n console.error('[shared-features] Broadcast subscription error:', error);\n }\n }\n );\n}\n\n// ============================================================================\n// BROADCAST ANALYTICS\n// ============================================================================\n\n/**\n * Record a broadcast event (impression, click, dismiss)\n */\nexport async function recordBroadcastEvent(\n broadcastId: string,\n action: 'impression' | 'click' | 'dismiss'\n): Promise<void> {\n const config = getConfig();\n const state = getState();\n const db = getSharedFeaturesDb();\n\n const eventData = {\n broadcastId,\n projectId: config.projectId,\n platform: config.platform,\n deviceId: state.deviceId || 'unknown',\n action,\n timestamp: serverTimestamp(),\n };\n\n try {\n await addDoc(collection(db, COLLECTION_BROADCAST_EVENTS), eventData);\n\n if (config.debug) {\n console.log('[shared-features] Recorded broadcast event:', eventData);\n }\n } catch (error) {\n if (config.debug) {\n console.error(\n '[shared-features] Failed to record broadcast event:',\n error\n );\n }\n // Don't throw - we don't want analytics failures to break the UI\n }\n}\n\n/**\n * Track broadcast impression\n */\nexport async function trackBroadcastImpression(\n broadcastId: string\n): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'impression');\n}\n\n/**\n * Track broadcast click\n */\nexport async function trackBroadcastClick(broadcastId: string): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'click');\n}\n\n/**\n * Track broadcast dismiss\n */\nexport async function trackBroadcastDismiss(\n broadcastId: string\n): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'dismiss');\n await dismissBroadcast(broadcastId);\n}\n\n// ============================================================================\n// CACHE MANAGEMENT\n// ============================================================================\n\n/**\n * Clear the broadcasts cache\n */\nexport function clearBroadcastsCache(): void {\n broadcastsCache = null;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"broadcasts-Dlu51_38.js","sources":["../src/services/broadcasts.ts"],"sourcesContent":["/**\n * Broadcasts Service\n *\n * Handles fetching and displaying cross-project broadcast notifications\n * from the centralized aoneahsan.com Firebase backend.\n *\n * Broadcasts are created via the admin panel and displayed across all\n * consumer projects based on targeting rules.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n getDocs,\n getDoc,\n doc,\n query,\n where,\n orderBy,\n onSnapshot,\n addDoc,\n serverTimestamp,\n Timestamp,\n Unsubscribe,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig, getState } from '../firebase/config';\nimport type {\n BroadcastNotification,\n BroadcastStatus,\n BroadcastVariant,\n NotificationPlatform,\n FetchBroadcastsOptions,\n} from '../types/notifications';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_BROADCASTS = 'zaions_broadcasts';\nconst COLLECTION_BROADCAST_EVENTS = 'zaions_broadcast_events';\nconst LOCAL_STORAGE_KEY = 'shared_features_dismissed_broadcasts';\n\n// Cache for broadcasts\ninterface BroadcastsCache {\n data: BroadcastNotification[];\n timestamp: number;\n}\nlet broadcastsCache: BroadcastsCache | null = null;\nconst CACHE_TTL_MS = 2 * 60 * 1000; // 2 minutes (shorter than campaigns)\n\n// ============================================================================\n// LOCAL DISMISSAL MANAGEMENT\n// ============================================================================\n\n/**\n * Get dismissed broadcast IDs from local storage\n */\nasync function getDismissedBroadcasts(): Promise<Set<string>> {\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n const result = await Preferences.get({ key: LOCAL_STORAGE_KEY });\n if (result.value) {\n const data = JSON.parse(result.value);\n return new Set(data.dismissedIds || []);\n }\n } catch {\n // Fallback to localStorage\n try {\n const stored = localStorage.getItem(LOCAL_STORAGE_KEY);\n if (stored) {\n const data = JSON.parse(stored);\n return new Set(data.dismissedIds || []);\n }\n } catch {\n // Ignore\n }\n }\n return new Set();\n}\n\n/**\n * Save dismissed broadcast ID to local storage\n */\nasync function saveDismissedBroadcast(broadcastId: string): Promise<void> {\n const dismissed = await getDismissedBroadcasts();\n dismissed.add(broadcastId);\n\n const serialized = JSON.stringify({\n dismissedIds: Array.from(dismissed),\n updatedAt: Date.now(),\n });\n\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n await Preferences.set({ key: LOCAL_STORAGE_KEY, value: serialized });\n } catch {\n // Fallback to localStorage\n try {\n localStorage.setItem(LOCAL_STORAGE_KEY, serialized);\n } catch {\n // Ignore storage errors\n }\n }\n}\n\n/**\n * Check if a broadcast has been dismissed\n */\nexport async function isBroadcastDismissed(\n broadcastId: string\n): Promise<boolean> {\n const dismissed = await getDismissedBroadcasts();\n return dismissed.has(broadcastId);\n}\n\n/**\n * Dismiss a broadcast (save locally)\n */\nexport async function dismissBroadcast(broadcastId: string): Promise<void> {\n await saveDismissedBroadcast(broadcastId);\n}\n\n/**\n * Clear all dismissed broadcasts (useful for testing)\n */\nexport async function clearDismissedBroadcasts(): Promise<void> {\n try {\n const { Preferences } = await import('@capacitor/preferences');\n await Preferences.remove({ key: LOCAL_STORAGE_KEY });\n } catch {\n try {\n localStorage.removeItem(LOCAL_STORAGE_KEY);\n } catch {\n // Ignore\n }\n }\n}\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Check if cache is valid\n */\nfunction isCacheValid(): boolean {\n if (!broadcastsCache) return false;\n return Date.now() - broadcastsCache.timestamp < CACHE_TTL_MS;\n}\n\n/**\n * Convert Firestore document to BroadcastNotification\n */\nfunction docToBroadcast(\n docId: string,\n data: Record<string, unknown>\n): BroadcastNotification {\n return {\n id: docId,\n title: data.title as string,\n message: data.message as string,\n type: data.type as BroadcastNotification['type'],\n category: data.category as BroadcastNotification['category'],\n isRead: false, // Broadcasts don't have read state per-user\n isImportant: data.isImportant as boolean | undefined,\n actionUrl: data.actionUrl as string | undefined,\n actionText: data.actionText as string | undefined,\n createdAt: data.createdAt as Timestamp,\n metadata: data.metadata as Record<string, unknown> | undefined,\n targetProjects: (data.targetProjects as string[]) || [],\n targetPlatforms:\n (data.targetPlatforms as NotificationPlatform[]) || [],\n targetAudience:\n (data.targetAudience as BroadcastNotification['targetAudience']) || 'all',\n status: data.status as BroadcastStatus,\n startDate: data.startDate as Timestamp,\n endDate: data.endDate as Timestamp | null | undefined,\n priority: (data.priority as number) || 50,\n dismissible: data.dismissible !== false, // Default true\n variant: (data.variant as BroadcastVariant) || 'banner',\n impressions: (data.impressions as number) || 0,\n clicks: (data.clicks as number) || 0,\n createdBy: data.createdBy as string,\n updatedBy: data.updatedBy as string | undefined,\n };\n}\n\n// ============================================================================\n// BROADCAST FETCHING\n// ============================================================================\n\n/**\n * Fetch broadcasts with optional filters\n */\nexport async function fetchBroadcasts(\n options: FetchBroadcastsOptions = {}\n): Promise<BroadcastNotification[]> {\n const config = getConfig();\n const db = getSharedFeaturesDb();\n\n // Check cache first (only for unfiltered queries)\n if (\n !options.projectId &&\n !options.platform &&\n !options.variant &&\n !options.status &&\n isCacheValid()\n ) {\n return broadcastsCache!.data;\n }\n\n let q = query(collection(db, COLLECTION_BROADCASTS));\n\n // Apply status filter (default to active)\n const status = options.status || 'active';\n q = query(q, where('status', '==', status));\n\n // Order by priority descending\n q = query(q, orderBy('priority', 'desc'));\n\n if (options.limit) {\n const { limit: firestoreLimit } = await import('firebase/firestore');\n q = query(q, firestoreLimit(options.limit));\n }\n\n const snapshot = await getDocs(q);\n let broadcasts = snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\n\n // Client-side filtering for array fields\n const now = Timestamp.now();\n\n broadcasts = broadcasts.filter((b) => {\n // Date range check\n if (b.startDate && (b.startDate as Timestamp).toMillis() > now.toMillis()) {\n return false;\n }\n if (\n b.endDate &&\n (b.endDate as Timestamp).toMillis() < now.toMillis()\n ) {\n return false;\n }\n\n // Platform check\n const targetPlatform = options.platform || config.platform;\n if (\n b.targetPlatforms.length > 0 &&\n !b.targetPlatforms.includes(targetPlatform as NotificationPlatform)\n ) {\n return false;\n }\n\n // Project check\n const targetProject = options.projectId || config.projectId;\n if (\n b.targetProjects.length > 0 &&\n !b.targetProjects.includes(targetProject)\n ) {\n return false;\n }\n\n // Variant check\n if (options.variant && b.variant !== options.variant) {\n return false;\n }\n\n return true;\n });\n\n // Cache unfiltered results\n if (\n !options.projectId &&\n !options.platform &&\n !options.variant &&\n !options.status\n ) {\n broadcastsCache = {\n data: broadcasts,\n timestamp: Date.now(),\n };\n }\n\n return broadcasts;\n}\n\n/**\n * Fetch active broadcasts for display, excluding dismissed ones\n */\nexport async function fetchActiveBroadcasts(\n options: Omit<FetchBroadcastsOptions, 'status'> = {}\n): Promise<BroadcastNotification[]> {\n const broadcasts = await fetchBroadcasts({ ...options, status: 'active' });\n const dismissed = await getDismissedBroadcasts();\n\n return broadcasts.filter((b) => !dismissed.has(b.id));\n}\n\n/**\n * Fetch broadcasts by variant type\n */\nexport async function fetchBroadcastsByVariant(\n variant: BroadcastVariant\n): Promise<BroadcastNotification[]> {\n return fetchActiveBroadcasts({ variant });\n}\n\n/**\n * Get a single broadcast by ID\n */\nexport async function getBroadcastById(\n broadcastId: string\n): Promise<BroadcastNotification | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));\n\n if (!docSnap.exists()) return null;\n return docToBroadcast(docSnap.id, docSnap.data());\n}\n\n// ============================================================================\n// REAL-TIME SUBSCRIPTION\n// ============================================================================\n\n/**\n * Subscribe to broadcast updates in real-time\n */\nexport function subscribeToBroadcasts(\n callback: (broadcasts: BroadcastNotification[]) => void,\n options: FetchBroadcastsOptions = {}\n): Unsubscribe {\n const config = getConfig();\n const db = getSharedFeaturesDb();\n\n let q = query(\n collection(db, COLLECTION_BROADCASTS),\n where('status', '==', 'active'),\n orderBy('priority', 'desc')\n );\n\n return onSnapshot(\n q,\n async (snapshot) => {\n let broadcasts = snapshot.docs.map((d) =>\n docToBroadcast(d.id, d.data())\n );\n\n // Client-side filtering\n const now = Timestamp.now();\n const dismissed = await getDismissedBroadcasts();\n\n broadcasts = broadcasts.filter((b) => {\n // Skip dismissed\n if (dismissed.has(b.id)) return false;\n\n // Date range check\n if (\n b.startDate &&\n (b.startDate as Timestamp).toMillis() > now.toMillis()\n ) {\n return false;\n }\n if (\n b.endDate &&\n (b.endDate as Timestamp).toMillis() < now.toMillis()\n ) {\n return false;\n }\n\n // Platform check\n const targetPlatform = options.platform || config.platform;\n if (\n b.targetPlatforms.length > 0 &&\n !b.targetPlatforms.includes(targetPlatform as NotificationPlatform)\n ) {\n return false;\n }\n\n // Project check\n const targetProject = options.projectId || config.projectId;\n if (\n b.targetProjects.length > 0 &&\n !b.targetProjects.includes(targetProject)\n ) {\n return false;\n }\n\n // Variant check\n if (options.variant && b.variant !== options.variant) {\n return false;\n }\n\n return true;\n });\n\n callback(broadcasts);\n },\n (error) => {\n const debug = config.debug;\n if (debug) {\n console.error('[shared-features] Broadcast subscription error:', error);\n }\n }\n );\n}\n\n// ============================================================================\n// BROADCAST ANALYTICS\n// ============================================================================\n\n/**\n * Record a broadcast event (impression, click, dismiss)\n */\nexport async function recordBroadcastEvent(\n broadcastId: string,\n action: 'impression' | 'click' | 'dismiss'\n): Promise<void> {\n const config = getConfig();\n const state = getState();\n const db = getSharedFeaturesDb();\n\n const eventData = {\n broadcastId,\n projectId: config.projectId,\n platform: config.platform,\n deviceId: state.deviceId || 'unknown',\n action,\n timestamp: serverTimestamp(),\n };\n\n try {\n await addDoc(collection(db, COLLECTION_BROADCAST_EVENTS), eventData);\n\n if (config.debug) {\n console.log('[shared-features] Recorded broadcast event:', eventData);\n }\n } catch (error) {\n if (config.debug) {\n console.error(\n '[shared-features] Failed to record broadcast event:',\n error\n );\n }\n // Don't throw - we don't want analytics failures to break the UI\n }\n}\n\n/**\n * Track broadcast impression\n */\nexport async function trackBroadcastImpression(\n broadcastId: string\n): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'impression');\n}\n\n/**\n * Track broadcast click\n */\nexport async function trackBroadcastClick(broadcastId: string): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'click');\n}\n\n/**\n * Track broadcast dismiss\n */\nexport async function trackBroadcastDismiss(\n broadcastId: string\n): Promise<void> {\n await recordBroadcastEvent(broadcastId, 'dismiss');\n await dismissBroadcast(broadcastId);\n}\n\n// ============================================================================\n// CACHE MANAGEMENT\n// ============================================================================\n\n/**\n * Clear the broadcasts cache\n */\nexport function clearBroadcastsCache(): void {\n broadcastsCache = null;\n}\n"],"names":[],"mappings":";;AAwCA,MAAM,wBAAwB;AAC9B,MAAM,8BAA8B;AACpC,MAAM,oBAAoB;AAO1B,IAAI,kBAA0C;AAC9C,MAAM,eAAe,IAAI,KAAK;AAS9B,eAAe,yBAA+C;AAC5D,MAAI;AAEF,UAAM,EAAE,YAAA,IAAgB,MAAM,OAAO,wBAAwB;AAC7D,UAAM,SAAS,MAAM,YAAY,IAAI,EAAE,KAAK,mBAAmB;AAC/D,QAAI,OAAO,OAAO;AAChB,YAAM,OAAO,KAAK,MAAM,OAAO,KAAK;AACpC,aAAO,IAAI,IAAI,KAAK,gBAAgB,CAAA,CAAE;AAAA,IACxC;AAAA,EACF,QAAQ;AAEN,QAAI;AACF,YAAM,SAAS,aAAa,QAAQ,iBAAiB;AACrD,UAAI,QAAQ;AACV,cAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,eAAO,IAAI,IAAI,KAAK,gBAAgB,CAAA,CAAE;AAAA,MACxC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,6BAAW,IAAA;AACb;AAKA,eAAe,uBAAuB,aAAoC;AACxE,QAAM,YAAY,MAAM,uBAAA;AACxB,YAAU,IAAI,WAAW;AAEzB,QAAM,aAAa,KAAK,UAAU;AAAA,IAChC,cAAc,MAAM,KAAK,SAAS;AAAA,IAClC,WAAW,KAAK,IAAA;AAAA,EAAI,CACrB;AAED,MAAI;AAEF,UAAM,EAAE,YAAA,IAAgB,MAAM,OAAO,wBAAwB;AAC7D,UAAM,YAAY,IAAI,EAAE,KAAK,mBAAmB,OAAO,YAAY;AAAA,EACrE,QAAQ;AAEN,QAAI;AACF,mBAAa,QAAQ,mBAAmB,UAAU;AAAA,IACpD,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAKA,eAAsB,qBACpB,aACkB;AAClB,QAAM,YAAY,MAAM,uBAAA;AACxB,SAAO,UAAU,IAAI,WAAW;AAClC;AAKA,eAAsB,iBAAiB,aAAoC;AACzE,QAAM,uBAAuB,WAAW;AAC1C;AAKA,eAAsB,2BAA0C;AAC9D,MAAI;AACF,UAAM,EAAE,YAAA,IAAgB,MAAM,OAAO,wBAAwB;AAC7D,UAAM,YAAY,OAAO,EAAE,KAAK,mBAAmB;AAAA,EACrD,QAAQ;AACN,QAAI;AACF,mBAAa,WAAW,iBAAiB;AAAA,IAC3C,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AASA,SAAS,eAAwB;AAC/B,MAAI,CAAC,gBAAiB,QAAO;AAC7B,SAAO,KAAK,IAAA,IAAQ,gBAAgB,YAAY;AAClD;AAKA,SAAS,eACP,OACA,MACuB;AACvB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,OAAO,KAAK;AAAA,IACZ,SAAS,KAAK;AAAA,IACd,MAAM,KAAK;AAAA,IACX,UAAU,KAAK;AAAA,IACf,QAAQ;AAAA;AAAA,IACR,aAAa,KAAK;AAAA,IAClB,WAAW,KAAK;AAAA,IAChB,YAAY,KAAK;AAAA,IACjB,WAAW,KAAK;AAAA,IAChB,UAAU,KAAK;AAAA,IACf,gBAAiB,KAAK,kBAA+B,CAAA;AAAA,IACrD,iBACG,KAAK,mBAA8C,CAAA;AAAA,IACtD,gBACG,KAAK,kBAA8D;AAAA,IACtE,QAAQ,KAAK;AAAA,IACb,WAAW,KAAK;AAAA,IAChB,SAAS,KAAK;AAAA,IACd,UAAW,KAAK,YAAuB;AAAA,IACvC,aAAa,KAAK,gBAAgB;AAAA;AAAA,IAClC,SAAU,KAAK,WAAgC;AAAA,IAC/C,aAAc,KAAK,eAA0B;AAAA,IAC7C,QAAS,KAAK,UAAqB;AAAA,IACnC,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK;AAAA,EAAA;AAEpB;AASA,eAAsB,gBACpB,UAAkC,IACA;AAClC,QAAM,SAAS,UAAA;AACf,QAAM,KAAK,oBAAA;AAGX,MACE,CAAC,QAAQ,aACT,CAAC,QAAQ,YACT,CAAC,QAAQ,WACT,CAAC,QAAQ,UACT,gBACA;AACA,WAAO,gBAAiB;AAAA,EAC1B;AAEA,MAAI,IAAI,MAAM,WAAW,IAAI,qBAAqB,CAAC;AAGnD,QAAM,SAAS,QAAQ,UAAU;AACjC,MAAI,MAAM,GAAG,MAAM,UAAU,MAAM,MAAM,CAAC;AAG1C,MAAI,MAAM,GAAG,QAAQ,YAAY,MAAM,CAAC;AAExC,MAAI,QAAQ,OAAO;AACjB,UAAM,EAAE,OAAO,mBAAmB,MAAM,OAAO,oBAAoB;AACnE,QAAI,MAAM,GAAG,eAAe,QAAQ,KAAK,CAAC;AAAA,EAC5C;AAEA,QAAM,WAAW,MAAM,QAAQ,CAAC;AAChC,MAAI,aAAa,SAAS,KAAK,IAAI,CAAC,MAAM,eAAe,EAAE,IAAI,EAAE,KAAA,CAAM,CAAC;AAGxE,QAAM,MAAM,UAAU,IAAA;AAEtB,eAAa,WAAW,OAAO,CAAC,MAAM;AAEpC,QAAI,EAAE,aAAc,EAAE,UAAwB,aAAa,IAAI,YAAY;AACzE,aAAO;AAAA,IACT;AACA,QACE,EAAE,WACD,EAAE,QAAsB,aAAa,IAAI,YAC1C;AACA,aAAO;AAAA,IACT;AAGA,UAAM,iBAAiB,QAAQ,YAAY,OAAO;AAClD,QACE,EAAE,gBAAgB,SAAS,KAC3B,CAAC,EAAE,gBAAgB,SAAS,cAAsC,GAClE;AACA,aAAO;AAAA,IACT;AAGA,UAAM,gBAAgB,QAAQ,aAAa,OAAO;AAClD,QACE,EAAE,eAAe,SAAS,KAC1B,CAAC,EAAE,eAAe,SAAS,aAAa,GACxC;AACA,aAAO;AAAA,IACT;AAGA,QAAI,QAAQ,WAAW,EAAE,YAAY,QAAQ,SAAS;AACpD,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT,CAAC;AAGD,MACE,CAAC,QAAQ,aACT,CAAC,QAAQ,YACT,CAAC,QAAQ,WACT,CAAC,QAAQ,QACT;AACA,sBAAkB;AAAA,MAChB,MAAM;AAAA,MACN,WAAW,KAAK,IAAA;AAAA,IAAI;AAAA,EAExB;AAEA,SAAO;AACT;AAKA,eAAsB,sBACpB,UAAkD,IAChB;AAClC,QAAM,aAAa,MAAM,gBAAgB,EAAE,GAAG,SAAS,QAAQ,UAAU;AACzE,QAAM,YAAY,MAAM,uBAAA;AAExB,SAAO,WAAW,OAAO,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;AACtD;AAKA,eAAsB,yBACpB,SACkC;AAClC,SAAO,sBAAsB,EAAE,SAAS;AAC1C;AAKA,eAAsB,iBACpB,aACuC;AACvC,QAAM,KAAK,oBAAA;AACX,QAAM,UAAU,MAAM,OAAO,IAAI,IAAI,uBAAuB,WAAW,CAAC;AAExE,MAAI,CAAC,QAAQ,OAAA,EAAU,QAAO;AAC9B,SAAO,eAAe,QAAQ,IAAI,QAAQ,MAAM;AAClD;AASO,SAAS,sBACd,UACA,UAAkC,IACrB;AACb,QAAM,SAAS,UAAA;AACf,QAAM,KAAK,oBAAA;AAEX,MAAI,IAAI;AAAA,IACN,WAAW,IAAI,qBAAqB;AAAA,IACpC,MAAM,UAAU,MAAM,QAAQ;AAAA,IAC9B,QAAQ,YAAY,MAAM;AAAA,EAAA;AAG5B,SAAO;AAAA,IACL;AAAA,IACA,OAAO,aAAa;AAClB,UAAI,aAAa,SAAS,KAAK;AAAA,QAAI,CAAC,MAClC,eAAe,EAAE,IAAI,EAAE,MAAM;AAAA,MAAA;AAI/B,YAAM,MAAM,UAAU,IAAA;AACtB,YAAM,YAAY,MAAM,uBAAA;AAExB,mBAAa,WAAW,OAAO,CAAC,MAAM;AAEpC,YAAI,UAAU,IAAI,EAAE,EAAE,EAAG,QAAO;AAGhC,YACE,EAAE,aACD,EAAE,UAAwB,aAAa,IAAI,YAC5C;AACA,iBAAO;AAAA,QACT;AACA,YACE,EAAE,WACD,EAAE,QAAsB,aAAa,IAAI,YAC1C;AACA,iBAAO;AAAA,QACT;AAGA,cAAM,iBAAiB,QAAQ,YAAY,OAAO;AAClD,YACE,EAAE,gBAAgB,SAAS,KAC3B,CAAC,EAAE,gBAAgB,SAAS,cAAsC,GAClE;AACA,iBAAO;AAAA,QACT;AAGA,cAAM,gBAAgB,QAAQ,aAAa,OAAO;AAClD,YACE,EAAE,eAAe,SAAS,KAC1B,CAAC,EAAE,eAAe,SAAS,aAAa,GACxC;AACA,iBAAO;AAAA,QACT;AAGA,YAAI,QAAQ,WAAW,EAAE,YAAY,QAAQ,SAAS;AACpD,iBAAO;AAAA,QACT;AAEA,eAAO;AAAA,MACT,CAAC;AAED,eAAS,UAAU;AAAA,IACrB;AAAA,IACA,CAAC,UAAU;AACT,YAAM,QAAQ,OAAO;AACrB,UAAI,OAAO;AACT,gBAAQ,MAAM,mDAAmD,KAAK;AAAA,MACxE;AAAA,IACF;AAAA,EAAA;AAEJ;AASA,eAAsB,qBACpB,aACA,QACe;AACf,QAAM,SAAS,UAAA;AACf,QAAM,QAAQ,SAAA;AACd,QAAM,KAAK,oBAAA;AAEX,QAAM,YAAY;AAAA,IAChB;AAAA,IACA,WAAW,OAAO;AAAA,IAClB,UAAU,OAAO;AAAA,IACjB,UAAU,MAAM,YAAY;AAAA,IAC5B;AAAA,IACA,WAAW,gBAAA;AAAA,EAAgB;AAG7B,MAAI;AACF,UAAM,OAAO,WAAW,IAAI,2BAA2B,GAAG,SAAS;AAEnE,QAAI,OAAO,OAAO;AAChB,cAAQ,IAAI,+CAA+C,SAAS;AAAA,IACtE;AAAA,EACF,SAAS,OAAO;AACd,QAAI,OAAO,OAAO;AAChB,cAAQ;AAAA,QACN;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EAEF;AACF;AAKA,eAAsB,yBACpB,aACe;AACf,QAAM,qBAAqB,aAAa,YAAY;AACtD;AAKA,eAAsB,oBAAoB,aAAoC;AAC5E,QAAM,qBAAqB,aAAa,OAAO;AACjD;AAKA,eAAsB,sBACpB,aACe;AACf,QAAM,qBAAqB,aAAa,SAAS;AACjD,QAAM,iBAAiB,WAAW;AACpC;AASO,SAAS,uBAA6B;AAC3C,oBAAkB;AACpB;"}
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  const firestore = require("firebase/firestore");
25
25
  const app = require("firebase/app");
26
26
  const auth = require("firebase/auth");
27
- const featureFlags = require("./featureFlags-C9iXfoJT.cjs");
27
+ const featureFlags = require("./featureFlags-CfDmshkF.cjs");
28
28
  let state = {
29
29
  initialized: false,
30
30
  config: null,
@@ -823,6 +823,7 @@ function clearAllCommonFeaturesCache() {
823
823
  projectsCache = null;
824
824
  }
825
825
  function docToContactInfo(docId, data) {
826
+ const locationObj = data.location;
826
827
  return {
827
828
  id: docId,
828
829
  email: data.email || "",
@@ -831,10 +832,10 @@ function docToContactInfo(docId, data) {
831
832
  whatsapp: data.whatsapp,
832
833
  telegram: data.telegram,
833
834
  skype: data.skype,
834
- freelanceAvailable: data.freelanceAvailable ?? false,
835
+ freelanceAvailable: data.freelanceAvailable ?? data.availableForFreelance ?? false,
835
836
  workingHours: data.workingHours,
836
- timezone: data.timezone,
837
- preferredContact: data.preferredContact,
837
+ timezone: data.timezone ?? (typeof locationObj === "object" && locationObj ? locationObj.timezone : void 0),
838
+ preferredContact: data.preferredContact ?? data.preferredContactMethod,
838
839
  responseTime: data.responseTime,
839
840
  updatedAt: data.updatedAt
840
841
  };
@@ -934,17 +935,26 @@ function clearDeveloperInfoCache() {
934
935
  developerInfoCache = null;
935
936
  }
936
937
  function docToAddressInfo(docId, data) {
938
+ const details = data.addressDetails || {};
939
+ const coords = data.coordinates;
937
940
  return {
938
941
  id: docId,
939
942
  label: data.label,
940
- streetAddress: data.streetAddress,
941
- city: data.city,
942
- state: data.state,
943
- postalCode: data.postalCode,
944
- country: data.country,
945
- fullAddress: data.fullAddress,
946
- googleMapsUrl: data.googleMapsUrl,
943
+ streetAddress: data.streetAddress ?? details.streetAddress,
944
+ apartment: data.apartment ?? details.apartment,
945
+ city: data.city ?? details.city,
946
+ state: data.state ?? details.state,
947
+ postalCode: data.postalCode ?? details.postalCode,
948
+ country: data.country ?? details.country,
949
+ landmark: data.landmark ?? details.landmark,
950
+ fullAddress: data.fullAddress ?? data.displayAddress,
951
+ googleMapsUrl: data.googleMapsUrl ?? data.googleMapsLink,
952
+ googleMapsEmbedUrl: data.googleMapsEmbedUrl,
953
+ coordinates: coords && typeof coords.lat === "number" && typeof coords.lng === "number" ? { lat: coords.lat, lng: coords.lng } : void 0,
954
+ showMap: data.showMap ?? false,
947
955
  isPublic: data.isPublic ?? false,
956
+ workingHours: data.workingHours,
957
+ additionalInfo: data.additionalInfo,
948
958
  updatedAt: data.updatedAt
949
959
  };
950
960
  }
@@ -973,6 +983,16 @@ async function fetchAddressInfo() {
973
983
  function clearAddressInfoCache() {
974
984
  addressInfoCache = null;
975
985
  }
986
+ function buildShowInArray(data) {
987
+ const hasAnyBool = "showInFooter" in data || "showInContact" in data || "showInAbout" in data || "showInHeader" in data;
988
+ if (!hasAnyBool) return void 0;
989
+ const result = [];
990
+ if (data.showInFooter) result.push("footer");
991
+ if (data.showInContact) result.push("contact");
992
+ if (data.showInAbout) result.push("about");
993
+ if (data.showInHeader) result.push("header");
994
+ return result.length > 0 ? result : ["footer"];
995
+ }
976
996
  function docToSocialLink(docId, data) {
977
997
  return {
978
998
  id: docId,
@@ -982,8 +1002,8 @@ function docToSocialLink(docId, data) {
982
1002
  username: data.username,
983
1003
  icon: data.icon,
984
1004
  order: data.order ?? 0,
985
- isActive: data.isActive ?? true,
986
- showIn: data.showIn ?? ["footer"],
1005
+ isActive: data.isActive ?? data.enabled ?? true,
1006
+ showIn: data.showIn ?? buildShowInArray(data) ?? ["footer"],
987
1007
  updatedAt: data.updatedAt
988
1008
  };
989
1009
  }
@@ -1025,10 +1045,12 @@ function docToPaymentOption(docId, data) {
1025
1045
  id: docId,
1026
1046
  type: data.type,
1027
1047
  name: data.name || "",
1048
+ displayName: data.displayName,
1028
1049
  description: data.description,
1050
+ instructions: data.instructions,
1029
1051
  icon: data.icon,
1030
1052
  details: data.details || {},
1031
- isActive: data.isActive ?? true,
1053
+ isActive: data.isActive ?? data.enabled ?? true,
1032
1054
  isPrimary: data.isPrimary ?? false,
1033
1055
  order: data.order ?? 0,
1034
1056
  updatedAt: data.updatedAt
@@ -1174,18 +1196,18 @@ function clearSkillsCache() {
1174
1196
  function docToTestimonial(docId, data) {
1175
1197
  return {
1176
1198
  id: docId,
1177
- authorName: data.authorName || "",
1178
- authorTitle: data.authorTitle,
1179
- authorCompany: data.authorCompany,
1180
- authorAvatar: data.authorAvatar,
1181
- authorLinkedin: data.authorLinkedin,
1182
- content: data.content || "",
1199
+ authorName: (data.authorName ?? data.name) || "",
1200
+ authorTitle: data.authorTitle ?? data.title,
1201
+ authorCompany: data.authorCompany ?? data.company,
1202
+ authorAvatar: data.authorAvatar ?? data.avatar,
1203
+ authorLinkedin: data.authorLinkedin ?? data.profileUrl,
1204
+ content: (data.content ?? data.text) || "",
1183
1205
  shortContent: data.shortContent,
1184
1206
  rating: data.rating,
1185
1207
  projectName: data.projectName,
1186
1208
  projectUrl: data.projectUrl,
1187
1209
  date: data.date,
1188
- isActive: data.isActive ?? true,
1210
+ isActive: data.isActive ?? data.enabled ?? true,
1189
1211
  isFeatured: data.isFeatured ?? false,
1190
1212
  order: data.order ?? 0,
1191
1213
  updatedAt: data.updatedAt
@@ -1227,24 +1249,38 @@ async function fetchTestimonials(options = {}) {
1227
1249
  function clearTestimonialsCache() {
1228
1250
  testimonialsCache = null;
1229
1251
  }
1252
+ function buildLinksFromFlatUrls(data) {
1253
+ const links = [];
1254
+ if (data.liveUrl) links.push({ type: "live", url: data.liveUrl });
1255
+ if (data.githubUrl) links.push({ type: "github", url: data.githubUrl });
1256
+ if (data.playStoreUrl) links.push({ type: "playstore", url: data.playStoreUrl });
1257
+ if (data.appStoreUrl) links.push({ type: "appstore", url: data.appStoreUrl });
1258
+ if (data.npmUrl) links.push({ type: "npm", url: data.npmUrl });
1259
+ if (data.docsUrl) links.push({ type: "docs", url: data.docsUrl });
1260
+ if (data.demoUrl) links.push({ type: "demo", url: data.demoUrl });
1261
+ if (data.chromeExtensionUrl) links.push({ type: "other", url: data.chromeExtensionUrl, label: "Chrome Extension" });
1262
+ if (data.firefoxExtensionUrl) links.push({ type: "other", url: data.firefoxExtensionUrl, label: "Firefox Extension" });
1263
+ if (data.edgeExtensionUrl) links.push({ type: "other", url: data.edgeExtensionUrl, label: "Edge Extension" });
1264
+ return links.length > 0 ? links : void 0;
1265
+ }
1230
1266
  function docToProject(docId, data) {
1231
1267
  return {
1232
1268
  id: docId,
1233
1269
  title: data.title || "",
1234
- slug: data.slug || "",
1235
- description: data.description || "",
1270
+ slug: (data.slug ?? data.appIdentifier) || "",
1271
+ description: (data.description ?? data.longDescription) || "",
1236
1272
  shortDescription: data.shortDescription,
1237
1273
  category: data.category,
1238
1274
  status: data.status,
1239
1275
  thumbnailUrl: data.thumbnailUrl,
1240
1276
  images: data.images,
1241
- technologies: data.technologies || [],
1277
+ technologies: (data.technologies ?? data.techStack) || [],
1242
1278
  features: data.features,
1243
- links: data.links,
1279
+ links: data.links ?? buildLinksFromFlatUrls(data),
1244
1280
  clientName: data.clientName,
1245
1281
  startDate: data.startDate,
1246
1282
  endDate: data.endDate,
1247
- isActive: data.isActive ?? true,
1283
+ isActive: data.isActive ?? data.enabled ?? true,
1248
1284
  isFeatured: data.isFeatured ?? false,
1249
1285
  order: data.order ?? 0,
1250
1286
  updatedAt: data.updatedAt
@@ -1361,4 +1397,4 @@ exports.trackClose = trackClose;
1361
1397
  exports.trackImpression = trackImpression;
1362
1398
  exports.updateFeatureConfig = updateFeatureConfig;
1363
1399
  exports.updateGlobalFlags = updateGlobalFlags;
1364
- //# sourceMappingURL=commonFeatures-DTaIBhdj.cjs.map
1400
+ //# sourceMappingURL=commonFeatures-BuY97_K4.cjs.map