shared-features 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/{admin-commonFeatures-CMRCJCHS.js → admin-commonFeatures-CFhvjgp9.js} +3 -3
  2. package/dist/{admin-commonFeatures-CMRCJCHS.js.map → admin-commonFeatures-CFhvjgp9.js.map} +1 -1
  3. package/dist/{admin-commonFeatures-CoJPCUYw.cjs → admin-commonFeatures-pnaXeix_.cjs} +3 -3
  4. package/dist/{admin-commonFeatures-CoJPCUYw.cjs.map → admin-commonFeatures-pnaXeix_.cjs.map} +1 -1
  5. package/dist/{broadcasts-CHTb-Z7-.cjs → broadcasts-DZsQNd4R.cjs} +2 -2
  6. package/dist/{broadcasts-CHTb-Z7-.cjs.map → broadcasts-DZsQNd4R.cjs.map} +1 -1
  7. package/dist/{broadcasts-Dkmto2dR.js → broadcasts-Dlu51_38.js} +2 -2
  8. package/dist/{broadcasts-Dkmto2dR.js.map → broadcasts-Dlu51_38.js.map} +1 -1
  9. package/dist/{commonFeatures-DTaIBhdj.cjs → commonFeatures-BuY97_K4.cjs} +63 -27
  10. package/dist/commonFeatures-BuY97_K4.cjs.map +1 -0
  11. package/dist/{commonFeatures-D-MZcecu.js → commonFeatures-LzPnbR6z.js} +63 -27
  12. package/dist/commonFeatures-LzPnbR6z.js.map +1 -0
  13. package/dist/components/index.cjs +1 -1
  14. package/dist/components/index.js +1 -1
  15. package/dist/{featureFlags-CqKSOF7q.js → featureFlags-CSudFX4x.js} +7 -2
  16. package/dist/{featureFlags-CqKSOF7q.js.map → featureFlags-CSudFX4x.js.map} +1 -1
  17. package/dist/{featureFlags-C9iXfoJT.cjs → featureFlags-CfDmshkF.cjs} +7 -2
  18. package/dist/{featureFlags-C9iXfoJT.cjs.map → featureFlags-CfDmshkF.cjs.map} +1 -1
  19. package/dist/hooks/index.cjs +2 -2
  20. package/dist/hooks/index.js +2 -2
  21. package/dist/{index-u8rmRNdW.js → index-D2YWycum.js} +3 -3
  22. package/dist/{index-u8rmRNdW.js.map → index-D2YWycum.js.map} +1 -1
  23. package/dist/{index-z5yRtI-J.cjs → index-DxjbpnFC.cjs} +3 -3
  24. package/dist/{index-z5yRtI-J.cjs.map → index-DxjbpnFC.cjs.map} +1 -1
  25. package/dist/index.cjs +7 -7
  26. package/dist/index.js +7 -7
  27. package/dist/services/commonFeatures.d.ts.map +1 -1
  28. package/dist/services/index.cjs +4 -4
  29. package/dist/services/index.js +4 -4
  30. package/dist/types/commonFeatures.d.ts +15 -2
  31. package/dist/types/commonFeatures.d.ts.map +1 -1
  32. package/dist/types/index.cjs +1 -1
  33. package/dist/types/index.js +1 -1
  34. package/dist/{useCommonFeatures-B0H0o03A.cjs → useCommonFeatures-CWqe4EhH.cjs} +2 -2
  35. package/dist/{useCommonFeatures-B0H0o03A.cjs.map → useCommonFeatures-CWqe4EhH.cjs.map} +1 -1
  36. package/dist/{useCommonFeatures-B2x-8atT.js → useCommonFeatures-CnmI83Er.js} +2 -2
  37. package/dist/{useCommonFeatures-B2x-8atT.js.map → useCommonFeatures-CnmI83Er.js.map} +1 -1
  38. package/dist/{useFeatureFlags-Da05kQKA.cjs → useFeatureFlags-9_E7gair.cjs} +3 -3
  39. package/dist/{useFeatureFlags-Da05kQKA.cjs.map → useFeatureFlags-9_E7gair.cjs.map} +1 -1
  40. package/dist/{useFeatureFlags-CeojCWSx.js → useFeatureFlags-DhIb0HYi.js} +3 -3
  41. package/dist/{useFeatureFlags-CeojCWSx.js.map → useFeatureFlags-DhIb0HYi.js.map} +1 -1
  42. package/package.json +1 -1
  43. package/dist/commonFeatures-D-MZcecu.js.map +0 -1
  44. package/dist/commonFeatures-DTaIBhdj.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"useCommonFeatures-B2x-8atT.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-CHTb-Z7-.cjs");
4
- const commonFeatures = require("./commonFeatures-DTaIBhdj.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-Da05kQKA.cjs.map
367
+ //# sourceMappingURL=useFeatureFlags-9_E7gair.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFeatureFlags-Da05kQKA.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-Dkmto2dR.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-D-MZcecu.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-CeojCWSx.js.map
368
+ //# sourceMappingURL=useFeatureFlags-DhIb0HYi.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFeatureFlags-CeojCWSx.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.5",
3
+ "version": "0.1.7",
4
4
  "engines": {
5
5
  "node": ">=24.13.0"
6
6
  },