shared-features 0.1.0 → 0.1.1

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 (55) hide show
  1. package/dist/{admin-notifications-D1GgYCJW.cjs → admin-notifications-CLzQc8zF.cjs} +2 -2
  2. package/dist/{admin-notifications-D1GgYCJW.cjs.map → admin-notifications-CLzQc8zF.cjs.map} +1 -1
  3. package/dist/{admin-notifications-NI7I76uY.js → admin-notifications-C_7ReIMi.js} +2 -2
  4. package/dist/{admin-notifications-NI7I76uY.js.map → admin-notifications-C_7ReIMi.js.map} +1 -1
  5. package/dist/{broadcasts-BMoTZIuX.cjs → broadcasts-6w_9X92Z.cjs} +2 -2
  6. package/dist/{broadcasts-BMoTZIuX.cjs.map → broadcasts-6w_9X92Z.cjs.map} +1 -1
  7. package/dist/{broadcasts-DnzZkCoy.js → broadcasts-CK4sGMz4.js} +2 -2
  8. package/dist/{broadcasts-DnzZkCoy.js.map → broadcasts-CK4sGMz4.js.map} +1 -1
  9. package/dist/{commonFeatures-Bdt0UZox.js → commonFeatures-CaqcEOik.js} +2 -2
  10. package/dist/commonFeatures-CaqcEOik.js.map +1 -0
  11. package/dist/{commonFeatures-Cr5g1E4M.cjs → commonFeatures-DhWaBEv_.cjs} +24 -2
  12. package/dist/commonFeatures-DhWaBEv_.cjs.map +1 -0
  13. package/dist/{commonFeatures-HT-UO7HW.js → commonFeatures-XJ9fuxg_.js} +27 -5
  14. package/dist/commonFeatures-XJ9fuxg_.js.map +1 -0
  15. package/dist/{commonFeatures-CiqxxOin.cjs → commonFeatures-ofZOjnZ0.cjs} +2 -2
  16. package/dist/commonFeatures-ofZOjnZ0.cjs.map +1 -0
  17. package/dist/components/common/index.d.ts +8 -1
  18. package/dist/components/common/index.d.ts.map +1 -1
  19. package/dist/components/index.cjs +1 -1
  20. package/dist/components/index.js +1 -1
  21. package/dist/hooks/index.cjs +2 -2
  22. package/dist/hooks/index.js +2 -2
  23. package/dist/hooks/useCommonFeatures.d.ts +29 -1
  24. package/dist/hooks/useCommonFeatures.d.ts.map +1 -1
  25. package/dist/{index-Dv34aG2I.js → index-BeNmzbTD.js} +3 -3
  26. package/dist/{index-Dv34aG2I.js.map → index-BeNmzbTD.js.map} +1 -1
  27. package/dist/{index-Dt5YjYnK.cjs → index-DB40ObYe.cjs} +3 -3
  28. package/dist/{index-Dt5YjYnK.cjs.map → index-DB40ObYe.cjs.map} +1 -1
  29. package/dist/index.cjs +9 -7
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +24 -22
  32. package/dist/services/commonFeatures.d.ts +4 -1
  33. package/dist/services/commonFeatures.d.ts.map +1 -1
  34. package/dist/services/index.cjs +4 -4
  35. package/dist/services/index.js +4 -4
  36. package/dist/types/commonFeatures.d.ts +38 -0
  37. package/dist/types/commonFeatures.d.ts.map +1 -1
  38. package/dist/types/index.cjs +3 -1
  39. package/dist/types/index.cjs.map +1 -1
  40. package/dist/types/index.js +6 -4
  41. package/dist/{useCommonFeatures-DnDlhmri.cjs → useCommonFeatures-0EU_VRqi.cjs} +2 -2
  42. package/dist/useCommonFeatures-0EU_VRqi.cjs.map +1 -0
  43. package/dist/{useCommonFeatures-CgyDq6LZ.js → useCommonFeatures-DP2YyB7m.js} +2 -2
  44. package/dist/useCommonFeatures-DP2YyB7m.js.map +1 -0
  45. package/dist/{useFeatureFlags-BRJSyH9M.js → useFeatureFlags-DHnOm0sA.js} +3 -3
  46. package/dist/{useFeatureFlags-BRJSyH9M.js.map → useFeatureFlags-DHnOm0sA.js.map} +1 -1
  47. package/dist/{useFeatureFlags-DXqBJ5Mh.cjs → useFeatureFlags-DRR1r3rt.cjs} +3 -3
  48. package/dist/{useFeatureFlags-DXqBJ5Mh.cjs.map → useFeatureFlags-DRR1r3rt.cjs.map} +1 -1
  49. package/package.json +1 -1
  50. package/dist/commonFeatures-Bdt0UZox.js.map +0 -1
  51. package/dist/commonFeatures-CiqxxOin.cjs.map +0 -1
  52. package/dist/commonFeatures-Cr5g1E4M.cjs.map +0 -1
  53. package/dist/commonFeatures-HT-UO7HW.js.map +0 -1
  54. package/dist/useCommonFeatures-CgyDq6LZ.js.map +0 -1
  55. package/dist/useCommonFeatures-DnDlhmri.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  const firestore = require("firebase/firestore");
3
- const commonFeatures = require("./commonFeatures-CiqxxOin.cjs");
3
+ const commonFeatures = require("./commonFeatures-ofZOjnZ0.cjs");
4
4
  const COLLECTION_BROADCASTS = "zaions_broadcasts";
5
5
  const COLLECTION_TEMPLATES = "zaions_notification_templates";
6
6
  const COLLECTION_BROADCAST_EVENTS = "zaions_broadcast_events";
@@ -359,4 +359,4 @@ exports.publishBroadcast = publishBroadcast;
359
359
  exports.scheduleBroadcast = scheduleBroadcast;
360
360
  exports.updateBroadcast = updateBroadcast;
361
361
  exports.updateTemplate = updateTemplate;
