shared-features 0.1.0 → 0.1.2

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 (63) hide show
  1. package/dist/admin-commonFeatures-BqSmAHvZ.js +730 -0
  2. package/dist/admin-commonFeatures-BqSmAHvZ.js.map +1 -0
  3. package/dist/{admin-notifications-D1GgYCJW.cjs → admin-commonFeatures-LWADQRoD.cjs} +369 -2
  4. package/dist/admin-commonFeatures-LWADQRoD.cjs.map +1 -0
  5. package/dist/{broadcasts-DnzZkCoy.js → broadcasts-B36bWyZf.js} +2 -2
  6. package/dist/{broadcasts-DnzZkCoy.js.map → broadcasts-B36bWyZf.js.map} +1 -1
  7. package/dist/{broadcasts-BMoTZIuX.cjs → broadcasts-D3_TQybH.cjs} +2 -2
  8. package/dist/{broadcasts-BMoTZIuX.cjs.map → broadcasts-D3_TQybH.cjs.map} +1 -1
  9. package/dist/{commonFeatures-CiqxxOin.cjs → commonFeatures-B9NKYWuL.cjs} +87 -2
  10. package/dist/commonFeatures-B9NKYWuL.cjs.map +1 -0
  11. package/dist/{commonFeatures-Bdt0UZox.js → commonFeatures-BNmLKLa9.js} +129 -44
  12. package/dist/commonFeatures-BNmLKLa9.js.map +1 -0
  13. package/dist/{commonFeatures-Cr5g1E4M.cjs → commonFeatures-DhWaBEv_.cjs} +24 -2
  14. package/dist/commonFeatures-DhWaBEv_.cjs.map +1 -0
  15. package/dist/{commonFeatures-HT-UO7HW.js → commonFeatures-XJ9fuxg_.js} +27 -5
  16. package/dist/commonFeatures-XJ9fuxg_.js.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-BJXr96cC.js} +3 -3
  26. package/dist/{index-Dv34aG2I.js.map → index-BJXr96cC.js.map} +1 -1
  27. package/dist/{index-Dt5YjYnK.cjs → index-CvRtEhjW.cjs} +3 -3
  28. package/dist/{index-Dt5YjYnK.cjs.map → index-CvRtEhjW.cjs.map} +1 -1
  29. package/dist/index.cjs +54 -25
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +121 -92
  32. package/dist/services/admin-commonFeatures.d.ts +35 -0
  33. package/dist/services/admin-commonFeatures.d.ts.map +1 -0
  34. package/dist/services/commonFeatures.d.ts +4 -1
  35. package/dist/services/commonFeatures.d.ts.map +1 -1
  36. package/dist/services/index.cjs +49 -22
  37. package/dist/services/index.cjs.map +1 -1
  38. package/dist/services/index.d.ts +2 -1
  39. package/dist/services/index.d.ts.map +1 -1
  40. package/dist/services/index.js +82 -55
  41. package/dist/types/commonFeatures.d.ts +38 -0
  42. package/dist/types/commonFeatures.d.ts.map +1 -1
  43. package/dist/types/index.cjs +3 -1
  44. package/dist/types/index.cjs.map +1 -1
  45. package/dist/types/index.js +6 -4
  46. package/dist/{useCommonFeatures-DnDlhmri.cjs → useCommonFeatures-C4fOj6rp.cjs} +2 -2
  47. package/dist/useCommonFeatures-C4fOj6rp.cjs.map +1 -0
  48. package/dist/{useCommonFeatures-CgyDq6LZ.js → useCommonFeatures-DgwIAj9a.js} +2 -2
  49. package/dist/useCommonFeatures-DgwIAj9a.js.map +1 -0
  50. package/dist/{useFeatureFlags-BRJSyH9M.js → useFeatureFlags-BHr1EOg0.js} +3 -3
  51. package/dist/{useFeatureFlags-BRJSyH9M.js.map → useFeatureFlags-BHr1EOg0.js.map} +1 -1
  52. package/dist/{useFeatureFlags-DXqBJ5Mh.cjs → useFeatureFlags-QBLhm28R.cjs} +3 -3
  53. package/dist/{useFeatureFlags-DXqBJ5Mh.cjs.map → useFeatureFlags-QBLhm28R.cjs.map} +1 -1
  54. package/package.json +1 -1
  55. package/dist/admin-notifications-D1GgYCJW.cjs.map +0 -1
  56. package/dist/admin-notifications-NI7I76uY.js +0 -363
  57. package/dist/admin-notifications-NI7I76uY.js.map +0 -1
  58. package/dist/commonFeatures-Bdt0UZox.js.map +0 -1
  59. package/dist/commonFeatures-CiqxxOin.cjs.map +0 -1
  60. package/dist/commonFeatures-Cr5g1E4M.cjs.map +0 -1
  61. package/dist/commonFeatures-HT-UO7HW.js.map +0 -1
  62. package/dist/useCommonFeatures-CgyDq6LZ.js.map +0 -1
  63. package/dist/useCommonFeatures-DnDlhmri.cjs.map +0 -1
@@ -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,
@@ -780,6 +780,7 @@ let paymentOptionsCache = null;
780
780
  let servicesCache = null;
781
781
  let skillsCache = null;
782
782
  let testimonialsCache = null;
