shared-features 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/{admin-commonFeatures-BKjytwx5.js → admin-commonFeatures-CFhvjgp9.js} +3 -3
  2. package/dist/{admin-commonFeatures-BKjytwx5.js.map → admin-commonFeatures-CFhvjgp9.js.map} +1 -1
  3. package/dist/{admin-commonFeatures-bjszYcI3.cjs → admin-commonFeatures-pnaXeix_.cjs} +3 -3
  4. package/dist/{admin-commonFeatures-bjszYcI3.cjs.map → admin-commonFeatures-pnaXeix_.cjs.map} +1 -1
  5. package/dist/{broadcasts-BeTm29_q.cjs → broadcasts-DZsQNd4R.cjs} +2 -2
  6. package/dist/{broadcasts-BeTm29_q.cjs.map → broadcasts-DZsQNd4R.cjs.map} +1 -1
  7. package/dist/{broadcasts-CbrWZpcI.js → broadcasts-Dlu51_38.js} +2 -2
  8. package/dist/{broadcasts-CbrWZpcI.js.map → broadcasts-Dlu51_38.js.map} +1 -1
  9. package/dist/{commonFeatures-DMYLR629.cjs → commonFeatures-BuY97_K4.cjs} +10 -2
  10. package/dist/{commonFeatures-DMYLR629.cjs.map → commonFeatures-BuY97_K4.cjs.map} +1 -1
  11. package/dist/{commonFeatures-78YVrQq1.js → commonFeatures-LzPnbR6z.js} +10 -2
  12. package/dist/{commonFeatures-78YVrQq1.js.map → commonFeatures-LzPnbR6z.js.map} +1 -1
  13. package/dist/components/index.cjs +1 -1
  14. package/dist/components/index.js +1 -1
  15. package/dist/{featureFlags-DLdibcv_.js → featureFlags-CSudFX4x.js} +3 -2
  16. package/dist/featureFlags-CSudFX4x.js.map +1 -0
  17. package/dist/{featureFlags-DOwy_uOj.cjs → featureFlags-CfDmshkF.cjs} +3 -2
  18. package/dist/featureFlags-CfDmshkF.cjs.map +1 -0
  19. package/dist/hooks/index.cjs +2 -2
  20. package/dist/hooks/index.js +2 -2
  21. package/dist/{index-CQNjI6V7.js → index-D2YWycum.js} +3 -3
  22. package/dist/{index-CQNjI6V7.js.map → index-D2YWycum.js.map} +1 -1
  23. package/dist/{index-B5uAGS9G.cjs → index-DxjbpnFC.cjs} +3 -3
  24. package/dist/{index-B5uAGS9G.cjs.map → index-DxjbpnFC.cjs.map} +1 -1
  25. package/dist/index.cjs +7 -7
  26. package/dist/index.js +7 -7
  27. package/dist/services/commonFeatures.d.ts.map +1 -1
  28. package/dist/services/index.cjs +4 -4
  29. package/dist/services/index.js +4 -4
  30. package/dist/types/commonFeatures.d.ts +11 -0
  31. package/dist/types/commonFeatures.d.ts.map +1 -1
  32. package/dist/types/index.cjs +1 -1
  33. package/dist/types/index.js +1 -1
  34. package/dist/{useCommonFeatures-NobppaW9.cjs → useCommonFeatures-CWqe4EhH.cjs} +2 -2
  35. package/dist/{useCommonFeatures-NobppaW9.cjs.map → useCommonFeatures-CWqe4EhH.cjs.map} +1 -1
  36. package/dist/{useCommonFeatures-BVaPDaPy.js → useCommonFeatures-CnmI83Er.js} +2 -2
  37. package/dist/{useCommonFeatures-BVaPDaPy.js.map → useCommonFeatures-CnmI83Er.js.map} +1 -1
  38. package/dist/{useFeatureFlags-8nC71ue-.cjs → useFeatureFlags-9_E7gair.cjs} +3 -3
  39. package/dist/{useFeatureFlags-8nC71ue-.cjs.map → useFeatureFlags-9_E7gair.cjs.map} +1 -1
  40. package/dist/{useFeatureFlags-DlMeLBH2.js → useFeatureFlags-DhIb0HYi.js} +3 -3
  41. package/dist/{useFeatureFlags-DlMeLBH2.js.map → useFeatureFlags-DhIb0HYi.js.map} +1 -1
  42. package/package.json +1 -1
  43. package/dist/featureFlags-DLdibcv_.js.map +0 -1
  44. package/dist/featureFlags-DOwy_uOj.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"useCommonFeatures-BVaPDaPy.js","sources":["../src/hooks/useCampaigns.ts","../src/hooks/useCommonFeatures.ts"],"sourcesContent":["/**\n * useCampaigns Hook\n *\n * React hook for fetching and displaying campaigns in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { fetchActiveCampaigns, clearCampaignsCache } from '../services/campaigns';\nimport {\n trackImpression,\n trackClick,\n trackClose,\n isEligibleForCampaign,\n} from '../services/analytics';\nimport { isInitialized } from '../firebase/config';\nimport type {\n CampaignWithProduct,\n AdPlacement,\n SmallPanelVariant,\n LargePanelVariant,\n} from '../types/campaigns';\n\nexport interface UseCampaignsOptions {\n /** Placement to fetch campaigns for */\n placement: AdPlacement;\n /** Maximum number of campaigns to fetch */\n maxCampaigns?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Default variant for small placements */\n defaultSmallVariant?: SmallPanelVariant;\n /** Default variant for large placements */\n defaultLargeVariant?: LargePanelVariant;\n}\n\nexport interface UseCampaignsResult {\n /** List of eligible campaigns with product data */\n campaigns: CampaignWithProduct[];\n /** Single campaign (first eligible) - convenience accessor */\n campaign: CampaignWithProduct | null;\n /** Whether campaigns are being fetched */\n loading: boolean;\n /** Error message if fetch failed */\n error: string | null;\n /** Refetch campaigns */\n refetch: () => Promise<void>;\n /** Record impression for a campaign */\n recordImpression: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record click for a campaign */\n recordClick: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record close/dismiss for a campaign */\n recordClose: (campaign: CampaignWithProduct) => Promise<void>;\n}\n\n/**\n * Hook to fetch and manage campaigns for a specific placement\n *\n * @example\n * ```tsx\n * const { campaigns, loading, recordImpression, recordClick } = useCampaigns({\n * placement: 'footer_slider',\n * maxCampaigns: 5,\n * });\n *\n * if (loading) return <Spinner />;\n *\n * return (\n * <AdSlider\n * campaigns={campaigns}\n * onImpression={recordImpression}\n * onClick={recordClick}\n * />\n * );\n * ```\n */\nexport function useCampaigns(options: UseCampaignsOptions): UseCampaignsResult {\n const {\n placement,\n maxCampaigns = 5,\n autoFetch = true,\n defaultSmallVariant = 'small_panel_2',\n defaultLargeVariant: _defaultLargeVariant = 'large_slider_1',\n } = options;\n // Note: _defaultLargeVariant reserved for large variant components\n\n const [campaigns, setCampaigns] = useState<CampaignWithProduct[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n\n const fetchCampaigns = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const allCampaigns = await fetchActiveCampaigns(placement);\n\n // Filter by frequency capping\n const eligibleCampaigns: CampaignWithProduct[] = [];\n\n for (const campaign of allCampaigns) {\n const eligible = await isEligibleForCampaign(\n campaign.id,\n campaign.frequencyDays\n );\n if (eligible) {\n eligibleCampaigns.push(campaign);\n if (eligibleCampaigns.length >= maxCampaigns) break;\n }\n }\n\n setCampaigns(eligibleCampaigns);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to fetch campaigns';\n setError(message);\n console.error('[shared-features] Error fetching campaigns:', err);\n } finally {\n setLoading(false);\n }\n }, [placement, maxCampaigns]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchCampaigns();\n }\n }, [autoFetch, fetchCampaigns]);\n\n // Record impression\n const handleRecordImpression = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant.startsWith('small_')\n ? campaign.variant\n : campaign.variant.startsWith('large_')\n ? campaign.variant\n : defaultSmallVariant;\n\n await trackImpression(\n campaign.id,\n campaign.productId,\n placement,\n variant,\n campaign.frequencyDays\n );\n },\n [placement, defaultSmallVariant]\n );\n\n // Record click\n const handleRecordClick = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClick(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Record close\n const handleRecordClose = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClose(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Refetch with cache clear\n const refetch = useCallback(async () => {\n clearCampaignsCache();\n await fetchCampaigns();\n }, [fetchCampaigns]);\n\n return {\n campaigns,\n campaign: campaigns[0] || null,\n loading,\n error,\n refetch,\n recordImpression: handleRecordImpression,\n recordClick: handleRecordClick,\n recordClose: handleRecordClose,\n };\n}\n\n/**\n * Hook to fetch a single campaign for a placement\n * Convenience wrapper around useCampaigns\n */\nexport function useCampaign(options: Omit<UseCampaignsOptions, 'maxCampaigns'>) {\n return useCampaigns({ ...options, maxCampaigns: 1 });\n}\n\n// Storage keys for modal hooks\nconst STORAGE_KEYS = {\n oneTimeShown: 'shared_features_onetime_ad_shown',\n appVersion: 'shared_features_app_version',\n} as const;\n\n/**\n * Hook to manage one-time ad modal visibility\n * Shows modal on first visit, then remembers user has seen it\n *\n * @example\n * ```tsx\n * const { shouldShow, markAsShown } = useOneTimeAdModal();\n *\n * if (shouldShow) {\n * return <AdModal onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useOneTimeAdModal() {\n const [hasShown, setHasShown] = useState(false);\n const [shouldShow, setShouldShow] = useState(false);\n\n useEffect(() => {\n // Check if modal has been shown before\n const hasSeenModal = localStorage.getItem(STORAGE_KEYS.oneTimeShown);\n if (!hasSeenModal) {\n setShouldShow(true);\n }\n }, []);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.oneTimeShown, 'true');\n setHasShown(true);\n setShouldShow(false);\n }, []);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow: shouldShow && !hasShown,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n\n/**\n * Hook to manage update ad modal visibility\n * Shows modal when app version changes\n *\n * @param currentVersion - Current app version (defaults to VITE_APP_VERSION env var or '1.0.0')\n *\n * @example\n * ```tsx\n * const { shouldShow, currentVersion, markAsShown } = useUpdateAdModal();\n *\n * if (shouldShow) {\n * return <AdUpdateModal version={currentVersion} onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useUpdateAdModal(currentVersion?: string) {\n const version = currentVersion || import.meta.env.VITE_APP_VERSION || '1.0.0';\n const [shouldShow, setShouldShow] = useState(false);\n const [previousVersion, setPreviousVersion] = useState<string | null>(null);\n\n useEffect(() => {\n const storedVersion = localStorage.getItem(STORAGE_KEYS.appVersion);\n\n if (!storedVersion) {\n // First time user - store version but don't show update modal\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n } else if (storedVersion !== version) {\n // Version changed - show update modal\n setShouldShow(true);\n setPreviousVersion(storedVersion);\n }\n }, [version]);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n setShouldShow(false);\n }, [version]);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow,\n /** Previous app version (before update) */\n previousVersion,\n /** Current app version */\n currentVersion: version,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n","/**\n * Common Features Hooks\n *\n * React hooks for fetching and using common features in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport { isInitialized } from '../firebase/config';\nimport {\n fetchContactInfo,\n subscribeToContactInfo,\n clearContactInfoCache,\n fetchDeveloperInfo,\n subscribeToDeveloperInfo,\n clearDeveloperInfoCache,\n fetchAddressInfo,\n clearAddressInfoCache,\n fetchSocialLinks,\n clearSocialLinksCache,\n fetchPaymentOptions,\n clearPaymentOptionsCache,\n fetchServices,\n clearServicesCache,\n fetchSkills,\n clearSkillsCache,\n fetchTestimonials,\n clearTestimonialsCache,\n fetchProjects,\n fetchProjectBySlug,\n clearProjectsCache,\n} from '../services/commonFeatures';\nimport type {\n ContactInfo,\n DeveloperInfo,\n AddressInfo,\n SocialLink,\n PaymentOption,\n Service,\n Skill,\n Testimonial,\n Project,\n UseCommonFeatureOptions,\n UseCommonFeatureResult,\n UseCommonFeaturesListResult,\n FetchSocialLinksOptions,\n FetchServicesOptions,\n FetchSkillsOptions,\n FetchTestimonialsOptions,\n FetchPaymentOptionsOptions,\n FetchProjectsOptions,\n} from '../types/commonFeatures';\n\n// ============================================================================\n// CONTACT INFO\n// ============================================================================\n\n/**\n * Hook to fetch contact information\n *\n * @example\n * ```tsx\n * const { data: contact, loading } = useContactInfo();\n *\n * if (loading) return <Spinner />;\n * if (!contact) return null;\n *\n * return <a href={`mailto:${contact.email}`}>{contact.email}</a>;\n * ```\n */\nexport function useContactInfo(\n options: UseCommonFeatureOptions = {}\n): UseCommonFeatureResult<ContactInfo> {\n const { autoFetch = true, realtime = false } = options;\n\n const [data, setData] = useState<ContactInfo | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchContactInfo();\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch contact info');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, []);\n\n const refetch = useCallback(async () => {\n clearContactInfoCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n\n if (realtime && isInitialized()) {\n const unsubscribe = subscribeToContactInfo((result) => {\n if (mountedRef.current) {\n setData(result);\n setLoading(false);\n }\n });\n return () => {\n mountedRef.current = false;\n unsubscribe();\n };\n }\n\n if (autoFetch) fetch();\n\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, realtime, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// DEVELOPER INFO\n// ============================================================================\n\n/**\n * Hook to fetch developer information\n *\n * @example\n * ```tsx\n * const { data: developer, loading } = useDeveloperInfo();\n *\n * if (loading) return <Spinner />;\n * if (!developer) return null;\n *\n * return (\n * <div>\n * <h1>{developer.name}</h1>\n * <p>{developer.title}</p>\n * </div>\n * );\n * ```\n */\nexport function useDeveloperInfo(\n options: UseCommonFeatureOptions = {}\n): UseCommonFeatureResult<DeveloperInfo> {\n const { autoFetch = true, realtime = false } = options;\n\n const [data, setData] = useState<DeveloperInfo | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchDeveloperInfo();\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch developer info');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, []);\n\n const refetch = useCallback(async () => {\n clearDeveloperInfoCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n\n if (realtime && isInitialized()) {\n const unsubscribe = subscribeToDeveloperInfo((result) => {\n if (mountedRef.current) {\n setData(result);\n setLoading(false);\n }\n });\n return () => {\n mountedRef.current = false;\n unsubscribe();\n };\n }\n\n if (autoFetch) fetch();\n\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, realtime, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// ADDRESS INFO\n// ============================================================================\n\n/**\n * Hook to fetch address information\n */\nexport function useAddressInfo(\n options: UseCommonFeatureOptions = {}\n): UseCommonFeatureResult<AddressInfo> {\n const { autoFetch = true } = options;\n\n const [data, setData] = useState<AddressInfo | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchAddressInfo();\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch address info');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, []);\n\n const refetch = useCallback(async () => {\n clearAddressInfoCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// SOCIAL LINKS\n// ============================================================================\n\n/**\n * Hook to fetch social links\n *\n * @example\n * ```tsx\n * const { data: links, loading } = useSocialLinks({ showIn: ['footer'] });\n *\n * if (loading) return <Spinner />;\n *\n * return (\n * <div>\n * {links.map(link => (\n * <a key={link.id} href={link.url}>{link.platform}</a>\n * ))}\n * </div>\n * );\n * ```\n */\nexport function useSocialLinks(\n options: FetchSocialLinksOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<SocialLink> {\n const { autoFetch = true, showIn, activeOnly } = options;\n\n const [data, setData] = useState<SocialLink[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchSocialLinks({ showIn, activeOnly });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch social links');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [showIn, activeOnly]);\n\n const refetch = useCallback(async () => {\n clearSocialLinksCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// PAYMENT OPTIONS\n// ============================================================================\n\n/**\n * Hook to fetch payment options\n */\nexport function usePaymentOptions(\n options: FetchPaymentOptionsOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<PaymentOption> {\n const { autoFetch = true, activeOnly, type } = options;\n\n const [data, setData] = useState<PaymentOption[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchPaymentOptions({ activeOnly, type });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch payment options');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [activeOnly, type]);\n\n const refetch = useCallback(async () => {\n clearPaymentOptionsCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// SERVICES\n// ============================================================================\n\n/**\n * Hook to fetch services\n */\nexport function useServices(\n options: FetchServicesOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<Service> {\n const { autoFetch = true, category, activeOnly, featuredOnly } = options;\n\n const [data, setData] = useState<Service[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchServices({ category, activeOnly, featuredOnly });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch services');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [category, activeOnly, featuredOnly]);\n\n const refetch = useCallback(async () => {\n clearServicesCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// SKILLS\n// ============================================================================\n\n/**\n * Hook to fetch skills\n */\nexport function useSkills(\n options: FetchSkillsOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<Skill> {\n const { autoFetch = true, category, activeOnly, featuredOnly } = options;\n\n const [data, setData] = useState<Skill[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchSkills({ category, activeOnly, featuredOnly });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch skills');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [category, activeOnly, featuredOnly]);\n\n const refetch = useCallback(async () => {\n clearSkillsCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// TESTIMONIALS\n// ============================================================================\n\n/**\n * Hook to fetch testimonials\n */\nexport function useTestimonials(\n options: FetchTestimonialsOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<Testimonial> {\n const { autoFetch = true, activeOnly, featuredOnly, limit } = options;\n\n const [data, setData] = useState<Testimonial[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchTestimonials({ activeOnly, featuredOnly, limit });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch testimonials');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [activeOnly, featuredOnly, limit]);\n\n const refetch = useCallback(async () => {\n clearTestimonialsCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// PROJECTS\n// ============================================================================\n\n/**\n * Hook to fetch projects\n *\n * @example\n * ```tsx\n * const { data: projects, loading } = useProjects({ featuredOnly: true });\n *\n * if (loading) return <Spinner />;\n *\n * return (\n * <div>\n * {projects.map(project => (\n * <div key={project.id}>\n * <h2>{project.title}</h2>\n * <p>{project.description}</p>\n * </div>\n * ))}\n * </div>\n * );\n * ```\n */\nexport function useProjects(\n options: FetchProjectsOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<Project> {\n const { autoFetch = true, category, status, activeOnly, featuredOnly, limit } = options;\n\n const [data, setData] = useState<Project[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchProjects({ category, status, activeOnly, featuredOnly, limit });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch projects');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [category, status, activeOnly, featuredOnly, limit]);\n\n const refetch = useCallback(async () => {\n clearProjectsCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n/**\n * Hook to fetch a single project by slug\n */\nexport function useProject(\n slug: string,\n options: { autoFetch?: boolean } = {}\n): UseCommonFeatureResult<Project> {\n const { autoFetch = true } = options;\n\n const [data, setData] = useState<Project | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n if (!slug) {\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchProjectBySlug(slug);\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch project');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [slug]);\n\n const refetch = useCallback(async () => {\n clearProjectsCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useCommonFeatures-CnmI83Er.js","sources":["../src/hooks/useCampaigns.ts","../src/hooks/useCommonFeatures.ts"],"sourcesContent":["/**\n * useCampaigns Hook\n *\n * React hook for fetching and displaying campaigns in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { fetchActiveCampaigns, clearCampaignsCache } from '../services/campaigns';\nimport {\n trackImpression,\n trackClick,\n trackClose,\n isEligibleForCampaign,\n} from '../services/analytics';\nimport { isInitialized } from '../firebase/config';\nimport type {\n CampaignWithProduct,\n AdPlacement,\n SmallPanelVariant,\n LargePanelVariant,\n} from '../types/campaigns';\n\nexport interface UseCampaignsOptions {\n /** Placement to fetch campaigns for */\n placement: AdPlacement;\n /** Maximum number of campaigns to fetch */\n maxCampaigns?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Default variant for small placements */\n defaultSmallVariant?: SmallPanelVariant;\n /** Default variant for large placements */\n defaultLargeVariant?: LargePanelVariant;\n}\n\nexport interface UseCampaignsResult {\n /** List of eligible campaigns with product data */\n campaigns: CampaignWithProduct[];\n /** Single campaign (first eligible) - convenience accessor */\n campaign: CampaignWithProduct | null;\n /** Whether campaigns are being fetched */\n loading: boolean;\n /** Error message if fetch failed */\n error: string | null;\n /** Refetch campaigns */\n refetch: () => Promise<void>;\n /** Record impression for a campaign */\n recordImpression: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record click for a campaign */\n recordClick: (campaign: CampaignWithProduct) => Promise<void>;\n /** Record close/dismiss for a campaign */\n recordClose: (campaign: CampaignWithProduct) => Promise<void>;\n}\n\n/**\n * Hook to fetch and manage campaigns for a specific placement\n *\n * @example\n * ```tsx\n * const { campaigns, loading, recordImpression, recordClick } = useCampaigns({\n * placement: 'footer_slider',\n * maxCampaigns: 5,\n * });\n *\n * if (loading) return <Spinner />;\n *\n * return (\n * <AdSlider\n * campaigns={campaigns}\n * onImpression={recordImpression}\n * onClick={recordClick}\n * />\n * );\n * ```\n */\nexport function useCampaigns(options: UseCampaignsOptions): UseCampaignsResult {\n const {\n placement,\n maxCampaigns = 5,\n autoFetch = true,\n defaultSmallVariant = 'small_panel_2',\n defaultLargeVariant: _defaultLargeVariant = 'large_slider_1',\n } = options;\n // Note: _defaultLargeVariant reserved for large variant components\n\n const [campaigns, setCampaigns] = useState<CampaignWithProduct[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n\n const fetchCampaigns = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const allCampaigns = await fetchActiveCampaigns(placement);\n\n // Filter by frequency capping\n const eligibleCampaigns: CampaignWithProduct[] = [];\n\n for (const campaign of allCampaigns) {\n const eligible = await isEligibleForCampaign(\n campaign.id,\n campaign.frequencyDays\n );\n if (eligible) {\n eligibleCampaigns.push(campaign);\n if (eligibleCampaigns.length >= maxCampaigns) break;\n }\n }\n\n setCampaigns(eligibleCampaigns);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to fetch campaigns';\n setError(message);\n console.error('[shared-features] Error fetching campaigns:', err);\n } finally {\n setLoading(false);\n }\n }, [placement, maxCampaigns]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchCampaigns();\n }\n }, [autoFetch, fetchCampaigns]);\n\n // Record impression\n const handleRecordImpression = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant.startsWith('small_')\n ? campaign.variant\n : campaign.variant.startsWith('large_')\n ? campaign.variant\n : defaultSmallVariant;\n\n await trackImpression(\n campaign.id,\n campaign.productId,\n placement,\n variant,\n campaign.frequencyDays\n );\n },\n [placement, defaultSmallVariant]\n );\n\n // Record click\n const handleRecordClick = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClick(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Record close\n const handleRecordClose = useCallback(\n async (campaign: CampaignWithProduct) => {\n const variant = campaign.variant;\n await trackClose(campaign.id, campaign.productId, placement, variant);\n },\n [placement]\n );\n\n // Refetch with cache clear\n const refetch = useCallback(async () => {\n clearCampaignsCache();\n await fetchCampaigns();\n }, [fetchCampaigns]);\n\n return {\n campaigns,\n campaign: campaigns[0] || null,\n loading,\n error,\n refetch,\n recordImpression: handleRecordImpression,\n recordClick: handleRecordClick,\n recordClose: handleRecordClose,\n };\n}\n\n/**\n * Hook to fetch a single campaign for a placement\n * Convenience wrapper around useCampaigns\n */\nexport function useCampaign(options: Omit<UseCampaignsOptions, 'maxCampaigns'>) {\n return useCampaigns({ ...options, maxCampaigns: 1 });\n}\n\n// Storage keys for modal hooks\nconst STORAGE_KEYS = {\n oneTimeShown: 'shared_features_onetime_ad_shown',\n appVersion: 'shared_features_app_version',\n} as const;\n\n/**\n * Hook to manage one-time ad modal visibility\n * Shows modal on first visit, then remembers user has seen it\n *\n * @example\n * ```tsx\n * const { shouldShow, markAsShown } = useOneTimeAdModal();\n *\n * if (shouldShow) {\n * return <AdModal onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useOneTimeAdModal() {\n const [hasShown, setHasShown] = useState(false);\n const [shouldShow, setShouldShow] = useState(false);\n\n useEffect(() => {\n // Check if modal has been shown before\n const hasSeenModal = localStorage.getItem(STORAGE_KEYS.oneTimeShown);\n if (!hasSeenModal) {\n setShouldShow(true);\n }\n }, []);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.oneTimeShown, 'true');\n setHasShown(true);\n setShouldShow(false);\n }, []);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow: shouldShow && !hasShown,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n\n/**\n * Hook to manage update ad modal visibility\n * Shows modal when app version changes\n *\n * @param currentVersion - Current app version (defaults to VITE_APP_VERSION env var or '1.0.0')\n *\n * @example\n * ```tsx\n * const { shouldShow, currentVersion, markAsShown } = useUpdateAdModal();\n *\n * if (shouldShow) {\n * return <AdUpdateModal version={currentVersion} onClose={markAsShown} />;\n * }\n * ```\n */\nexport function useUpdateAdModal(currentVersion?: string) {\n const version = currentVersion || import.meta.env.VITE_APP_VERSION || '1.0.0';\n const [shouldShow, setShouldShow] = useState(false);\n const [previousVersion, setPreviousVersion] = useState<string | null>(null);\n\n useEffect(() => {\n const storedVersion = localStorage.getItem(STORAGE_KEYS.appVersion);\n\n if (!storedVersion) {\n // First time user - store version but don't show update modal\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n } else if (storedVersion !== version) {\n // Version changed - show update modal\n setShouldShow(true);\n setPreviousVersion(storedVersion);\n }\n }, [version]);\n\n const markAsShown = useCallback(() => {\n localStorage.setItem(STORAGE_KEYS.appVersion, version);\n setShouldShow(false);\n }, [version]);\n\n return {\n /** Whether the modal should be displayed */\n shouldShow,\n /** Previous app version (before update) */\n previousVersion,\n /** Current app version */\n currentVersion: version,\n /** Mark the modal as shown (call when user dismisses) */\n markAsShown,\n };\n}\n","/**\n * Common Features Hooks\n *\n * React hooks for fetching and using common features in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport { isInitialized } from '../firebase/config';\nimport {\n fetchContactInfo,\n subscribeToContactInfo,\n clearContactInfoCache,\n fetchDeveloperInfo,\n subscribeToDeveloperInfo,\n clearDeveloperInfoCache,\n fetchAddressInfo,\n clearAddressInfoCache,\n fetchSocialLinks,\n clearSocialLinksCache,\n fetchPaymentOptions,\n clearPaymentOptionsCache,\n fetchServices,\n clearServicesCache,\n fetchSkills,\n clearSkillsCache,\n fetchTestimonials,\n clearTestimonialsCache,\n fetchProjects,\n fetchProjectBySlug,\n clearProjectsCache,\n} from '../services/commonFeatures';\nimport type {\n ContactInfo,\n DeveloperInfo,\n AddressInfo,\n SocialLink,\n PaymentOption,\n Service,\n Skill,\n Testimonial,\n Project,\n UseCommonFeatureOptions,\n UseCommonFeatureResult,\n UseCommonFeaturesListResult,\n FetchSocialLinksOptions,\n FetchServicesOptions,\n FetchSkillsOptions,\n FetchTestimonialsOptions,\n FetchPaymentOptionsOptions,\n FetchProjectsOptions,\n} from '../types/commonFeatures';\n\n// ============================================================================\n// CONTACT INFO\n// ============================================================================\n\n/**\n * Hook to fetch contact information\n *\n * @example\n * ```tsx\n * const { data: contact, loading } = useContactInfo();\n *\n * if (loading) return <Spinner />;\n * if (!contact) return null;\n *\n * return <a href={`mailto:${contact.email}`}>{contact.email}</a>;\n * ```\n */\nexport function useContactInfo(\n options: UseCommonFeatureOptions = {}\n): UseCommonFeatureResult<ContactInfo> {\n const { autoFetch = true, realtime = false } = options;\n\n const [data, setData] = useState<ContactInfo | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchContactInfo();\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch contact info');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, []);\n\n const refetch = useCallback(async () => {\n clearContactInfoCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n\n if (realtime && isInitialized()) {\n const unsubscribe = subscribeToContactInfo((result) => {\n if (mountedRef.current) {\n setData(result);\n setLoading(false);\n }\n });\n return () => {\n mountedRef.current = false;\n unsubscribe();\n };\n }\n\n if (autoFetch) fetch();\n\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, realtime, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// DEVELOPER INFO\n// ============================================================================\n\n/**\n * Hook to fetch developer information\n *\n * @example\n * ```tsx\n * const { data: developer, loading } = useDeveloperInfo();\n *\n * if (loading) return <Spinner />;\n * if (!developer) return null;\n *\n * return (\n * <div>\n * <h1>{developer.name}</h1>\n * <p>{developer.title}</p>\n * </div>\n * );\n * ```\n */\nexport function useDeveloperInfo(\n options: UseCommonFeatureOptions = {}\n): UseCommonFeatureResult<DeveloperInfo> {\n const { autoFetch = true, realtime = false } = options;\n\n const [data, setData] = useState<DeveloperInfo | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchDeveloperInfo();\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch developer info');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, []);\n\n const refetch = useCallback(async () => {\n clearDeveloperInfoCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n\n if (realtime && isInitialized()) {\n const unsubscribe = subscribeToDeveloperInfo((result) => {\n if (mountedRef.current) {\n setData(result);\n setLoading(false);\n }\n });\n return () => {\n mountedRef.current = false;\n unsubscribe();\n };\n }\n\n if (autoFetch) fetch();\n\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, realtime, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// ADDRESS INFO\n// ============================================================================\n\n/**\n * Hook to fetch address information\n */\nexport function useAddressInfo(\n options: UseCommonFeatureOptions = {}\n): UseCommonFeatureResult<AddressInfo> {\n const { autoFetch = true } = options;\n\n const [data, setData] = useState<AddressInfo | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchAddressInfo();\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch address info');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, []);\n\n const refetch = useCallback(async () => {\n clearAddressInfoCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// SOCIAL LINKS\n// ============================================================================\n\n/**\n * Hook to fetch social links\n *\n * @example\n * ```tsx\n * const { data: links, loading } = useSocialLinks({ showIn: ['footer'] });\n *\n * if (loading) return <Spinner />;\n *\n * return (\n * <div>\n * {links.map(link => (\n * <a key={link.id} href={link.url}>{link.platform}</a>\n * ))}\n * </div>\n * );\n * ```\n */\nexport function useSocialLinks(\n options: FetchSocialLinksOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<SocialLink> {\n const { autoFetch = true, showIn, activeOnly } = options;\n\n const [data, setData] = useState<SocialLink[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchSocialLinks({ showIn, activeOnly });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch social links');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [showIn, activeOnly]);\n\n const refetch = useCallback(async () => {\n clearSocialLinksCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// PAYMENT OPTIONS\n// ============================================================================\n\n/**\n * Hook to fetch payment options\n */\nexport function usePaymentOptions(\n options: FetchPaymentOptionsOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<PaymentOption> {\n const { autoFetch = true, activeOnly, type } = options;\n\n const [data, setData] = useState<PaymentOption[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchPaymentOptions({ activeOnly, type });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch payment options');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [activeOnly, type]);\n\n const refetch = useCallback(async () => {\n clearPaymentOptionsCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// SERVICES\n// ============================================================================\n\n/**\n * Hook to fetch services\n */\nexport function useServices(\n options: FetchServicesOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<Service> {\n const { autoFetch = true, category, activeOnly, featuredOnly } = options;\n\n const [data, setData] = useState<Service[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchServices({ category, activeOnly, featuredOnly });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch services');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [category, activeOnly, featuredOnly]);\n\n const refetch = useCallback(async () => {\n clearServicesCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// SKILLS\n// ============================================================================\n\n/**\n * Hook to fetch skills\n */\nexport function useSkills(\n options: FetchSkillsOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<Skill> {\n const { autoFetch = true, category, activeOnly, featuredOnly } = options;\n\n const [data, setData] = useState<Skill[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchSkills({ category, activeOnly, featuredOnly });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch skills');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [category, activeOnly, featuredOnly]);\n\n const refetch = useCallback(async () => {\n clearSkillsCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// TESTIMONIALS\n// ============================================================================\n\n/**\n * Hook to fetch testimonials\n */\nexport function useTestimonials(\n options: FetchTestimonialsOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<Testimonial> {\n const { autoFetch = true, activeOnly, featuredOnly, limit } = options;\n\n const [data, setData] = useState<Testimonial[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchTestimonials({ activeOnly, featuredOnly, limit });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch testimonials');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [activeOnly, featuredOnly, limit]);\n\n const refetch = useCallback(async () => {\n clearTestimonialsCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n// ============================================================================\n// PROJECTS\n// ============================================================================\n\n/**\n * Hook to fetch projects\n *\n * @example\n * ```tsx\n * const { data: projects, loading } = useProjects({ featuredOnly: true });\n *\n * if (loading) return <Spinner />;\n *\n * return (\n * <div>\n * {projects.map(project => (\n * <div key={project.id}>\n * <h2>{project.title}</h2>\n * <p>{project.description}</p>\n * </div>\n * ))}\n * </div>\n * );\n * ```\n */\nexport function useProjects(\n options: FetchProjectsOptions & UseCommonFeatureOptions = {}\n): UseCommonFeaturesListResult<Project> {\n const { autoFetch = true, category, status, activeOnly, featuredOnly, limit } = options;\n\n const [data, setData] = useState<Project[]>([]);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchProjects({ category, status, activeOnly, featuredOnly, limit });\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch projects');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [category, status, activeOnly, featuredOnly, limit]);\n\n const refetch = useCallback(async () => {\n clearProjectsCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n\n/**\n * Hook to fetch a single project by slug\n */\nexport function useProject(\n slug: string,\n options: { autoFetch?: boolean } = {}\n): UseCommonFeatureResult<Project> {\n const { autoFetch = true } = options;\n\n const [data, setData] = useState<Project | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n const mountedRef = useRef(true);\n\n const fetch = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n if (!slug) {\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await fetchProjectBySlug(slug);\n if (mountedRef.current) setData(result);\n } catch (err) {\n if (mountedRef.current) {\n setError(err instanceof Error ? err.message : 'Failed to fetch project');\n }\n } finally {\n if (mountedRef.current) setLoading(false);\n }\n }, [slug]);\n\n const refetch = useCallback(async () => {\n clearProjectsCache();\n await fetch();\n }, [fetch]);\n\n useEffect(() => {\n mountedRef.current = true;\n if (autoFetch) fetch();\n return () => {\n mountedRef.current = false;\n };\n }, [autoFetch, fetch]);\n\n return { data, loading, error, refetch };\n}\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  const react = require("react");
3
- const broadcasts = require("./broadcasts-BeTm29_q.cjs");
4
- const commonFeatures = require("./commonFeatures-DMYLR629.cjs");
3
+ const broadcasts = require("./broadcasts-DZsQNd4R.cjs");
4
+ const commonFeatures = require("./commonFeatures-BuY97_K4.cjs");
5
5
  function useBroadcasts(options = {}) {
6
6
  const {
7
7
  variant,
@@ -364,4 +364,4 @@ exports.useModalBroadcasts = useModalBroadcasts;
364
364
  exports.useSharedFeaturesOperational = useSharedFeaturesOperational;
365
365
  exports.useSingleBroadcast = useSingleBroadcast;
366
366
  exports.useToastBroadcasts = useToastBroadcasts;
367
- //# sourceMappingURL=useFeatureFlags-8nC71ue-.cjs.map
367
+ //# sourceMappingURL=useFeatureFlags-9_E7gair.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFeatureFlags-8nC71ue-.cjs","sources":["../src/hooks/useBroadcasts.ts","../src/hooks/useFeatureFlags.ts"],"sourcesContent":["/**\n * useBroadcasts Hook\n *\n * React hook for fetching and displaying broadcast notifications in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n fetchActiveBroadcasts,\n subscribeToBroadcasts,\n trackBroadcastImpression,\n trackBroadcastClick,\n trackBroadcastDismiss,\n clearBroadcastsCache,\n} from '../services/broadcasts';\nimport { isInitialized } from '../firebase/config';\nimport type {\n BroadcastNotification,\n BroadcastVariant,\n NotificationPlatform,\n UseBroadcastsReturn,\n} from '../types/notifications';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface UseBroadcastsOptions {\n /** Filter by variant type */\n variant?: BroadcastVariant;\n /** Override platform detection */\n platform?: NotificationPlatform;\n /** Maximum number of broadcasts to return */\n maxBroadcasts?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Whether to subscribe to real-time updates (default: false) */\n realtime?: boolean;\n}\n\n// ============================================================================\n// MAIN HOOK\n// ============================================================================\n\n/**\n * Hook to fetch and manage broadcast notifications\n *\n * @example\n * ```tsx\n * const { broadcasts, isLoading, dismissBroadcast, trackClick } = useBroadcasts({\n * variant: 'banner',\n * maxBroadcasts: 3,\n * });\n *\n * return (\n * <div>\n * {broadcasts.map(broadcast => (\n * <BroadcastBanner\n * key={broadcast.id}\n * broadcast={broadcast}\n * onDismiss={() => dismissBroadcast(broadcast.id)}\n * onActionClick={() => trackClick(broadcast.id)}\n * />\n * ))}\n * </div>\n * );\n * ```\n */\nexport function useBroadcasts(\n options: UseBroadcastsOptions = {}\n): UseBroadcastsReturn {\n const {\n variant,\n platform,\n maxBroadcasts = 10,\n autoFetch = true,\n realtime = false,\n } = options;\n\n const [broadcasts, setBroadcasts] = useState<BroadcastNotification[]>([]);\n const [isLoading, setIsLoading] = useState(autoFetch);\n const [error, setError] = useState<Error | null>(null);\n const dismissedIdsRef = useRef<Set<string>>(new Set());\n const impressionTrackedRef = useRef<Set<string>>(new Set());\n\n // Fetch broadcasts\n const fetchBroadcastsData = useCallback(async () => {\n if (!isInitialized()) {\n setError(new Error('shared-features not initialized'));\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const allBroadcasts = await fetchActiveBroadcasts({\n variant,\n platform,\n });\n\n // Filter out locally dismissed and limit count\n const filteredBroadcasts = allBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filteredBroadcasts);\n } catch (err) {\n const errorObj =\n err instanceof Error ? err : new Error('Failed to fetch broadcasts');\n setError(errorObj);\n console.error('[shared-features] Error fetching broadcasts:', err);\n } finally {\n setIsLoading(false);\n }\n }, [variant, platform, maxBroadcasts]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchBroadcastsData();\n }\n }, [autoFetch, fetchBroadcastsData]);\n\n // Real-time subscription\n useEffect(() => {\n if (!realtime || !isInitialized()) return;\n\n const unsubscribe = subscribeToBroadcasts(\n (newBroadcasts) => {\n // Filter and limit\n const filtered = newBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .filter((b) => !variant || b.variant === variant)\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filtered);\n setIsLoading(false);\n },\n { variant, platform }\n );\n\n return () => {\n unsubscribe();\n };\n }, [realtime, variant, platform, maxBroadcasts]);\n\n // Dismiss broadcast\n const handleDismissBroadcast = useCallback((broadcastId: string) => {\n // Update local state immediately\n dismissedIdsRef.current.add(broadcastId);\n setBroadcasts((prev) => prev.filter((b) => b.id !== broadcastId));\n\n // Track dismiss and persist\n trackBroadcastDismiss(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track dismiss:', err);\n });\n }, []);\n\n // Track impression\n const handleTrackImpression = useCallback((broadcastId: string) => {\n // Only track once per session\n if (impressionTrackedRef.current.has(broadcastId)) return;\n impressionTrackedRef.current.add(broadcastId);\n\n trackBroadcastImpression(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track impression:', err);\n });\n }, []);\n\n // Track click\n const handleTrackClick = useCallback((broadcastId: string) => {\n trackBroadcastClick(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track click:', err);\n });\n }, []);\n\n // Check if dismissed\n const checkIsDismissed = useCallback((broadcastId: string) => {\n return dismissedIdsRef.current.has(broadcastId);\n }, []);\n\n // Refresh broadcasts\n const handleRefresh = useCallback(async () => {\n clearBroadcastsCache();\n await fetchBroadcastsData();\n }, [fetchBroadcastsData]);\n\n return {\n broadcasts,\n isLoading,\n error,\n dismissBroadcast: handleDismissBroadcast,\n trackImpression: handleTrackImpression,\n trackClick: handleTrackClick,\n isDismissed: checkIsDismissed,\n refresh: handleRefresh,\n };\n}\n\n// ============================================================================\n// SPECIALIZED HOOKS\n// ============================================================================\n\n/**\n * Hook specifically for banner broadcasts\n */\nexport function useBannerBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'banner' });\n}\n\n/**\n * Hook specifically for modal broadcasts\n */\nexport function useModalBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'modal', maxBroadcasts: 1 });\n}\n\n/**\n * Hook specifically for toast broadcasts\n */\nexport function useToastBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'toast' });\n}\n\n/**\n * Hook specifically for bell/notification center broadcasts\n */\nexport function useBellBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'bell' });\n}\n\n// ============================================================================\n// SINGLE BROADCAST HOOK\n// ============================================================================\n\nexport interface UseSingleBroadcastReturn {\n /** The broadcast (first matching one) */\n broadcast: BroadcastNotification | null;\n /** Whether loading */\n isLoading: boolean;\n /** Error if any */\n error: Error | null;\n /** Dismiss the broadcast */\n dismiss: () => void;\n /** Track impression */\n trackImpression: () => void;\n /** Track click */\n trackClick: () => void;\n /** Whether broadcast was dismissed */\n isDismissed: boolean;\n}\n\n/**\n * Hook to get a single broadcast (convenience wrapper)\n *\n * @example\n * ```tsx\n * const { broadcast, dismiss, trackClick } = useSingleBroadcast({ variant: 'modal' });\n *\n * if (!broadcast) return null;\n *\n * return (\n * <AnnouncementModal\n * broadcast={broadcast}\n * onClose={dismiss}\n * onActionClick={trackClick}\n * />\n * );\n * ```\n */\nexport function useSingleBroadcast(\n options: Omit<UseBroadcastsOptions, 'maxBroadcasts'> = {}\n): UseSingleBroadcastReturn {\n const result = useBroadcasts({ ...options, maxBroadcasts: 1 });\n const broadcast = result.broadcasts[0] || null;\n\n return {\n broadcast,\n isLoading: result.isLoading,\n error: result.error,\n dismiss: () => broadcast && result.dismissBroadcast(broadcast.id),\n trackImpression: () => broadcast && result.trackImpression(broadcast.id),\n trackClick: () => broadcast && result.trackClick(broadcast.id),\n isDismissed: broadcast ? result.isDismissed(broadcast.id) : false,\n };\n}\n\n// ============================================================================\n// ANNOUNCEMENT MODAL HOOK\n// ============================================================================\n\nexport interface UseAnnouncementModalReturn {\n /** The modal broadcast to display */\n broadcast: BroadcastNotification | null;\n /** Whether the modal should be shown */\n isOpen: boolean;\n /** Close the modal */\n close: () => void;\n /** Handle action button click */\n handleAction: () => void;\n}\n\n/**\n * Hook for managing announcement modal display\n * Automatically tracks impressions and handles dismissal\n *\n * @example\n * ```tsx\n * const { broadcast, isOpen, close, handleAction } = useAnnouncementModal();\n *\n * return (\n * <Dialog open={isOpen} onOpenChange={(open) => !open && close()}>\n * <DialogContent>\n * <h2>{broadcast?.title}</h2>\n * <p>{broadcast?.message}</p>\n * {broadcast?.actionUrl && (\n * <Button onClick={handleAction}>{broadcast.actionText || 'Learn More'}</Button>\n * )}\n * </DialogContent>\n * </Dialog>\n * );\n * ```\n */\nexport function useAnnouncementModal(): UseAnnouncementModalReturn {\n const { broadcast, dismiss, trackImpression, trackClick } = useSingleBroadcast({\n variant: 'modal',\n });\n\n const [isOpen, setIsOpen] = useState(false);\n const impressionTrackedRef = useRef(false);\n\n // Show modal when broadcast is available\n useEffect(() => {\n if (broadcast && !impressionTrackedRef.current) {\n setIsOpen(true);\n trackImpression();\n impressionTrackedRef.current = true;\n }\n }, [broadcast, trackImpression]);\n\n const handleClose = useCallback(() => {\n setIsOpen(false);\n dismiss();\n }, [dismiss]);\n\n const handleAction = useCallback(() => {\n trackClick();\n if (broadcast?.actionUrl) {\n window.open(broadcast.actionUrl, '_blank');\n }\n handleClose();\n }, [broadcast, trackClick, handleClose]);\n\n return {\n broadcast,\n isOpen,\n close: handleClose,\n handleAction,\n };\n}\n","/**\n * useFeatureFlags Hook\n *\n * React hook for checking feature availability and status in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n getSharedFeaturesStatus,\n checkFeatureAvailability,\n isFeatureEnabled,\n subscribeToFeatureFlags,\n clearFeatureFlagsCache,\n} from '../services/featureFlags';\nimport { isInitialized, getState } from '../firebase/config';\nimport type {\n FeatureId,\n FeatureAvailability,\n SharedFeaturesStatus,\n UseFeatureFlagsOptions,\n UseFeatureFlagsResult,\n ConsumerFeatureVersions,\n} from '../types/featureFlags';\n\n/**\n * Hook to fetch and monitor feature flags\n *\n * @param options - Hook options\n * @returns Feature flags status and utilities\n *\n * @example\n * ```tsx\n * const {\n * status,\n * loading,\n * isFeatureAvailable,\n * hasDeprecatedFeatures\n * } = useFeatureFlags();\n *\n * if (loading) return <Spinner />;\n *\n * if (!status?.operational) {\n * return <MaintenancePage message={status?.maintenanceMessage} />;\n * }\n *\n * if (isFeatureAvailable('contactInfo')) {\n * return <ContactInfo />;\n * }\n * ```\n */\nexport function useFeatureFlags(\n options: UseFeatureFlagsOptions = {}\n): UseFeatureFlagsResult {\n const {\n autoRefresh = false,\n refreshInterval = 5 * 60 * 1000, // 5 minutes\n autoFetch = true,\n } = options;\n\n const [status, setStatus] = useState<SharedFeaturesStatus | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n\n // Track mounted state to prevent state updates after unmount\n const mountedRef = useRef(true);\n\n // Get consumer versions from config if available\n const consumerVersions = useRef<ConsumerFeatureVersions | undefined>(\n getState().config?.featureVersions\n );\n\n // Fetch status\n const fetchStatus = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const newStatus = await getSharedFeaturesStatus(consumerVersions.current);\n\n if (mountedRef.current) {\n setStatus(newStatus);\n }\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Failed to fetch feature flags';\n\n if (mountedRef.current) {\n setError(message);\n console.error('[shared-features] Error fetching feature flags:', err);\n }\n } finally {\n if (mountedRef.current) {\n setLoading(false);\n }\n }\n }, []);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchStatus();\n }\n }, [autoFetch, fetchStatus]);\n\n // Auto-refresh interval\n useEffect(() => {\n if (!autoRefresh) return;\n\n const interval = setInterval(() => {\n fetchStatus();\n }, refreshInterval);\n\n return () => clearInterval(interval);\n }, [autoRefresh, refreshInterval, fetchStatus]);\n\n // Cleanup on unmount\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n // Check if a feature is available\n const checkFeatureAvailable = useCallback(\n (featureId: FeatureId): boolean => {\n // Quick check using cached data\n if (!status) return isFeatureEnabled(featureId);\n\n const availability = status.features[featureId];\n return availability?.available ?? false;\n },\n [status]\n );\n\n // Get feature availability details\n const getAvailability = useCallback(\n (featureId: FeatureId): FeatureAvailability | null => {\n if (!status) return null;\n return status.features[featureId] ?? null;\n },\n [status]\n );\n\n // Refetch with cache clear\n const refetch = useCallback(async () => {\n clearFeatureFlagsCache();\n await fetchStatus();\n }, [fetchStatus]);\n\n return {\n status,\n loading,\n error,\n refetch,\n isFeatureAvailable: checkFeatureAvailable,\n getFeatureAvailability: getAvailability,\n hasDeprecatedFeatures: (status?.deprecatedFeatures.length ?? 0) > 0,\n hasUpgradeRequired: (status?.upgradeRequiredFeatures.length ?? 0) > 0,\n };\n}\n\n/**\n * Hook to check a single feature's availability\n *\n * @param featureId - The feature to check\n * @returns Feature availability and loading state\n *\n * @example\n * ```tsx\n * const { available, loading, deprecated } = useFeature('contactInfo');\n *\n * if (loading) return <Spinner />;\n * if (!available) return null;\n *\n * return <ContactInfo />;\n * ```\n */\nexport function useFeature(featureId: FeatureId) {\n const [availability, setAvailability] = useState<FeatureAvailability | null>(\n null\n );\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let mounted = true;\n\n const checkFeature = async () => {\n if (!isInitialized()) {\n setLoading(false);\n return;\n }\n\n try {\n const consumerVersions = getState().config?.featureVersions;\n const result = await checkFeatureAvailability(\n featureId,\n consumerVersions\n );\n\n if (mounted) {\n setAvailability(result);\n }\n } catch (err) {\n console.error(\n `[shared-features] Error checking feature '${featureId}':`,\n err\n );\n } finally {\n if (mounted) {\n setLoading(false);\n }\n }\n };\n\n checkFeature();\n\n return () => {\n mounted = false;\n };\n }, [featureId]);\n\n return {\n /** Whether the feature is available */\n available: availability?.available ?? false,\n /** Whether the check is in progress */\n loading,\n /** Full availability details */\n availability,\n /** Whether the feature is enabled (but might need upgrade) */\n enabled: availability?.enabled ?? false,\n /** Whether using a deprecated version */\n deprecated: availability?.deprecated ?? false,\n /** Whether an upgrade is required */\n upgradeRequired: availability?.upgradeRequired ?? false,\n /** Deprecation warning if applicable */\n deprecationWarning: availability?.deprecationWarning,\n /** Reason feature is unavailable */\n unavailableReason: availability?.unavailableReason,\n };\n}\n\n/**\n * Hook to subscribe to real-time feature flag updates\n *\n * @param callback - Function to call when flags change\n *\n * @example\n * ```tsx\n * useFeatureFlagsSubscription((flags) => {\n * if (flags?.maintenanceMode) {\n * showMaintenanceBanner();\n * }\n * });\n * ```\n */\nexport function useFeatureFlagsSubscription(\n callback: (status: SharedFeaturesStatus | null) => void\n) {\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n if (!isInitialized()) return;\n\n const consumerVersions = getState().config?.featureVersions;\n\n const unsubscribe = subscribeToFeatureFlags(async (flags) => {\n if (!flags) {\n callbackRef.current(null);\n return;\n }\n\n // Get full status with current flags\n const status = await getSharedFeaturesStatus(consumerVersions);\n callbackRef.current(status);\n });\n\n return () => unsubscribe();\n }, []);\n}\n\n/**\n * Hook to check if shared-features is operational\n *\n * @returns Whether shared-features is operational\n *\n * @example\n * ```tsx\n * const { operational, maintenanceMessage } = useSharedFeaturesOperational();\n *\n * if (!operational) {\n * return <MaintenancePage message={maintenanceMessage} />;\n * }\n * ```\n */\nexport function useSharedFeaturesOperational() {\n const { status, loading } = useFeatureFlags({ autoFetch: true });\n\n return {\n /** Whether shared-features is operational */\n operational: status?.operational ?? false,\n /** Whether check is in progress */\n loading,\n /** Maintenance message if in maintenance mode */\n maintenanceMessage: status?.maintenanceMessage,\n /** Whether in maintenance mode */\n maintenanceMode: status?.maintenanceMode ?? false,\n /** Current API version */\n apiVersion: status?.apiVersion ?? 'v1',\n };\n}\n\n/**\n * Hook for conditional rendering based on feature availability\n *\n * @param featureId - The feature to check\n * @returns Object with show/hide helpers\n *\n * @example\n * ```tsx\n * const { shouldRender, FallbackOrChildren } = useFeatureGate('contactInfo');\n *\n * return (\n * <FallbackOrChildren fallback={<OldContactInfo />}>\n * <NewContactInfo />\n * </FallbackOrChildren>\n * );\n * ```\n */\nexport function useFeatureGate(featureId: FeatureId) {\n const { available, loading, deprecated, deprecationWarning } =\n useFeature(featureId);\n\n // Log deprecation warning in development\n useEffect(() => {\n if (deprecated && deprecationWarning) {\n console.warn(`[shared-features] ${deprecationWarning}`);\n }\n }, [deprecated, deprecationWarning]);\n\n return {\n /** Whether the feature should be rendered */\n shouldRender: available,\n /** Whether still checking availability */\n loading,\n /** Whether using deprecated version */\n deprecated,\n /** Component that renders children if available, fallback otherwise */\n FallbackOrChildren: useCallback(\n ({\n children,\n fallback = null,\n }: {\n children: React.ReactNode;\n fallback?: React.ReactNode;\n }) => {\n if (loading) return null;\n return available ? children : fallback;\n },\n [available, loading]\n ),\n };\n}\n"],"names":["broadcasts","useState","useRef","useCallback","isInitialized","fetchActiveBroadcasts","useEffect","subscribeToBroadcasts","trackBroadcastDismiss","trackBroadcastImpression","trackBroadcastClick","clearBroadcastsCache","getState","getSharedFeaturesStatus","isFeatureEnabled","clearFeatureFlagsCache","checkFeatureAvailability","subscribeToFeatureFlags"],"mappings":"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"}
1
+ {"version":3,"file":"useFeatureFlags-9_E7gair.cjs","sources":["../src/hooks/useBroadcasts.ts","../src/hooks/useFeatureFlags.ts"],"sourcesContent":["/**\n * useBroadcasts Hook\n *\n * React hook for fetching and displaying broadcast notifications in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n fetchActiveBroadcasts,\n subscribeToBroadcasts,\n trackBroadcastImpression,\n trackBroadcastClick,\n trackBroadcastDismiss,\n clearBroadcastsCache,\n} from '../services/broadcasts';\nimport { isInitialized } from '../firebase/config';\nimport type {\n BroadcastNotification,\n BroadcastVariant,\n NotificationPlatform,\n UseBroadcastsReturn,\n} from '../types/notifications';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface UseBroadcastsOptions {\n /** Filter by variant type */\n variant?: BroadcastVariant;\n /** Override platform detection */\n platform?: NotificationPlatform;\n /** Maximum number of broadcasts to return */\n maxBroadcasts?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Whether to subscribe to real-time updates (default: false) */\n realtime?: boolean;\n}\n\n// ============================================================================\n// MAIN HOOK\n// ============================================================================\n\n/**\n * Hook to fetch and manage broadcast notifications\n *\n * @example\n * ```tsx\n * const { broadcasts, isLoading, dismissBroadcast, trackClick } = useBroadcasts({\n * variant: 'banner',\n * maxBroadcasts: 3,\n * });\n *\n * return (\n * <div>\n * {broadcasts.map(broadcast => (\n * <BroadcastBanner\n * key={broadcast.id}\n * broadcast={broadcast}\n * onDismiss={() => dismissBroadcast(broadcast.id)}\n * onActionClick={() => trackClick(broadcast.id)}\n * />\n * ))}\n * </div>\n * );\n * ```\n */\nexport function useBroadcasts(\n options: UseBroadcastsOptions = {}\n): UseBroadcastsReturn {\n const {\n variant,\n platform,\n maxBroadcasts = 10,\n autoFetch = true,\n realtime = false,\n } = options;\n\n const [broadcasts, setBroadcasts] = useState<BroadcastNotification[]>([]);\n const [isLoading, setIsLoading] = useState(autoFetch);\n const [error, setError] = useState<Error | null>(null);\n const dismissedIdsRef = useRef<Set<string>>(new Set());\n const impressionTrackedRef = useRef<Set<string>>(new Set());\n\n // Fetch broadcasts\n const fetchBroadcastsData = useCallback(async () => {\n if (!isInitialized()) {\n setError(new Error('shared-features not initialized'));\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const allBroadcasts = await fetchActiveBroadcasts({\n variant,\n platform,\n });\n\n // Filter out locally dismissed and limit count\n const filteredBroadcasts = allBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filteredBroadcasts);\n } catch (err) {\n const errorObj =\n err instanceof Error ? err : new Error('Failed to fetch broadcasts');\n setError(errorObj);\n console.error('[shared-features] Error fetching broadcasts:', err);\n } finally {\n setIsLoading(false);\n }\n }, [variant, platform, maxBroadcasts]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchBroadcastsData();\n }\n }, [autoFetch, fetchBroadcastsData]);\n\n // Real-time subscription\n useEffect(() => {\n if (!realtime || !isInitialized()) return;\n\n const unsubscribe = subscribeToBroadcasts(\n (newBroadcasts) => {\n // Filter and limit\n const filtered = newBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .filter((b) => !variant || b.variant === variant)\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filtered);\n setIsLoading(false);\n },\n { variant, platform }\n );\n\n return () => {\n unsubscribe();\n };\n }, [realtime, variant, platform, maxBroadcasts]);\n\n // Dismiss broadcast\n const handleDismissBroadcast = useCallback((broadcastId: string) => {\n // Update local state immediately\n dismissedIdsRef.current.add(broadcastId);\n setBroadcasts((prev) => prev.filter((b) => b.id !== broadcastId));\n\n // Track dismiss and persist\n trackBroadcastDismiss(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track dismiss:', err);\n });\n }, []);\n\n // Track impression\n const handleTrackImpression = useCallback((broadcastId: string) => {\n // Only track once per session\n if (impressionTrackedRef.current.has(broadcastId)) return;\n impressionTrackedRef.current.add(broadcastId);\n\n trackBroadcastImpression(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track impression:', err);\n });\n }, []);\n\n // Track click\n const handleTrackClick = useCallback((broadcastId: string) => {\n trackBroadcastClick(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track click:', err);\n });\n }, []);\n\n // Check if dismissed\n const checkIsDismissed = useCallback((broadcastId: string) => {\n return dismissedIdsRef.current.has(broadcastId);\n }, []);\n\n // Refresh broadcasts\n const handleRefresh = useCallback(async () => {\n clearBroadcastsCache();\n await fetchBroadcastsData();\n }, [fetchBroadcastsData]);\n\n return {\n broadcasts,\n isLoading,\n error,\n dismissBroadcast: handleDismissBroadcast,\n trackImpression: handleTrackImpression,\n trackClick: handleTrackClick,\n isDismissed: checkIsDismissed,\n refresh: handleRefresh,\n };\n}\n\n// ============================================================================\n// SPECIALIZED HOOKS\n// ============================================================================\n\n/**\n * Hook specifically for banner broadcasts\n */\nexport function useBannerBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'banner' });\n}\n\n/**\n * Hook specifically for modal broadcasts\n */\nexport function useModalBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'modal', maxBroadcasts: 1 });\n}\n\n/**\n * Hook specifically for toast broadcasts\n */\nexport function useToastBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'toast' });\n}\n\n/**\n * Hook specifically for bell/notification center broadcasts\n */\nexport function useBellBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'bell' });\n}\n\n// ============================================================================\n// SINGLE BROADCAST HOOK\n// ============================================================================\n\nexport interface UseSingleBroadcastReturn {\n /** The broadcast (first matching one) */\n broadcast: BroadcastNotification | null;\n /** Whether loading */\n isLoading: boolean;\n /** Error if any */\n error: Error | null;\n /** Dismiss the broadcast */\n dismiss: () => void;\n /** Track impression */\n trackImpression: () => void;\n /** Track click */\n trackClick: () => void;\n /** Whether broadcast was dismissed */\n isDismissed: boolean;\n}\n\n/**\n * Hook to get a single broadcast (convenience wrapper)\n *\n * @example\n * ```tsx\n * const { broadcast, dismiss, trackClick } = useSingleBroadcast({ variant: 'modal' });\n *\n * if (!broadcast) return null;\n *\n * return (\n * <AnnouncementModal\n * broadcast={broadcast}\n * onClose={dismiss}\n * onActionClick={trackClick}\n * />\n * );\n * ```\n */\nexport function useSingleBroadcast(\n options: Omit<UseBroadcastsOptions, 'maxBroadcasts'> = {}\n): UseSingleBroadcastReturn {\n const result = useBroadcasts({ ...options, maxBroadcasts: 1 });\n const broadcast = result.broadcasts[0] || null;\n\n return {\n broadcast,\n isLoading: result.isLoading,\n error: result.error,\n dismiss: () => broadcast && result.dismissBroadcast(broadcast.id),\n trackImpression: () => broadcast && result.trackImpression(broadcast.id),\n trackClick: () => broadcast && result.trackClick(broadcast.id),\n isDismissed: broadcast ? result.isDismissed(broadcast.id) : false,\n };\n}\n\n// ============================================================================\n// ANNOUNCEMENT MODAL HOOK\n// ============================================================================\n\nexport interface UseAnnouncementModalReturn {\n /** The modal broadcast to display */\n broadcast: BroadcastNotification | null;\n /** Whether the modal should be shown */\n isOpen: boolean;\n /** Close the modal */\n close: () => void;\n /** Handle action button click */\n handleAction: () => void;\n}\n\n/**\n * Hook for managing announcement modal display\n * Automatically tracks impressions and handles dismissal\n *\n * @example\n * ```tsx\n * const { broadcast, isOpen, close, handleAction } = useAnnouncementModal();\n *\n * return (\n * <Dialog open={isOpen} onOpenChange={(open) => !open && close()}>\n * <DialogContent>\n * <h2>{broadcast?.title}</h2>\n * <p>{broadcast?.message}</p>\n * {broadcast?.actionUrl && (\n * <Button onClick={handleAction}>{broadcast.actionText || 'Learn More'}</Button>\n * )}\n * </DialogContent>\n * </Dialog>\n * );\n * ```\n */\nexport function useAnnouncementModal(): UseAnnouncementModalReturn {\n const { broadcast, dismiss, trackImpression, trackClick } = useSingleBroadcast({\n variant: 'modal',\n });\n\n const [isOpen, setIsOpen] = useState(false);\n const impressionTrackedRef = useRef(false);\n\n // Show modal when broadcast is available\n useEffect(() => {\n if (broadcast && !impressionTrackedRef.current) {\n setIsOpen(true);\n trackImpression();\n impressionTrackedRef.current = true;\n }\n }, [broadcast, trackImpression]);\n\n const handleClose = useCallback(() => {\n setIsOpen(false);\n dismiss();\n }, [dismiss]);\n\n const handleAction = useCallback(() => {\n trackClick();\n if (broadcast?.actionUrl) {\n window.open(broadcast.actionUrl, '_blank');\n }\n handleClose();\n }, [broadcast, trackClick, handleClose]);\n\n return {\n broadcast,\n isOpen,\n close: handleClose,\n handleAction,\n };\n}\n","/**\n * useFeatureFlags Hook\n *\n * React hook for checking feature availability and status in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n getSharedFeaturesStatus,\n checkFeatureAvailability,\n isFeatureEnabled,\n subscribeToFeatureFlags,\n clearFeatureFlagsCache,\n} from '../services/featureFlags';\nimport { isInitialized, getState } from '../firebase/config';\nimport type {\n FeatureId,\n FeatureAvailability,\n SharedFeaturesStatus,\n UseFeatureFlagsOptions,\n UseFeatureFlagsResult,\n ConsumerFeatureVersions,\n} from '../types/featureFlags';\n\n/**\n * Hook to fetch and monitor feature flags\n *\n * @param options - Hook options\n * @returns Feature flags status and utilities\n *\n * @example\n * ```tsx\n * const {\n * status,\n * loading,\n * isFeatureAvailable,\n * hasDeprecatedFeatures\n * } = useFeatureFlags();\n *\n * if (loading) return <Spinner />;\n *\n * if (!status?.operational) {\n * return <MaintenancePage message={status?.maintenanceMessage} />;\n * }\n *\n * if (isFeatureAvailable('contactInfo')) {\n * return <ContactInfo />;\n * }\n * ```\n */\nexport function useFeatureFlags(\n options: UseFeatureFlagsOptions = {}\n): UseFeatureFlagsResult {\n const {\n autoRefresh = false,\n refreshInterval = 5 * 60 * 1000, // 5 minutes\n autoFetch = true,\n } = options;\n\n const [status, setStatus] = useState<SharedFeaturesStatus | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n\n // Track mounted state to prevent state updates after unmount\n const mountedRef = useRef(true);\n\n // Get consumer versions from config if available\n const consumerVersions = useRef<ConsumerFeatureVersions | undefined>(\n getState().config?.featureVersions\n );\n\n // Fetch status\n const fetchStatus = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const newStatus = await getSharedFeaturesStatus(consumerVersions.current);\n\n if (mountedRef.current) {\n setStatus(newStatus);\n }\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Failed to fetch feature flags';\n\n if (mountedRef.current) {\n setError(message);\n console.error('[shared-features] Error fetching feature flags:', err);\n }\n } finally {\n if (mountedRef.current) {\n setLoading(false);\n }\n }\n }, []);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchStatus();\n }\n }, [autoFetch, fetchStatus]);\n\n // Auto-refresh interval\n useEffect(() => {\n if (!autoRefresh) return;\n\n const interval = setInterval(() => {\n fetchStatus();\n }, refreshInterval);\n\n return () => clearInterval(interval);\n }, [autoRefresh, refreshInterval, fetchStatus]);\n\n // Cleanup on unmount\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n // Check if a feature is available\n const checkFeatureAvailable = useCallback(\n (featureId: FeatureId): boolean => {\n // Quick check using cached data\n if (!status) return isFeatureEnabled(featureId);\n\n const availability = status.features[featureId];\n return availability?.available ?? false;\n },\n [status]\n );\n\n // Get feature availability details\n const getAvailability = useCallback(\n (featureId: FeatureId): FeatureAvailability | null => {\n if (!status) return null;\n return status.features[featureId] ?? null;\n },\n [status]\n );\n\n // Refetch with cache clear\n const refetch = useCallback(async () => {\n clearFeatureFlagsCache();\n await fetchStatus();\n }, [fetchStatus]);\n\n return {\n status,\n loading,\n error,\n refetch,\n isFeatureAvailable: checkFeatureAvailable,\n getFeatureAvailability: getAvailability,\n hasDeprecatedFeatures: (status?.deprecatedFeatures.length ?? 0) > 0,\n hasUpgradeRequired: (status?.upgradeRequiredFeatures.length ?? 0) > 0,\n };\n}\n\n/**\n * Hook to check a single feature's availability\n *\n * @param featureId - The feature to check\n * @returns Feature availability and loading state\n *\n * @example\n * ```tsx\n * const { available, loading, deprecated } = useFeature('contactInfo');\n *\n * if (loading) return <Spinner />;\n * if (!available) return null;\n *\n * return <ContactInfo />;\n * ```\n */\nexport function useFeature(featureId: FeatureId) {\n const [availability, setAvailability] = useState<FeatureAvailability | null>(\n null\n );\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let mounted = true;\n\n const checkFeature = async () => {\n if (!isInitialized()) {\n setLoading(false);\n return;\n }\n\n try {\n const consumerVersions = getState().config?.featureVersions;\n const result = await checkFeatureAvailability(\n featureId,\n consumerVersions\n );\n\n if (mounted) {\n setAvailability(result);\n }\n } catch (err) {\n console.error(\n `[shared-features] Error checking feature '${featureId}':`,\n err\n );\n } finally {\n if (mounted) {\n setLoading(false);\n }\n }\n };\n\n checkFeature();\n\n return () => {\n mounted = false;\n };\n }, [featureId]);\n\n return {\n /** Whether the feature is available */\n available: availability?.available ?? false,\n /** Whether the check is in progress */\n loading,\n /** Full availability details */\n availability,\n /** Whether the feature is enabled (but might need upgrade) */\n enabled: availability?.enabled ?? false,\n /** Whether using a deprecated version */\n deprecated: availability?.deprecated ?? false,\n /** Whether an upgrade is required */\n upgradeRequired: availability?.upgradeRequired ?? false,\n /** Deprecation warning if applicable */\n deprecationWarning: availability?.deprecationWarning,\n /** Reason feature is unavailable */\n unavailableReason: availability?.unavailableReason,\n };\n}\n\n/**\n * Hook to subscribe to real-time feature flag updates\n *\n * @param callback - Function to call when flags change\n *\n * @example\n * ```tsx\n * useFeatureFlagsSubscription((flags) => {\n * if (flags?.maintenanceMode) {\n * showMaintenanceBanner();\n * }\n * });\n * ```\n */\nexport function useFeatureFlagsSubscription(\n callback: (status: SharedFeaturesStatus | null) => void\n) {\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n if (!isInitialized()) return;\n\n const consumerVersions = getState().config?.featureVersions;\n\n const unsubscribe = subscribeToFeatureFlags(async (flags) => {\n if (!flags) {\n callbackRef.current(null);\n return;\n }\n\n // Get full status with current flags\n const status = await getSharedFeaturesStatus(consumerVersions);\n callbackRef.current(status);\n });\n\n return () => unsubscribe();\n }, []);\n}\n\n/**\n * Hook to check if shared-features is operational\n *\n * @returns Whether shared-features is operational\n *\n * @example\n * ```tsx\n * const { operational, maintenanceMessage } = useSharedFeaturesOperational();\n *\n * if (!operational) {\n * return <MaintenancePage message={maintenanceMessage} />;\n * }\n * ```\n */\nexport function useSharedFeaturesOperational() {\n const { status, loading } = useFeatureFlags({ autoFetch: true });\n\n return {\n /** Whether shared-features is operational */\n operational: status?.operational ?? false,\n /** Whether check is in progress */\n loading,\n /** Maintenance message if in maintenance mode */\n maintenanceMessage: status?.maintenanceMessage,\n /** Whether in maintenance mode */\n maintenanceMode: status?.maintenanceMode ?? false,\n /** Current API version */\n apiVersion: status?.apiVersion ?? 'v1',\n };\n}\n\n/**\n * Hook for conditional rendering based on feature availability\n *\n * @param featureId - The feature to check\n * @returns Object with show/hide helpers\n *\n * @example\n * ```tsx\n * const { shouldRender, FallbackOrChildren } = useFeatureGate('contactInfo');\n *\n * return (\n * <FallbackOrChildren fallback={<OldContactInfo />}>\n * <NewContactInfo />\n * </FallbackOrChildren>\n * );\n * ```\n */\nexport function useFeatureGate(featureId: FeatureId) {\n const { available, loading, deprecated, deprecationWarning } =\n useFeature(featureId);\n\n // Log deprecation warning in development\n useEffect(() => {\n if (deprecated && deprecationWarning) {\n console.warn(`[shared-features] ${deprecationWarning}`);\n }\n }, [deprecated, deprecationWarning]);\n\n return {\n /** Whether the feature should be rendered */\n shouldRender: available,\n /** Whether still checking availability */\n loading,\n /** Whether using deprecated version */\n deprecated,\n /** Component that renders children if available, fallback otherwise */\n FallbackOrChildren: useCallback(\n ({\n children,\n fallback = null,\n }: {\n children: React.ReactNode;\n fallback?: React.ReactNode;\n }) => {\n if (loading) return null;\n return available ? children : fallback;\n },\n [available, loading]\n ),\n };\n}\n"],"names":["broadcasts","useState","useRef","useCallback","isInitialized","fetchActiveBroadcasts","useEffect","subscribeToBroadcasts","trackBroadcastDismiss","trackBroadcastImpression","trackBroadcastClick","clearBroadcastsCache","getState","getSharedFeaturesStatus","isFeatureEnabled","clearFeatureFlagsCache","checkFeatureAvailability","subscribeToFeatureFlags"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { useState, useRef, useEffect, useCallback } from "react";
2
- import { f as fetchActiveBroadcasts, s as subscribeToBroadcasts, h as trackBroadcastDismiss, j as trackBroadcastImpression, t as trackBroadcastClick, c as clearBroadcastsCache } from "./broadcasts-CbrWZpcI.js";
3
- import { R as isInitialized, $ as getState, M as getSharedFeaturesStatus, Q as isFeatureEnabled, g as clearFeatureFlagsCache, V as subscribeToFeatureFlags, c as checkFeatureAvailability } from "./commonFeatures-78YVrQq1.js";
2
+ import { f as fetchActiveBroadcasts, s as subscribeToBroadcasts, h as trackBroadcastDismiss, j as trackBroadcastImpression, t as trackBroadcastClick, c as clearBroadcastsCache } from "./broadcasts-Dlu51_38.js";
3
+ import { R as isInitialized, $ as getState, M as getSharedFeaturesStatus, Q as isFeatureEnabled, g as clearFeatureFlagsCache, V as subscribeToFeatureFlags, c as checkFeatureAvailability } from "./commonFeatures-LzPnbR6z.js";
4
4
  function useBroadcasts(options = {}) {
5
5
  const {
6
6
  variant,
@@ -365,4 +365,4 @@ export {
365
365
  useToastBroadcasts as k,
366
366
  useAnnouncementModal as u
367
367
  };
368
- //# sourceMappingURL=useFeatureFlags-DlMeLBH2.js.map
368
+ //# sourceMappingURL=useFeatureFlags-DhIb0HYi.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFeatureFlags-DlMeLBH2.js","sources":["../src/hooks/useBroadcasts.ts","../src/hooks/useFeatureFlags.ts"],"sourcesContent":["/**\n * useBroadcasts Hook\n *\n * React hook for fetching and displaying broadcast notifications in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n fetchActiveBroadcasts,\n subscribeToBroadcasts,\n trackBroadcastImpression,\n trackBroadcastClick,\n trackBroadcastDismiss,\n clearBroadcastsCache,\n} from '../services/broadcasts';\nimport { isInitialized } from '../firebase/config';\nimport type {\n BroadcastNotification,\n BroadcastVariant,\n NotificationPlatform,\n UseBroadcastsReturn,\n} from '../types/notifications';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface UseBroadcastsOptions {\n /** Filter by variant type */\n variant?: BroadcastVariant;\n /** Override platform detection */\n platform?: NotificationPlatform;\n /** Maximum number of broadcasts to return */\n maxBroadcasts?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Whether to subscribe to real-time updates (default: false) */\n realtime?: boolean;\n}\n\n// ============================================================================\n// MAIN HOOK\n// ============================================================================\n\n/**\n * Hook to fetch and manage broadcast notifications\n *\n * @example\n * ```tsx\n * const { broadcasts, isLoading, dismissBroadcast, trackClick } = useBroadcasts({\n * variant: 'banner',\n * maxBroadcasts: 3,\n * });\n *\n * return (\n * <div>\n * {broadcasts.map(broadcast => (\n * <BroadcastBanner\n * key={broadcast.id}\n * broadcast={broadcast}\n * onDismiss={() => dismissBroadcast(broadcast.id)}\n * onActionClick={() => trackClick(broadcast.id)}\n * />\n * ))}\n * </div>\n * );\n * ```\n */\nexport function useBroadcasts(\n options: UseBroadcastsOptions = {}\n): UseBroadcastsReturn {\n const {\n variant,\n platform,\n maxBroadcasts = 10,\n autoFetch = true,\n realtime = false,\n } = options;\n\n const [broadcasts, setBroadcasts] = useState<BroadcastNotification[]>([]);\n const [isLoading, setIsLoading] = useState(autoFetch);\n const [error, setError] = useState<Error | null>(null);\n const dismissedIdsRef = useRef<Set<string>>(new Set());\n const impressionTrackedRef = useRef<Set<string>>(new Set());\n\n // Fetch broadcasts\n const fetchBroadcastsData = useCallback(async () => {\n if (!isInitialized()) {\n setError(new Error('shared-features not initialized'));\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const allBroadcasts = await fetchActiveBroadcasts({\n variant,\n platform,\n });\n\n // Filter out locally dismissed and limit count\n const filteredBroadcasts = allBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filteredBroadcasts);\n } catch (err) {\n const errorObj =\n err instanceof Error ? err : new Error('Failed to fetch broadcasts');\n setError(errorObj);\n console.error('[shared-features] Error fetching broadcasts:', err);\n } finally {\n setIsLoading(false);\n }\n }, [variant, platform, maxBroadcasts]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchBroadcastsData();\n }\n }, [autoFetch, fetchBroadcastsData]);\n\n // Real-time subscription\n useEffect(() => {\n if (!realtime || !isInitialized()) return;\n\n const unsubscribe = subscribeToBroadcasts(\n (newBroadcasts) => {\n // Filter and limit\n const filtered = newBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .filter((b) => !variant || b.variant === variant)\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filtered);\n setIsLoading(false);\n },\n { variant, platform }\n );\n\n return () => {\n unsubscribe();\n };\n }, [realtime, variant, platform, maxBroadcasts]);\n\n // Dismiss broadcast\n const handleDismissBroadcast = useCallback((broadcastId: string) => {\n // Update local state immediately\n dismissedIdsRef.current.add(broadcastId);\n setBroadcasts((prev) => prev.filter((b) => b.id !== broadcastId));\n\n // Track dismiss and persist\n trackBroadcastDismiss(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track dismiss:', err);\n });\n }, []);\n\n // Track impression\n const handleTrackImpression = useCallback((broadcastId: string) => {\n // Only track once per session\n if (impressionTrackedRef.current.has(broadcastId)) return;\n impressionTrackedRef.current.add(broadcastId);\n\n trackBroadcastImpression(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track impression:', err);\n });\n }, []);\n\n // Track click\n const handleTrackClick = useCallback((broadcastId: string) => {\n trackBroadcastClick(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track click:', err);\n });\n }, []);\n\n // Check if dismissed\n const checkIsDismissed = useCallback((broadcastId: string) => {\n return dismissedIdsRef.current.has(broadcastId);\n }, []);\n\n // Refresh broadcasts\n const handleRefresh = useCallback(async () => {\n clearBroadcastsCache();\n await fetchBroadcastsData();\n }, [fetchBroadcastsData]);\n\n return {\n broadcasts,\n isLoading,\n error,\n dismissBroadcast: handleDismissBroadcast,\n trackImpression: handleTrackImpression,\n trackClick: handleTrackClick,\n isDismissed: checkIsDismissed,\n refresh: handleRefresh,\n };\n}\n\n// ============================================================================\n// SPECIALIZED HOOKS\n// ============================================================================\n\n/**\n * Hook specifically for banner broadcasts\n */\nexport function useBannerBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'banner' });\n}\n\n/**\n * Hook specifically for modal broadcasts\n */\nexport function useModalBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'modal', maxBroadcasts: 1 });\n}\n\n/**\n * Hook specifically for toast broadcasts\n */\nexport function useToastBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'toast' });\n}\n\n/**\n * Hook specifically for bell/notification center broadcasts\n */\nexport function useBellBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'bell' });\n}\n\n// ============================================================================\n// SINGLE BROADCAST HOOK\n// ============================================================================\n\nexport interface UseSingleBroadcastReturn {\n /** The broadcast (first matching one) */\n broadcast: BroadcastNotification | null;\n /** Whether loading */\n isLoading: boolean;\n /** Error if any */\n error: Error | null;\n /** Dismiss the broadcast */\n dismiss: () => void;\n /** Track impression */\n trackImpression: () => void;\n /** Track click */\n trackClick: () => void;\n /** Whether broadcast was dismissed */\n isDismissed: boolean;\n}\n\n/**\n * Hook to get a single broadcast (convenience wrapper)\n *\n * @example\n * ```tsx\n * const { broadcast, dismiss, trackClick } = useSingleBroadcast({ variant: 'modal' });\n *\n * if (!broadcast) return null;\n *\n * return (\n * <AnnouncementModal\n * broadcast={broadcast}\n * onClose={dismiss}\n * onActionClick={trackClick}\n * />\n * );\n * ```\n */\nexport function useSingleBroadcast(\n options: Omit<UseBroadcastsOptions, 'maxBroadcasts'> = {}\n): UseSingleBroadcastReturn {\n const result = useBroadcasts({ ...options, maxBroadcasts: 1 });\n const broadcast = result.broadcasts[0] || null;\n\n return {\n broadcast,\n isLoading: result.isLoading,\n error: result.error,\n dismiss: () => broadcast && result.dismissBroadcast(broadcast.id),\n trackImpression: () => broadcast && result.trackImpression(broadcast.id),\n trackClick: () => broadcast && result.trackClick(broadcast.id),\n isDismissed: broadcast ? result.isDismissed(broadcast.id) : false,\n };\n}\n\n// ============================================================================\n// ANNOUNCEMENT MODAL HOOK\n// ============================================================================\n\nexport interface UseAnnouncementModalReturn {\n /** The modal broadcast to display */\n broadcast: BroadcastNotification | null;\n /** Whether the modal should be shown */\n isOpen: boolean;\n /** Close the modal */\n close: () => void;\n /** Handle action button click */\n handleAction: () => void;\n}\n\n/**\n * Hook for managing announcement modal display\n * Automatically tracks impressions and handles dismissal\n *\n * @example\n * ```tsx\n * const { broadcast, isOpen, close, handleAction } = useAnnouncementModal();\n *\n * return (\n * <Dialog open={isOpen} onOpenChange={(open) => !open && close()}>\n * <DialogContent>\n * <h2>{broadcast?.title}</h2>\n * <p>{broadcast?.message}</p>\n * {broadcast?.actionUrl && (\n * <Button onClick={handleAction}>{broadcast.actionText || 'Learn More'}</Button>\n * )}\n * </DialogContent>\n * </Dialog>\n * );\n * ```\n */\nexport function useAnnouncementModal(): UseAnnouncementModalReturn {\n const { broadcast, dismiss, trackImpression, trackClick } = useSingleBroadcast({\n variant: 'modal',\n });\n\n const [isOpen, setIsOpen] = useState(false);\n const impressionTrackedRef = useRef(false);\n\n // Show modal when broadcast is available\n useEffect(() => {\n if (broadcast && !impressionTrackedRef.current) {\n setIsOpen(true);\n trackImpression();\n impressionTrackedRef.current = true;\n }\n }, [broadcast, trackImpression]);\n\n const handleClose = useCallback(() => {\n setIsOpen(false);\n dismiss();\n }, [dismiss]);\n\n const handleAction = useCallback(() => {\n trackClick();\n if (broadcast?.actionUrl) {\n window.open(broadcast.actionUrl, '_blank');\n }\n handleClose();\n }, [broadcast, trackClick, handleClose]);\n\n return {\n broadcast,\n isOpen,\n close: handleClose,\n handleAction,\n };\n}\n","/**\n * useFeatureFlags Hook\n *\n * React hook for checking feature availability and status in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n getSharedFeaturesStatus,\n checkFeatureAvailability,\n isFeatureEnabled,\n subscribeToFeatureFlags,\n clearFeatureFlagsCache,\n} from '../services/featureFlags';\nimport { isInitialized, getState } from '../firebase/config';\nimport type {\n FeatureId,\n FeatureAvailability,\n SharedFeaturesStatus,\n UseFeatureFlagsOptions,\n UseFeatureFlagsResult,\n ConsumerFeatureVersions,\n} from '../types/featureFlags';\n\n/**\n * Hook to fetch and monitor feature flags\n *\n * @param options - Hook options\n * @returns Feature flags status and utilities\n *\n * @example\n * ```tsx\n * const {\n * status,\n * loading,\n * isFeatureAvailable,\n * hasDeprecatedFeatures\n * } = useFeatureFlags();\n *\n * if (loading) return <Spinner />;\n *\n * if (!status?.operational) {\n * return <MaintenancePage message={status?.maintenanceMessage} />;\n * }\n *\n * if (isFeatureAvailable('contactInfo')) {\n * return <ContactInfo />;\n * }\n * ```\n */\nexport function useFeatureFlags(\n options: UseFeatureFlagsOptions = {}\n): UseFeatureFlagsResult {\n const {\n autoRefresh = false,\n refreshInterval = 5 * 60 * 1000, // 5 minutes\n autoFetch = true,\n } = options;\n\n const [status, setStatus] = useState<SharedFeaturesStatus | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n\n // Track mounted state to prevent state updates after unmount\n const mountedRef = useRef(true);\n\n // Get consumer versions from config if available\n const consumerVersions = useRef<ConsumerFeatureVersions | undefined>(\n getState().config?.featureVersions\n );\n\n // Fetch status\n const fetchStatus = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const newStatus = await getSharedFeaturesStatus(consumerVersions.current);\n\n if (mountedRef.current) {\n setStatus(newStatus);\n }\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Failed to fetch feature flags';\n\n if (mountedRef.current) {\n setError(message);\n console.error('[shared-features] Error fetching feature flags:', err);\n }\n } finally {\n if (mountedRef.current) {\n setLoading(false);\n }\n }\n }, []);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchStatus();\n }\n }, [autoFetch, fetchStatus]);\n\n // Auto-refresh interval\n useEffect(() => {\n if (!autoRefresh) return;\n\n const interval = setInterval(() => {\n fetchStatus();\n }, refreshInterval);\n\n return () => clearInterval(interval);\n }, [autoRefresh, refreshInterval, fetchStatus]);\n\n // Cleanup on unmount\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n // Check if a feature is available\n const checkFeatureAvailable = useCallback(\n (featureId: FeatureId): boolean => {\n // Quick check using cached data\n if (!status) return isFeatureEnabled(featureId);\n\n const availability = status.features[featureId];\n return availability?.available ?? false;\n },\n [status]\n );\n\n // Get feature availability details\n const getAvailability = useCallback(\n (featureId: FeatureId): FeatureAvailability | null => {\n if (!status) return null;\n return status.features[featureId] ?? null;\n },\n [status]\n );\n\n // Refetch with cache clear\n const refetch = useCallback(async () => {\n clearFeatureFlagsCache();\n await fetchStatus();\n }, [fetchStatus]);\n\n return {\n status,\n loading,\n error,\n refetch,\n isFeatureAvailable: checkFeatureAvailable,\n getFeatureAvailability: getAvailability,\n hasDeprecatedFeatures: (status?.deprecatedFeatures.length ?? 0) > 0,\n hasUpgradeRequired: (status?.upgradeRequiredFeatures.length ?? 0) > 0,\n };\n}\n\n/**\n * Hook to check a single feature's availability\n *\n * @param featureId - The feature to check\n * @returns Feature availability and loading state\n *\n * @example\n * ```tsx\n * const { available, loading, deprecated } = useFeature('contactInfo');\n *\n * if (loading) return <Spinner />;\n * if (!available) return null;\n *\n * return <ContactInfo />;\n * ```\n */\nexport function useFeature(featureId: FeatureId) {\n const [availability, setAvailability] = useState<FeatureAvailability | null>(\n null\n );\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let mounted = true;\n\n const checkFeature = async () => {\n if (!isInitialized()) {\n setLoading(false);\n return;\n }\n\n try {\n const consumerVersions = getState().config?.featureVersions;\n const result = await checkFeatureAvailability(\n featureId,\n consumerVersions\n );\n\n if (mounted) {\n setAvailability(result);\n }\n } catch (err) {\n console.error(\n `[shared-features] Error checking feature '${featureId}':`,\n err\n );\n } finally {\n if (mounted) {\n setLoading(false);\n }\n }\n };\n\n checkFeature();\n\n return () => {\n mounted = false;\n };\n }, [featureId]);\n\n return {\n /** Whether the feature is available */\n available: availability?.available ?? false,\n /** Whether the check is in progress */\n loading,\n /** Full availability details */\n availability,\n /** Whether the feature is enabled (but might need upgrade) */\n enabled: availability?.enabled ?? false,\n /** Whether using a deprecated version */\n deprecated: availability?.deprecated ?? false,\n /** Whether an upgrade is required */\n upgradeRequired: availability?.upgradeRequired ?? false,\n /** Deprecation warning if applicable */\n deprecationWarning: availability?.deprecationWarning,\n /** Reason feature is unavailable */\n unavailableReason: availability?.unavailableReason,\n };\n}\n\n/**\n * Hook to subscribe to real-time feature flag updates\n *\n * @param callback - Function to call when flags change\n *\n * @example\n * ```tsx\n * useFeatureFlagsSubscription((flags) => {\n * if (flags?.maintenanceMode) {\n * showMaintenanceBanner();\n * }\n * });\n * ```\n */\nexport function useFeatureFlagsSubscription(\n callback: (status: SharedFeaturesStatus | null) => void\n) {\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n if (!isInitialized()) return;\n\n const consumerVersions = getState().config?.featureVersions;\n\n const unsubscribe = subscribeToFeatureFlags(async (flags) => {\n if (!flags) {\n callbackRef.current(null);\n return;\n }\n\n // Get full status with current flags\n const status = await getSharedFeaturesStatus(consumerVersions);\n callbackRef.current(status);\n });\n\n return () => unsubscribe();\n }, []);\n}\n\n/**\n * Hook to check if shared-features is operational\n *\n * @returns Whether shared-features is operational\n *\n * @example\n * ```tsx\n * const { operational, maintenanceMessage } = useSharedFeaturesOperational();\n *\n * if (!operational) {\n * return <MaintenancePage message={maintenanceMessage} />;\n * }\n * ```\n */\nexport function useSharedFeaturesOperational() {\n const { status, loading } = useFeatureFlags({ autoFetch: true });\n\n return {\n /** Whether shared-features is operational */\n operational: status?.operational ?? false,\n /** Whether check is in progress */\n loading,\n /** Maintenance message if in maintenance mode */\n maintenanceMessage: status?.maintenanceMessage,\n /** Whether in maintenance mode */\n maintenanceMode: status?.maintenanceMode ?? false,\n /** Current API version */\n apiVersion: status?.apiVersion ?? 'v1',\n };\n}\n\n/**\n * Hook for conditional rendering based on feature availability\n *\n * @param featureId - The feature to check\n * @returns Object with show/hide helpers\n *\n * @example\n * ```tsx\n * const { shouldRender, FallbackOrChildren } = useFeatureGate('contactInfo');\n *\n * return (\n * <FallbackOrChildren fallback={<OldContactInfo />}>\n * <NewContactInfo />\n * </FallbackOrChildren>\n * );\n * ```\n */\nexport function useFeatureGate(featureId: FeatureId) {\n const { available, loading, deprecated, deprecationWarning } =\n useFeature(featureId);\n\n // Log deprecation warning in development\n useEffect(() => {\n if (deprecated && deprecationWarning) {\n console.warn(`[shared-features] ${deprecationWarning}`);\n }\n }, [deprecated, deprecationWarning]);\n\n return {\n /** Whether the feature should be rendered */\n shouldRender: available,\n /** Whether still checking availability */\n loading,\n /** Whether using deprecated version */\n deprecated,\n /** Component that renders children if available, fallback otherwise */\n FallbackOrChildren: useCallback(\n ({\n children,\n fallback = null,\n }: {\n children: React.ReactNode;\n fallback?: React.ReactNode;\n }) => {\n if (loading) return null;\n return available ? children : fallback;\n },\n [available, loading]\n ),\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useFeatureFlags-DhIb0HYi.js","sources":["../src/hooks/useBroadcasts.ts","../src/hooks/useFeatureFlags.ts"],"sourcesContent":["/**\n * useBroadcasts Hook\n *\n * React hook for fetching and displaying broadcast notifications in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n fetchActiveBroadcasts,\n subscribeToBroadcasts,\n trackBroadcastImpression,\n trackBroadcastClick,\n trackBroadcastDismiss,\n clearBroadcastsCache,\n} from '../services/broadcasts';\nimport { isInitialized } from '../firebase/config';\nimport type {\n BroadcastNotification,\n BroadcastVariant,\n NotificationPlatform,\n UseBroadcastsReturn,\n} from '../types/notifications';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\nexport interface UseBroadcastsOptions {\n /** Filter by variant type */\n variant?: BroadcastVariant;\n /** Override platform detection */\n platform?: NotificationPlatform;\n /** Maximum number of broadcasts to return */\n maxBroadcasts?: number;\n /** Whether to auto-fetch on mount (default: true) */\n autoFetch?: boolean;\n /** Whether to subscribe to real-time updates (default: false) */\n realtime?: boolean;\n}\n\n// ============================================================================\n// MAIN HOOK\n// ============================================================================\n\n/**\n * Hook to fetch and manage broadcast notifications\n *\n * @example\n * ```tsx\n * const { broadcasts, isLoading, dismissBroadcast, trackClick } = useBroadcasts({\n * variant: 'banner',\n * maxBroadcasts: 3,\n * });\n *\n * return (\n * <div>\n * {broadcasts.map(broadcast => (\n * <BroadcastBanner\n * key={broadcast.id}\n * broadcast={broadcast}\n * onDismiss={() => dismissBroadcast(broadcast.id)}\n * onActionClick={() => trackClick(broadcast.id)}\n * />\n * ))}\n * </div>\n * );\n * ```\n */\nexport function useBroadcasts(\n options: UseBroadcastsOptions = {}\n): UseBroadcastsReturn {\n const {\n variant,\n platform,\n maxBroadcasts = 10,\n autoFetch = true,\n realtime = false,\n } = options;\n\n const [broadcasts, setBroadcasts] = useState<BroadcastNotification[]>([]);\n const [isLoading, setIsLoading] = useState(autoFetch);\n const [error, setError] = useState<Error | null>(null);\n const dismissedIdsRef = useRef<Set<string>>(new Set());\n const impressionTrackedRef = useRef<Set<string>>(new Set());\n\n // Fetch broadcasts\n const fetchBroadcastsData = useCallback(async () => {\n if (!isInitialized()) {\n setError(new Error('shared-features not initialized'));\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const allBroadcasts = await fetchActiveBroadcasts({\n variant,\n platform,\n });\n\n // Filter out locally dismissed and limit count\n const filteredBroadcasts = allBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filteredBroadcasts);\n } catch (err) {\n const errorObj =\n err instanceof Error ? err : new Error('Failed to fetch broadcasts');\n setError(errorObj);\n console.error('[shared-features] Error fetching broadcasts:', err);\n } finally {\n setIsLoading(false);\n }\n }, [variant, platform, maxBroadcasts]);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchBroadcastsData();\n }\n }, [autoFetch, fetchBroadcastsData]);\n\n // Real-time subscription\n useEffect(() => {\n if (!realtime || !isInitialized()) return;\n\n const unsubscribe = subscribeToBroadcasts(\n (newBroadcasts) => {\n // Filter and limit\n const filtered = newBroadcasts\n .filter((b) => !dismissedIdsRef.current.has(b.id))\n .filter((b) => !variant || b.variant === variant)\n .slice(0, maxBroadcasts);\n\n setBroadcasts(filtered);\n setIsLoading(false);\n },\n { variant, platform }\n );\n\n return () => {\n unsubscribe();\n };\n }, [realtime, variant, platform, maxBroadcasts]);\n\n // Dismiss broadcast\n const handleDismissBroadcast = useCallback((broadcastId: string) => {\n // Update local state immediately\n dismissedIdsRef.current.add(broadcastId);\n setBroadcasts((prev) => prev.filter((b) => b.id !== broadcastId));\n\n // Track dismiss and persist\n trackBroadcastDismiss(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track dismiss:', err);\n });\n }, []);\n\n // Track impression\n const handleTrackImpression = useCallback((broadcastId: string) => {\n // Only track once per session\n if (impressionTrackedRef.current.has(broadcastId)) return;\n impressionTrackedRef.current.add(broadcastId);\n\n trackBroadcastImpression(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track impression:', err);\n });\n }, []);\n\n // Track click\n const handleTrackClick = useCallback((broadcastId: string) => {\n trackBroadcastClick(broadcastId).catch((err) => {\n console.error('[shared-features] Failed to track click:', err);\n });\n }, []);\n\n // Check if dismissed\n const checkIsDismissed = useCallback((broadcastId: string) => {\n return dismissedIdsRef.current.has(broadcastId);\n }, []);\n\n // Refresh broadcasts\n const handleRefresh = useCallback(async () => {\n clearBroadcastsCache();\n await fetchBroadcastsData();\n }, [fetchBroadcastsData]);\n\n return {\n broadcasts,\n isLoading,\n error,\n dismissBroadcast: handleDismissBroadcast,\n trackImpression: handleTrackImpression,\n trackClick: handleTrackClick,\n isDismissed: checkIsDismissed,\n refresh: handleRefresh,\n };\n}\n\n// ============================================================================\n// SPECIALIZED HOOKS\n// ============================================================================\n\n/**\n * Hook specifically for banner broadcasts\n */\nexport function useBannerBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'banner' });\n}\n\n/**\n * Hook specifically for modal broadcasts\n */\nexport function useModalBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'modal', maxBroadcasts: 1 });\n}\n\n/**\n * Hook specifically for toast broadcasts\n */\nexport function useToastBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'toast' });\n}\n\n/**\n * Hook specifically for bell/notification center broadcasts\n */\nexport function useBellBroadcasts(\n options: Omit<UseBroadcastsOptions, 'variant'> = {}\n) {\n return useBroadcasts({ ...options, variant: 'bell' });\n}\n\n// ============================================================================\n// SINGLE BROADCAST HOOK\n// ============================================================================\n\nexport interface UseSingleBroadcastReturn {\n /** The broadcast (first matching one) */\n broadcast: BroadcastNotification | null;\n /** Whether loading */\n isLoading: boolean;\n /** Error if any */\n error: Error | null;\n /** Dismiss the broadcast */\n dismiss: () => void;\n /** Track impression */\n trackImpression: () => void;\n /** Track click */\n trackClick: () => void;\n /** Whether broadcast was dismissed */\n isDismissed: boolean;\n}\n\n/**\n * Hook to get a single broadcast (convenience wrapper)\n *\n * @example\n * ```tsx\n * const { broadcast, dismiss, trackClick } = useSingleBroadcast({ variant: 'modal' });\n *\n * if (!broadcast) return null;\n *\n * return (\n * <AnnouncementModal\n * broadcast={broadcast}\n * onClose={dismiss}\n * onActionClick={trackClick}\n * />\n * );\n * ```\n */\nexport function useSingleBroadcast(\n options: Omit<UseBroadcastsOptions, 'maxBroadcasts'> = {}\n): UseSingleBroadcastReturn {\n const result = useBroadcasts({ ...options, maxBroadcasts: 1 });\n const broadcast = result.broadcasts[0] || null;\n\n return {\n broadcast,\n isLoading: result.isLoading,\n error: result.error,\n dismiss: () => broadcast && result.dismissBroadcast(broadcast.id),\n trackImpression: () => broadcast && result.trackImpression(broadcast.id),\n trackClick: () => broadcast && result.trackClick(broadcast.id),\n isDismissed: broadcast ? result.isDismissed(broadcast.id) : false,\n };\n}\n\n// ============================================================================\n// ANNOUNCEMENT MODAL HOOK\n// ============================================================================\n\nexport interface UseAnnouncementModalReturn {\n /** The modal broadcast to display */\n broadcast: BroadcastNotification | null;\n /** Whether the modal should be shown */\n isOpen: boolean;\n /** Close the modal */\n close: () => void;\n /** Handle action button click */\n handleAction: () => void;\n}\n\n/**\n * Hook for managing announcement modal display\n * Automatically tracks impressions and handles dismissal\n *\n * @example\n * ```tsx\n * const { broadcast, isOpen, close, handleAction } = useAnnouncementModal();\n *\n * return (\n * <Dialog open={isOpen} onOpenChange={(open) => !open && close()}>\n * <DialogContent>\n * <h2>{broadcast?.title}</h2>\n * <p>{broadcast?.message}</p>\n * {broadcast?.actionUrl && (\n * <Button onClick={handleAction}>{broadcast.actionText || 'Learn More'}</Button>\n * )}\n * </DialogContent>\n * </Dialog>\n * );\n * ```\n */\nexport function useAnnouncementModal(): UseAnnouncementModalReturn {\n const { broadcast, dismiss, trackImpression, trackClick } = useSingleBroadcast({\n variant: 'modal',\n });\n\n const [isOpen, setIsOpen] = useState(false);\n const impressionTrackedRef = useRef(false);\n\n // Show modal when broadcast is available\n useEffect(() => {\n if (broadcast && !impressionTrackedRef.current) {\n setIsOpen(true);\n trackImpression();\n impressionTrackedRef.current = true;\n }\n }, [broadcast, trackImpression]);\n\n const handleClose = useCallback(() => {\n setIsOpen(false);\n dismiss();\n }, [dismiss]);\n\n const handleAction = useCallback(() => {\n trackClick();\n if (broadcast?.actionUrl) {\n window.open(broadcast.actionUrl, '_blank');\n }\n handleClose();\n }, [broadcast, trackClick, handleClose]);\n\n return {\n broadcast,\n isOpen,\n close: handleClose,\n handleAction,\n };\n}\n","/**\n * useFeatureFlags Hook\n *\n * React hook for checking feature availability and status in consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n getSharedFeaturesStatus,\n checkFeatureAvailability,\n isFeatureEnabled,\n subscribeToFeatureFlags,\n clearFeatureFlagsCache,\n} from '../services/featureFlags';\nimport { isInitialized, getState } from '../firebase/config';\nimport type {\n FeatureId,\n FeatureAvailability,\n SharedFeaturesStatus,\n UseFeatureFlagsOptions,\n UseFeatureFlagsResult,\n ConsumerFeatureVersions,\n} from '../types/featureFlags';\n\n/**\n * Hook to fetch and monitor feature flags\n *\n * @param options - Hook options\n * @returns Feature flags status and utilities\n *\n * @example\n * ```tsx\n * const {\n * status,\n * loading,\n * isFeatureAvailable,\n * hasDeprecatedFeatures\n * } = useFeatureFlags();\n *\n * if (loading) return <Spinner />;\n *\n * if (!status?.operational) {\n * return <MaintenancePage message={status?.maintenanceMessage} />;\n * }\n *\n * if (isFeatureAvailable('contactInfo')) {\n * return <ContactInfo />;\n * }\n * ```\n */\nexport function useFeatureFlags(\n options: UseFeatureFlagsOptions = {}\n): UseFeatureFlagsResult {\n const {\n autoRefresh = false,\n refreshInterval = 5 * 60 * 1000, // 5 minutes\n autoFetch = true,\n } = options;\n\n const [status, setStatus] = useState<SharedFeaturesStatus | null>(null);\n const [loading, setLoading] = useState(autoFetch);\n const [error, setError] = useState<string | null>(null);\n\n // Track mounted state to prevent state updates after unmount\n const mountedRef = useRef(true);\n\n // Get consumer versions from config if available\n const consumerVersions = useRef<ConsumerFeatureVersions | undefined>(\n getState().config?.featureVersions\n );\n\n // Fetch status\n const fetchStatus = useCallback(async () => {\n if (!isInitialized()) {\n setError('shared-features not initialized');\n setLoading(false);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n const newStatus = await getSharedFeaturesStatus(consumerVersions.current);\n\n if (mountedRef.current) {\n setStatus(newStatus);\n }\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Failed to fetch feature flags';\n\n if (mountedRef.current) {\n setError(message);\n console.error('[shared-features] Error fetching feature flags:', err);\n }\n } finally {\n if (mountedRef.current) {\n setLoading(false);\n }\n }\n }, []);\n\n // Auto-fetch on mount\n useEffect(() => {\n if (autoFetch) {\n fetchStatus();\n }\n }, [autoFetch, fetchStatus]);\n\n // Auto-refresh interval\n useEffect(() => {\n if (!autoRefresh) return;\n\n const interval = setInterval(() => {\n fetchStatus();\n }, refreshInterval);\n\n return () => clearInterval(interval);\n }, [autoRefresh, refreshInterval, fetchStatus]);\n\n // Cleanup on unmount\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n // Check if a feature is available\n const checkFeatureAvailable = useCallback(\n (featureId: FeatureId): boolean => {\n // Quick check using cached data\n if (!status) return isFeatureEnabled(featureId);\n\n const availability = status.features[featureId];\n return availability?.available ?? false;\n },\n [status]\n );\n\n // Get feature availability details\n const getAvailability = useCallback(\n (featureId: FeatureId): FeatureAvailability | null => {\n if (!status) return null;\n return status.features[featureId] ?? null;\n },\n [status]\n );\n\n // Refetch with cache clear\n const refetch = useCallback(async () => {\n clearFeatureFlagsCache();\n await fetchStatus();\n }, [fetchStatus]);\n\n return {\n status,\n loading,\n error,\n refetch,\n isFeatureAvailable: checkFeatureAvailable,\n getFeatureAvailability: getAvailability,\n hasDeprecatedFeatures: (status?.deprecatedFeatures.length ?? 0) > 0,\n hasUpgradeRequired: (status?.upgradeRequiredFeatures.length ?? 0) > 0,\n };\n}\n\n/**\n * Hook to check a single feature's availability\n *\n * @param featureId - The feature to check\n * @returns Feature availability and loading state\n *\n * @example\n * ```tsx\n * const { available, loading, deprecated } = useFeature('contactInfo');\n *\n * if (loading) return <Spinner />;\n * if (!available) return null;\n *\n * return <ContactInfo />;\n * ```\n */\nexport function useFeature(featureId: FeatureId) {\n const [availability, setAvailability] = useState<FeatureAvailability | null>(\n null\n );\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let mounted = true;\n\n const checkFeature = async () => {\n if (!isInitialized()) {\n setLoading(false);\n return;\n }\n\n try {\n const consumerVersions = getState().config?.featureVersions;\n const result = await checkFeatureAvailability(\n featureId,\n consumerVersions\n );\n\n if (mounted) {\n setAvailability(result);\n }\n } catch (err) {\n console.error(\n `[shared-features] Error checking feature '${featureId}':`,\n err\n );\n } finally {\n if (mounted) {\n setLoading(false);\n }\n }\n };\n\n checkFeature();\n\n return () => {\n mounted = false;\n };\n }, [featureId]);\n\n return {\n /** Whether the feature is available */\n available: availability?.available ?? false,\n /** Whether the check is in progress */\n loading,\n /** Full availability details */\n availability,\n /** Whether the feature is enabled (but might need upgrade) */\n enabled: availability?.enabled ?? false,\n /** Whether using a deprecated version */\n deprecated: availability?.deprecated ?? false,\n /** Whether an upgrade is required */\n upgradeRequired: availability?.upgradeRequired ?? false,\n /** Deprecation warning if applicable */\n deprecationWarning: availability?.deprecationWarning,\n /** Reason feature is unavailable */\n unavailableReason: availability?.unavailableReason,\n };\n}\n\n/**\n * Hook to subscribe to real-time feature flag updates\n *\n * @param callback - Function to call when flags change\n *\n * @example\n * ```tsx\n * useFeatureFlagsSubscription((flags) => {\n * if (flags?.maintenanceMode) {\n * showMaintenanceBanner();\n * }\n * });\n * ```\n */\nexport function useFeatureFlagsSubscription(\n callback: (status: SharedFeaturesStatus | null) => void\n) {\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n if (!isInitialized()) return;\n\n const consumerVersions = getState().config?.featureVersions;\n\n const unsubscribe = subscribeToFeatureFlags(async (flags) => {\n if (!flags) {\n callbackRef.current(null);\n return;\n }\n\n // Get full status with current flags\n const status = await getSharedFeaturesStatus(consumerVersions);\n callbackRef.current(status);\n });\n\n return () => unsubscribe();\n }, []);\n}\n\n/**\n * Hook to check if shared-features is operational\n *\n * @returns Whether shared-features is operational\n *\n * @example\n * ```tsx\n * const { operational, maintenanceMessage } = useSharedFeaturesOperational();\n *\n * if (!operational) {\n * return <MaintenancePage message={maintenanceMessage} />;\n * }\n * ```\n */\nexport function useSharedFeaturesOperational() {\n const { status, loading } = useFeatureFlags({ autoFetch: true });\n\n return {\n /** Whether shared-features is operational */\n operational: status?.operational ?? false,\n /** Whether check is in progress */\n loading,\n /** Maintenance message if in maintenance mode */\n maintenanceMessage: status?.maintenanceMessage,\n /** Whether in maintenance mode */\n maintenanceMode: status?.maintenanceMode ?? false,\n /** Current API version */\n apiVersion: status?.apiVersion ?? 'v1',\n };\n}\n\n/**\n * Hook for conditional rendering based on feature availability\n *\n * @param featureId - The feature to check\n * @returns Object with show/hide helpers\n *\n * @example\n * ```tsx\n * const { shouldRender, FallbackOrChildren } = useFeatureGate('contactInfo');\n *\n * return (\n * <FallbackOrChildren fallback={<OldContactInfo />}>\n * <NewContactInfo />\n * </FallbackOrChildren>\n * );\n * ```\n */\nexport function useFeatureGate(featureId: FeatureId) {\n const { available, loading, deprecated, deprecationWarning } =\n useFeature(featureId);\n\n // Log deprecation warning in development\n useEffect(() => {\n if (deprecated && deprecationWarning) {\n console.warn(`[shared-features] ${deprecationWarning}`);\n }\n }, [deprecated, deprecationWarning]);\n\n return {\n /** Whether the feature should be rendered */\n shouldRender: available,\n /** Whether still checking availability */\n loading,\n /** Whether using deprecated version */\n deprecated,\n /** Component that renders children if available, fallback otherwise */\n FallbackOrChildren: useCallback(\n ({\n children,\n fallback = null,\n }: {\n children: React.ReactNode;\n fallback?: React.ReactNode;\n }) => {\n if (loading) return null;\n return available ? children : fallback;\n },\n [available, loading]\n ),\n };\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shared-features",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "engines": {
5
5
  "node": ">=24.13.0"
6
6
  },
@@ -1 +0,0 @@
1
- {"version":3,"file":"featureFlags-DLdibcv_.js","sources":["../src/types/commonFeatures.ts","../src/types/featureFlags.ts"],"sourcesContent":["/**\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\n/**\n * Keys for common feature collection name overrides.\n * Consumers can pass custom collection names via initSharedFeatures()\n * to reuse existing collections instead of creating new ones.\n */\nexport type CommonFeatureCollectionKey =\n | 'CONTACT_INFO'\n | 'DEVELOPER_INFO'\n | 'SOCIAL_LINKS'\n | 'ADDRESS_INFO'\n | 'PAYMENT_OPTIONS'\n | 'SERVICES'\n | 'SKILLS'\n | 'TESTIMONIALS'\n | 'PROJECTS';\n\nconst DEFAULT_COMMON_FEATURE_COLLECTIONS: Record<CommonFeatureCollectionKey, string> = {\n CONTACT_INFO: 'portfolio_contact_info',\n DEVELOPER_INFO: 'portfolio_developer_info',\n SOCIAL_LINKS: 'portfolio_social_links',\n ADDRESS_INFO: 'portfolio_address_info',\n PAYMENT_OPTIONS: 'portfolio_payment_options',\n SERVICES: 'portfolio_services',\n SKILLS: 'portfolio_skills',\n TESTIMONIALS: 'testimonials',\n PROJECTS: 'portfolio_projects',\n};\n\n/**\n * Active collection names. Defaults to portfolio_ prefixed collections\n * managed from aoneahsan-portfolio admin panel.\n * Can be overridden via initSharedFeatures({ collectionNames: {...} })\n * if a consumer needs custom collection names.\n */\nexport let COMMON_FEATURE_COLLECTIONS: Record<CommonFeatureCollectionKey, string> = {\n ...DEFAULT_COMMON_FEATURE_COLLECTIONS,\n};\n\n/**\n * Override collection names for common features.\n * Called internally by initSharedFeatures when collectionNames config is provided.\n */\nexport function setCommonFeatureCollectionNames(\n overrides: Partial<Record<CommonFeatureCollectionKey, string>>\n): void {\n COMMON_FEATURE_COLLECTIONS = {\n ...DEFAULT_COMMON_FEATURE_COLLECTIONS,\n ...overrides,\n };\n}\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 | 'platform'\n | 'wallet'\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 platform: 'Platform',\n wallet: 'Digital Wallet',\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 displayName?: string;\n description?: string;\n instructions?: 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 | 'extension'\n | 'full-stack'\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 extension: 'Extension',\n 'full-stack': 'Full Stack',\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","/**\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"],"names":[],"mappings":"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"}