362
- //# sourceMappingURL=admin-notifications-D1GgYCJW.cjs.map
362
+ //# sourceMappingURL=admin-notifications-CLzQc8zF.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin-notifications-D1GgYCJW.cjs","sources":["../src/services/admin-notifications.ts"],"sourcesContent":["/**\n * Admin Notifications Service\n *\n * Service for managing broadcasts and templates from the admin panel.\n * Used in aoneahsan.com admin panel to manage cross-project notifications.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n doc,\n getDocs,\n getDoc,\n updateDoc,\n deleteDoc,\n addDoc,\n query,\n where,\n orderBy,\n serverTimestamp,\n Timestamp,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport type {\n BroadcastNotification,\n BroadcastStatus,\n CreateBroadcastInput,\n UpdateBroadcastInput,\n NotificationTemplate,\n CreateTemplateInput,\n BroadcastAnalytics,\n NotificationAnalytics,\n} from '../types/notifications';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_BROADCASTS = 'zaions_broadcasts';\nconst COLLECTION_TEMPLATES = 'zaions_notification_templates';\nconst COLLECTION_BROADCAST_EVENTS = 'zaions_broadcast_events';\n\n// ============================================================================\n// HELPER FUNCTIONS\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,\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: (data.targetPlatforms as BroadcastNotification['targetPlatforms']) || [],\n targetAudience: (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,\n variant: (data.variant as BroadcastNotification['variant']) || '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 * Convert Firestore document to NotificationTemplate\n */\nfunction docToTemplate(\n docId: string,\n data: Record<string, unknown>\n): NotificationTemplate {\n return {\n id: docId,\n name: data.name as string,\n eventType: data.eventType as string,\n category: data.category as NotificationTemplate['category'],\n title: data.title as string,\n message: data.message as string,\n variables: (data.variables as string[]) || [],\n type: data.type as NotificationTemplate['type'],\n isImportant: data.isImportant as boolean,\n actionUrl: data.actionUrl as string | undefined,\n actionText: data.actionText as string | undefined,\n enabled: data.enabled !== false,\n createdAt: (data.createdAt as Timestamp)?.toDate() || new Date(),\n updatedAt: (data.updatedAt as Timestamp)?.toDate() || new Date(),\n };\n}\n\n// ============================================================================\n// BROADCAST CRUD\n// ============================================================================\n\n/**\n * Create a new broadcast\n */\nexport async function createBroadcast(\n input: CreateBroadcastInput,\n adminUserId: string\n): Promise<string> {\n const db = getSharedFeaturesDb();\n\n const broadcastData = {\n ...input,\n status: 'draft' as BroadcastStatus,\n isRead: false,\n impressions: 0,\n clicks: 0,\n createdBy: adminUserId,\n createdAt: serverTimestamp(),\n startDate: Timestamp.fromDate(input.startDate),\n endDate: input.endDate ? Timestamp.fromDate(input.endDate) : null,\n };\n\n const docRef = await addDoc(\n collection(db, COLLECTION_BROADCASTS),\n broadcastData\n );\n\n return docRef.id;\n}\n\n/**\n * Update an existing broadcast\n */\nexport async function updateBroadcast(\n input: UpdateBroadcastInput,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n const updateData: Record<string, unknown> = {\n ...input,\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n };\n\n // Convert dates if provided\n if (input.startDate) {\n updateData.startDate = Timestamp.fromDate(input.startDate);\n }\n if (input.endDate !== undefined) {\n updateData.endDate = input.endDate ? Timestamp.fromDate(input.endDate) : null;\n }\n\n // Remove id from update data\n delete updateData.id;\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, input.id), updateData);\n}\n\n/**\n * Delete a broadcast\n */\nexport async function deleteBroadcast(broadcastId: string): Promise<void> {\n const db = getSharedFeaturesDb();\n await deleteDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));\n}\n\n/**\n * Get all broadcasts (for admin listing)\n */\nexport async function getAllBroadcasts(): Promise<BroadcastNotification[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_BROADCASTS),\n orderBy('createdAt', 'desc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\n}\n\n/**\n * Get broadcasts by status\n */\nexport async function getBroadcastsByStatus(\n status: BroadcastStatus\n): Promise<BroadcastNotification[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_BROADCASTS),\n where('status', '==', status),\n orderBy('createdAt', 'desc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\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// BROADCAST STATUS MANAGEMENT\n// ============================================================================\n\n/**\n * Publish a draft broadcast (set to active)\n */\nexport async function publishBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'active',\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Schedule a broadcast for later\n */\nexport async function scheduleBroadcast(\n broadcastId: string,\n scheduledDate: Date,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'scheduled',\n startDate: Timestamp.fromDate(scheduledDate),\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Pause an active broadcast\n */\nexport async function pauseBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'draft', // Move back to draft\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * End a broadcast\n */\nexport async function endBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'ended',\n endDate: serverTimestamp(),\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n// ============================================================================\n// TEMPLATE CRUD\n// ============================================================================\n\n/**\n * Create a new template\n */\nexport async function createTemplate(\n input: CreateTemplateInput\n): Promise<string> {\n const db = getSharedFeaturesDb();\n\n const templateData = {\n ...input,\n createdAt: serverTimestamp(),\n updatedAt: serverTimestamp(),\n };\n\n const docRef = await addDoc(\n collection(db, COLLECTION_TEMPLATES),\n templateData\n );\n\n return docRef.id;\n}\n\n/**\n * Update an existing template\n */\nexport async function updateTemplate(\n templateId: string,\n input: Partial<CreateTemplateInput>\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_TEMPLATES, templateId), {\n ...input,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Delete a template\n */\nexport async function deleteTemplate(templateId: string): Promise<void> {\n const db = getSharedFeaturesDb();\n await deleteDoc(doc(db, COLLECTION_TEMPLATES, templateId));\n}\n\n/**\n * Get all templates\n */\nexport async function getAllTemplates(): Promise<NotificationTemplate[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_TEMPLATES),\n orderBy('name', 'asc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToTemplate(d.id, d.data()));\n}\n\n/**\n * Get template by ID\n */\nexport async function getTemplateById(\n templateId: string\n): Promise<NotificationTemplate | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_TEMPLATES, templateId));\n\n if (!docSnap.exists()) return null;\n return docToTemplate(docSnap.id, docSnap.data());\n}\n\n/**\n * Get template by event type from Firestore\n */\nexport async function getFirestoreTemplateByEventType(\n eventType: string\n): Promise<NotificationTemplate | null> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_TEMPLATES),\n where('eventType', '==', eventType)\n );\n\n const snapshot = await getDocs(q);\n if (snapshot.empty) return null;\n\n const docSnap = snapshot.docs[0];\n if (!docSnap) return null;\n\n return docToTemplate(docSnap.id, docSnap.data());\n}\n\n// ============================================================================\n// ANALYTICS\n// ============================================================================\n\n/**\n * Get analytics for a specific broadcast\n */\nexport async function getBroadcastAnalytics(\n broadcastId: string\n): Promise<BroadcastAnalytics | null> {\n const db = getSharedFeaturesDb();\n\n // Get broadcast\n const broadcast = await getBroadcastById(broadcastId);\n if (!broadcast) return null;\n\n // Get events\n const eventsQuery = query(\n collection(db, COLLECTION_BROADCAST_EVENTS),\n where('broadcastId', '==', broadcastId)\n );\n\n const eventsSnapshot = await getDocs(eventsQuery);\n const events = eventsSnapshot.docs.map((d) => d.data());\n\n // Calculate analytics\n const impressions = events.filter((e) => e.action === 'impression').length;\n const clicks = events.filter((e) => e.action === 'click').length;\n const dismissals = events.filter((e) => e.action === 'dismiss').length;\n\n // Group by platform\n const byPlatform: BroadcastAnalytics['byPlatform'] = {\n web: { impressions: 0, clicks: 0 },\n android: { impressions: 0, clicks: 0 },\n ios: { impressions: 0, clicks: 0 },\n };\n\n events.forEach((e) => {\n const platform = e.platform as keyof typeof byPlatform;\n if (byPlatform[platform]) {\n if (e.action === 'impression') byPlatform[platform].impressions++;\n if (e.action === 'click') byPlatform[platform].clicks++;\n }\n });\n\n // Group by project\n const byProject: BroadcastAnalytics['byProject'] = {};\n events.forEach((e) => {\n const projectId = e.projectId as string;\n if (!byProject[projectId]) {\n byProject[projectId] = { impressions: 0, clicks: 0 };\n }\n if (e.action === 'impression') byProject[projectId].impressions++;\n if (e.action === 'click') byProject[projectId].clicks++;\n });\n\n // Group by date\n const byDateMap: Record<string, { impressions: number; clicks: number }> = {};\n events.forEach((e) => {\n const dateStr = (e.timestamp as Timestamp).toDate().toISOString().split('T')[0];\n if (dateStr) {\n if (!byDateMap[dateStr]) {\n byDateMap[dateStr] = { impressions: 0, clicks: 0 };\n }\n if (e.action === 'impression') byDateMap[dateStr].impressions++;\n if (e.action === 'click') byDateMap[dateStr].clicks++;\n }\n });\n\n const byDate = Object.entries(byDateMap).map(([date, data]) => ({\n date,\n impressions: data.impressions,\n clicks: data.clicks,\n }));\n\n return {\n broadcastId,\n title: broadcast.title,\n status: broadcast.status,\n impressions,\n clicks,\n dismissals,\n ctr: impressions > 0 ? clicks / impressions : 0,\n byPlatform,\n byProject,\n byDate,\n };\n}\n\n/**\n * Get overall notification analytics\n */\nexport async function getOverallAnalytics(\n startDate: Date,\n endDate: Date\n): Promise<NotificationAnalytics> {\n const db = getSharedFeaturesDb();\n\n // Get all broadcast events in date range\n const eventsQuery = query(\n collection(db, COLLECTION_BROADCAST_EVENTS),\n where('timestamp', '>=', Timestamp.fromDate(startDate)),\n where('timestamp', '<=', Timestamp.fromDate(endDate))\n );\n\n const eventsSnapshot = await getDocs(eventsQuery);\n const events = eventsSnapshot.docs.map((d) => d.data());\n\n // Calculate totals\n const totalSent = events.filter((e) => e.action === 'impression').length;\n const totalRead = events.filter((e) => e.action === 'impression').length; // Impressions = read for broadcasts\n const totalClicked = events.filter((e) => e.action === 'click').length;\n\n // Initialize category and type breakdowns\n const byCategory: NotificationAnalytics['byCategory'] = {\n system: { sent: 0, read: 0, clicked: 0 },\n account: { sent: 0, read: 0, clicked: 0 },\n activity: { sent: 0, read: 0, clicked: 0 },\n report: { sent: 0, read: 0, clicked: 0 },\n promotional: { sent: 0, read: 0, clicked: 0 },\n social: { sent: 0, read: 0, clicked: 0 },\n };\n\n const byType: NotificationAnalytics['byType'] = {\n info: { sent: 0, read: 0, clicked: 0 },\n success: { sent: 0, read: 0, clicked: 0 },\n warning: { sent: 0, read: 0, clicked: 0 },\n error: { sent: 0, read: 0, clicked: 0 },\n reminder: { sent: 0, read: 0, clicked: 0 },\n milestone: { sent: 0, read: 0, clicked: 0 },\n announcement: { sent: 0, read: 0, clicked: 0 },\n };\n\n // Group by date\n const byDateMap: Record<string, { sent: number; read: number; clicked: number }> = {};\n events.forEach((e) => {\n const dateStr = (e.timestamp as Timestamp).toDate().toISOString().split('T')[0];\n if (dateStr) {\n if (!byDateMap[dateStr]) {\n byDateMap[dateStr] = { sent: 0, read: 0, clicked: 0 };\n }\n if (e.action === 'impression') {\n byDateMap[dateStr].sent++;\n byDateMap[dateStr].read++;\n }\n if (e.action === 'click') byDateMap[dateStr].clicked++;\n }\n });\n\n const byDate = Object.entries(byDateMap)\n .map(([date, data]) => ({ date, ...data }))\n .sort((a, b) => a.date.localeCompare(b.date));\n\n return {\n totalSent,\n totalRead,\n totalClicked,\n readRate: totalSent > 0 ? totalRead / totalSent : 0,\n clickRate: totalSent > 0 ? totalClicked / totalSent : 0,\n byCategory,\n byType,\n byDate,\n };\n}\n\n// ============================================================================\n// EXPORT SERVICE OBJECT\n// ============================================================================\n\nexport const adminNotificationService = {\n // Broadcasts\n createBroadcast,\n updateBroadcast,\n deleteBroadcast,\n getAllBroadcasts,\n getBroadcastsByStatus,\n getBroadcastById,\n publishBroadcast,\n scheduleBroadcast,\n pauseBroadcast,\n endBroadcast,\n\n // Templates\n createTemplate,\n updateTemplate,\n deleteTemplate,\n getAllTemplates,\n getTemplateById,\n getFirestoreTemplateByEventType,\n\n // Analytics\n getBroadcastAnalytics,\n getOverallAnalytics,\n};\n\nexport default adminNotificationService;\n"],"names":["getSharedFeaturesDb","serverTimestamp","Timestamp","addDoc","collection","updateDoc","doc","deleteDoc","query","orderBy","getDocs","where","getDoc"],"mappings":";;;AAuCA,MAAM,wBAAwB;AAC9B,MAAM,uBAAuB;AAC7B,MAAM,8BAA8B;AASpC,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,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,iBAAkB,KAAK,mBAAgE,CAAA;AAAA,IACvF,gBAAiB,KAAK,kBAA8D;AAAA,IACpF,QAAQ,KAAK;AAAA,IACb,WAAW,KAAK;AAAA,IAChB,SAAS,KAAK;AAAA,IACd,UAAW,KAAK,YAAuB;AAAA,IACvC,aAAa,KAAK,gBAAgB;AAAA,IAClC,SAAU,KAAK,WAAgD;AAAA,IAC/D,aAAc,KAAK,eAA0B;AAAA,IAC7C,QAAS,KAAK,UAAqB;AAAA,IACnC,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK;AAAA,EAAA;AAEpB;AAKA,SAAS,cACP,OACA,MACsB;AACtB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM,KAAK;AAAA,IACX,WAAW,KAAK;AAAA,IAChB,UAAU,KAAK;AAAA,IACf,OAAO,KAAK;AAAA,IACZ,SAAS,KAAK;AAAA,IACd,WAAY,KAAK,aAA0B,CAAA;AAAA,IAC3C,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,WAAW,KAAK;AAAA,IAChB,YAAY,KAAK;AAAA,IACjB,SAAS,KAAK,YAAY;AAAA,IAC1B,WAAY,KAAK,WAAyB,OAAA,yBAAgB,KAAA;AAAA,IAC1D,WAAY,KAAK,WAAyB,OAAA,yBAAgB,KAAA;AAAA,EAAK;AAEnE;AASA,eAAsB,gBACpB,OACA,aACiB;AACjB,QAAM,KAAKA,eAAAA,oBAAA;AAEX,QAAM,gBAAgB;AAAA,IACpB,GAAG;AAAA,IACH,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,WAAWC,UAAAA,gBAAA;AAAA,IACX,WAAWC,UAAAA,UAAU,SAAS,MAAM,SAAS;AAAA,IAC7C,SAAS,MAAM,UAAUA,UAAAA,UAAU,SAAS,MAAM,OAAO,IAAI;AAAA,EAAA;AAG/D,QAAM,SAAS,MAAMC,UAAAA;AAAAA,IACnBC,UAAAA,WAAW,IAAI,qBAAqB;AAAA,IACpC;AAAA,EAAA;AAGF,SAAO,OAAO;AAChB;AAKA,eAAsB,gBACpB,OACA,aACe;AACf,QAAM,KAAKJ,eAAAA,oBAAA;AAEX,QAAM,aAAsC;AAAA,IAC1C,GAAG;AAAA,IACH,WAAW;AAAA,IACX,WAAWC,UAAAA,gBAAA;AAAA,EAAgB;AAI7B,MAAI,MAAM,WAAW;AACnB,eAAW,YAAYC,UAAAA,UAAU,SAAS,MAAM,SAAS;AAAA,EAC3D;AACA,MAAI,MAAM,YAAY,QAAW;AAC/B,eAAW,UAAU,MAAM,UAAUA,UAAAA,UAAU,SAAS,MAAM,OAAO,IAAI;AAAA,EAC3E;AAGA,SAAO,WAAW;AAElB,QAAMG,UAAAA,UAAUC,UAAAA,IAAI,IAAI,uBAAuB,MAAM,EAAE,GAAG,UAAU;AACtE;AAKA,eAAsB,gBAAgB,aAAoC;AACxE,QAAM,KAAKN,eAAAA,oBAAA;AACX,QAAMO,UAAAA,UAAUD,UAAAA,IAAI,IAAI,uBAAuB,WAAW,CAAC;AAC7D;AAKA,eAAsB,mBAAqD;AACzE,QAAM,KAAKN,eAAAA,oBAAA;AAEX,QAAM,IAAIQ,UAAAA;AAAAA,IACRJ,UAAAA,WAAW,IAAI,qBAAqB;AAAA,IACpCK,UAAAA,QAAQ,aAAa,MAAM;AAAA,EAAA;AAG7B,QAAM,WAAW,MAAMC,UAAAA,QAAQ,CAAC;AAChC,SAAO,SAAS,KAAK,IAAI,CAAC,MAAM,eAAe,EAAE,IAAI,EAAE,KAAA,CAAM,CAAC;AAChE;AAKA,eAAsB,sBACpB,QACkC;AAClC,QAAM,KAAKV,eAAAA,oBAAA;AAEX,QAAM,IAAIQ,UAAAA;AAAAA,IACRJ,UAAAA,WAAW,IAAI,qBAAqB;AAAA,IACpCO,gBAAM,UAAU,MAAM,MAAM;AAAA,IAC5BF,UAAAA,QAAQ,aAAa,MAAM;AAAA,EAAA;AAG7B,QAAM,WAAW,MAAMC,UAAAA,QAAQ,CAAC;AAChC,SAAO,SAAS,KAAK,IAAI,CAAC,MAAM,eAAe,EAAE,IAAI,EAAE,KAAA,CAAM,CAAC;AAChE;AAKA,eAAsB,iBACpB,aACuC;AACvC,QAAM,KAAKV,eAAAA,oBAAA;AACX,QAAM,UAAU,MAAMY,iBAAON,UAAAA,IAAI,IAAI,uBAAuB,WAAW,CAAC;AAExE,MAAI,CAAC,QAAQ,OAAA,EAAU,QAAO;AAC9B,SAAO,eAAe,QAAQ,IAAI,QAAQ,MAAM;AAClD;AASA,eAAsB,iBACpB,aACA,aACe;AACf,QAAM,KAAKN,eAAAA,oBAAA;AAEX,QAAMK,UAAAA,UAAUC,UAAAA,IAAI,IAAI,uBAAuB,WAAW,GAAG;AAAA,IAC3D,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,WAAWL,UAAAA,gBAAA;AAAA,EAAgB,CAC5B;AACH;AAKA,eAAsB,kBACpB,aACA,eACA,aACe;AACf,QAAM,KAAKD,eAAAA,oBAAA;AAEX,QAAMK,UAAAA,UAAUC,UAAAA,IAAI,IAAI,uBAAuB,WAAW,GAAG;AAAA,IAC3D,QAAQ;AAAA,IACR,WAAWJ,UAAAA,UAAU,SAAS,aAAa;AAAA,IAC3C,WAAW;AAAA,IACX,WAAWD,UAAAA,gBAAA;AAAA,EAAgB,CAC5B;AACH;AAKA,eAAsB,eACpB,aACA,aACe;AACf,QAAM,KAAKD,eAAAA,oBAAA;AAEX,QAAMK,UAAAA,UAAUC,UAAAA,IAAI,IAAI,uBAAuB,WAAW,GAAG;AAAA,IAC3D,QAAQ;AAAA;AAAA,IACR,WAAW;AAAA,IACX,WAAWL,UAAAA,gBAAA;AAAA,EAAgB,CAC5B;AACH;AAKA,eAAsB,aACpB,aACA,aACe;AACf,QAAM,KAAKD,eAAAA,oBAAA;AAEX,QAAMK,UAAAA,UAAUC,UAAAA,IAAI,IAAI,uBAAuB,WAAW,GAAG;AAAA,IAC3D,QAAQ;AAAA,IACR,SAASL,UAAAA,gBAAA;AAAA,IACT,WAAW;AAAA,IACX,WAAWA,UAAAA,gBAAA;AAAA,EAAgB,CAC5B;AACH;AASA,eAAsB,eACpB,OACiB;AACjB,QAAM,KAAKD,eAAAA,oBAAA;AAEX,QAAM,eAAe;AAAA,IACnB,GAAG;AAAA,IACH,WAAWC,UAAAA,gBAAA;AAAA,IACX,WAAWA,UAAAA,gBAAA;AAAA,EAAgB;AAG7B,QAAM,SAAS,MAAME,UAAAA;AAAAA,IACnBC,UAAAA,WAAW,IAAI,oBAAoB;AAAA,IACnC;AAAA,EAAA;AAGF,SAAO,OAAO;AAChB;AAKA,eAAsB,eACpB,YACA,OACe;AACf,QAAM,KAAKJ,eAAAA,oBAAA;AAEX,QAAMK,UAAAA,UAAUC,UAAAA,IAAI,IAAI,sBAAsB,UAAU,GAAG;AAAA,IACzD,GAAG;AAAA,IACH,WAAWL,UAAAA,gBAAA;AAAA,EAAgB,CAC5B;AACH;AAKA,eAAsB,eAAe,YAAmC;AACtE,QAAM,KAAKD,eAAAA,oBAAA;AACX,QAAMO,UAAAA,UAAUD,UAAAA,IAAI,IAAI,sBAAsB,UAAU,CAAC;AAC3D;AAKA,eAAsB,kBAAmD;AACvE,QAAM,KAAKN,eAAAA,oBAAA;AAEX,QAAM,IAAIQ,UAAAA;AAAAA,IACRJ,UAAAA,WAAW,IAAI,oBAAoB;AAAA,IACnCK,UAAAA,QAAQ,QAAQ,KAAK;AAAA,EAAA;AAGvB,QAAM,WAAW,MAAMC,UAAAA,QAAQ,CAAC;AAChC,SAAO,SAAS,KAAK,IAAI,CAAC,MAAM,cAAc,EAAE,IAAI,EAAE,KAAA,CAAM,CAAC;AAC/D;AAKA,eAAsB,gBACpB,YACsC;AACtC,QAAM,KAAKV,eAAAA,oBAAA;AACX,QAAM,UAAU,MAAMY,iBAAON,UAAAA,IAAI,IAAI,sBAAsB,UAAU,CAAC;AAEtE,MAAI,CAAC,QAAQ,OAAA,EAAU,QAAO;AAC9B,SAAO,cAAc,QAAQ,IAAI,QAAQ,MAAM;AACjD;AAKA,eAAsB,gCACpB,WACsC;AACtC,QAAM,KAAKN,eAAAA,oBAAA;AAEX,QAAM,IAAIQ,UAAAA;AAAAA,IACRJ,UAAAA,WAAW,IAAI,oBAAoB;AAAA,IACnCO,gBAAM,aAAa,MAAM,SAAS;AAAA,EAAA;AAGpC,QAAM,WAAW,MAAMD,UAAAA,QAAQ,CAAC;AAChC,MAAI,SAAS,MAAO,QAAO;AAE3B,QAAM,UAAU,SAAS,KAAK,CAAC;AAC/B,MAAI,CAAC,QAAS,QAAO;AAErB,SAAO,cAAc,QAAQ,IAAI,QAAQ,MAAM;AACjD;AASA,eAAsB,sBACpB,aACoC;AACpC,QAAM,KAAKV,eAAAA,oBAAA;AAGX,QAAM,YAAY,MAAM,iBAAiB,WAAW;AACpD,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,cAAcQ,UAAAA;AAAAA,IAClBJ,UAAAA,WAAW,IAAI,2BAA2B;AAAA,IAC1CO,gBAAM,eAAe,MAAM,WAAW;AAAA,EAAA;AAGxC,QAAM,iBAAiB,MAAMD,UAAAA,QAAQ,WAAW;AAChD,QAAM,SAAS,eAAe,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM;AAGtD,QAAM,cAAc,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,YAAY,EAAE;AACpE,QAAM,SAAS,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,EAAE;AAC1D,QAAM,aAAa,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EAAE;AAGhE,QAAM,aAA+C;AAAA,IACnD,KAAK,EAAE,aAAa,GAAG,QAAQ,EAAA;AAAA,IAC/B,SAAS,EAAE,aAAa,GAAG,QAAQ,EAAA;AAAA,IACnC,KAAK,EAAE,aAAa,GAAG,QAAQ,EAAA;AAAA,EAAE;AAGnC,SAAO,QAAQ,CAAC,MAAM;AACpB,UAAM,WAAW,EAAE;AACnB,QAAI,WAAW,QAAQ,GAAG;AACxB,UAAI,EAAE,WAAW,aAAc,YAAW,QAAQ,EAAE;AACpD,UAAI,EAAE,WAAW,QAAS,YAAW,QAAQ,EAAE;AAAA,IACjD;AAAA,EACF,CAAC;AAGD,QAAM,YAA6C,CAAA;AACnD,SAAO,QAAQ,CAAC,MAAM;AACpB,UAAM,YAAY,EAAE;AACpB,QAAI,CAAC,UAAU,SAAS,GAAG;AACzB,gBAAU,SAAS,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAA;AAAA,IACnD;AACA,QAAI,EAAE,WAAW,aAAc,WAAU,SAAS,EAAE;AACpD,QAAI,EAAE,WAAW,QAAS,WAAU,SAAS,EAAE;AAAA,EACjD,CAAC;AAGD,QAAM,YAAqE,CAAA;AAC3E,SAAO,QAAQ,CAAC,MAAM;AACpB,UAAM,UAAW,EAAE,UAAwB,OAAA,EAAS,cAAc,MAAM,GAAG,EAAE,CAAC;AAC9E,QAAI,SAAS;AACX,UAAI,CAAC,UAAU,OAAO,GAAG;AACvB,kBAAU,OAAO,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAA;AAAA,MACjD;AACA,UAAI,EAAE,WAAW,aAAc,WAAU,OAAO,EAAE;AAClD,UAAI,EAAE,WAAW,QAAS,WAAU,OAAO,EAAE;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,QAAM,SAAS,OAAO,QAAQ,SAAS,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,OAAO;AAAA,IAC9D;AAAA,IACA,aAAa,KAAK;AAAA,IAClB,QAAQ,KAAK;AAAA,EAAA,EACb;AAEF,SAAO;AAAA,IACL;AAAA,IACA,OAAO,UAAU;AAAA,IACjB,QAAQ,UAAU;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK,cAAc,IAAI,SAAS,cAAc;AAAA,IAC9C;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AAKA,eAAsB,oBACpB,WACA,SACgC;AAChC,QAAM,KAAKV,eAAAA,oBAAA;AAGX,QAAM,cAAcQ,UAAAA;AAAAA,IAClBJ,UAAAA,WAAW,IAAI,2BAA2B;AAAA,IAC1CO,UAAAA,MAAM,aAAa,MAAMT,UAAAA,UAAU,SAAS,SAAS,CAAC;AAAA,IACtDS,UAAAA,MAAM,aAAa,MAAMT,UAAAA,UAAU,SAAS,OAAO,CAAC;AAAA,EAAA;AAGtD,QAAM,iBAAiB,MAAMQ,UAAAA,QAAQ,WAAW;AAChD,QAAM,SAAS,eAAe,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM;AAGtD,QAAM,YAAY,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,YAAY,EAAE;AAClE,QAAM,YAAY,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,YAAY,EAAE;AAClE,QAAM,eAAe,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,EAAE;AAGhE,QAAM,aAAkD;AAAA,IACtD,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACrC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACtC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACvC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACrC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IAC1C,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,EAAE;AAGzC,QAAM,SAA0C;AAAA,IAC9C,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACnC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACtC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACtC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACpC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACvC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACxC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,EAAE;AAI/C,QAAM,YAA6E,CAAA;AACnF,SAAO,QAAQ,CAAC,MAAM;AACpB,UAAM,UAAW,EAAE,UAAwB,OAAA,EAAS,cAAc,MAAM,GAAG,EAAE,CAAC;AAC9E,QAAI,SAAS;AACX,UAAI,CAAC,UAAU,OAAO,GAAG;AACvB,kBAAU,OAAO,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,MACpD;AACA,UAAI,EAAE,WAAW,cAAc;AAC7B,kBAAU,OAAO,EAAE;AACnB,kBAAU,OAAO,EAAE;AAAA,MACrB;AACA,UAAI,EAAE,WAAW,QAAS,WAAU,OAAO,EAAE;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,QAAM,SAAS,OAAO,QAAQ,SAAS,EACpC,IAAI,CAAC,CAAC,MAAM,IAAI,OAAO,EAAE,MAAM,GAAG,KAAA,EAAO,EACzC,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAE9C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,YAAY,IAAI,YAAY,YAAY;AAAA,IAClD,WAAW,YAAY,IAAI,eAAe,YAAY;AAAA,IACtD;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AAMO,MAAM,2BAA2B;AAAA;AAAA,EAEtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AACF;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"admin-notifications-CLzQc8zF.cjs","sources":["../src/services/admin-notifications.ts"],"sourcesContent":["/**\n * Admin Notifications Service\n *\n * Service for managing broadcasts and templates from the admin panel.\n * Used in aoneahsan.com admin panel to manage cross-project notifications.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n doc,\n getDocs,\n getDoc,\n updateDoc,\n deleteDoc,\n addDoc,\n query,\n where,\n orderBy,\n serverTimestamp,\n Timestamp,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport type {\n BroadcastNotification,\n BroadcastStatus,\n CreateBroadcastInput,\n UpdateBroadcastInput,\n NotificationTemplate,\n CreateTemplateInput,\n BroadcastAnalytics,\n NotificationAnalytics,\n} from '../types/notifications';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_BROADCASTS = 'zaions_broadcasts';\nconst COLLECTION_TEMPLATES = 'zaions_notification_templates';\nconst COLLECTION_BROADCAST_EVENTS = 'zaions_broadcast_events';\n\n// ============================================================================\n// HELPER FUNCTIONS\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,\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: (data.targetPlatforms as BroadcastNotification['targetPlatforms']) || [],\n targetAudience: (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,\n variant: (data.variant as BroadcastNotification['variant']) || '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 * Convert Firestore document to NotificationTemplate\n */\nfunction docToTemplate(\n docId: string,\n data: Record<string, unknown>\n): NotificationTemplate {\n return {\n id: docId,\n name: data.name as string,\n eventType: data.eventType as string,\n category: data.category as NotificationTemplate['category'],\n title: data.title as string,\n message: data.message as string,\n variables: (data.variables as string[]) || [],\n type: data.type as NotificationTemplate['type'],\n isImportant: data.isImportant as boolean,\n actionUrl: data.actionUrl as string | undefined,\n actionText: data.actionText as string | undefined,\n enabled: data.enabled !== false,\n createdAt: (data.createdAt as Timestamp)?.toDate() || new Date(),\n updatedAt: (data.updatedAt as Timestamp)?.toDate() || new Date(),\n };\n}\n\n// ============================================================================\n// BROADCAST CRUD\n// ============================================================================\n\n/**\n * Create a new broadcast\n */\nexport async function createBroadcast(\n input: CreateBroadcastInput,\n adminUserId: string\n): Promise<string> {\n const db = getSharedFeaturesDb();\n\n const broadcastData = {\n ...input,\n status: 'draft' as BroadcastStatus,\n isRead: false,\n impressions: 0,\n clicks: 0,\n createdBy: adminUserId,\n createdAt: serverTimestamp(),\n startDate: Timestamp.fromDate(input.startDate),\n endDate: input.endDate ? Timestamp.fromDate(input.endDate) : null,\n };\n\n const docRef = await addDoc(\n collection(db, COLLECTION_BROADCASTS),\n broadcastData\n );\n\n return docRef.id;\n}\n\n/**\n * Update an existing broadcast\n */\nexport async function updateBroadcast(\n input: UpdateBroadcastInput,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n const updateData: Record<string, unknown> = {\n ...input,\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n };\n\n // Convert dates if provided\n if (input.startDate) {\n updateData.startDate = Timestamp.fromDate(input.startDate);\n }\n if (input.endDate !== undefined) {\n updateData.endDate = input.endDate ? Timestamp.fromDate(input.endDate) : null;\n }\n\n // Remove id from update data\n delete updateData.id;\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, input.id), updateData);\n}\n\n/**\n * Delete a broadcast\n */\nexport async function deleteBroadcast(broadcastId: string): Promise<void> {\n const db = getSharedFeaturesDb();\n await deleteDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));\n}\n\n/**\n * Get all broadcasts (for admin listing)\n */\nexport async function getAllBroadcasts(): Promise<BroadcastNotification[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_BROADCASTS),\n orderBy('createdAt', 'desc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\n}\n\n/**\n * Get broadcasts by status\n */\nexport async function getBroadcastsByStatus(\n status: BroadcastStatus\n): Promise<BroadcastNotification[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_BROADCASTS),\n where('status', '==', status),\n orderBy('createdAt', 'desc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\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// BROADCAST STATUS MANAGEMENT\n// ============================================================================\n\n/**\n * Publish a draft broadcast (set to active)\n */\nexport async function publishBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'active',\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Schedule a broadcast for later\n */\nexport async function scheduleBroadcast(\n broadcastId: string,\n scheduledDate: Date,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'scheduled',\n startDate: Timestamp.fromDate(scheduledDate),\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Pause an active broadcast\n */\nexport async function pauseBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'draft', // Move back to draft\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * End a broadcast\n */\nexport async function endBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'ended',\n endDate: serverTimestamp(),\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n// ============================================================================\n// TEMPLATE CRUD\n// ============================================================================\n\n/**\n * Create a new template\n */\nexport async function createTemplate(\n input: CreateTemplateInput\n): Promise<string> {\n const db = getSharedFeaturesDb();\n\n const templateData = {\n ...input,\n createdAt: serverTimestamp(),\n updatedAt: serverTimestamp(),\n };\n\n const docRef = await addDoc(\n collection(db, COLLECTION_TEMPLATES),\n templateData\n );\n\n return docRef.id;\n}\n\n/**\n * Update an existing template\n */\nexport async function updateTemplate(\n templateId: string,\n input: Partial<CreateTemplateInput>\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_TEMPLATES, templateId), {\n ...input,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Delete a template\n */\nexport async function deleteTemplate(templateId: string): Promise<void> {\n const db = getSharedFeaturesDb();\n await deleteDoc(doc(db, COLLECTION_TEMPLATES, templateId));\n}\n\n/**\n * Get all templates\n */\nexport async function getAllTemplates(): Promise<NotificationTemplate[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_TEMPLATES),\n orderBy('name', 'asc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToTemplate(d.id, d.data()));\n}\n\n/**\n * Get template by ID\n */\nexport async function getTemplateById(\n templateId: string\n): Promise<NotificationTemplate | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_TEMPLATES, templateId));\n\n if (!docSnap.exists()) return null;\n return docToTemplate(docSnap.id, docSnap.data());\n}\n\n/**\n * Get template by event type from Firestore\n */\nexport async function getFirestoreTemplateByEventType(\n eventType: string\n): Promise<NotificationTemplate | null> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_TEMPLATES),\n where('eventType', '==', eventType)\n );\n\n const snapshot = await getDocs(q);\n if (snapshot.empty) return null;\n\n const docSnap = snapshot.docs[0];\n if (!docSnap) return null;\n\n return docToTemplate(docSnap.id, docSnap.data());\n}\n\n// ============================================================================\n// ANALYTICS\n// ============================================================================\n\n/**\n * Get analytics for a specific broadcast\n */\nexport async function getBroadcastAnalytics(\n broadcastId: string\n): Promise<BroadcastAnalytics | null> {\n const db = getSharedFeaturesDb();\n\n // Get broadcast\n const broadcast = await getBroadcastById(broadcastId);\n if (!broadcast) return null;\n\n // Get events\n const eventsQuery = query(\n collection(db, COLLECTION_BROADCAST_EVENTS),\n where('broadcastId', '==', broadcastId)\n );\n\n const eventsSnapshot = await getDocs(eventsQuery);\n const events = eventsSnapshot.docs.map((d) => d.data());\n\n // Calculate analytics\n const impressions = events.filter((e) => e.action === 'impression').length;\n const clicks = events.filter((e) => e.action === 'click').length;\n const dismissals = events.filter((e) => e.action === 'dismiss').length;\n\n // Group by platform\n const byPlatform: BroadcastAnalytics['byPlatform'] = {\n web: { impressions: 0, clicks: 0 },\n android: { impressions: 0, clicks: 0 },\n ios: { impressions: 0, clicks: 0 },\n };\n\n events.forEach((e) => {\n const platform = e.platform as keyof typeof byPlatform;\n if (byPlatform[platform]) {\n if (e.action === 'impression') byPlatform[platform].impressions++;\n if (e.action === 'click') byPlatform[platform].clicks++;\n }\n });\n\n // Group by project\n const byProject: BroadcastAnalytics['byProject'] = {};\n events.forEach((e) => {\n const projectId = e.projectId as string;\n if (!byProject[projectId]) {\n byProject[projectId] = { impressions: 0, clicks: 0 };\n }\n if (e.action === 'impression') byProject[projectId].impressions++;\n if (e.action === 'click') byProject[projectId].clicks++;\n });\n\n // Group by date\n const byDateMap: Record<string, { impressions: number; clicks: number }> = {};\n events.forEach((e) => {\n const dateStr = (e.timestamp as Timestamp).toDate().toISOString().split('T')[0];\n if (dateStr) {\n if (!byDateMap[dateStr]) {\n byDateMap[dateStr] = { impressions: 0, clicks: 0 };\n }\n if (e.action === 'impression') byDateMap[dateStr].impressions++;\n if (e.action === 'click') byDateMap[dateStr].clicks++;\n }\n });\n\n const byDate = Object.entries(byDateMap).map(([date, data]) => ({\n date,\n impressions: data.impressions,\n clicks: data.clicks,\n }));\n\n return {\n broadcastId,\n title: broadcast.title,\n status: broadcast.status,\n impressions,\n clicks,\n dismissals,\n ctr: impressions > 0 ? clicks / impressions : 0,\n byPlatform,\n byProject,\n byDate,\n };\n}\n\n/**\n * Get overall notification analytics\n */\nexport async function getOverallAnalytics(\n startDate: Date,\n endDate: Date\n): Promise<NotificationAnalytics> {\n const db = getSharedFeaturesDb();\n\n // Get all broadcast events in date range\n const eventsQuery = query(\n collection(db, COLLECTION_BROADCAST_EVENTS),\n where('timestamp', '>=', Timestamp.fromDate(startDate)),\n where('timestamp', '<=', Timestamp.fromDate(endDate))\n );\n\n const eventsSnapshot = await getDocs(eventsQuery);\n const events = eventsSnapshot.docs.map((d) => d.data());\n\n // Calculate totals\n const totalSent = events.filter((e) => e.action === 'impression').length;\n const totalRead = events.filter((e) => e.action === 'impression').length; // Impressions = read for broadcasts\n const totalClicked = events.filter((e) => e.action === 'click').length;\n\n // Initialize category and type breakdowns\n const byCategory: NotificationAnalytics['byCategory'] = {\n system: { sent: 0, read: 0, clicked: 0 },\n account: { sent: 0, read: 0, clicked: 0 },\n activity: { sent: 0, read: 0, clicked: 0 },\n report: { sent: 0, read: 0, clicked: 0 },\n promotional: { sent: 0, read: 0, clicked: 0 },\n social: { sent: 0, read: 0, clicked: 0 },\n };\n\n const byType: NotificationAnalytics['byType'] = {\n info: { sent: 0, read: 0, clicked: 0 },\n success: { sent: 0, read: 0, clicked: 0 },\n warning: { sent: 0, read: 0, clicked: 0 },\n error: { sent: 0, read: 0, clicked: 0 },\n reminder: { sent: 0, read: 0, clicked: 0 },\n milestone: { sent: 0, read: 0, clicked: 0 },\n announcement: { sent: 0, read: 0, clicked: 0 },\n };\n\n // Group by date\n const byDateMap: Record<string, { sent: number; read: number; clicked: number }> = {};\n events.forEach((e) => {\n const dateStr = (e.timestamp as Timestamp).toDate().toISOString().split('T')[0];\n if (dateStr) {\n if (!byDateMap[dateStr]) {\n byDateMap[dateStr] = { sent: 0, read: 0, clicked: 0 };\n }\n if (e.action === 'impression') {\n byDateMap[dateStr].sent++;\n byDateMap[dateStr].read++;\n }\n if (e.action === 'click') byDateMap[dateStr].clicked++;\n }\n });\n\n const byDate = Object.entries(byDateMap)\n .map(([date, data]) => ({ date, ...data }))\n .sort((a, b) => a.date.localeCompare(b.date));\n\n return {\n totalSent,\n totalRead,\n totalClicked,\n readRate: totalSent > 0 ? totalRead / totalSent : 0,\n clickRate: totalSent > 0 ? totalClicked / totalSent : 0,\n byCategory,\n byType,\n byDate,\n };\n}\n\n// ============================================================================\n// EXPORT SERVICE OBJECT\n// ============================================================================\n\nexport const adminNotificationService = {\n // Broadcasts\n createBroadcast,\n updateBroadcast,\n deleteBroadcast,\n getAllBroadcasts,\n getBroadcastsByStatus,\n getBroadcastById,\n publishBroadcast,\n scheduleBroadcast,\n pauseBroadcast,\n endBroadcast,\n\n // Templates\n createTemplate,\n updateTemplate,\n deleteTemplate,\n getAllTemplates,\n getTemplateById,\n getFirestoreTemplateByEventType,\n\n // Analytics\n getBroadcastAnalytics,\n getOverallAnalytics,\n};\n\nexport default adminNotificationService;\n"],"names":["getSharedFeaturesDb","serverTimestamp","Timestamp","addDoc","collection","updateDoc","doc","deleteDoc","query","orderBy","getDocs","where","getDoc"],"mappings":";;;AAuCA,MAAM,wBAAwB;AAC9B,MAAM,uBAAuB;AAC7B,MAAM,8BAA8B;AASpC,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,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,iBAAkB,KAAK,mBAAgE,CAAA;AAAA,IACvF,gBAAiB,KAAK,kBAA8D;AAAA,IACpF,QAAQ,KAAK;AAAA,IACb,WAAW,KAAK;AAAA,IAChB,SAAS,KAAK;AAAA,IACd,UAAW,KAAK,YAAuB;AAAA,IACvC,aAAa,KAAK,gBAAgB;AAAA,IAClC,SAAU,KAAK,WAAgD;AAAA,IAC/D,aAAc,KAAK,eAA0B;AAAA,IAC7C,QAAS,KAAK,UAAqB;AAAA,IACnC,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK;AAAA,EAAA;AAEpB;AAKA,SAAS,cACP,OACA,MACsB;AACtB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM,KAAK;AAAA,IACX,WAAW,KAAK;AAAA,IAChB,UAAU,KAAK;AAAA,IACf,OAAO,KAAK;AAAA,IACZ,SAAS,KAAK;AAAA,IACd,WAAY,KAAK,aAA0B,CAAA;AAAA,IAC3C,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,WAAW,KAAK;AAAA,IAChB,YAAY,KAAK;AAAA,IACjB,SAAS,KAAK,YAAY;AAAA,IAC1B,WAAY,KAAK,WAAyB,OAAA,yBAAgB,KAAA;AAAA,IAC1D,WAAY,KAAK,WAAyB,OAAA,yBAAgB,KAAA;AAAA,EAAK;AAEnE;AASA,eAAsB,gBACpB,OACA,aACiB;AACjB,QAAM,KAAKA,eAAAA,oBAAA;AAEX,QAAM,gBAAgB;AAAA,IACpB,GAAG;AAAA,IACH,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,WAAWC,UAAAA,gBAAA;AAAA,IACX,WAAWC,UAAAA,UAAU,SAAS,MAAM,SAAS;AAAA,IAC7C,SAAS,MAAM,UAAUA,UAAAA,UAAU,SAAS,MAAM,OAAO,IAAI;AAAA,EAAA;AAG/D,QAAM,SAAS,MAAMC,UAAAA;AAAAA,IACnBC,UAAAA,WAAW,IAAI,qBAAqB;AAAA,IACpC;AAAA,EAAA;AAGF,SAAO,OAAO;AAChB;AAKA,eAAsB,gBACpB,OACA,aACe;AACf,QAAM,KAAKJ,eAAAA,oBAAA;AAEX,QAAM,aAAsC;AAAA,IAC1C,GAAG;AAAA,IACH,WAAW;AAAA,IACX,WAAWC,UAAAA,gBAAA;AAAA,EAAgB;AAI7B,MAAI,MAAM,WAAW;AACnB,eAAW,YAAYC,UAAAA,UAAU,SAAS,MAAM,SAAS;AAAA,EAC3D;AACA,MAAI,MAAM,YAAY,QAAW;AAC/B,eAAW,UAAU,MAAM,UAAUA,UAAAA,UAAU,SAAS,MAAM,OAAO,IAAI;AAAA,EAC3E;AAGA,SAAO,WAAW;AAElB,QAAMG,UAAAA,UAAUC,UAAAA,IAAI,IAAI,uBAAuB,MAAM,EAAE,GAAG,UAAU;AACtE;AAKA,eAAsB,gBAAgB,aAAoC;AACxE,QAAM,KAAKN,eAAAA,oBAAA;AACX,QAAMO,UAAAA,UAAUD,UAAAA,IAAI,IAAI,uBAAuB,WAAW,CAAC;AAC7D;AAKA,eAAsB,mBAAqD;AACzE,QAAM,KAAKN,eAAAA,oBAAA;AAEX,QAAM,IAAIQ,UAAAA;AAAAA,IACRJ,UAAAA,WAAW,IAAI,qBAAqB;AAAA,IACpCK,UAAAA,QAAQ,aAAa,MAAM;AAAA,EAAA;AAG7B,QAAM,WAAW,MAAMC,UAAAA,QAAQ,CAAC;AAChC,SAAO,SAAS,KAAK,IAAI,CAAC,MAAM,eAAe,EAAE,IAAI,EAAE,KAAA,CAAM,CAAC;AAChE;AAKA,eAAsB,sBACpB,QACkC;AAClC,QAAM,KAAKV,eAAAA,oBAAA;AAEX,QAAM,IAAIQ,UAAAA;AAAAA,IACRJ,UAAAA,WAAW,IAAI,qBAAqB;AAAA,IACpCO,gBAAM,UAAU,MAAM,MAAM;AAAA,IAC5BF,UAAAA,QAAQ,aAAa,MAAM;AAAA,EAAA;AAG7B,QAAM,WAAW,MAAMC,UAAAA,QAAQ,CAAC;AAChC,SAAO,SAAS,KAAK,IAAI,CAAC,MAAM,eAAe,EAAE,IAAI,EAAE,KAAA,CAAM,CAAC;AAChE;AAKA,eAAsB,iBACpB,aACuC;AACvC,QAAM,KAAKV,eAAAA,oBAAA;AACX,QAAM,UAAU,MAAMY,iBAAON,UAAAA,IAAI,IAAI,uBAAuB,WAAW,CAAC;AAExE,MAAI,CAAC,QAAQ,OAAA,EAAU,QAAO;AAC9B,SAAO,eAAe,QAAQ,IAAI,QAAQ,MAAM;AAClD;AASA,eAAsB,iBACpB,aACA,aACe;AACf,QAAM,KAAKN,eAAAA,oBAAA;AAEX,QAAMK,UAAAA,UAAUC,UAAAA,IAAI,IAAI,uBAAuB,WAAW,GAAG;AAAA,IAC3D,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,WAAWL,UAAAA,gBAAA;AAAA,EAAgB,CAC5B;AACH;AAKA,eAAsB,kBACpB,aACA,eACA,aACe;AACf,QAAM,KAAKD,eAAAA,oBAAA;AAEX,QAAMK,UAAAA,UAAUC,UAAAA,IAAI,IAAI,uBAAuB,WAAW,GAAG;AAAA,IAC3D,QAAQ;AAAA,IACR,WAAWJ,UAAAA,UAAU,SAAS,aAAa;AAAA,IAC3C,WAAW;AAAA,IACX,WAAWD,UAAAA,gBAAA;AAAA,EAAgB,CAC5B;AACH;AAKA,eAAsB,eACpB,aACA,aACe;AACf,QAAM,KAAKD,eAAAA,oBAAA;AAEX,QAAMK,UAAAA,UAAUC,UAAAA,IAAI,IAAI,uBAAuB,WAAW,GAAG;AAAA,IAC3D,QAAQ;AAAA;AAAA,IACR,WAAW;AAAA,IACX,WAAWL,UAAAA,gBAAA;AAAA,EAAgB,CAC5B;AACH;AAKA,eAAsB,aACpB,aACA,aACe;AACf,QAAM,KAAKD,eAAAA,oBAAA;AAEX,QAAMK,UAAAA,UAAUC,UAAAA,IAAI,IAAI,uBAAuB,WAAW,GAAG;AAAA,IAC3D,QAAQ;AAAA,IACR,SAASL,UAAAA,gBAAA;AAAA,IACT,WAAW;AAAA,IACX,WAAWA,UAAAA,gBAAA;AAAA,EAAgB,CAC5B;AACH;AASA,eAAsB,eACpB,OACiB;AACjB,QAAM,KAAKD,eAAAA,oBAAA;AAEX,QAAM,eAAe;AAAA,IACnB,GAAG;AAAA,IACH,WAAWC,UAAAA,gBAAA;AAAA,IACX,WAAWA,UAAAA,gBAAA;AAAA,EAAgB;AAG7B,QAAM,SAAS,MAAME,UAAAA;AAAAA,IACnBC,UAAAA,WAAW,IAAI,oBAAoB;AAAA,IACnC;AAAA,EAAA;AAGF,SAAO,OAAO;AAChB;AAKA,eAAsB,eACpB,YACA,OACe;AACf,QAAM,KAAKJ,eAAAA,oBAAA;AAEX,QAAMK,UAAAA,UAAUC,UAAAA,IAAI,IAAI,sBAAsB,UAAU,GAAG;AAAA,IACzD,GAAG;AAAA,IACH,WAAWL,UAAAA,gBAAA;AAAA,EAAgB,CAC5B;AACH;AAKA,eAAsB,eAAe,YAAmC;AACtE,QAAM,KAAKD,eAAAA,oBAAA;AACX,QAAMO,UAAAA,UAAUD,UAAAA,IAAI,IAAI,sBAAsB,UAAU,CAAC;AAC3D;AAKA,eAAsB,kBAAmD;AACvE,QAAM,KAAKN,eAAAA,oBAAA;AAEX,QAAM,IAAIQ,UAAAA;AAAAA,IACRJ,UAAAA,WAAW,IAAI,oBAAoB;AAAA,IACnCK,UAAAA,QAAQ,QAAQ,KAAK;AAAA,EAAA;AAGvB,QAAM,WAAW,MAAMC,UAAAA,QAAQ,CAAC;AAChC,SAAO,SAAS,KAAK,IAAI,CAAC,MAAM,cAAc,EAAE,IAAI,EAAE,KAAA,CAAM,CAAC;AAC/D;AAKA,eAAsB,gBACpB,YACsC;AACtC,QAAM,KAAKV,eAAAA,oBAAA;AACX,QAAM,UAAU,MAAMY,iBAAON,UAAAA,IAAI,IAAI,sBAAsB,UAAU,CAAC;AAEtE,MAAI,CAAC,QAAQ,OAAA,EAAU,QAAO;AAC9B,SAAO,cAAc,QAAQ,IAAI,QAAQ,MAAM;AACjD;AAKA,eAAsB,gCACpB,WACsC;AACtC,QAAM,KAAKN,eAAAA,oBAAA;AAEX,QAAM,IAAIQ,UAAAA;AAAAA,IACRJ,UAAAA,WAAW,IAAI,oBAAoB;AAAA,IACnCO,gBAAM,aAAa,MAAM,SAAS;AAAA,EAAA;AAGpC,QAAM,WAAW,MAAMD,UAAAA,QAAQ,CAAC;AAChC,MAAI,SAAS,MAAO,QAAO;AAE3B,QAAM,UAAU,SAAS,KAAK,CAAC;AAC/B,MAAI,CAAC,QAAS,QAAO;AAErB,SAAO,cAAc,QAAQ,IAAI,QAAQ,MAAM;AACjD;AASA,eAAsB,sBACpB,aACoC;AACpC,QAAM,KAAKV,eAAAA,oBAAA;AAGX,QAAM,YAAY,MAAM,iBAAiB,WAAW;AACpD,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,cAAcQ,UAAAA;AAAAA,IAClBJ,UAAAA,WAAW,IAAI,2BAA2B;AAAA,IAC1CO,gBAAM,eAAe,MAAM,WAAW;AAAA,EAAA;AAGxC,QAAM,iBAAiB,MAAMD,UAAAA,QAAQ,WAAW;AAChD,QAAM,SAAS,eAAe,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM;AAGtD,QAAM,cAAc,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,YAAY,EAAE;AACpE,QAAM,SAAS,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,EAAE;AAC1D,QAAM,aAAa,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EAAE;AAGhE,QAAM,aAA+C;AAAA,IACnD,KAAK,EAAE,aAAa,GAAG,QAAQ,EAAA;AAAA,IAC/B,SAAS,EAAE,aAAa,GAAG,QAAQ,EAAA;AAAA,IACnC,KAAK,EAAE,aAAa,GAAG,QAAQ,EAAA;AAAA,EAAE;AAGnC,SAAO,QAAQ,CAAC,MAAM;AACpB,UAAM,WAAW,EAAE;AACnB,QAAI,WAAW,QAAQ,GAAG;AACxB,UAAI,EAAE,WAAW,aAAc,YAAW,QAAQ,EAAE;AACpD,UAAI,EAAE,WAAW,QAAS,YAAW,QAAQ,EAAE;AAAA,IACjD;AAAA,EACF,CAAC;AAGD,QAAM,YAA6C,CAAA;AACnD,SAAO,QAAQ,CAAC,MAAM;AACpB,UAAM,YAAY,EAAE;AACpB,QAAI,CAAC,UAAU,SAAS,GAAG;AACzB,gBAAU,SAAS,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAA;AAAA,IACnD;AACA,QAAI,EAAE,WAAW,aAAc,WAAU,SAAS,EAAE;AACpD,QAAI,EAAE,WAAW,QAAS,WAAU,SAAS,EAAE;AAAA,EACjD,CAAC;AAGD,QAAM,YAAqE,CAAA;AAC3E,SAAO,QAAQ,CAAC,MAAM;AACpB,UAAM,UAAW,EAAE,UAAwB,OAAA,EAAS,cAAc,MAAM,GAAG,EAAE,CAAC;AAC9E,QAAI,SAAS;AACX,UAAI,CAAC,UAAU,OAAO,GAAG;AACvB,kBAAU,OAAO,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAA;AAAA,MACjD;AACA,UAAI,EAAE,WAAW,aAAc,WAAU,OAAO,EAAE;AAClD,UAAI,EAAE,WAAW,QAAS,WAAU,OAAO,EAAE;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,QAAM,SAAS,OAAO,QAAQ,SAAS,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,OAAO;AAAA,IAC9D;AAAA,IACA,aAAa,KAAK;AAAA,IAClB,QAAQ,KAAK;AAAA,EAAA,EACb;AAEF,SAAO;AAAA,IACL;AAAA,IACA,OAAO,UAAU;AAAA,IACjB,QAAQ,UAAU;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK,cAAc,IAAI,SAAS,cAAc;AAAA,IAC9C;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AAKA,eAAsB,oBACpB,WACA,SACgC;AAChC,QAAM,KAAKV,eAAAA,oBAAA;AAGX,QAAM,cAAcQ,UAAAA;AAAAA,IAClBJ,UAAAA,WAAW,IAAI,2BAA2B;AAAA,IAC1CO,UAAAA,MAAM,aAAa,MAAMT,UAAAA,UAAU,SAAS,SAAS,CAAC;AAAA,IACtDS,UAAAA,MAAM,aAAa,MAAMT,UAAAA,UAAU,SAAS,OAAO,CAAC;AAAA,EAAA;AAGtD,QAAM,iBAAiB,MAAMQ,UAAAA,QAAQ,WAAW;AAChD,QAAM,SAAS,eAAe,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM;AAGtD,QAAM,YAAY,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,YAAY,EAAE;AAClE,QAAM,YAAY,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,YAAY,EAAE;AAClE,QAAM,eAAe,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,EAAE;AAGhE,QAAM,aAAkD;AAAA,IACtD,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACrC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACtC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACvC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACrC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IAC1C,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,EAAE;AAGzC,QAAM,SAA0C;AAAA,IAC9C,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACnC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACtC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACtC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACpC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACvC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,IACxC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,EAAE;AAI/C,QAAM,YAA6E,CAAA;AACnF,SAAO,QAAQ,CAAC,MAAM;AACpB,UAAM,UAAW,EAAE,UAAwB,OAAA,EAAS,cAAc,MAAM,GAAG,EAAE,CAAC;AAC9E,QAAI,SAAS;AACX,UAAI,CAAC,UAAU,OAAO,GAAG;AACvB,kBAAU,OAAO,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAAA;AAAA,MACpD;AACA,UAAI,EAAE,WAAW,cAAc;AAC7B,kBAAU,OAAO,EAAE;AACnB,kBAAU,OAAO,EAAE;AAAA,MACrB;AACA,UAAI,EAAE,WAAW,QAAS,WAAU,OAAO,EAAE;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,QAAM,SAAS,OAAO,QAAQ,SAAS,EACpC,IAAI,CAAC,CAAC,MAAM,IAAI,OAAO,EAAE,MAAM,GAAG,KAAA,EAAO,EACzC,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAE9C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,YAAY,IAAI,YAAY,YAAY;AAAA,IAClD,WAAW,YAAY,IAAI,eAAe,YAAY;AAAA,IACtD;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AAMO,MAAM,2BAA2B;AAAA;AAAA,EAEtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EACA;AACF;;;;;;;;;;;;;;;;;;;"}
@@ -1,5 +1,5 @@
1
1
  import { query, collection, where, Timestamp, getDocs, getDoc, doc, orderBy, deleteDoc, updateDoc, serverTimestamp, addDoc } from "firebase/firestore";