783
+ let projectsCache = null;
783
784
  function isCacheValid(cache) {
784
785
  if (!cache || !cache.data) return false;
785
786
  return Date.now() - cache.timestamp < CACHE_TTL_MS;
@@ -793,6 +794,7 @@ function clearAllCommonFeaturesCache() {
793
794
  servicesCache = null;
794
795
  skillsCache = null;
795
796
  testimonialsCache = null;
797
+ projectsCache = null;
796
798
  }
797
799
  function docToContactInfo(docId, data) {
798
800
  return {
@@ -1199,32 +1201,115 @@ async function fetchTestimonials(options = {}) {
1199
1201
  function clearTestimonialsCache() {
1200
1202
  testimonialsCache = null;
1201
1203
  }
1204
+ function docToProject(docId, data) {
1205
+ return {
1206
+ id: docId,
1207
+ title: data.title || "",
1208
+ slug: data.slug || "",
1209
+ description: data.description || "",
1210
+ shortDescription: data.shortDescription,
1211
+ category: data.category,
1212
+ status: data.status,
1213
+ thumbnailUrl: data.thumbnailUrl,
1214
+ images: data.images,
1215
+ technologies: data.technologies || [],
1216
+ features: data.features,
1217
+ links: data.links,
1218
+ clientName: data.clientName,
1219
+ startDate: data.startDate,
1220
+ endDate: data.endDate,
1221
+ isActive: data.isActive ?? true,
1222
+ isFeatured: data.isFeatured ?? false,
1223
+ order: data.order ?? 0,
1224
+ updatedAt: data.updatedAt
1225
+ };
1226
+ }
1227
+ async function fetchProjects(options = {}) {
1228
+ if (!isFeatureEnabled("projects")) {
1229
+ const config = getConfig();
1230
+ if (config.debug) console.log("[shared-features] projects feature is disabled");
1231
+ return [];
1232
+ }
1233
+ if (!options.category && !options.status && !options.featuredOnly && !options.limit && options.activeOnly !== false && isCacheValid(projectsCache)) {
1234
+ return projectsCache.data ?? [];
1235
+ }
1236
+ try {
1237
+ const db = getSharedFeaturesDb();
1238
+ let q = query(collection(db, COMMON_FEATURE_COLLECTIONS.PROJECTS), orderBy("order", "asc"));
1239
+ if (options.limit) {
1240
+ q = query(q, limit(options.limit));
1241
+ }
1242
+ const snapshot = await getDocs(q);
1243
+ let items = snapshot.docs.map((d) => docToProject(d.id, d.data()));
1244
+ if (options.activeOnly !== false) {
1245
+ items = items.filter((i) => i.isActive);
1246
+ }
1247
+ if (options.featuredOnly) {
1248
+ items = items.filter((i) => i.isFeatured);
1249
+ }
1250
+ if (options.category) {
1251
+ items = items.filter((i) => i.category === options.category);
1252
+ }
1253
+ if (options.status) {
1254
+ items = items.filter((i) => i.status === options.status);
1255
+ }
1256
+ if (!options.category && !options.status && !options.featuredOnly && !options.limit && options.activeOnly !== false) {
1257
+ projectsCache = { data: items, timestamp: Date.now() };
1258
+ }
1259
+ return items;
1260
+ } catch (error) {
1261
+ const config = getConfig();
1262
+ if (config.debug) console.error("[shared-features] Error fetching projects:", error);
1263
+ return projectsCache?.data ?? [];
1264
+ }
1265
+ }
1266
+ async function fetchProjectBySlug(slug) {
1267
+ if (!isFeatureEnabled("projects")) return null;
1268
+ try {
1269
+ if (isCacheValid(projectsCache)) {
1270
+ const cached = projectsCache.data?.find((p) => p.slug === slug);
1271
+ if (cached) return cached;
1272
+ }
1273
+ const projects = await fetchProjects();
1274
+ return projects.find((p) => p.slug === slug) ?? null;
1275
+ } catch (error) {
1276
+ const config = getConfig();
1277
+ if (config.debug) console.error("[shared-features] Error fetching project by slug:", error);
1278
+ return null;
1279
+ }
1280
+ }
1281
+ function clearProjectsCache() {
1282
+ projectsCache = null;
1283
+ }
1202
1284
  export {
1203
- getCampaignHistory as A,
1204
- getConfig as B,
1205
- getDeviceId as C,
1206
- getEligibleCampaignIds as D,
1207
- getFeatureVersion as E,
1208
- getProductById as F,
1209
- getSharedFeaturesApp as G,
1210
- getSharedFeaturesAuth as H,
1211
- getSharedFeaturesDb as I,
1212
- getSharedFeaturesStatus as J,
1213
- initSharedFeatures as K,
1214
- initializeFeatureFlags as L,
1215
- isEligibleForCampaign as M,
1216
- isFeatureEnabled as N,
1217
- isInitialized as O,
1218
- recordImpression as P,
1219
- subscribeToContactInfo as Q,
1220
- subscribeToDeveloperInfo as R,
1221
- subscribeToFeatureFlags as S,
1222
- trackClick as T,
1223
- trackClose as U,
1224
- trackImpression as V,
1225
- updateFeatureConfig as W,
1226
- updateGlobalFlags as X,
1227
- getState as Y,
1285
+ getState as $,
1286
+ fetchSocialLinks as A,
1287
+ fetchTestimonials as B,
1288
+ getCampaignById as C,
1289
+ getCampaignHistory as D,
1290
+ getConfig as E,
1291
+ getDeviceId as F,
1292
+ getEligibleCampaignIds as G,
1293
+ getFeatureVersion as H,
1294
+ getProductById as I,
1295
+ getSharedFeaturesApp as J,
1296
+ getSharedFeaturesAuth as K,
1297
+ getSharedFeaturesDb as L,
1298
+ getSharedFeaturesStatus as M,
1299
+ initSharedFeatures as N,
1300
+ initializeFeatureFlags as O,
1301
+ isEligibleForCampaign as P,
1302
+ isFeatureEnabled as Q,
1303
+ isInitialized as R,
1304
+ recordImpression as S,
1305
+ subscribeToContactInfo as T,
1306
+ subscribeToDeveloperInfo as U,
1307
+ subscribeToFeatureFlags as V,
1308
+ trackClick as W,
1309
+ trackClose as X,
1310
+ trackImpression as Y,
1311
+ updateFeatureConfig as Z,
1312
+ updateGlobalFlags as _,
1228
1313
  clearAddressInfoCache as a,
1229
1314
  clearAllCommonFeaturesCache as b,
1230
1315
  checkFeatureAvailability as c,
@@ -1234,22 +1319,22 @@ export {
1234
1319
  clearFeatureFlagsCache as g,
1235
1320
  clearPaymentOptionsCache as h,
1236
1321
  clearProductsCache as i,
1237
- clearServicesCache as j,
1238
- clearSkillsCache as k,
1239
- clearSocialLinksCache as l,
1240
- clearTestimonialsCache as m,
1241
- fetchActiveCampaigns as n,
1242
- fetchAddressInfo as o,
1243
- fetchCampaigns as p,
1244
- fetchContactInfo as q,
1245
- fetchDeveloperInfo as r,
1246
- fetchFeatureFlags as s,
1247
- fetchPaymentOptions as t,
1248
- fetchProducts as u,
1249
- fetchServices as v,
1250
- fetchSkills as w,
1251
- fetchSocialLinks as x,
1252
- fetchTestimonials as y,
1253
- getCampaignById as z
1322
+ clearProjectsCache as j,
1323
+ clearServicesCache as k,
1324
+ clearSkillsCache as l,
1325
+ clearSocialLinksCache as m,
1326
+ clearTestimonialsCache as n,
1327
+ fetchActiveCampaigns as o,
1328
+ fetchAddressInfo as p,
1329
+ fetchCampaigns as q,
1330
+ fetchContactInfo as r,
1331
+ fetchDeveloperInfo as s,
1332
+ fetchFeatureFlags as t,
1333
+ fetchPaymentOptions as u,
1334
+ fetchProducts as v,
1335
+ fetchProjectBySlug as w,
1336
+ fetchProjects as x,
1337
+ fetchServices as y,
1338
+ fetchSkills as z
1254
1339
  };
1255
- //# sourceMappingURL=commonFeatures-Bdt0UZox.js.map
1340
+ //# sourceMappingURL=commonFeatures-BNmLKLa9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commonFeatures-BNmLKLa9.js","sources":["../src/firebase/config.ts","../src/firebase/init.ts","../src/services/campaigns.ts","../src/services/analytics.ts","../src/services/featureFlags.ts","../src/services/commonFeatures.ts"],"sourcesContent":["/**\n * Firebase Configuration Types\n *\n * Types and interfaces for configuring the shared-features Firebase connection.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\n/**\n * Firebase configuration object\n */\nexport interface FirebaseConfig {\n apiKey: string;\n authDomain: string;\n projectId: string;\n storageBucket?: string;\n messagingSenderId?: string;\n appId?: string;\n measurementId?: string;\n}\n\n/**\n * Target platform for the consumer project\n */\nexport type ConsumerPlatform = 'web' | 'android' | 'ios' | 'extension';\n\n/**\n * Feature version preferences for a consumer project\n * Specifies which API versions the consumer supports\n */\nexport interface ConsumerFeatureVersions {\n /** Campaigns API version */\n campaigns?: number;\n /** Broadcasts API version */\n broadcasts?: number;\n /** Contact info API version */\n contactInfo?: number;\n /** Developer info API version */\n developerInfo?: number;\n /** Social links API version */\n socialLinks?: number;\n /** Payment options API version */\n paymentOptions?: number;\n /** Address info API version */\n addressInfo?: number;\n /** Services API version */\n services?: number;\n /** Skills API version */\n skills?: number;\n /** Testimonials API version */\n testimonials?: number;\n /** Projects API version */\n projects?: number;\n}\n\n/**\n * Configuration for initializing shared-features\n */\nexport interface SharedFeaturesConfig {\n /**\n * Firebase configuration for aoneahsan.com's Firebase project.\n * All values from environment variables.\n */\n firebaseConfig: FirebaseConfig;\n\n /**\n * Unique identifier for this project (e.g., 'ztools', '2fa-studio')\n */\n projectId: string;\n\n /**\n * Display name for this project (e.g., 'ZTools', '2FA Studio')\n */\n projectName: string;\n\n /**\n * Platform type for targeting\n */\n platform: ConsumerPlatform;\n\n /**\n * Whether to enable debug logging (default: false)\n */\n debug?: boolean;\n\n /**\n * Feature version preferences for this consumer.\n * Specifies which API versions this consumer supports.\n * If not specified, latest versions are used.\n */\n featureVersions?: ConsumerFeatureVersions;\n}\n\n/**\n * Internal state after initialization\n */\nexport interface SharedFeaturesState {\n /** Whether the package has been initialized */\n initialized: boolean;\n /** The active configuration */\n config: SharedFeaturesConfig | null;\n /** Unique device ID for anonymous tracking */\n deviceId: string | null;\n}\n\n/**\n * Singleton state\n */\nlet state: SharedFeaturesState = {\n initialized: false,\n config: null,\n deviceId: null,\n};\n\n/**\n * Get current state\n */\nexport function getState(): SharedFeaturesState {\n return state;\n}\n\n/**\n * Set state (internal use only)\n */\nexport function setState(newState: Partial<SharedFeaturesState>): void {\n state = { ...state, ...newState };\n}\n\n/**\n * Get configuration (throws if not initialized)\n */\nexport function getConfig(): SharedFeaturesConfig {\n if (!state.initialized || !state.config) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return state.config;\n}\n\n/**\n * Check if initialized\n */\nexport function isInitialized(): boolean {\n return state.initialized;\n}\n","/**\n * Firebase Initialization\n *\n * Initialize a secondary Firebase app for connecting to aoneahsan.com's\n * Firebase project from consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { initializeApp, getApps, FirebaseApp } from 'firebase/app';\nimport { getFirestore, Firestore } from 'firebase/firestore';\nimport { getAuth, Auth } from 'firebase/auth';\nimport {\n SharedFeaturesConfig,\n setState,\n getState,\n isInitialized,\n} from './config';\n\nconst SHARED_FEATURES_APP_NAME = 'shared-features';\n\nlet firebaseApp: FirebaseApp | null = null;\nlet firestoreDb: Firestore | null = null;\nlet firebaseAuth: Auth | null = null;\n\n/**\n * Generate a unique device ID for anonymous tracking\n */\nfunction generateDeviceId(): string {\n const timestamp = Date.now();\n const randomPart = Math.random().toString(36).substring(2, 15);\n return `device_${timestamp}_${randomPart}`;\n}\n\n/**\n * Get or create device ID from storage\n */\nasync function getOrCreateDeviceId(): Promise<string> {\n const STORAGE_KEY = 'shared_features_device_id';\n\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n const result = await Preferences.get({ key: STORAGE_KEY });\n if (result.value) {\n return result.value;\n }\n const newId = generateDeviceId();\n await Preferences.set({ key: STORAGE_KEY, value: newId });\n return newId;\n } catch {\n // Fallback to localStorage for web\n try {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (stored) {\n return stored;\n }\n const newId = generateDeviceId();\n localStorage.setItem(STORAGE_KEY, newId);\n return newId;\n } catch {\n // If all storage fails, generate ephemeral ID\n return generateDeviceId();\n }\n }\n}\n\n/**\n * Initialize shared-features with the given configuration.\n *\n * This creates a secondary Firebase app connection to aoneahsan.com's\n * Firebase project, separate from the consumer project's own Firebase.\n *\n * @param config - Configuration object with Firebase config and project info\n * @returns Initialized state object\n *\n * @example\n * ```typescript\n * import { initSharedFeatures } from 'shared-features';\n *\n * initSharedFeatures({\n * firebaseConfig: {\n * apiKey: import.meta.env.VITE_SHARED_FEATURES_API_KEY,\n * authDomain: import.meta.env.VITE_SHARED_FEATURES_AUTH_DOMAIN,\n * projectId: import.meta.env.VITE_SHARED_FEATURES_PROJECT_ID,\n * },\n * projectId: 'ztools',\n * projectName: 'ZTools',\n * platform: 'web',\n * });\n * ```\n */\nexport async function initSharedFeatures(\n config: SharedFeaturesConfig\n): Promise<{ app: FirebaseApp; db: Firestore; auth: Auth }> {\n // Return existing instance if already initialized with same config\n if (isInitialized() && firebaseApp && firestoreDb && firebaseAuth) {\n const currentConfig = getState().config;\n if (\n currentConfig &&\n currentConfig.firebaseConfig.projectId === config.firebaseConfig.projectId\n ) {\n return { app: firebaseApp, db: firestoreDb, auth: firebaseAuth };\n }\n }\n\n // Check if app already exists\n const existingApps = getApps();\n const existingApp = existingApps.find(\n (app) => app.name === SHARED_FEATURES_APP_NAME\n );\n\n if (existingApp) {\n firebaseApp = existingApp;\n } else {\n // Initialize new Firebase app with config from environment variables\n firebaseApp = initializeApp(config.firebaseConfig, SHARED_FEATURES_APP_NAME);\n }\n\n // Get Firestore and Auth instances\n firestoreDb = getFirestore(firebaseApp);\n firebaseAuth = getAuth(firebaseApp);\n\n // Get or create device ID\n const deviceId = await getOrCreateDeviceId();\n\n // Update state\n setState({\n initialized: true,\n config,\n deviceId,\n });\n\n if (config.debug) {\n console.log('[shared-features] Initialized:', {\n projectId: config.projectId,\n projectName: config.projectName,\n platform: config.platform,\n deviceId,\n });\n }\n\n return { app: firebaseApp, db: firestoreDb, auth: firebaseAuth };\n}\n\n/**\n * Get the shared-features Firebase app instance.\n * Throws if not initialized.\n */\nexport function getSharedFeaturesApp(): FirebaseApp {\n if (!firebaseApp) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return firebaseApp;\n}\n\n/**\n * Get the shared-features Firestore instance.\n * Throws if not initialized.\n */\nexport function getSharedFeaturesDb(): Firestore {\n if (!firestoreDb) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return firestoreDb;\n}\n\n/**\n * Get the shared-features Auth instance.\n * Throws if not initialized.\n */\nexport function getSharedFeaturesAuth(): Auth {\n if (!firebaseAuth) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return firebaseAuth;\n}\n\n/**\n * Get the device ID for anonymous tracking.\n * Returns null if not initialized.\n */\nexport function getDeviceId(): string | null {\n return getState().deviceId;\n}\n","/**\n * Campaigns Service\n *\n * Handles fetching and displaying advertising campaigns from the\n * centralized aoneahsan.com Firebase backend.\n *\n * Consumer projects only need read access - all campaign management\n * is done through the aoneahsan.com admin panel.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n doc,\n getDocs,\n getDoc,\n query,\n where,\n orderBy,\n limit as firestoreLimit,\n Timestamp,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig } from '../firebase/config';\nimport type {\n Campaign,\n CampaignWithProduct,\n Product,\n AdPlacement,\n CampaignStatus,\n FetchCampaignsOptions,\n} from '../types/campaigns';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_CAMPAIGNS = 'zaions_campaigns';\nconst COLLECTION_PRODUCTS = 'zaions_products';\n\n// Cache for campaigns\ninterface CampaignsCache {\n data: Campaign[];\n timestamp: number;\n}\nlet campaignsCache: CampaignsCache | null = null;\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\n// Products cache\nlet productsCache: Map<string, Product> | null = null;\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Check if cache is valid\n */\nfunction isCacheValid(): boolean {\n if (!campaignsCache) return false;\n return Date.now() - campaignsCache.timestamp < CACHE_TTL_MS;\n}\n\n/**\n * Convert Firestore document to Campaign\n */\nfunction docToCampaign(\n docId: string,\n data: Record<string, unknown>\n): Campaign {\n return {\n id: docId,\n productId: data.productId as string,\n name: data.name as string,\n status: data.status as CampaignStatus,\n targetPlatforms: data.targetPlatforms as Campaign['targetPlatforms'],\n targetAudience: data.targetAudience as Campaign['targetAudience'],\n targetProjects: (data.targetProjects as string[]) || [],\n excludeProductUsers: data.excludeProductUsers as boolean,\n placements: data.placements as AdPlacement[],\n priority: data.priority as number,\n frequencyDays: data.frequencyDays as number,\n maxImpressions: data.maxImpressions as number | null,\n startDate: data.startDate as Timestamp,\n endDate: data.endDate as Timestamp | null,\n variant: data.variant as Campaign['variant'],\n customTitle: data.customTitle as string | undefined,\n customTagline: data.customTagline as string | undefined,\n customCta: data.customCta as string | undefined,\n customCtaUrl: data.customCtaUrl as string | undefined,\n customDescription: data.customDescription as string | undefined,\n customProductColor: data.customProductColor as string | undefined,\n customIcon: data.customIcon as string | undefined,\n customFeatures: data.customFeatures as string[] | undefined,\n totalImpressions: data.totalImpressions as number,\n totalClicks: data.totalClicks as number,\n totalCloses: data.totalCloses as number,\n createdAt: data.createdAt as Timestamp,\n updatedAt: data.updatedAt as Timestamp,\n createdBy: data.createdBy as string,\n updatedBy: data.updatedBy as string | undefined,\n };\n}\n\n/**\n * Convert Firestore document to Product\n */\nfunction docToProduct(docId: string, data: Record<string, unknown>): Product {\n return {\n id: docId,\n name: data.name as string,\n tagline: data.tagline as string,\n description: data.description as string,\n type: data.type as Product['type'],\n url: data.url as string,\n color: data.color as string,\n features: (data.features as string[]) || [],\n icon64: data.icon64 as string | undefined,\n icon128: data.icon128 as string | undefined,\n chromeStoreUrl: data.chromeStoreUrl as string | undefined,\n playStoreUrl: data.playStoreUrl as string | undefined,\n appStoreUrl: data.appStoreUrl as string | undefined,\n webUrl: data.webUrl as string | undefined,\n enabled: data.enabled as boolean,\n createdAt: data.createdAt as Timestamp,\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\n// ============================================================================\n// PRODUCTS\n// ============================================================================\n\n/**\n * Fetch all products from Firestore\n */\nexport async function fetchProducts(): Promise<Product[]> {\n const db = getSharedFeaturesDb();\n const snapshot = await getDocs(collection(db, COLLECTION_PRODUCTS));\n const products = snapshot.docs.map((d) => docToProduct(d.id, d.data()));\n\n // Update cache\n productsCache = new Map(products.map((p) => [p.id, p]));\n\n return products;\n}\n\n/**\n * Get a product by ID (uses cache if available)\n */\nexport async function getProductById(\n productId: string\n): Promise<Product | null> {\n // Check cache first\n if (productsCache?.has(productId)) {\n return productsCache.get(productId) || null;\n }\n\n // Fetch from Firestore\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_PRODUCTS, productId));\n\n if (!docSnap.exists()) return null;\n\n const product = docToProduct(docSnap.id, docSnap.data());\n\n // Add to cache\n if (!productsCache) productsCache = new Map();\n productsCache.set(productId, product);\n\n return product;\n}\n\n// ============================================================================\n// CAMPAIGNS\n// ============================================================================\n\n/**\n * Fetch campaigns with optional filters\n */\nexport async function fetchCampaigns(\n options: FetchCampaignsOptions = {}\n): Promise<Campaign[]> {\n const config = getConfig();\n const db = getSharedFeaturesDb();\n\n // Check cache first (only for unfiltered queries)\n if (\n !options.placement &&\n !options.status &&\n !options.productId &&\n isCacheValid()\n ) {\n return campaignsCache!.data;\n }\n\n let q = query(collection(db, COLLECTION_CAMPAIGNS));\n\n // Apply filters\n if (options.status) {\n q = query(q, where('status', '==', options.status));\n }\n\n if (options.productId) {\n q = query(q, where('productId', '==', options.productId));\n }\n\n q = query(q, orderBy('priority', 'desc'));\n\n if (options.limit) {\n q = query(q, firestoreLimit(options.limit));\n }\n\n const snapshot = await getDocs(q);\n let campaigns = snapshot.docs.map((d) => docToCampaign(d.id, d.data()));\n\n // Client-side filtering for array fields\n if (options.placement) {\n campaigns = campaigns.filter((c) =>\n c.placements.includes(options.placement!)\n );\n }\n\n // Filter by target platform (from config)\n campaigns = campaigns.filter(\n (c) =>\n c.targetPlatforms.includes(config.platform) ||\n c.targetPlatforms.length === 0\n );\n\n // Filter by target projects (empty = all projects)\n campaigns = campaigns.filter(\n (c) =>\n c.targetProjects.length === 0 ||\n c.targetProjects.includes(config.projectId)\n );\n\n // Cache unfiltered results\n if (!options.placement && !options.status && !options.productId) {\n campaignsCache = {\n data: campaigns,\n timestamp: Date.now(),\n };\n }\n\n return campaigns;\n}\n\n/**\n * Fetch active campaigns for a specific placement\n * Returns campaigns with resolved product data\n */\nexport async function fetchActiveCampaigns(\n placement: AdPlacement\n): Promise<CampaignWithProduct[]> {\n const now = Timestamp.now();\n\n const campaigns = await fetchCampaigns({ status: 'active' });\n\n // Ensure products are loaded\n if (!productsCache || productsCache.size === 0) {\n await fetchProducts();\n }\n\n // Filter by placement and date range\n const eligible = campaigns.filter((c) => {\n // Placement check\n if (!c.placements.includes(placement)) return false;\n\n // Date range check\n if (c.startDate.toMillis() > now.toMillis()) return false;\n if (c.endDate && c.endDate.toMillis() < now.toMillis()) return false;\n\n // Max impressions check\n if (c.maxImpressions !== null && c.totalImpressions >= c.maxImpressions)\n return false;\n\n return true;\n });\n\n // Resolve products\n const result: CampaignWithProduct[] = [];\n\n for (const campaign of eligible) {\n const product = await getProductById(campaign.productId);\n if (product && product.enabled) {\n result.push({ ...campaign, product });\n }\n }\n\n return result;\n}\n\n/**\n * Get a single campaign by ID\n */\nexport async function getCampaignById(\n campaignId: string\n): Promise<Campaign | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_CAMPAIGNS, campaignId));\n\n if (!docSnap.exists()) return null;\n return docToCampaign(docSnap.id, docSnap.data());\n}\n\n/**\n * Clear the campaigns cache (useful for manual refresh)\n */\nexport function clearCampaignsCache(): void {\n campaignsCache = null;\n}\n\n/**\n * Clear the products cache\n */\nexport function clearProductsCache(): void {\n productsCache = null;\n}\n","/**\n * Analytics Service\n *\n * Handles recording impressions, clicks, and other ad interactions\n * from consumer projects to the centralized aoneahsan.com Firebase.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n addDoc,\n serverTimestamp,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig, getState } from '../firebase/config';\nimport type {\n AdAction,\n AdPlacement,\n AdVariant,\n RecordImpressionInput,\n AdHistoryEntry,\n} from '../types/campaigns';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_IMPRESSIONS = 'zaions_impressions';\nconst LOCAL_STORAGE_KEY = 'shared_features_ad_history';\n\n// ============================================================================\n// LOCAL HISTORY MANAGEMENT\n// ============================================================================\n\n/**\n * Get ad history from local storage\n */\nasync function getLocalAdHistory(): Promise<Record<string, AdHistoryEntry>> {\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 return JSON.parse(result.value);\n }\n } catch {\n // Fallback to localStorage\n try {\n const stored = localStorage.getItem(LOCAL_STORAGE_KEY);\n if (stored) {\n return JSON.parse(stored);\n }\n } catch {\n // Ignore\n }\n }\n return {};\n}\n\n/**\n * Save ad history to local storage\n */\nasync function saveLocalAdHistory(\n history: Record<string, AdHistoryEntry>\n): Promise<void> {\n const serialized = JSON.stringify(history);\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 * Update local ad history for a campaign\n */\nasync function updateLocalHistory(\n campaignId: string,\n productId: string,\n action: AdAction,\n frequencyDays: number\n): Promise<void> {\n const history = await getLocalAdHistory();\n const now = Date.now();\n const nextEligibleAt = now + frequencyDays * 24 * 60 * 60 * 1000;\n\n const existing = history[campaignId];\n\n if (existing) {\n history[campaignId] = {\n ...existing,\n lastSeenAt: now,\n impressionCount:\n action === 'impression'\n ? existing.impressionCount + 1\n : existing.impressionCount,\n clicked: existing.clicked || action === 'click',\n closed: existing.closed || action === 'close',\n nextEligibleAt:\n action === 'impression' ? nextEligibleAt : existing.nextEligibleAt,\n };\n } else {\n history[campaignId] = {\n campaignId,\n productId,\n lastSeenAt: now,\n impressionCount: action === 'impression' ? 1 : 0,\n clicked: action === 'click',\n closed: action === 'close',\n nextEligibleAt,\n };\n }\n\n await saveLocalAdHistory(history);\n}\n\n// ============================================================================\n// ELIGIBILITY CHECKING\n// ============================================================================\n\n/**\n * Check if user is eligible to see a campaign (based on frequency capping)\n */\nexport async function isEligibleForCampaign(\n campaignId: string,\n _frequencyDays: number = 20\n): Promise<boolean> {\n // Note: _frequencyDays kept for API consistency; eligibility uses stored nextEligibleAt\n const history = await getLocalAdHistory();\n const entry = history[campaignId];\n\n if (!entry) return true;\n\n return Date.now() >= entry.nextEligibleAt;\n}\n\n/**\n * Get all campaigns the user is currently eligible to see\n */\nexport async function getEligibleCampaignIds(): Promise<string[]> {\n const history = await getLocalAdHistory();\n const now = Date.now();\n\n return Object.entries(history)\n .filter(([, entry]) => now >= entry.nextEligibleAt)\n .map(([campaignId]) => campaignId);\n}\n\n/**\n * Get local ad history for a campaign\n */\nexport async function getCampaignHistory(\n campaignId: string\n): Promise<AdHistoryEntry | null> {\n const history = await getLocalAdHistory();\n return history[campaignId] || null;\n}\n\n// ============================================================================\n// IMPRESSION RECORDING\n// ============================================================================\n\n/**\n * Record an ad impression, click, or close\n *\n * This sends the event to the centralized Firebase and updates local history.\n */\nexport async function recordImpression(\n input: RecordImpressionInput,\n frequencyDays: number = 20\n): Promise<void> {\n const config = getConfig();\n const state = getState();\n const db = getSharedFeaturesDb();\n\n const impressionData = {\n campaignId: input.campaignId,\n productId: input.productId,\n projectId: config.projectId,\n userId: null, // Consumer projects don't authenticate with aoneahsan.com\n deviceId: state.deviceId || 'unknown',\n platform: config.platform,\n placement: input.placement,\n action: input.action,\n variant: input.variant,\n timestamp: serverTimestamp(),\n ...(input.sessionId && { sessionId: input.sessionId }),\n };\n\n try {\n // Record to centralized Firebase\n await addDoc(collection(db, COLLECTION_IMPRESSIONS), impressionData);\n\n if (config.debug) {\n console.log('[shared-features] Recorded impression:', impressionData);\n }\n } catch (error) {\n if (config.debug) {\n console.error('[shared-features] Failed to record impression:', error);\n }\n // Don't throw - we don't want analytics failures to break the UI\n }\n\n // Update local history for frequency capping\n await updateLocalHistory(\n input.campaignId,\n input.productId,\n input.action,\n frequencyDays\n );\n}\n\n/**\n * Convenience function to record an impression\n */\nexport async function trackImpression(\n campaignId: string,\n productId: string,\n placement: AdPlacement,\n variant: AdVariant,\n frequencyDays?: number\n): Promise<void> {\n await recordImpression(\n {\n campaignId,\n productId,\n placement,\n action: 'impression',\n variant,\n },\n frequencyDays\n );\n}\n\n/**\n * Convenience function to record a click\n */\nexport async function trackClick(\n campaignId: string,\n productId: string,\n placement: AdPlacement,\n variant: AdVariant\n): Promise<void> {\n await recordImpression({\n campaignId,\n productId,\n placement,\n action: 'click',\n variant,\n });\n}\n\n/**\n * Convenience function to record a close/dismiss\n */\nexport async function trackClose(\n campaignId: string,\n productId: string,\n placement: AdPlacement,\n variant: AdVariant\n): Promise<void> {\n await recordImpression({\n campaignId,\n productId,\n placement,\n action: 'close',\n variant,\n });\n}\n","/**\n * Feature Flags Service\n *\n * Handles fetching and checking feature flags from the centralized\n * aoneahsan.com Firebase backend. Consumer projects use this to\n * determine which features are available and at what version.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n doc,\n getDoc,\n setDoc,\n onSnapshot,\n Timestamp,\n Unsubscribe,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig } from '../firebase/config';\nimport type {\n FeatureId,\n FeatureConfig,\n FeatureFlagsDocument,\n FeatureAvailability,\n SharedFeaturesStatus,\n UpdateFeatureConfigInput,\n UpdateGlobalFlagsInput,\n ConsumerFeatureVersions,\n} from '../types/featureFlags';\nimport {\n COLLECTION_FEATURE_FLAGS,\n FEATURE_FLAGS_DOC_ID,\n DEFAULT_FEATURE_FLAGS,\n} from '../types/featureFlags';\n\n// Re-export constants\nexport { COLLECTION_FEATURE_FLAGS, FEATURE_FLAGS_DOC_ID } from '../types/featureFlags';\n\n// ============================================================================\n// CACHE\n// ============================================================================\n\ninterface FeatureFlagsCache {\n data: FeatureFlagsDocument | null;\n timestamp: number;\n}\n\nlet flagsCache: FeatureFlagsCache | null = null;\nconst CACHE_TTL_MS = 2 * 60 * 1000; // 2 minutes (shorter than campaigns for fresher data)\n\n/**\n * Check if cache is valid\n */\nfunction isCacheValid(): boolean {\n if (!flagsCache || !flagsCache.data) return false;\n return Date.now() - flagsCache.timestamp < CACHE_TTL_MS;\n}\n\n/**\n * Clear the feature flags cache\n */\nexport function clearFeatureFlagsCache(): void {\n flagsCache = null;\n}\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Convert Firestore document to FeatureFlagsDocument\n */\nfunction docToFeatureFlags(\n docId: string,\n data: Record<string, unknown>\n): FeatureFlagsDocument {\n return {\n id: docId,\n globalEnabled: (data.globalEnabled as boolean) ?? true,\n currentApiVersion: (data.currentApiVersion as string) ?? 'v1',\n supportedApiVersions: (data.supportedApiVersions as string[]) ?? ['v1'],\n features: data.features as FeatureFlagsDocument['features'],\n maintenanceMode: (data.maintenanceMode as boolean) ?? false,\n maintenanceMessage: data.maintenanceMessage as string | undefined,\n maintenanceEndTime: data.maintenanceEndTime as Timestamp | undefined,\n updatedAt: data.updatedAt as Timestamp,\n updatedBy: data.updatedBy as string,\n };\n}\n\n/**\n * Get the consumer's requested version for a feature\n */\nfunction getConsumerVersion(\n featureId: FeatureId,\n consumerVersions?: ConsumerFeatureVersions\n): number | undefined {\n if (!consumerVersions) return undefined;\n return consumerVersions[featureId];\n}\n\n// ============================================================================\n// FETCH FUNCTIONS\n// ============================================================================\n\n/**\n * Fetch feature flags from Firestore\n *\n * @param forceRefresh - Skip cache and fetch fresh data\n * @returns Feature flags document or null if not found\n */\nexport async function fetchFeatureFlags(\n forceRefresh = false\n): Promise<FeatureFlagsDocument | null> {\n // Check cache first\n if (!forceRefresh && isCacheValid()) {\n return flagsCache!.data;\n }\n\n try {\n const db = getSharedFeaturesDb();\n const docRef = doc(db, COLLECTION_FEATURE_FLAGS, FEATURE_FLAGS_DOC_ID);\n const docSnap = await getDoc(docRef);\n\n if (!docSnap.exists()) {\n // Return default flags if document doesn't exist\n const config = getConfig();\n if (config.debug) {\n console.log(\n '[shared-features] Feature flags document not found, using defaults'\n );\n }\n\n // Create default document for first-time setup\n const defaultDoc: FeatureFlagsDocument = {\n ...DEFAULT_FEATURE_FLAGS,\n updatedAt: Timestamp.now(),\n updatedBy: 'system',\n } as FeatureFlagsDocument;\n\n flagsCache = {\n data: defaultDoc,\n timestamp: Date.now(),\n };\n\n return defaultDoc;\n }\n\n const flags = docToFeatureFlags(docSnap.id, docSnap.data());\n\n // Update cache\n flagsCache = {\n data: flags,\n timestamp: Date.now(),\n };\n\n const config = getConfig();\n if (config.debug) {\n console.log('[shared-features] Feature flags fetched:', {\n globalEnabled: flags.globalEnabled,\n maintenanceMode: flags.maintenanceMode,\n apiVersion: flags.currentApiVersion,\n });\n }\n\n return flags;\n } catch (error) {\n const config = getConfig();\n if (config.debug) {\n console.error('[shared-features] Error fetching feature flags:', error);\n }\n\n // Return cached data if available, even if stale\n if (flagsCache?.data) {\n return flagsCache.data;\n }\n\n return null;\n }\n}\n\n/**\n * Subscribe to real-time feature flag updates\n *\n * @param callback - Function to call when flags change\n * @returns Unsubscribe function\n */\nexport function subscribeToFeatureFlags(\n callback: (flags: FeatureFlagsDocument | null) => void\n): Unsubscribe {\n const db = getSharedFeaturesDb();\n const docRef = doc(db, COLLECTION_FEATURE_FLAGS, FEATURE_FLAGS_DOC_ID);\n\n return onSnapshot(\n docRef,\n (docSnap) => {\n if (!docSnap.exists()) {\n callback(null);\n return;\n }\n\n const flags = docToFeatureFlags(docSnap.id, docSnap.data());\n\n // Update cache\n flagsCache = {\n data: flags,\n timestamp: Date.now(),\n };\n\n callback(flags);\n },\n (error) => {\n const config = getConfig();\n if (config.debug) {\n console.error(\n '[shared-features] Error subscribing to feature flags:',\n error\n );\n }\n callback(null);\n }\n );\n}\n\n// ============================================================================\n// FEATURE AVAILABILITY CHECKS\n// ============================================================================\n\n/**\n * Check if a specific feature is available\n *\n * @param featureId - The feature to check\n * @param consumerVersions - Optional consumer version preferences\n * @returns Availability details\n */\nexport async function checkFeatureAvailability(\n featureId: FeatureId,\n consumerVersions?: ConsumerFeatureVersions\n): Promise<FeatureAvailability> {\n const flags = await fetchFeatureFlags();\n const config = getConfig();\n\n // Default unavailable response\n const unavailable: FeatureAvailability = {\n available: false,\n enabled: false,\n version: 1,\n deprecated: false,\n upgradeRequired: false,\n unavailableReason: 'Feature flags not loaded',\n };\n\n if (!flags) {\n return unavailable;\n }\n\n // Check global enabled\n if (!flags.globalEnabled) {\n return {\n ...unavailable,\n unavailableReason: 'shared-features is globally disabled',\n };\n }\n\n // Check maintenance mode\n if (flags.maintenanceMode) {\n return {\n ...unavailable,\n unavailableReason: flags.maintenanceMessage || 'Maintenance in progress',\n };\n }\n\n // Get feature config\n const featureConfig = flags.features[featureId];\n if (!featureConfig) {\n return {\n ...unavailable,\n unavailableReason: `Feature '${featureId}' not found in configuration`,\n };\n }\n\n // Check if feature is enabled\n if (!featureConfig.enabled) {\n return {\n ...unavailable,\n version: featureConfig.version,\n unavailableReason: `Feature '${featureId}' is not enabled`,\n };\n }\n\n // Check platform availability\n if (\n featureConfig.availablePlatforms &&\n featureConfig.availablePlatforms.length > 0\n ) {\n if (!featureConfig.availablePlatforms.includes(config.platform)) {\n return {\n ...unavailable,\n version: featureConfig.version,\n unavailableReason: `Feature '${featureId}' is not available on platform '${config.platform}'`,\n };\n }\n }\n\n // Check project availability\n if (\n featureConfig.availableProjects &&\n featureConfig.availableProjects.length > 0\n ) {\n if (!featureConfig.availableProjects.includes(config.projectId)) {\n return {\n ...unavailable,\n version: featureConfig.version,\n unavailableReason: `Feature '${featureId}' is not available for project '${config.projectId}'`,\n };\n }\n }\n\n // Check consumer version compatibility\n const consumerVersion = getConsumerVersion(featureId, consumerVersions);\n const currentVersion = featureConfig.version;\n const minVersion = featureConfig.minVersion;\n const maxVersion = featureConfig.maxVersion;\n\n let deprecated = false;\n let deprecationWarning: string | undefined;\n let upgradeRequired = false;\n let upgradeMessage: string | undefined;\n\n if (consumerVersion !== undefined) {\n // Consumer specified a version\n if (consumerVersion < minVersion) {\n // Version too old, upgrade required\n upgradeRequired = true;\n upgradeMessage = `Feature '${featureId}' requires minimum version ${minVersion}, but consumer is using version ${consumerVersion}. Please upgrade.`;\n } else if (consumerVersion < currentVersion) {\n // Version is older but still supported\n deprecated = true;\n deprecationWarning =\n featureConfig.deprecationMessage ||\n `Feature '${featureId}' version ${consumerVersion} is deprecated. Current version is ${currentVersion}.`;\n } else if (consumerVersion > maxVersion) {\n // Version too new (edge case)\n return {\n ...unavailable,\n version: currentVersion,\n unavailableReason: `Feature '${featureId}' version ${consumerVersion} is not yet supported. Maximum supported version is ${maxVersion}.`,\n };\n }\n }\n\n // Feature is available\n return {\n available: !upgradeRequired,\n enabled: true,\n version: currentVersion,\n deprecated,\n deprecationWarning,\n upgradeRequired,\n upgradeMessage,\n };\n}\n\n/**\n * Quick check if a feature is available (cached)\n *\n * @param featureId - The feature to check\n * @returns true if feature is available\n */\nexport function isFeatureEnabled(featureId: FeatureId): boolean {\n // Use cached data for quick checks\n if (!flagsCache?.data) return false;\n\n const flags = flagsCache.data;\n\n if (!flags.globalEnabled || flags.maintenanceMode) return false;\n\n const featureConfig = flags.features[featureId];\n if (!featureConfig || !featureConfig.enabled) return false;\n\n // Quick platform/project check\n const config = getConfig();\n\n if (\n featureConfig.availablePlatforms &&\n featureConfig.availablePlatforms.length > 0\n ) {\n if (!featureConfig.availablePlatforms.includes(config.platform)) {\n return false;\n }\n }\n\n if (\n featureConfig.availableProjects &&\n featureConfig.availableProjects.length > 0\n ) {\n if (!featureConfig.availableProjects.includes(config.projectId)) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Get the current version of a feature\n *\n * @param featureId - The feature to check\n * @returns Current version number or 0 if not available\n */\nexport function getFeatureVersion(featureId: FeatureId): number {\n if (!flagsCache?.data) return 0;\n return flagsCache.data.features[featureId]?.version ?? 0;\n}\n\n// ============================================================================\n// STATUS FUNCTIONS\n// ============================================================================\n\n/**\n * Get overall status of shared-features for the current consumer\n *\n * @param consumerVersions - Optional consumer version preferences\n * @returns Complete status object\n */\nexport async function getSharedFeaturesStatus(\n consumerVersions?: ConsumerFeatureVersions\n): Promise<SharedFeaturesStatus> {\n const flags = await fetchFeatureFlags();\n\n const defaultStatus: SharedFeaturesStatus = {\n operational: false,\n maintenanceMode: false,\n apiVersion: 'v1',\n features: {} as Record<FeatureId, FeatureAvailability>,\n deprecatedFeatures: [],\n upgradeRequiredFeatures: [],\n fetchedAt: new Date(),\n };\n\n if (!flags) {\n return defaultStatus;\n }\n\n // Check all features\n const featureIds: FeatureId[] = [\n 'campaigns',\n 'broadcasts',\n 'contactInfo',\n 'developerInfo',\n 'socialLinks',\n 'paymentOptions',\n 'addressInfo',\n 'services',\n 'skills',\n 'testimonials',\n 'projects',\n ];\n\n const features: Record<FeatureId, FeatureAvailability> = {} as Record<\n FeatureId,\n FeatureAvailability\n >;\n const deprecatedFeatures: FeatureId[] = [];\n const upgradeRequiredFeatures: FeatureId[] = [];\n\n for (const featureId of featureIds) {\n const availability = await checkFeatureAvailability(\n featureId,\n consumerVersions\n );\n features[featureId] = availability;\n\n if (availability.deprecated) {\n deprecatedFeatures.push(featureId);\n }\n\n if (availability.upgradeRequired) {\n upgradeRequiredFeatures.push(featureId);\n }\n }\n\n return {\n operational: flags.globalEnabled && !flags.maintenanceMode,\n maintenanceMode: flags.maintenanceMode,\n maintenanceMessage: flags.maintenanceMessage,\n apiVersion: flags.currentApiVersion,\n features,\n deprecatedFeatures,\n upgradeRequiredFeatures,\n fetchedAt: new Date(),\n };\n}\n\n// ============================================================================\n// ADMIN FUNCTIONS\n// ============================================================================\n\n/**\n * Update a feature's configuration (admin only)\n *\n * @param input - Feature configuration updates\n * @param adminEmail - Email of admin making the change\n */\nexport async function updateFeatureConfig(\n input: UpdateFeatureConfigInput,\n adminEmail: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n const docRef = doc(db, COLLECTION_FEATURE_FLAGS, FEATURE_FLAGS_DOC_ID);\n\n // Fetch current document\n const docSnap = await getDoc(docRef);\n let currentFlags: FeatureFlagsDocument;\n\n if (!docSnap.exists()) {\n // Create with defaults\n currentFlags = {\n ...DEFAULT_FEATURE_FLAGS,\n updatedAt: Timestamp.now(),\n updatedBy: adminEmail,\n } as FeatureFlagsDocument;\n } else {\n currentFlags = docToFeatureFlags(docSnap.id, docSnap.data());\n }\n\n // Update the specific feature\n const featureConfig = currentFlags.features[input.featureId];\n const updatedFeature: FeatureConfig = {\n enabled: input.enabled ?? featureConfig.enabled,\n version: input.version ?? featureConfig.version,\n minVersion: input.minVersion ?? featureConfig.minVersion,\n maxVersion: input.maxVersion ?? featureConfig.maxVersion,\n deprecationMessage: input.deprecationMessage ?? featureConfig.deprecationMessage,\n requiresAuth: input.requiresAuth ?? featureConfig.requiresAuth,\n availablePlatforms: input.availablePlatforms ?? featureConfig.availablePlatforms,\n availableProjects: input.availableProjects ?? featureConfig.availableProjects,\n };\n\n // Update document\n await setDoc(\n docRef,\n {\n ...currentFlags,\n features: {\n ...currentFlags.features,\n [input.featureId]: updatedFeature,\n },\n updatedAt: Timestamp.now(),\n updatedBy: adminEmail,\n },\n { merge: true }\n );\n\n // Clear cache\n clearFeatureFlagsCache();\n}\n\n/**\n * Update global flags (admin only)\n *\n * @param input - Global flag updates\n * @param adminEmail - Email of admin making the change\n */\nexport async function updateGlobalFlags(\n input: UpdateGlobalFlagsInput,\n adminEmail: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n const docRef = doc(db, COLLECTION_FEATURE_FLAGS, FEATURE_FLAGS_DOC_ID);\n\n const updateData: Record<string, unknown> = {\n updatedAt: Timestamp.now(),\n updatedBy: adminEmail,\n };\n\n if (input.globalEnabled !== undefined) {\n updateData.globalEnabled = input.globalEnabled;\n }\n\n if (input.currentApiVersion !== undefined) {\n updateData.currentApiVersion = input.currentApiVersion;\n }\n\n if (input.supportedApiVersions !== undefined) {\n updateData.supportedApiVersions = input.supportedApiVersions;\n }\n\n if (input.maintenanceMode !== undefined) {\n updateData.maintenanceMode = input.maintenanceMode;\n }\n\n if (input.maintenanceMessage !== undefined) {\n updateData.maintenanceMessage = input.maintenanceMessage;\n }\n\n if (input.maintenanceEndTime !== undefined) {\n updateData.maintenanceEndTime = input.maintenanceEndTime\n ? Timestamp.fromDate(input.maintenanceEndTime)\n : null;\n }\n\n await setDoc(docRef, updateData, { merge: true });\n\n // Clear cache\n clearFeatureFlagsCache();\n}\n\n/**\n * Initialize feature flags document with defaults (admin only)\n *\n * @param adminEmail - Email of admin creating the document\n */\nexport async function initializeFeatureFlags(\n adminEmail: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n const docRef = doc(db, COLLECTION_FEATURE_FLAGS, FEATURE_FLAGS_DOC_ID);\n\n const docSnap = await getDoc(docRef);\n\n if (docSnap.exists()) {\n throw new Error('Feature flags document already exists');\n }\n\n await setDoc(docRef, {\n ...DEFAULT_FEATURE_FLAGS,\n updatedAt: Timestamp.now(),\n updatedBy: adminEmail,\n });\n\n // Clear cache\n clearFeatureFlagsCache();\n}\n","/**\n * Common Features Service\n *\n * Handles fetching common features from the centralized\n * aoneahsan.com Firebase backend.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n doc,\n getDoc,\n getDocs,\n query,\n orderBy,\n limit as firestoreLimit,\n onSnapshot,\n Timestamp,\n Unsubscribe,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig } from '../firebase/config';\nimport { isFeatureEnabled } from './featureFlags';\nimport {\n COMMON_FEATURE_COLLECTIONS,\n type ContactInfo,\n type DeveloperInfo,\n type SocialLink,\n type AddressInfo,\n type PaymentOption,\n type Service,\n type Skill,\n type Testimonial,\n type Project,\n type FetchSocialLinksOptions,\n type FetchServicesOptions,\n type FetchSkillsOptions,\n type FetchTestimonialsOptions,\n type FetchPaymentOptionsOptions,\n type FetchProjectsOptions,\n} from '../types/commonFeatures';\n\n// ============================================================================\n// CACHE\n// ============================================================================\n\ninterface Cache<T> {\n data: T | null;\n timestamp: number;\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\nlet contactInfoCache: Cache<ContactInfo> | null = null;\nlet developerInfoCache: Cache<DeveloperInfo> | null = null;\nlet addressInfoCache: Cache<AddressInfo> | null = null;\nlet socialLinksCache: Cache<SocialLink[]> | null = null;\nlet paymentOptionsCache: Cache<PaymentOption[]> | null = null;\nlet servicesCache: Cache<Service[]> | null = null;\nlet skillsCache: Cache<Skill[]> | null = null;\nlet testimonialsCache: Cache<Testimonial[]> | null = null;\nlet projectsCache: Cache<Project[]> | null = null;\n\nfunction isCacheValid<T>(cache: Cache<T> | null): boolean {\n if (!cache || !cache.data) return false;\n return Date.now() - cache.timestamp < CACHE_TTL_MS;\n}\n\nexport function clearAllCommonFeaturesCache(): void {\n contactInfoCache = null;\n developerInfoCache = null;\n addressInfoCache = null;\n socialLinksCache = null;\n paymentOptionsCache = null;\n servicesCache = null;\n skillsCache = null;\n testimonialsCache = null;\n projectsCache = null;\n}\n\n// ============================================================================\n// CONTACT INFO\n// ============================================================================\n\nfunction docToContactInfo(docId: string, data: Record<string, unknown>): ContactInfo {\n return {\n id: docId,\n email: (data.email as string) || '',\n supportEmail: data.supportEmail as string | undefined,\n phone: data.phone as string | undefined,\n whatsapp: data.whatsapp as string | undefined,\n telegram: data.telegram as string | undefined,\n skype: data.skype as string | undefined,\n freelanceAvailable: (data.freelanceAvailable as boolean) ?? false,\n workingHours: data.workingHours as string | undefined,\n timezone: data.timezone as string | undefined,\n preferredContact: data.preferredContact as ContactInfo['preferredContact'],\n responseTime: data.responseTime as string | undefined,\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\nexport async function fetchContactInfo(): Promise<ContactInfo | null> {\n if (!isFeatureEnabled('contactInfo')) {\n const config = getConfig();\n if (config.debug) console.log('[shared-features] contactInfo feature is disabled');\n return null;\n }\n\n if (isCacheValid(contactInfoCache)) return contactInfoCache!.data;\n\n try {\n const db = getSharedFeaturesDb();\n const docRef = doc(db, COMMON_FEATURE_COLLECTIONS.CONTACT_INFO, 'main');\n const docSnap = await getDoc(docRef);\n\n if (!docSnap.exists()) return null;\n\n const data = docToContactInfo(docSnap.id, docSnap.data());\n contactInfoCache = { data, timestamp: Date.now() };\n return data;\n } catch (error) {\n const config = getConfig();\n if (config.debug) console.error('[shared-features] Error fetching contact info:', error);\n return contactInfoCache?.data ?? null;\n }\n}\n\nexport function subscribeToContactInfo(callback: (data: ContactInfo | null) => void): Unsubscribe {\n const db = getSharedFeaturesDb();\n const docRef = doc(db, COMMON_FEATURE_COLLECTIONS.CONTACT_INFO, 'main');\n\n return onSnapshot(docRef, (docSnap) => {\n if (!docSnap.exists()) {\n callback(null);\n return;\n }\n const data = docToContactInfo(docSnap.id, docSnap.data());\n contactInfoCache = { data, timestamp: Date.now() };\n callback(data);\n });\n}\n\nexport function clearContactInfoCache(): void {\n contactInfoCache = null;\n}\n\n// ============================================================================\n// DEVELOPER INFO\n// ============================================================================\n\nfunction docToDeveloperInfo(docId: string, data: Record<string, unknown>): DeveloperInfo {\n return {\n id: docId,\n name: (data.name as string) || '',\n title: (data.title as string) || '',\n tagline: data.tagline as string | undefined,\n bio: (data.bio as string) || '',\n shortBio: data.shortBio as string | undefined,\n avatar: data.avatar as string | undefined,\n website: data.website as string | undefined,\n github: data.github as string | undefined,\n linkedin: data.linkedin as string | undefined,\n twitter: data.twitter as string | undefined,\n yearsOfExperience: data.yearsOfExperience as number | undefined,\n location: data.location as string | undefined,\n availableForHire: (data.availableForHire as boolean) ?? false,\n resumeUrl: data.resumeUrl as string | undefined,\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\nexport async function fetchDeveloperInfo(): Promise<DeveloperInfo | null> {\n if (!isFeatureEnabled('developerInfo')) {\n const config = getConfig();\n if (config.debug) console.log('[shared-features] developerInfo feature is disabled');\n return null;\n }\n\n if (isCacheValid(developerInfoCache)) return developerInfoCache!.data;\n\n try {\n const db = getSharedFeaturesDb();\n const docRef = doc(db, COMMON_FEATURE_COLLECTIONS.DEVELOPER_INFO, 'main');\n const docSnap = await getDoc(docRef);\n\n if (!docSnap.exists()) return null;\n\n const data = docToDeveloperInfo(docSnap.id, docSnap.data());\n developerInfoCache = { data, timestamp: Date.now() };\n return data;\n } catch (error) {\n const config = getConfig();\n if (config.debug) console.error('[shared-features] Error fetching developer info:', error);\n return developerInfoCache?.data ?? null;\n }\n}\n\nexport function subscribeToDeveloperInfo(callback: (data: DeveloperInfo | null) => void): Unsubscribe {\n const db = getSharedFeaturesDb();\n const docRef = doc(db, COMMON_FEATURE_COLLECTIONS.DEVELOPER_INFO, 'main');\n\n return onSnapshot(docRef, (docSnap) => {\n if (!docSnap.exists()) {\n callback(null);\n return;\n }\n const data = docToDeveloperInfo(docSnap.id, docSnap.data());\n developerInfoCache = { data, timestamp: Date.now() };\n callback(data);\n });\n}\n\nexport function clearDeveloperInfoCache(): void {\n developerInfoCache = null;\n}\n\n// ============================================================================\n// ADDRESS INFO\n// ============================================================================\n\nfunction docToAddressInfo(docId: string, data: Record<string, unknown>): AddressInfo {\n return {\n id: docId,\n label: data.label as string | undefined,\n streetAddress: data.streetAddress as string | undefined,\n city: data.city as string | undefined,\n state: data.state as string | undefined,\n postalCode: data.postalCode as string | undefined,\n country: data.country as string | undefined,\n fullAddress: data.fullAddress as string | undefined,\n googleMapsUrl: data.googleMapsUrl as string | undefined,\n isPublic: (data.isPublic as boolean) ?? false,\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\nexport async function fetchAddressInfo(): Promise<AddressInfo | null> {\n if (!isFeatureEnabled('addressInfo')) {\n const config = getConfig();\n if (config.debug) console.log('[shared-features] addressInfo feature is disabled');\n return null;\n }\n\n if (isCacheValid(addressInfoCache)) return addressInfoCache!.data;\n\n try {\n const db = getSharedFeaturesDb();\n const docRef = doc(db, COMMON_FEATURE_COLLECTIONS.ADDRESS_INFO, 'main');\n const docSnap = await getDoc(docRef);\n\n if (!docSnap.exists()) return null;\n\n const data = docToAddressInfo(docSnap.id, docSnap.data());\n\n // Only return if public or user is authorized\n if (!data.isPublic) return null;\n\n addressInfoCache = { data, timestamp: Date.now() };\n return data;\n } catch (error) {\n const config = getConfig();\n if (config.debug) console.error('[shared-features] Error fetching address info:', error);\n return addressInfoCache?.data ?? null;\n }\n}\n\nexport function clearAddressInfoCache(): void {\n addressInfoCache = null;\n}\n\n// ============================================================================\n// SOCIAL LINKS\n// ============================================================================\n\nfunction docToSocialLink(docId: string, data: Record<string, unknown>): SocialLink {\n return {\n id: docId,\n platform: data.platform as SocialLink['platform'],\n url: (data.url as string) || '',\n displayName: data.displayName as string | undefined,\n username: data.username as string | undefined,\n icon: data.icon as string | undefined,\n order: (data.order as number) ?? 0,\n isActive: (data.isActive as boolean) ?? true,\n showIn: (data.showIn as SocialLink['showIn']) ?? ['footer'],\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\nexport async function fetchSocialLinks(options: FetchSocialLinksOptions = {}): Promise<SocialLink[]> {\n if (!isFeatureEnabled('socialLinks')) {\n const config = getConfig();\n if (config.debug) console.log('[shared-features] socialLinks feature is disabled');\n return [];\n }\n\n // Use cache for unfiltered queries\n if (!options.showIn && options.activeOnly !== false && isCacheValid(socialLinksCache)) {\n return socialLinksCache!.data ?? [];\n }\n\n try {\n const db = getSharedFeaturesDb();\n let q = query(collection(db, COMMON_FEATURE_COLLECTIONS.SOCIAL_LINKS), orderBy('order', 'asc'));\n\n const snapshot = await getDocs(q);\n let links = snapshot.docs.map((d) => docToSocialLink(d.id, d.data()));\n\n // Filter by active\n if (options.activeOnly !== false) {\n links = links.filter((l) => l.isActive);\n }\n\n // Filter by showIn\n if (options.showIn && options.showIn.length > 0) {\n links = links.filter((l) => options.showIn!.some((loc) => l.showIn.includes(loc)));\n }\n\n // Cache unfiltered results\n if (!options.showIn && options.activeOnly !== false) {\n socialLinksCache = { data: links, timestamp: Date.now() };\n }\n\n return links;\n } catch (error) {\n const config = getConfig();\n if (config.debug) console.error('[shared-features] Error fetching social links:', error);\n return socialLinksCache?.data ?? [];\n }\n}\n\nexport function clearSocialLinksCache(): void {\n socialLinksCache = null;\n}\n\n// ============================================================================\n// PAYMENT OPTIONS\n// ============================================================================\n\nfunction docToPaymentOption(docId: string, data: Record<string, unknown>): PaymentOption {\n return {\n id: docId,\n type: data.type as PaymentOption['type'],\n name: (data.name as string) || '',\n description: data.description as string | undefined,\n icon: data.icon as string | undefined,\n details: (data.details as PaymentOption['details']) || {},\n isActive: (data.isActive as boolean) ?? true,\n isPrimary: (data.isPrimary as boolean) ?? false,\n order: (data.order as number) ?? 0,\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\nexport async function fetchPaymentOptions(options: FetchPaymentOptionsOptions = {}): Promise<PaymentOption[]> {\n if (!isFeatureEnabled('paymentOptions')) {\n const config = getConfig();\n if (config.debug) console.log('[shared-features] paymentOptions feature is disabled');\n return [];\n }\n\n if (!options.type && options.activeOnly !== false && isCacheValid(paymentOptionsCache)) {\n return paymentOptionsCache!.data ?? [];\n }\n\n try {\n const db = getSharedFeaturesDb();\n let q = query(collection(db, COMMON_FEATURE_COLLECTIONS.PAYMENT_OPTIONS), orderBy('order', 'asc'));\n\n const snapshot = await getDocs(q);\n let items = snapshot.docs.map((d) => docToPaymentOption(d.id, d.data()));\n\n if (options.activeOnly !== false) {\n items = items.filter((i) => i.isActive);\n }\n\n if (options.type) {\n items = items.filter((i) => i.type === options.type);\n }\n\n if (!options.type && options.activeOnly !== false) {\n paymentOptionsCache = { data: items, timestamp: Date.now() };\n }\n\n return items;\n } catch (error) {\n const config = getConfig();\n if (config.debug) console.error('[shared-features] Error fetching payment options:', error);\n return paymentOptionsCache?.data ?? [];\n }\n}\n\nexport function clearPaymentOptionsCache(): void {\n paymentOptionsCache = null;\n}\n\n// ============================================================================\n// SERVICES\n// ============================================================================\n\nfunction docToService(docId: string, data: Record<string, unknown>): Service {\n return {\n id: docId,\n title: (data.title as string) || '',\n description: (data.description as string) || '',\n shortDescription: data.shortDescription as string | undefined,\n category: data.category as Service['category'],\n icon: data.icon as string | undefined,\n features: data.features as string[] | undefined,\n technologies: data.technologies as string[] | undefined,\n priceRange: data.priceRange as string | undefined,\n isActive: (data.isActive as boolean) ?? true,\n isFeatured: (data.isFeatured as boolean) ?? false,\n order: (data.order as number) ?? 0,\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\nexport async function fetchServices(options: FetchServicesOptions = {}): Promise<Service[]> {\n if (!isFeatureEnabled('services')) {\n const config = getConfig();\n if (config.debug) console.log('[shared-features] services feature is disabled');\n return [];\n }\n\n if (!options.category && !options.featuredOnly && options.activeOnly !== false && isCacheValid(servicesCache)) {\n return servicesCache!.data ?? [];\n }\n\n try {\n const db = getSharedFeaturesDb();\n let q = query(collection(db, COMMON_FEATURE_COLLECTIONS.SERVICES), orderBy('order', 'asc'));\n\n const snapshot = await getDocs(q);\n let items = snapshot.docs.map((d) => docToService(d.id, d.data()));\n\n if (options.activeOnly !== false) {\n items = items.filter((i) => i.isActive);\n }\n\n if (options.featuredOnly) {\n items = items.filter((i) => i.isFeatured);\n }\n\n if (options.category) {\n items = items.filter((i) => i.category === options.category);\n }\n\n if (!options.category && !options.featuredOnly && options.activeOnly !== false) {\n servicesCache = { data: items, timestamp: Date.now() };\n }\n\n return items;\n } catch (error) {\n const config = getConfig();\n if (config.debug) console.error('[shared-features] Error fetching services:', error);\n return servicesCache?.data ?? [];\n }\n}\n\nexport function clearServicesCache(): void {\n servicesCache = null;\n}\n\n// ============================================================================\n// SKILLS\n// ============================================================================\n\nfunction docToSkill(docId: string, data: Record<string, unknown>): Skill {\n return {\n id: docId,\n name: (data.name as string) || '',\n category: data.category as Skill['category'],\n level: data.level as Skill['level'],\n yearsOfExperience: data.yearsOfExperience as number | undefined,\n icon: data.icon as string | undefined,\n color: data.color as string | undefined,\n isActive: (data.isActive as boolean) ?? true,\n isFeatured: (data.isFeatured as boolean) ?? false,\n order: (data.order as number) ?? 0,\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\nexport async function fetchSkills(options: FetchSkillsOptions = {}): Promise<Skill[]> {\n if (!isFeatureEnabled('skills')) {\n const config = getConfig();\n if (config.debug) console.log('[shared-features] skills feature is disabled');\n return [];\n }\n\n if (!options.category && !options.featuredOnly && options.activeOnly !== false && isCacheValid(skillsCache)) {\n return skillsCache!.data ?? [];\n }\n\n try {\n const db = getSharedFeaturesDb();\n let q = query(collection(db, COMMON_FEATURE_COLLECTIONS.SKILLS), orderBy('order', 'asc'));\n\n const snapshot = await getDocs(q);\n let items = snapshot.docs.map((d) => docToSkill(d.id, d.data()));\n\n if (options.activeOnly !== false) {\n items = items.filter((i) => i.isActive);\n }\n\n if (options.featuredOnly) {\n items = items.filter((i) => i.isFeatured);\n }\n\n if (options.category) {\n items = items.filter((i) => i.category === options.category);\n }\n\n if (!options.category && !options.featuredOnly && options.activeOnly !== false) {\n skillsCache = { data: items, timestamp: Date.now() };\n }\n\n return items;\n } catch (error) {\n const config = getConfig();\n if (config.debug) console.error('[shared-features] Error fetching skills:', error);\n return skillsCache?.data ?? [];\n }\n}\n\nexport function clearSkillsCache(): void {\n skillsCache = null;\n}\n\n// ============================================================================\n// TESTIMONIALS\n// ============================================================================\n\nfunction docToTestimonial(docId: string, data: Record<string, unknown>): Testimonial {\n return {\n id: docId,\n authorName: (data.authorName as string) || '',\n authorTitle: data.authorTitle as string | undefined,\n authorCompany: data.authorCompany as string | undefined,\n authorAvatar: data.authorAvatar as string | undefined,\n authorLinkedin: data.authorLinkedin as string | undefined,\n content: (data.content as string) || '',\n shortContent: data.shortContent as string | undefined,\n rating: data.rating as number | undefined,\n projectName: data.projectName as string | undefined,\n projectUrl: data.projectUrl as string | undefined,\n date: data.date as Timestamp | undefined,\n isActive: (data.isActive as boolean) ?? true,\n isFeatured: (data.isFeatured as boolean) ?? false,\n order: (data.order as number) ?? 0,\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\nexport async function fetchTestimonials(options: FetchTestimonialsOptions = {}): Promise<Testimonial[]> {\n if (!isFeatureEnabled('testimonials')) {\n const config = getConfig();\n if (config.debug) console.log('[shared-features] testimonials feature is disabled');\n return [];\n }\n\n if (!options.featuredOnly && !options.limit && options.activeOnly !== false && isCacheValid(testimonialsCache)) {\n return testimonialsCache!.data ?? [];\n }\n\n try {\n const db = getSharedFeaturesDb();\n let q = query(collection(db, COMMON_FEATURE_COLLECTIONS.TESTIMONIALS), orderBy('order', 'asc'));\n\n if (options.limit) {\n q = query(q, firestoreLimit(options.limit));\n }\n\n const snapshot = await getDocs(q);\n let items = snapshot.docs.map((d) => docToTestimonial(d.id, d.data()));\n\n if (options.activeOnly !== false) {\n items = items.filter((i) => i.isActive);\n }\n\n if (options.featuredOnly) {\n items = items.filter((i) => i.isFeatured);\n }\n\n if (!options.featuredOnly && !options.limit && options.activeOnly !== false) {\n testimonialsCache = { data: items, timestamp: Date.now() };\n }\n\n return items;\n } catch (error) {\n const config = getConfig();\n if (config.debug) console.error('[shared-features] Error fetching testimonials:', error);\n return testimonialsCache?.data ?? [];\n }\n}\n\nexport function clearTestimonialsCache(): void {\n testimonialsCache = null;\n}\n\n// ============================================================================\n// PROJECTS\n// ============================================================================\n\nfunction docToProject(docId: string, data: Record<string, unknown>): Project {\n return {\n id: docId,\n title: (data.title as string) || '',\n slug: (data.slug as string) || '',\n description: (data.description as string) || '',\n shortDescription: data.shortDescription as string | undefined,\n category: data.category as Project['category'],\n status: data.status as Project['status'],\n thumbnailUrl: data.thumbnailUrl as string | undefined,\n images: data.images as string[] | undefined,\n technologies: (data.technologies as string[]) || [],\n features: data.features as string[] | undefined,\n links: data.links as Project['links'] | undefined,\n clientName: data.clientName as string | undefined,\n startDate: data.startDate as string | undefined,\n endDate: data.endDate as string | undefined,\n isActive: (data.isActive as boolean) ?? true,\n isFeatured: (data.isFeatured as boolean) ?? false,\n order: (data.order as number) ?? 0,\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\nexport async function fetchProjects(options: FetchProjectsOptions = {}): Promise<Project[]> {\n if (!isFeatureEnabled('projects')) {\n const config = getConfig();\n if (config.debug) console.log('[shared-features] projects feature is disabled');\n return [];\n }\n\n if (!options.category && !options.status && !options.featuredOnly && !options.limit && options.activeOnly !== false && isCacheValid(projectsCache)) {\n return projectsCache!.data ?? [];\n }\n\n try {\n const db = getSharedFeaturesDb();\n let q = query(collection(db, COMMON_FEATURE_COLLECTIONS.PROJECTS), orderBy('order', 'asc'));\n\n if (options.limit) {\n q = query(q, firestoreLimit(options.limit));\n }\n\n const snapshot = await getDocs(q);\n let items = snapshot.docs.map((d) => docToProject(d.id, d.data()));\n\n if (options.activeOnly !== false) {\n items = items.filter((i) => i.isActive);\n }\n\n if (options.featuredOnly) {\n items = items.filter((i) => i.isFeatured);\n }\n\n if (options.category) {\n items = items.filter((i) => i.category === options.category);\n }\n\n if (options.status) {\n items = items.filter((i) => i.status === options.status);\n }\n\n if (!options.category && !options.status && !options.featuredOnly && !options.limit && options.activeOnly !== false) {\n projectsCache = { data: items, timestamp: Date.now() };\n }\n\n return items;\n } catch (error) {\n const config = getConfig();\n if (config.debug) console.error('[shared-features] Error fetching projects:', error);\n return projectsCache?.data ?? [];\n }\n}\n\nexport async function fetchProjectBySlug(slug: string): Promise<Project | null> {\n if (!isFeatureEnabled('projects')) return null;\n\n try {\n // First check cache\n if (isCacheValid(projectsCache)) {\n const cached = projectsCache!.data?.find((p) => p.slug === slug);\n if (cached) return cached;\n }\n\n // Fetch all and find\n const projects = await fetchProjects();\n return projects.find((p) => p.slug === slug) ?? null;\n } catch (error) {\n const config = getConfig();\n if (config.debug) console.error('[shared-features] Error fetching project by slug:', error);\n return null;\n }\n}\n\nexport function clearProjectsCache(): void {\n projectsCache = null;\n}\n"],"names":["CACHE_TTL_MS","isCacheValid","firestoreLimit","state","config"],"mappings":"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"}
@@ -103,7 +103,8 @@ const COMMON_FEATURE_COLLECTIONS = {
103
103
  PAYMENT_OPTIONS: "zaions_payment_options",
104
104
  SERVICES: "zaions_services",
105
105
  SKILLS: "zaions_skills",
106
- TESTIMONIALS: "zaions_testimonials"
106
+ TESTIMONIALS: "zaions_testimonials",
107
+ PROJECTS: "zaions_projects"
107
108
  };
108
109
  const DEFAULT_CONTACT_INFO = {
109
110
  email: "",
@@ -183,6 +184,25 @@ const SKILL_LEVEL_NAMES = {
183
184
  advanced: "Advanced",
184
185
  expert: "Expert"
185
186
  };
187
+ const PROJECT_STATUS_NAMES = {
188
+ completed: "Completed",
189
+ "in-progress": "In Progress",
190
+ planned: "Planned",
191
+ archived: "Archived"
192
+ };
193
+ const PROJECT_CATEGORY_NAMES = {
194
+ "web-app": "Web Application",
195
+ "mobile-app": "Mobile App",
196
+ "desktop-app": "Desktop App",
197
+ api: "API / Backend",
198
+ library: "Library / Package",
199
+ "cli-tool": "CLI Tool",
200
+ "browser-extension": "Browser Extension",
201
+ "open-source": "Open Source",
202
+ "client-work": "Client Work",
203
+ personal: "Personal Project",
204
+ other: "Other"
205
+ };
186
206
  exports.COLLECTION_FEATURE_FLAGS = COLLECTION_FEATURE_FLAGS;
187
207
  exports.COMMON_FEATURE_COLLECTIONS = COMMON_FEATURE_COLLECTIONS;
188
208
  exports.DEFAULT_ADDRESS_INFO = DEFAULT_ADDRESS_INFO;
@@ -193,8 +213,10 @@ exports.DEFAULT_FEATURE_FLAGS = DEFAULT_FEATURE_FLAGS;
193
213
  exports.FEATURE_FLAGS_DOC_ID = FEATURE_FLAGS_DOC_ID;
194
214
  exports.FEATURE_NAMES = FEATURE_NAMES;
195
215
  exports.PAYMENT_TYPE_NAMES = PAYMENT_TYPE_NAMES;
216
+ exports.PROJECT_CATEGORY_NAMES = PROJECT_CATEGORY_NAMES;
217
+ exports.PROJECT_STATUS_NAMES = PROJECT_STATUS_NAMES;
196
218
  exports.SERVICE_CATEGORY_NAMES = SERVICE_CATEGORY_NAMES;
197
219
  exports.SKILL_CATEGORY_NAMES = SKILL_CATEGORY_NAMES;
198
220
  exports.SKILL_LEVEL_NAMES = SKILL_LEVEL_NAMES;
199
221
  exports.SOCIAL_PLATFORM_NAMES = SOCIAL_PLATFORM_NAMES;
200
- //# sourceMappingURL=commonFeatures-Cr5g1E4M.cjs.map
222
+ //# sourceMappingURL=commonFeatures-DhWaBEv_.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commonFeatures-DhWaBEv_.cjs","sources":["../src/types/featureFlags.ts","../src/types/commonFeatures.ts"],"sourcesContent":["/**\n * Feature Flags Types\n *\n * Type definitions for the shared-features feature flags system.\n * Feature flags allow breaking changes to be versioned, enabling consumers\n * to upgrade at their own pace without breaking existing implementations.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport type { Timestamp } from 'firebase/firestore';\n\n// ============================================================================\n// FEATURE IDENTIFIERS\n// ============================================================================\n\n/**\n * All available features in shared-features\n */\nexport type FeatureId =\n | 'campaigns'\n | 'broadcasts'\n | 'contactInfo'\n | 'developerInfo'\n | 'socialLinks'\n | 'paymentOptions'\n | 'addressInfo'\n | 'services'\n | 'skills'\n | 'testimonials'\n | 'projects';\n\n/**\n * Feature names for display\n */\nexport const FEATURE_NAMES: Record<FeatureId, string> = {\n campaigns: 'Advertising Campaigns',\n broadcasts: 'Broadcasts & Notifications',\n contactInfo: 'Contact Information',\n developerInfo: 'Developer Information',\n socialLinks: 'Social Links',\n paymentOptions: 'Payment Options',\n addressInfo: 'Address Information',\n services: 'Services',\n skills: 'Skills',\n testimonials: 'Testimonials',\n projects: 'Portfolio Projects',\n};\n\n// ============================================================================\n// FEATURE CONFIGURATION\n// ============================================================================\n\n/**\n * Configuration for a single feature\n */\nexport interface FeatureConfig {\n /** Whether the feature is globally enabled */\n enabled: boolean;\n\n /** Current version of this feature's API */\n version: number;\n\n /** Minimum version required (older versions will get deprecation warnings) */\n minVersion: number;\n\n /** Maximum supported version */\n maxVersion: number;\n\n /** Optional deprecation message for older versions */\n deprecationMessage?: string;\n\n /** Whether this feature requires authentication */\n requiresAuth?: boolean;\n\n /** Platforms this feature is available on (empty = all) */\n availablePlatforms?: string[];\n\n /** Projects this feature is available for (empty = all) */\n availableProjects?: string[];\n}\n\n/**\n * All feature configurations stored in Firestore\n */\nexport type FeatureConfigs = Record<FeatureId, FeatureConfig>;\n\n// ============================================================================\n// FIRESTORE DOCUMENT\n// ============================================================================\n\n/**\n * Feature flags document stored in Firestore (zaions_feature_flags/main)\n */\nexport interface FeatureFlagsDocument {\n /** Document ID (always 'main') */\n id: string;\n\n /** Global kill switch - disables all shared-features */\n globalEnabled: boolean;\n\n /** Current global API version (e.g., 'v1', 'v2') */\n currentApiVersion: string;\n\n /** Supported API versions (for backwards compatibility) */\n supportedApiVersions: string[];\n\n /** Individual feature configurations */\n features: FeatureConfigs;\n\n /** Maintenance mode - shows maintenance message to users */\n maintenanceMode: boolean;\n\n /** Maintenance message to display */\n maintenanceMessage?: string;\n\n /** Expected maintenance end time */\n maintenanceEndTime?: Timestamp;\n\n /** Last updated timestamp */\n updatedAt: Timestamp;\n\n /** Who last updated the flags */\n updatedBy: string;\n}\n\n// ============================================================================\n// CONSUMER CONFIGURATION\n// ============================================================================\n\n/**\n * Feature version preferences for a consumer project\n * Used during initialization to specify which versions to use\n */\nexport interface ConsumerFeatureVersions {\n /** Campaigns API version (default: latest) */\n campaigns?: number;\n\n /** Broadcasts API version (default: latest) */\n broadcasts?: number;\n\n /** Contact info API version (default: latest) */\n contactInfo?: number;\n\n /** Developer info API version (default: latest) */\n developerInfo?: number;\n\n /** Social links API version (default: latest) */\n socialLinks?: number;\n\n /** Payment options API version (default: latest) */\n paymentOptions?: number;\n\n /** Address info API version (default: latest) */\n addressInfo?: number;\n\n /** Services API version (default: latest) */\n services?: number;\n\n /** Skills API version (default: latest) */\n skills?: number;\n\n /** Testimonials API version (default: latest) */\n testimonials?: number;\n\n /** Projects API version (default: latest) */\n projects?: number;\n}\n\n// ============================================================================\n// FEATURE CHECK RESULTS\n// ============================================================================\n\n/**\n * Result of checking if a feature is available\n */\nexport interface FeatureAvailability {\n /** Whether the feature is available for use */\n available: boolean;\n\n /** Whether the feature is enabled globally */\n enabled: boolean;\n\n /** Current feature version */\n version: number;\n\n /** Whether the consumer's version is deprecated */\n deprecated: boolean;\n\n /** Deprecation warning message if applicable */\n deprecationWarning?: string;\n\n /** Reason why feature is unavailable (if not available) */\n unavailableReason?: string;\n\n /** Whether an upgrade is required */\n upgradeRequired: boolean;\n\n /** Message about available upgrade */\n upgradeMessage?: string;\n}\n\n/**\n * Overall status of shared-features for a consumer\n */\nexport interface SharedFeaturesStatus {\n /** Whether shared-features is operational */\n operational: boolean;\n\n /** Whether in maintenance mode */\n maintenanceMode: boolean;\n\n /** Maintenance message if applicable */\n maintenanceMessage?: string;\n\n /** Global API version */\n apiVersion: string;\n\n /** Individual feature availabilities */\n features: Record<FeatureId, FeatureAvailability>;\n\n /** List of deprecated features being used */\n deprecatedFeatures: FeatureId[];\n\n /** List of features requiring upgrade */\n upgradeRequiredFeatures: FeatureId[];\n\n /** When status was last fetched */\n fetchedAt: Date;\n}\n\n// ============================================================================\n// ADMIN TYPES\n// ============================================================================\n\n/**\n * Input for updating a feature's configuration\n */\nexport interface UpdateFeatureConfigInput {\n featureId: FeatureId;\n enabled?: boolean;\n version?: number;\n minVersion?: number;\n maxVersion?: number;\n deprecationMessage?: string;\n requiresAuth?: boolean;\n availablePlatforms?: string[];\n availableProjects?: string[];\n}\n\n/**\n * Input for updating global flags\n */\nexport interface UpdateGlobalFlagsInput {\n globalEnabled?: boolean;\n currentApiVersion?: string;\n supportedApiVersions?: string[];\n maintenanceMode?: boolean;\n maintenanceMessage?: string;\n maintenanceEndTime?: Date | null;\n}\n\n// ============================================================================\n// HOOK OPTIONS AND RESULTS\n// ============================================================================\n\n/**\n * Options for useFeatureFlags hook\n */\nexport interface UseFeatureFlagsOptions {\n /** Whether to auto-refresh flags periodically */\n autoRefresh?: boolean;\n\n /** Refresh interval in milliseconds (default: 5 minutes) */\n refreshInterval?: number;\n\n /** Whether to fetch immediately on mount (default: true) */\n autoFetch?: boolean;\n}\n\n/**\n * Result of useFeatureFlags hook\n */\nexport interface UseFeatureFlagsResult {\n /** Overall status of shared-features */\n status: SharedFeaturesStatus | null;\n\n /** Whether flags are being fetched */\n loading: boolean;\n\n /** Error message if fetch failed */\n error: string | null;\n\n /** Refetch feature flags */\n refetch: () => Promise<void>;\n\n /** Check if a specific feature is available */\n isFeatureAvailable: (featureId: FeatureId) => boolean;\n\n /** Get availability details for a feature */\n getFeatureAvailability: (featureId: FeatureId) => FeatureAvailability | null;\n\n /** Check if any features are deprecated */\n hasDeprecatedFeatures: boolean;\n\n /** Check if any features require upgrade */\n hasUpgradeRequired: boolean;\n}\n\n// ============================================================================\n// DEFAULT VALUES\n// ============================================================================\n\n/**\n * Default feature configuration for new features\n */\nexport const DEFAULT_FEATURE_CONFIG: FeatureConfig = {\n enabled: false,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n};\n\n/**\n * Default feature flags document\n */\nexport const DEFAULT_FEATURE_FLAGS: Omit<\n FeatureFlagsDocument,\n 'updatedAt' | 'updatedBy'\n> = {\n id: 'main',\n globalEnabled: true,\n currentApiVersion: 'v1',\n supportedApiVersions: ['v1'],\n maintenanceMode: false,\n features: {\n campaigns: {\n enabled: true,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n },\n broadcasts: {\n enabled: true,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n },\n contactInfo: {\n enabled: false,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n },\n developerInfo: {\n enabled: false,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n },\n socialLinks: {\n enabled: false,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n },\n paymentOptions: {\n enabled: false,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n },\n addressInfo: {\n enabled: false,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n },\n services: {\n enabled: false,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n },\n skills: {\n enabled: false,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n },\n testimonials: {\n enabled: false,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n },\n projects: {\n enabled: false,\n version: 1,\n minVersion: 1,\n maxVersion: 1,\n },\n },\n};\n\n// ============================================================================\n// COLLECTION NAME\n// ============================================================================\n\n/**\n * Firestore collection name for feature flags\n */\nexport const COLLECTION_FEATURE_FLAGS = 'zaions_feature_flags';\n\n/**\n * Document ID for the main feature flags document\n */\nexport const FEATURE_FLAGS_DOC_ID = 'main';\n","/**\n * Common Features Types\n *\n * Type definitions for centralized common features:\n * - Contact Info\n * - Developer Info\n * - Social Links\n * - Address Info\n * - Payment Options\n * - Services\n * - Skills\n * - Testimonials\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport type { Timestamp } from 'firebase/firestore';\n\n// ============================================================================\n// COLLECTION NAMES\n// ============================================================================\n\nexport const COMMON_FEATURE_COLLECTIONS = {\n CONTACT_INFO: 'zaions_contact_info',\n DEVELOPER_INFO: 'zaions_developer_info',\n SOCIAL_LINKS: 'zaions_social_links',\n ADDRESS_INFO: 'zaions_address_info',\n PAYMENT_OPTIONS: 'zaions_payment_options',\n SERVICES: 'zaions_services',\n SKILLS: 'zaions_skills',\n TESTIMONIALS: 'zaions_testimonials',\n PROJECTS: 'zaions_projects',\n} as const;\n\n// ============================================================================\n// CONTACT INFO\n// ============================================================================\n\nexport interface ContactInfo {\n id: string;\n email: string;\n supportEmail?: string;\n phone?: string;\n whatsapp?: string;\n telegram?: string;\n skype?: string;\n freelanceAvailable: boolean;\n workingHours?: string;\n timezone?: string;\n preferredContact?: 'email' | 'phone' | 'whatsapp' | 'telegram';\n responseTime?: string;\n updatedAt: Timestamp;\n}\n\nexport const DEFAULT_CONTACT_INFO: Omit<ContactInfo, 'id' | 'updatedAt'> = {\n email: '',\n freelanceAvailable: false,\n};\n\n// ============================================================================\n// DEVELOPER INFO\n// ============================================================================\n\nexport interface DeveloperInfo {\n id: string;\n name: string;\n title: string;\n tagline?: string;\n bio: string;\n shortBio?: string;\n avatar?: string;\n website?: string;\n github?: string;\n linkedin?: string;\n twitter?: string;\n yearsOfExperience?: number;\n location?: string;\n availableForHire: boolean;\n resumeUrl?: string;\n updatedAt: Timestamp;\n}\n\nexport const DEFAULT_DEVELOPER_INFO: Omit<DeveloperInfo, 'id' | 'updatedAt'> = {\n name: '',\n title: '',\n bio: '',\n availableForHire: false,\n};\n\n// ============================================================================\n// SOCIAL LINKS\n// ============================================================================\n\nexport type SocialPlatform =\n | 'github'\n | 'linkedin'\n | 'twitter'\n | 'facebook'\n | 'instagram'\n | 'youtube'\n | 'tiktok'\n | 'discord'\n | 'telegram'\n | 'whatsapp'\n | 'medium'\n | 'devto'\n | 'stackoverflow'\n | 'dribbble'\n | 'behance'\n | 'codepen'\n | 'npm'\n | 'website'\n | 'email'\n | 'other';\n\nexport const SOCIAL_PLATFORM_NAMES: Record<SocialPlatform, string> = {\n github: 'GitHub',\n linkedin: 'LinkedIn',\n twitter: 'Twitter/X',\n facebook: 'Facebook',\n instagram: 'Instagram',\n youtube: 'YouTube',\n tiktok: 'TikTok',\n discord: 'Discord',\n telegram: 'Telegram',\n whatsapp: 'WhatsApp',\n medium: 'Medium',\n devto: 'Dev.to',\n stackoverflow: 'Stack Overflow',\n dribbble: 'Dribbble',\n behance: 'Behance',\n codepen: 'CodePen',\n npm: 'NPM',\n website: 'Website',\n email: 'Email',\n other: 'Other',\n};\n\nexport interface SocialLink {\n id: string;\n platform: SocialPlatform;\n url: string;\n displayName?: string;\n username?: string;\n icon?: string;\n order: number;\n isActive: boolean;\n showIn: ('footer' | 'contact' | 'about' | 'header')[];\n updatedAt: Timestamp;\n}\n\n// ============================================================================\n// ADDRESS INFO\n// ============================================================================\n\nexport interface AddressInfo {\n id: string;\n label?: string;\n streetAddress?: string;\n city?: string;\n state?: string;\n postalCode?: string;\n country?: string;\n fullAddress?: string;\n googleMapsUrl?: string;\n isPublic: boolean;\n updatedAt: Timestamp;\n}\n\nexport const DEFAULT_ADDRESS_INFO: Omit<AddressInfo, 'id' | 'updatedAt'> = {\n isPublic: false,\n};\n\n// ============================================================================\n// PAYMENT OPTIONS\n// ============================================================================\n\nexport type PaymentType =\n | 'bank'\n | 'paypal'\n | 'stripe'\n | 'wise'\n | 'crypto'\n | 'upi'\n | 'venmo'\n | 'cashapp'\n | 'other';\n\nexport const PAYMENT_TYPE_NAMES: Record<PaymentType, string> = {\n bank: 'Bank Transfer',\n paypal: 'PayPal',\n stripe: 'Stripe',\n wise: 'Wise',\n crypto: 'Cryptocurrency',\n upi: 'UPI',\n venmo: 'Venmo',\n cashapp: 'Cash App',\n other: 'Other',\n};\n\nexport interface BankDetails {\n bankName: string;\n accountName: string;\n accountNumber: string;\n routingNumber?: string;\n swiftCode?: string;\n iban?: string;\n branch?: string;\n}\n\nexport interface CryptoDetails {\n currency: string;\n walletAddress: string;\n network?: string;\n}\n\nexport interface PaymentOption {\n id: string;\n type: PaymentType;\n name: string;\n description?: string;\n icon?: string;\n details: BankDetails | CryptoDetails | Record<string, string>;\n isActive: boolean;\n isPrimary: boolean;\n order: number;\n updatedAt: Timestamp;\n}\n\n// ============================================================================\n// SERVICES\n// ============================================================================\n\nexport type ServiceCategory =\n | 'web-development'\n | 'mobile-development'\n | 'backend-development'\n | 'devops'\n | 'consulting'\n | 'ui-ux-design'\n | 'api-development'\n | 'database'\n | 'cloud'\n | 'security'\n | 'other';\n\nexport const SERVICE_CATEGORY_NAMES: Record<ServiceCategory, string> = {\n 'web-development': 'Web Development',\n 'mobile-development': 'Mobile Development',\n 'backend-development': 'Backend Development',\n devops: 'DevOps',\n consulting: 'Consulting',\n 'ui-ux-design': 'UI/UX Design',\n 'api-development': 'API Development',\n database: 'Database',\n cloud: 'Cloud Services',\n security: 'Security',\n other: 'Other',\n};\n\nexport interface Service {\n id: string;\n title: string;\n description: string;\n shortDescription?: string;\n category: ServiceCategory;\n icon?: string;\n features?: string[];\n technologies?: string[];\n priceRange?: string;\n isActive: boolean;\n isFeatured: boolean;\n order: number;\n updatedAt: Timestamp;\n}\n\n// ============================================================================\n// SKILLS\n// ============================================================================\n\nexport type SkillCategory =\n | 'frontend'\n | 'backend'\n | 'mobile'\n | 'database'\n | 'devops'\n | 'cloud'\n | 'tools'\n | 'languages'\n | 'frameworks'\n | 'soft-skills'\n | 'other';\n\nexport const SKILL_CATEGORY_NAMES: Record<SkillCategory, string> = {\n frontend: 'Frontend',\n backend: 'Backend',\n mobile: 'Mobile',\n database: 'Database',\n devops: 'DevOps',\n cloud: 'Cloud',\n tools: 'Tools',\n languages: 'Languages',\n frameworks: 'Frameworks',\n 'soft-skills': 'Soft Skills',\n other: 'Other',\n};\n\nexport type SkillLevel = 'beginner' | 'intermediate' | 'advanced' | 'expert';\n\nexport const SKILL_LEVEL_NAMES: Record<SkillLevel, string> = {\n beginner: 'Beginner',\n intermediate: 'Intermediate',\n advanced: 'Advanced',\n expert: 'Expert',\n};\n\nexport interface Skill {\n id: string;\n name: string;\n category: SkillCategory;\n level: SkillLevel;\n yearsOfExperience?: number;\n icon?: string;\n color?: string;\n isActive: boolean;\n isFeatured: boolean;\n order: number;\n updatedAt: Timestamp;\n}\n\n// ============================================================================\n// TESTIMONIALS\n// ============================================================================\n\nexport interface Testimonial {\n id: string;\n authorName: string;\n authorTitle?: string;\n authorCompany?: string;\n authorAvatar?: string;\n authorLinkedin?: string;\n content: string;\n shortContent?: string;\n rating?: number;\n projectName?: string;\n projectUrl?: string;\n date?: Timestamp;\n isActive: boolean;\n isFeatured: boolean;\n order: number;\n updatedAt: Timestamp;\n}\n\n// ============================================================================\n// HOOK OPTIONS & RESULTS\n// ============================================================================\n\nexport interface UseCommonFeatureOptions {\n autoFetch?: boolean;\n realtime?: boolean;\n}\n\nexport interface UseCommonFeatureResult<T> {\n data: T | null;\n loading: boolean;\n error: string | null;\n refetch: () => Promise<void>;\n}\n\nexport interface UseCommonFeaturesListResult<T> {\n data: T[];\n loading: boolean;\n error: string | null;\n refetch: () => Promise<void>;\n}\n\n// ============================================================================\n// FETCH OPTIONS\n// ============================================================================\n\nexport interface FetchSocialLinksOptions {\n showIn?: ('footer' | 'contact' | 'about' | 'header')[];\n activeOnly?: boolean;\n}\n\nexport interface FetchServicesOptions {\n category?: ServiceCategory;\n activeOnly?: boolean;\n featuredOnly?: boolean;\n}\n\nexport interface FetchSkillsOptions {\n category?: SkillCategory;\n activeOnly?: boolean;\n featuredOnly?: boolean;\n}\n\nexport interface FetchTestimonialsOptions {\n activeOnly?: boolean;\n featuredOnly?: boolean;\n limit?: number;\n}\n\nexport interface FetchPaymentOptionsOptions {\n activeOnly?: boolean;\n type?: PaymentType;\n}\n\n// ============================================================================\n// PROJECTS / PORTFOLIO\n// ============================================================================\n\nexport type ProjectStatus = 'completed' | 'in-progress' | 'planned' | 'archived';\n\nexport const PROJECT_STATUS_NAMES: Record<ProjectStatus, string> = {\n completed: 'Completed',\n 'in-progress': 'In Progress',\n planned: 'Planned',\n archived: 'Archived',\n};\n\nexport type ProjectCategory =\n | 'web-app'\n | 'mobile-app'\n | 'desktop-app'\n | 'api'\n | 'library'\n | 'cli-tool'\n | 'browser-extension'\n | 'open-source'\n | 'client-work'\n | 'personal'\n | 'other';\n\nexport const PROJECT_CATEGORY_NAMES: Record<ProjectCategory, string> = {\n 'web-app': 'Web Application',\n 'mobile-app': 'Mobile App',\n 'desktop-app': 'Desktop App',\n api: 'API / Backend',\n library: 'Library / Package',\n 'cli-tool': 'CLI Tool',\n 'browser-extension': 'Browser Extension',\n 'open-source': 'Open Source',\n 'client-work': 'Client Work',\n personal: 'Personal Project',\n other: 'Other',\n};\n\nexport interface ProjectLink {\n type: 'live' | 'github' | 'demo' | 'docs' | 'playstore' | 'appstore' | 'npm' | 'other';\n url: string;\n label?: string;\n}\n\nexport interface Project {\n id: string;\n title: string;\n slug: string;\n description: string;\n shortDescription?: string;\n category: ProjectCategory;\n status: ProjectStatus;\n thumbnailUrl?: string;\n images?: string[];\n technologies: string[];\n features?: string[];\n links?: ProjectLink[];\n clientName?: string;\n startDate?: string;\n endDate?: string;\n isActive: boolean;\n isFeatured: boolean;\n order: number;\n updatedAt: Timestamp;\n}\n\nexport interface FetchProjectsOptions {\n category?: ProjectCategory;\n status?: ProjectStatus;\n activeOnly?: boolean;\n featuredOnly?: boolean;\n limit?: number;\n}\n"],"names":[],"mappings":"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"}
@@ -102,7 +102,8 @@ const COMMON_FEATURE_COLLECTIONS = {
102
102
  PAYMENT_OPTIONS: "zaions_payment_options",
103
103
  SERVICES: "zaions_services",
104
104
  SKILLS: "zaions_skills",
105
- TESTIMONIALS: "zaions_testimonials"
105
+ TESTIMONIALS: "zaions_testimonials",
106
+ PROJECTS: "zaions_projects"
106
107
  };
107
108
  const DEFAULT_CONTACT_INFO = {
108
109
  email: "",
@@ -182,6 +183,25 @@ const SKILL_LEVEL_NAMES = {
182
183
  advanced: "Advanced",
183
184
  expert: "Expert"
184
185
  };
186
+ const PROJECT_STATUS_NAMES = {
187
+ completed: "Completed",
188
+ "in-progress": "In Progress",
189
+ planned: "Planned",
190
+ archived: "Archived"
191
+ };
192
+ const PROJECT_CATEGORY_NAMES = {
193
+ "web-app": "Web Application",
194
+ "mobile-app": "Mobile App",
195
+ "desktop-app": "Desktop App",
196
+ api: "API / Backend",
197
+ library: "Library / Package",
198
+ "cli-tool": "CLI Tool",
199
+ "browser-extension": "Browser Extension",
200
+ "open-source": "Open Source",
201
+ "client-work": "Client Work",
202
+ personal: "Personal Project",
203
+ other: "Other"
204
+ };
185
205
  export {
186
206
  COLLECTION_FEATURE_FLAGS as C,
187
207
  DEFAULT_ADDRESS_INFO as D,
@@ -194,8 +214,10 @@ export {
194
214
  DEFAULT_FEATURE_CONFIG as d,
195
215
  DEFAULT_FEATURE_FLAGS as e,
196
216
  FEATURE_NAMES as f,
197
- SKILL_CATEGORY_NAMES as g,
198
- SKILL_LEVEL_NAMES as h,
199
- SOCIAL_PLATFORM_NAMES as i
217
+ PROJECT_CATEGORY_NAMES as g,
218
+ PROJECT_STATUS_NAMES as h,
219
+ SKILL_CATEGORY_NAMES as i,
220
+ SKILL_LEVEL_NAMES as j,
221
+ SOCIAL_PLATFORM_NAMES as k
200
222
  };
201
- //# sourceMappingURL=commonFeatures-HT-UO7HW.js.map
223
+ //# sourceMappingURL=commonFeatures-XJ9fuxg_.js.map