2
- import { I as getSharedFeaturesDb } from "./commonFeatures-Bdt0UZox.js";
2
+ import { I as getSharedFeaturesDb } from "./commonFeatures-CaqcEOik.js";
3
3
  const COLLECTION_BROADCASTS = "zaions_broadcasts";
4
4
  const COLLECTION_TEMPLATES = "zaions_notification_templates";
5
5
  const COLLECTION_BROADCAST_EVENTS = "zaions_broadcast_events";
@@ -360,4 +360,4 @@ export {
360
360
  scheduleBroadcast as s,
361
361
  updateBroadcast as u
362
362
  };
363
- //# sourceMappingURL=admin-notifications-NI7I76uY.js.map
363
+ //# sourceMappingURL=admin-notifications-C_7ReIMi.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin-notifications-NI7I76uY.js","sources":["../src/services/admin-notifications.ts"],"sourcesContent":["/**\n * Admin Notifications Service\n *\n * Service for managing broadcasts and templates from the admin panel.\n * Used in aoneahsan.com admin panel to manage cross-project notifications.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n doc,\n getDocs,\n getDoc,\n updateDoc,\n deleteDoc,\n addDoc,\n query,\n where,\n orderBy,\n serverTimestamp,\n Timestamp,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport type {\n BroadcastNotification,\n BroadcastStatus,\n CreateBroadcastInput,\n UpdateBroadcastInput,\n NotificationTemplate,\n CreateTemplateInput,\n BroadcastAnalytics,\n NotificationAnalytics,\n} from '../types/notifications';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_BROADCASTS = 'zaions_broadcasts';\nconst COLLECTION_TEMPLATES = 'zaions_notification_templates';\nconst COLLECTION_BROADCAST_EVENTS = 'zaions_broadcast_events';\n\n// ============================================================================\n// HELPER FUNCTIONS\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,\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: (data.targetPlatforms as BroadcastNotification['targetPlatforms']) || [],\n targetAudience: (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,\n variant: (data.variant as BroadcastNotification['variant']) || '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 * Convert Firestore document to NotificationTemplate\n */\nfunction docToTemplate(\n docId: string,\n data: Record<string, unknown>\n): NotificationTemplate {\n return {\n id: docId,\n name: data.name as string,\n eventType: data.eventType as string,\n category: data.category as NotificationTemplate['category'],\n title: data.title as string,\n message: data.message as string,\n variables: (data.variables as string[]) || [],\n type: data.type as NotificationTemplate['type'],\n isImportant: data.isImportant as boolean,\n actionUrl: data.actionUrl as string | undefined,\n actionText: data.actionText as string | undefined,\n enabled: data.enabled !== false,\n createdAt: (data.createdAt as Timestamp)?.toDate() || new Date(),\n updatedAt: (data.updatedAt as Timestamp)?.toDate() || new Date(),\n };\n}\n\n// ============================================================================\n// BROADCAST CRUD\n// ============================================================================\n\n/**\n * Create a new broadcast\n */\nexport async function createBroadcast(\n input: CreateBroadcastInput,\n adminUserId: string\n): Promise<string> {\n const db = getSharedFeaturesDb();\n\n const broadcastData = {\n ...input,\n status: 'draft' as BroadcastStatus,\n isRead: false,\n impressions: 0,\n clicks: 0,\n createdBy: adminUserId,\n createdAt: serverTimestamp(),\n startDate: Timestamp.fromDate(input.startDate),\n endDate: input.endDate ? Timestamp.fromDate(input.endDate) : null,\n };\n\n const docRef = await addDoc(\n collection(db, COLLECTION_BROADCASTS),\n broadcastData\n );\n\n return docRef.id;\n}\n\n/**\n * Update an existing broadcast\n */\nexport async function updateBroadcast(\n input: UpdateBroadcastInput,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n const updateData: Record<string, unknown> = {\n ...input,\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n };\n\n // Convert dates if provided\n if (input.startDate) {\n updateData.startDate = Timestamp.fromDate(input.startDate);\n }\n if (input.endDate !== undefined) {\n updateData.endDate = input.endDate ? Timestamp.fromDate(input.endDate) : null;\n }\n\n // Remove id from update data\n delete updateData.id;\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, input.id), updateData);\n}\n\n/**\n * Delete a broadcast\n */\nexport async function deleteBroadcast(broadcastId: string): Promise<void> {\n const db = getSharedFeaturesDb();\n await deleteDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));\n}\n\n/**\n * Get all broadcasts (for admin listing)\n */\nexport async function getAllBroadcasts(): Promise<BroadcastNotification[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_BROADCASTS),\n orderBy('createdAt', 'desc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\n}\n\n/**\n * Get broadcasts by status\n */\nexport async function getBroadcastsByStatus(\n status: BroadcastStatus\n): Promise<BroadcastNotification[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_BROADCASTS),\n where('status', '==', status),\n orderBy('createdAt', 'desc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\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// BROADCAST STATUS MANAGEMENT\n// ============================================================================\n\n/**\n * Publish a draft broadcast (set to active)\n */\nexport async function publishBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'active',\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Schedule a broadcast for later\n */\nexport async function scheduleBroadcast(\n broadcastId: string,\n scheduledDate: Date,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'scheduled',\n startDate: Timestamp.fromDate(scheduledDate),\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Pause an active broadcast\n */\nexport async function pauseBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'draft', // Move back to draft\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * End a broadcast\n */\nexport async function endBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'ended',\n endDate: serverTimestamp(),\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n// ============================================================================\n// TEMPLATE CRUD\n// ============================================================================\n\n/**\n * Create a new template\n */\nexport async function createTemplate(\n input: CreateTemplateInput\n): Promise<string> {\n const db = getSharedFeaturesDb();\n\n const templateData = {\n ...input,\n createdAt: serverTimestamp(),\n updatedAt: serverTimestamp(),\n };\n\n const docRef = await addDoc(\n collection(db, COLLECTION_TEMPLATES),\n templateData\n );\n\n return docRef.id;\n}\n\n/**\n * Update an existing template\n */\nexport async function updateTemplate(\n templateId: string,\n input: Partial<CreateTemplateInput>\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_TEMPLATES, templateId), {\n ...input,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Delete a template\n */\nexport async function deleteTemplate(templateId: string): Promise<void> {\n const db = getSharedFeaturesDb();\n await deleteDoc(doc(db, COLLECTION_TEMPLATES, templateId));\n}\n\n/**\n * Get all templates\n */\nexport async function getAllTemplates(): Promise<NotificationTemplate[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_TEMPLATES),\n orderBy('name', 'asc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToTemplate(d.id, d.data()));\n}\n\n/**\n * Get template by ID\n */\nexport async function getTemplateById(\n templateId: string\n): Promise<NotificationTemplate | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_TEMPLATES, templateId));\n\n if (!docSnap.exists()) return null;\n return docToTemplate(docSnap.id, docSnap.data());\n}\n\n/**\n * Get template by event type from Firestore\n */\nexport async function getFirestoreTemplateByEventType(\n eventType: string\n): Promise<NotificationTemplate | null> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_TEMPLATES),\n where('eventType', '==', eventType)\n );\n\n const snapshot = await getDocs(q);\n if (snapshot.empty) return null;\n\n const docSnap = snapshot.docs[0];\n if (!docSnap) return null;\n\n return docToTemplate(docSnap.id, docSnap.data());\n}\n\n// ============================================================================\n// ANALYTICS\n// ============================================================================\n\n/**\n * Get analytics for a specific broadcast\n */\nexport async function getBroadcastAnalytics(\n broadcastId: string\n): Promise<BroadcastAnalytics | null> {\n const db = getSharedFeaturesDb();\n\n // Get broadcast\n const broadcast = await getBroadcastById(broadcastId);\n if (!broadcast) return null;\n\n // Get events\n const eventsQuery = query(\n collection(db, COLLECTION_BROADCAST_EVENTS),\n where('broadcastId', '==', broadcastId)\n );\n\n const eventsSnapshot = await getDocs(eventsQuery);\n const events = eventsSnapshot.docs.map((d) => d.data());\n\n // Calculate analytics\n const impressions = events.filter((e) => e.action === 'impression').length;\n const clicks = events.filter((e) => e.action === 'click').length;\n const dismissals = events.filter((e) => e.action === 'dismiss').length;\n\n // Group by platform\n const byPlatform: BroadcastAnalytics['byPlatform'] = {\n web: { impressions: 0, clicks: 0 },\n android: { impressions: 0, clicks: 0 },\n ios: { impressions: 0, clicks: 0 },\n };\n\n events.forEach((e) => {\n const platform = e.platform as keyof typeof byPlatform;\n if (byPlatform[platform]) {\n if (e.action === 'impression') byPlatform[platform].impressions++;\n if (e.action === 'click') byPlatform[platform].clicks++;\n }\n });\n\n // Group by project\n const byProject: BroadcastAnalytics['byProject'] = {};\n events.forEach((e) => {\n const projectId = e.projectId as string;\n if (!byProject[projectId]) {\n byProject[projectId] = { impressions: 0, clicks: 0 };\n }\n if (e.action === 'impression') byProject[projectId].impressions++;\n if (e.action === 'click') byProject[projectId].clicks++;\n });\n\n // Group by date\n const byDateMap: Record<string, { impressions: number; clicks: number }> = {};\n events.forEach((e) => {\n const dateStr = (e.timestamp as Timestamp).toDate().toISOString().split('T')[0];\n if (dateStr) {\n if (!byDateMap[dateStr]) {\n byDateMap[dateStr] = { impressions: 0, clicks: 0 };\n }\n if (e.action === 'impression') byDateMap[dateStr].impressions++;\n if (e.action === 'click') byDateMap[dateStr].clicks++;\n }\n });\n\n const byDate = Object.entries(byDateMap).map(([date, data]) => ({\n date,\n impressions: data.impressions,\n clicks: data.clicks,\n }));\n\n return {\n broadcastId,\n title: broadcast.title,\n status: broadcast.status,\n impressions,\n clicks,\n dismissals,\n ctr: impressions > 0 ? clicks / impressions : 0,\n byPlatform,\n byProject,\n byDate,\n };\n}\n\n/**\n * Get overall notification analytics\n */\nexport async function getOverallAnalytics(\n startDate: Date,\n endDate: Date\n): Promise<NotificationAnalytics> {\n const db = getSharedFeaturesDb();\n\n // Get all broadcast events in date range\n const eventsQuery = query(\n collection(db, COLLECTION_BROADCAST_EVENTS),\n where('timestamp', '>=', Timestamp.fromDate(startDate)),\n where('timestamp', '<=', Timestamp.fromDate(endDate))\n );\n\n const eventsSnapshot = await getDocs(eventsQuery);\n const events = eventsSnapshot.docs.map((d) => d.data());\n\n // Calculate totals\n const totalSent = events.filter((e) => e.action === 'impression').length;\n const totalRead = events.filter((e) => e.action === 'impression').length; // Impressions = read for broadcasts\n const totalClicked = events.filter((e) => e.action === 'click').length;\n\n // Initialize category and type breakdowns\n const byCategory: NotificationAnalytics['byCategory'] = {\n system: { sent: 0, read: 0, clicked: 0 },\n account: { sent: 0, read: 0, clicked: 0 },\n activity: { sent: 0, read: 0, clicked: 0 },\n report: { sent: 0, read: 0, clicked: 0 },\n promotional: { sent: 0, read: 0, clicked: 0 },\n social: { sent: 0, read: 0, clicked: 0 },\n };\n\n const byType: NotificationAnalytics['byType'] = {\n info: { sent: 0, read: 0, clicked: 0 },\n success: { sent: 0, read: 0, clicked: 0 },\n warning: { sent: 0, read: 0, clicked: 0 },\n error: { sent: 0, read: 0, clicked: 0 },\n reminder: { sent: 0, read: 0, clicked: 0 },\n milestone: { sent: 0, read: 0, clicked: 0 },\n announcement: { sent: 0, read: 0, clicked: 0 },\n };\n\n // Group by date\n const byDateMap: Record<string, { sent: number; read: number; clicked: number }> = {};\n events.forEach((e) => {\n const dateStr = (e.timestamp as Timestamp).toDate().toISOString().split('T')[0];\n if (dateStr) {\n if (!byDateMap[dateStr]) {\n byDateMap[dateStr] = { sent: 0, read: 0, clicked: 0 };\n }\n if (e.action === 'impression') {\n byDateMap[dateStr].sent++;\n byDateMap[dateStr].read++;\n }\n if (e.action === 'click') byDateMap[dateStr].clicked++;\n }\n });\n\n const byDate = Object.entries(byDateMap)\n .map(([date, data]) => ({ date, ...data }))\n .sort((a, b) => a.date.localeCompare(b.date));\n\n return {\n totalSent,\n totalRead,\n totalClicked,\n readRate: totalSent > 0 ? totalRead / totalSent : 0,\n clickRate: totalSent > 0 ? totalClicked / totalSent : 0,\n byCategory,\n byType,\n byDate,\n };\n}\n\n// ============================================================================\n// EXPORT SERVICE OBJECT\n// ============================================================================\n\nexport const adminNotificationService = {\n // Broadcasts\n createBroadcast,\n updateBroadcast,\n deleteBroadcast,\n getAllBroadcasts,\n getBroadcastsByStatus,\n getBroadcastById,\n publishBroadcast,\n scheduleBroadcast,\n pauseBroadcast,\n endBroadcast,\n\n // Templates\n createTemplate,\n updateTemplate,\n deleteTemplate,\n getAllTemplates,\n getTemplateById,\n getFirestoreTemplateByEventType,\n\n // Analytics\n getBroadcastAnalytics,\n getOverallAnalytics,\n};\n\nexport default adminNotificationService;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"admin-notifications-C_7ReIMi.js","sources":["../src/services/admin-notifications.ts"],"sourcesContent":["/**\n * Admin Notifications Service\n *\n * Service for managing broadcasts and templates from the admin panel.\n * Used in aoneahsan.com admin panel to manage cross-project notifications.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n doc,\n getDocs,\n getDoc,\n updateDoc,\n deleteDoc,\n addDoc,\n query,\n where,\n orderBy,\n serverTimestamp,\n Timestamp,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport type {\n BroadcastNotification,\n BroadcastStatus,\n CreateBroadcastInput,\n UpdateBroadcastInput,\n NotificationTemplate,\n CreateTemplateInput,\n BroadcastAnalytics,\n NotificationAnalytics,\n} from '../types/notifications';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_BROADCASTS = 'zaions_broadcasts';\nconst COLLECTION_TEMPLATES = 'zaions_notification_templates';\nconst COLLECTION_BROADCAST_EVENTS = 'zaions_broadcast_events';\n\n// ============================================================================\n// HELPER FUNCTIONS\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,\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: (data.targetPlatforms as BroadcastNotification['targetPlatforms']) || [],\n targetAudience: (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,\n variant: (data.variant as BroadcastNotification['variant']) || '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 * Convert Firestore document to NotificationTemplate\n */\nfunction docToTemplate(\n docId: string,\n data: Record<string, unknown>\n): NotificationTemplate {\n return {\n id: docId,\n name: data.name as string,\n eventType: data.eventType as string,\n category: data.category as NotificationTemplate['category'],\n title: data.title as string,\n message: data.message as string,\n variables: (data.variables as string[]) || [],\n type: data.type as NotificationTemplate['type'],\n isImportant: data.isImportant as boolean,\n actionUrl: data.actionUrl as string | undefined,\n actionText: data.actionText as string | undefined,\n enabled: data.enabled !== false,\n createdAt: (data.createdAt as Timestamp)?.toDate() || new Date(),\n updatedAt: (data.updatedAt as Timestamp)?.toDate() || new Date(),\n };\n}\n\n// ============================================================================\n// BROADCAST CRUD\n// ============================================================================\n\n/**\n * Create a new broadcast\n */\nexport async function createBroadcast(\n input: CreateBroadcastInput,\n adminUserId: string\n): Promise<string> {\n const db = getSharedFeaturesDb();\n\n const broadcastData = {\n ...input,\n status: 'draft' as BroadcastStatus,\n isRead: false,\n impressions: 0,\n clicks: 0,\n createdBy: adminUserId,\n createdAt: serverTimestamp(),\n startDate: Timestamp.fromDate(input.startDate),\n endDate: input.endDate ? Timestamp.fromDate(input.endDate) : null,\n };\n\n const docRef = await addDoc(\n collection(db, COLLECTION_BROADCASTS),\n broadcastData\n );\n\n return docRef.id;\n}\n\n/**\n * Update an existing broadcast\n */\nexport async function updateBroadcast(\n input: UpdateBroadcastInput,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n const updateData: Record<string, unknown> = {\n ...input,\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n };\n\n // Convert dates if provided\n if (input.startDate) {\n updateData.startDate = Timestamp.fromDate(input.startDate);\n }\n if (input.endDate !== undefined) {\n updateData.endDate = input.endDate ? Timestamp.fromDate(input.endDate) : null;\n }\n\n // Remove id from update data\n delete updateData.id;\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, input.id), updateData);\n}\n\n/**\n * Delete a broadcast\n */\nexport async function deleteBroadcast(broadcastId: string): Promise<void> {\n const db = getSharedFeaturesDb();\n await deleteDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));\n}\n\n/**\n * Get all broadcasts (for admin listing)\n */\nexport async function getAllBroadcasts(): Promise<BroadcastNotification[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_BROADCASTS),\n orderBy('createdAt', 'desc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\n}\n\n/**\n * Get broadcasts by status\n */\nexport async function getBroadcastsByStatus(\n status: BroadcastStatus\n): Promise<BroadcastNotification[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_BROADCASTS),\n where('status', '==', status),\n orderBy('createdAt', 'desc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\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// BROADCAST STATUS MANAGEMENT\n// ============================================================================\n\n/**\n * Publish a draft broadcast (set to active)\n */\nexport async function publishBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'active',\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Schedule a broadcast for later\n */\nexport async function scheduleBroadcast(\n broadcastId: string,\n scheduledDate: Date,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'scheduled',\n startDate: Timestamp.fromDate(scheduledDate),\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Pause an active broadcast\n */\nexport async function pauseBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'draft', // Move back to draft\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * End a broadcast\n */\nexport async function endBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'ended',\n endDate: serverTimestamp(),\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n// ============================================================================\n// TEMPLATE CRUD\n// ============================================================================\n\n/**\n * Create a new template\n */\nexport async function createTemplate(\n input: CreateTemplateInput\n): Promise<string> {\n const db = getSharedFeaturesDb();\n\n const templateData = {\n ...input,\n createdAt: serverTimestamp(),\n updatedAt: serverTimestamp(),\n };\n\n const docRef = await addDoc(\n collection(db, COLLECTION_TEMPLATES),\n templateData\n );\n\n return docRef.id;\n}\n\n/**\n * Update an existing template\n */\nexport async function updateTemplate(\n templateId: string,\n input: Partial<CreateTemplateInput>\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_TEMPLATES, templateId), {\n ...input,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Delete a template\n */\nexport async function deleteTemplate(templateId: string): Promise<void> {\n const db = getSharedFeaturesDb();\n await deleteDoc(doc(db, COLLECTION_TEMPLATES, templateId));\n}\n\n/**\n * Get all templates\n */\nexport async function getAllTemplates(): Promise<NotificationTemplate[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_TEMPLATES),\n orderBy('name', 'asc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToTemplate(d.id, d.data()));\n}\n\n/**\n * Get template by ID\n */\nexport async function getTemplateById(\n templateId: string\n): Promise<NotificationTemplate | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_TEMPLATES, templateId));\n\n if (!docSnap.exists()) return null;\n return docToTemplate(docSnap.id, docSnap.data());\n}\n\n/**\n * Get template by event type from Firestore\n */\nexport async function getFirestoreTemplateByEventType(\n eventType: string\n): Promise<NotificationTemplate | null> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_TEMPLATES),\n where('eventType', '==', eventType)\n );\n\n const snapshot = await getDocs(q);\n if (snapshot.empty) return null;\n\n const docSnap = snapshot.docs[0];\n if (!docSnap) return null;\n\n return docToTemplate(docSnap.id, docSnap.data());\n}\n\n// ============================================================================\n// ANALYTICS\n// ============================================================================\n\n/**\n * Get analytics for a specific broadcast\n */\nexport async function getBroadcastAnalytics(\n broadcastId: string\n): Promise<BroadcastAnalytics | null> {\n const db = getSharedFeaturesDb();\n\n // Get broadcast\n const broadcast = await getBroadcastById(broadcastId);\n if (!broadcast) return null;\n\n // Get events\n const eventsQuery = query(\n collection(db, COLLECTION_BROADCAST_EVENTS),\n where('broadcastId', '==', broadcastId)\n );\n\n const eventsSnapshot = await getDocs(eventsQuery);\n const events = eventsSnapshot.docs.map((d) => d.data());\n\n // Calculate analytics\n const impressions = events.filter((e) => e.action === 'impression').length;\n const clicks = events.filter((e) => e.action === 'click').length;\n const dismissals = events.filter((e) => e.action === 'dismiss').length;\n\n // Group by platform\n const byPlatform: BroadcastAnalytics['byPlatform'] = {\n web: { impressions: 0, clicks: 0 },\n android: { impressions: 0, clicks: 0 },\n ios: { impressions: 0, clicks: 0 },\n };\n\n events.forEach((e) => {\n const platform = e.platform as keyof typeof byPlatform;\n if (byPlatform[platform]) {\n if (e.action === 'impression') byPlatform[platform].impressions++;\n if (e.action === 'click') byPlatform[platform].clicks++;\n }\n });\n\n // Group by project\n const byProject: BroadcastAnalytics['byProject'] = {};\n events.forEach((e) => {\n const projectId = e.projectId as string;\n if (!byProject[projectId]) {\n byProject[projectId] = { impressions: 0, clicks: 0 };\n }\n if (e.action === 'impression') byProject[projectId].impressions++;\n if (e.action === 'click') byProject[projectId].clicks++;\n });\n\n // Group by date\n const byDateMap: Record<string, { impressions: number; clicks: number }> = {};\n events.forEach((e) => {\n const dateStr = (e.timestamp as Timestamp).toDate().toISOString().split('T')[0];\n if (dateStr) {\n if (!byDateMap[dateStr]) {\n byDateMap[dateStr] = { impressions: 0, clicks: 0 };\n }\n if (e.action === 'impression') byDateMap[dateStr].impressions++;\n if (e.action === 'click') byDateMap[dateStr].clicks++;\n }\n });\n\n const byDate = Object.entries(byDateMap).map(([date, data]) => ({\n date,\n impressions: data.impressions,\n clicks: data.clicks,\n }));\n\n return {\n broadcastId,\n title: broadcast.title,\n status: broadcast.status,\n impressions,\n clicks,\n dismissals,\n ctr: impressions > 0 ? clicks / impressions : 0,\n byPlatform,\n byProject,\n byDate,\n };\n}\n\n/**\n * Get overall notification analytics\n */\nexport async function getOverallAnalytics(\n startDate: Date,\n endDate: Date\n): Promise<NotificationAnalytics> {\n const db = getSharedFeaturesDb();\n\n // Get all broadcast events in date range\n const eventsQuery = query(\n collection(db, COLLECTION_BROADCAST_EVENTS),\n where('timestamp', '>=', Timestamp.fromDate(startDate)),\n where('timestamp', '<=', Timestamp.fromDate(endDate))\n );\n\n const eventsSnapshot = await getDocs(eventsQuery);\n const events = eventsSnapshot.docs.map((d) => d.data());\n\n // Calculate totals\n const totalSent = events.filter((e) => e.action === 'impression').length;\n const totalRead = events.filter((e) => e.action === 'impression').length; // Impressions = read for broadcasts\n const totalClicked = events.filter((e) => e.action === 'click').length;\n\n // Initialize category and type breakdowns\n const byCategory: NotificationAnalytics['byCategory'] = {\n system: { sent: 0, read: 0, clicked: 0 },\n account: { sent: 0, read: 0, clicked: 0 },\n activity: { sent: 0, read: 0, clicked: 0 },\n report: { sent: 0, read: 0, clicked: 0 },\n promotional: { sent: 0, read: 0, clicked: 0 },\n social: { sent: 0, read: 0, clicked: 0 },\n };\n\n const byType: NotificationAnalytics['byType'] = {\n info: { sent: 0, read: 0, clicked: 0 },\n success: { sent: 0, read: 0, clicked: 0 },\n warning: { sent: 0, read: 0, clicked: 0 },\n error: { sent: 0, read: 0, clicked: 0 },\n reminder: { sent: 0, read: 0, clicked: 0 },\n milestone: { sent: 0, read: 0, clicked: 0 },\n announcement: { sent: 0, read: 0, clicked: 0 },\n };\n\n // Group by date\n const byDateMap: Record<string, { sent: number; read: number; clicked: number }> = {};\n events.forEach((e) => {\n const dateStr = (e.timestamp as Timestamp).toDate().toISOString().split('T')[0];\n if (dateStr) {\n if (!byDateMap[dateStr]) {\n byDateMap[dateStr] = { sent: 0, read: 0, clicked: 0 };\n }\n if (e.action === 'impression') {\n byDateMap[dateStr].sent++;\n byDateMap[dateStr].read++;\n }\n if (e.action === 'click') byDateMap[dateStr].clicked++;\n }\n });\n\n const byDate = Object.entries(byDateMap)\n .map(([date, data]) => ({ date, ...data }))\n .sort((a, b) => a.date.localeCompare(b.date));\n\n return {\n totalSent,\n totalRead,\n totalClicked,\n readRate: totalSent > 0 ? totalRead / totalSent : 0,\n clickRate: totalSent > 0 ? totalClicked / totalSent : 0,\n byCategory,\n byType,\n byDate,\n };\n}\n\n// ============================================================================\n// EXPORT SERVICE OBJECT\n// ============================================================================\n\nexport const adminNotificationService = {\n // Broadcasts\n createBroadcast,\n updateBroadcast,\n deleteBroadcast,\n getAllBroadcasts,\n getBroadcastsByStatus,\n getBroadcastById,\n publishBroadcast,\n scheduleBroadcast,\n pauseBroadcast,\n endBroadcast,\n\n // Templates\n createTemplate,\n updateTemplate,\n deleteTemplate,\n getAllTemplates,\n getTemplateById,\n getFirestoreTemplateByEventType,\n\n // Analytics\n getBroadcastAnalytics,\n getOverallAnalytics,\n};\n\nexport default adminNotificationService;\n"],"names":[],"mappings":"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"}
@@ -22,7 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  const firestore = require("firebase/firestore");
25
- const commonFeatures = require("./commonFeatures-CiqxxOin.cjs");
25
+ const commonFeatures = require("./commonFeatures-ofZOjnZ0.cjs");
26
26
  const COLLECTION_BROADCASTS = "zaions_broadcasts";
27
27
  const COLLECTION_BROADCAST_EVENTS = "zaions_broadcast_events";
28
28
  const LOCAL_STORAGE_KEY = "shared_features_dismissed_broadcasts";
@@ -275,4 +275,4 @@ exports.subscribeToBroadcasts = subscribeToBroadcasts;
275
275
  exports.trackBroadcastClick = trackBroadcastClick;
276
276
  exports.trackBroadcastDismiss = trackBroadcastDismiss;
277
277
  exports.trackBroadcastImpression = trackBroadcastImpression;
278
- //# sourceMappingURL=broadcasts-BMoTZIuX.cjs.map
278
+ //# sourceMappingURL=broadcasts-6w_9X92Z.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"broadcasts-BMoTZIuX.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
+ {"version":3,"file":"broadcasts-6w_9X92Z.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 { B as getConfig, I as getSharedFeaturesDb, Y as getState } from "./commonFeatures-Bdt0UZox.js";
2
+ import { B as getConfig, I as getSharedFeaturesDb, Y as getState } from "./commonFeatures-CaqcEOik.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-DnzZkCoy.js.map
257
+ //# sourceMappingURL=broadcasts-CK4sGMz4.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"broadcasts-DnzZkCoy.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-CK4sGMz4.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,7 +1,7 @@
1
1
  import { getFirestore, Timestamp, query, collection, where, orderBy, limit, getDocs, getDoc, doc, serverTimestamp, addDoc, setDoc, onSnapshot } from "firebase/firestore";
2
2
  import { getApps, initializeApp } from "firebase/app";
3
3
  import { getAuth } from "firebase/auth";
4
- import { C as COLLECTION_FEATURE_FLAGS, F as FEATURE_FLAGS_DOC_ID, e as DEFAULT_FEATURE_FLAGS, a as COMMON_FEATURE_COLLECTIONS } from "./commonFeatures-HT-UO7HW.js";
4
+ import { C as COLLECTION_FEATURE_FLAGS, F as FEATURE_FLAGS_DOC_ID, e as DEFAULT_FEATURE_FLAGS, a as COMMON_FEATURE_COLLECTIONS } from "./commonFeatures-XJ9fuxg_.js";
5
5
  let state = {
6
6
  initialized: false,
7
7
  config: null,
@@ -1252,4 +1252,4 @@ export {
1252
1252
  fetchTestimonials as y,
1253
1253
  getCampaignById as z
1254
1254
  };
1255
- //# sourceMappingURL=commonFeatures-Bdt0UZox.js.map
1255
+ //# sourceMappingURL=commonFeatures-CaqcEOik.js.map