shared-features 0.0.8 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +205 -2
  2. package/dist/{admin-notifications-D9n9h-eY.cjs → admin-notifications-CLzQc8zF.cjs} +20 -20
  3. package/dist/{admin-notifications-D9n9h-eY.cjs.map → admin-notifications-CLzQc8zF.cjs.map} +1 -1
  4. package/dist/{admin-notifications-p1dy3zIP.js → admin-notifications-C_7ReIMi.js} +13 -13
  5. package/dist/{admin-notifications-p1dy3zIP.js.map → admin-notifications-C_7ReIMi.js.map} +1 -1
  6. package/dist/{broadcasts-3_WfQMNL.cjs → broadcasts-6w_9X92Z.cjs} +10 -10
  7. package/dist/{broadcasts-3_WfQMNL.cjs.map → broadcasts-6w_9X92Z.cjs.map} +1 -1
  8. package/dist/{broadcasts-DgZUzqMf.js → broadcasts-CK4sGMz4.js} +12 -12
  9. package/dist/{broadcasts-DgZUzqMf.js.map → broadcasts-CK4sGMz4.js.map} +1 -1
  10. package/dist/commonFeatures-CaqcEOik.js +1255 -0
  11. package/dist/commonFeatures-CaqcEOik.js.map +1 -0
  12. package/dist/commonFeatures-DhWaBEv_.cjs +222 -0
  13. package/dist/commonFeatures-DhWaBEv_.cjs.map +1 -0
  14. package/dist/commonFeatures-XJ9fuxg_.js +223 -0
  15. package/dist/commonFeatures-XJ9fuxg_.js.map +1 -0
  16. package/dist/commonFeatures-ofZOjnZ0.cjs +1276 -0
  17. package/dist/commonFeatures-ofZOjnZ0.cjs.map +1 -0
  18. package/dist/components/common/index.d.ts +60 -0
  19. package/dist/components/common/index.d.ts.map +1 -0
  20. package/dist/components/index.cjs +31 -23
  21. package/dist/components/index.cjs.map +1 -1
  22. package/dist/components/index.d.ts +1 -0
  23. package/dist/components/index.d.ts.map +1 -1
  24. package/dist/components/index.js +21 -13
  25. package/dist/firebase/config.d.ts +34 -0
  26. package/dist/firebase/config.d.ts.map +1 -1
  27. package/dist/hooks/index.cjs +26 -13
  28. package/dist/hooks/index.cjs.map +1 -1
  29. package/dist/hooks/index.d.ts +2 -0
  30. package/dist/hooks/index.d.ts.map +1 -1
  31. package/dist/hooks/index.js +24 -11
  32. package/dist/hooks/useCommonFeatures.d.ts +102 -0
  33. package/dist/hooks/useCommonFeatures.d.ts.map +1 -0
  34. package/dist/hooks/useFeatureFlags.d.ts +134 -0
  35. package/dist/hooks/useFeatureFlags.d.ts.map +1 -0
  36. package/dist/{AnnouncementModal-sxH4K5gy.js → index-BeNmzbTD.js} +212 -17
  37. package/dist/index-BeNmzbTD.js.map +1 -0
  38. package/dist/{AnnouncementModal-Bqy0pn3V.cjs → index-DB40ObYe.cjs} +209 -14
  39. package/dist/index-DB40ObYe.cjs.map +1 -0
  40. package/dist/index.cjs +127 -60
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.ts +10 -5
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +175 -108
  45. package/dist/index.js.map +1 -1
  46. package/dist/notifications/index.js +14 -14
  47. package/dist/services/commonFeatures.d.ts +25 -0
  48. package/dist/services/commonFeatures.d.ts.map +1 -0
  49. package/dist/services/featureFlags.d.ts +71 -0
  50. package/dist/services/featureFlags.d.ts.map +1 -0
  51. package/dist/services/index.cjs +49 -17
  52. package/dist/services/index.cjs.map +1 -1
  53. package/dist/services/index.d.ts +2 -0
  54. package/dist/services/index.d.ts.map +1 -1
  55. package/dist/services/index.js +75 -43
  56. package/dist/services/index.js.map +1 -1
  57. package/dist/types/commonFeatures.d.ts +232 -0
  58. package/dist/types/commonFeatures.d.ts.map +1 -0
  59. package/dist/types/featureFlags.d.ts +203 -0
  60. package/dist/types/featureFlags.d.ts.map +1 -0
  61. package/dist/types/index.cjs +17 -0
  62. package/dist/types/index.cjs.map +1 -1
  63. package/dist/types/index.d.ts +3 -1
  64. package/dist/types/index.d.ts.map +1 -1
  65. package/dist/types/index.js +17 -0
  66. package/dist/types/index.js.map +1 -1
  67. package/dist/useCommonFeatures-0EU_VRqi.cjs +488 -0
  68. package/dist/useCommonFeatures-0EU_VRqi.cjs.map +1 -0
  69. package/dist/useCommonFeatures-DP2YyB7m.js +489 -0
  70. package/dist/useCommonFeatures-DP2YyB7m.js.map +1 -0
  71. package/dist/useFeatureFlags-DHnOm0sA.js +368 -0
  72. package/dist/useFeatureFlags-DHnOm0sA.js.map +1 -0
  73. package/dist/useFeatureFlags-DRR1r3rt.cjs +367 -0
  74. package/dist/useFeatureFlags-DRR1r3rt.cjs.map +1 -0
  75. package/dist/{useNotificationEvents-D8DVxah1.js → useNotificationEvents-DAmR7FYF.js} +14 -14
  76. package/dist/{useNotificationEvents-D8DVxah1.js.map → useNotificationEvents-DAmR7FYF.js.map} +1 -1
  77. package/package.json +15 -8
  78. package/dist/AnnouncementModal-Bqy0pn3V.cjs.map +0 -1
  79. package/dist/AnnouncementModal-sxH4K5gy.js.map +0 -1
  80. package/dist/analytics-40-S_fHC.js +0 -440
  81. package/dist/analytics-40-S_fHC.js.map +0 -1
  82. package/dist/analytics-lEzOx2vl.cjs +0 -461
  83. package/dist/analytics-lEzOx2vl.cjs.map +0 -1
  84. package/dist/useBroadcasts-DzpCcbC8.js +0 -161
  85. package/dist/useBroadcasts-DzpCcbC8.js.map +0 -1
  86. package/dist/useBroadcasts-FP6ZrcY_.cjs +0 -160
  87. package/dist/useBroadcasts-FP6ZrcY_.cjs.map +0 -1
  88. package/dist/useCampaigns-BOZ9dDsG.cjs +0 -152
  89. package/dist/useCampaigns-BOZ9dDsG.cjs.map +0 -1
  90. package/dist/useCampaigns-D46b9zuf.js +0 -153
  91. package/dist/useCampaigns-D46b9zuf.js.map +0 -1
@@ -0,0 +1,489 @@
1
+ import { useState, useCallback, useEffect, useRef } from "react";
2
+ import { O as isInitialized, n as fetchActiveCampaigns, M as isEligibleForCampaign, V as trackImpression, T as trackClick, U as trackClose, d as clearCampaignsCache, o as fetchAddressInfo, a as clearAddressInfoCache, q as fetchContactInfo, e as clearContactInfoCache, Q as subscribeToContactInfo, r as fetchDeveloperInfo, f as clearDeveloperInfoCache, R as subscribeToDeveloperInfo, t as fetchPaymentOptions, h as clearPaymentOptionsCache, v as fetchServices, j as clearServicesCache, w as fetchSkills, k as clearSkillsCache, x as fetchSocialLinks, l as clearSocialLinksCache, y as fetchTestimonials, m as clearTestimonialsCache } from "./commonFeatures-CaqcEOik.js";
3
+ function useCampaigns(options) {
4
+ const {
5
+ placement,
6
+ maxCampaigns = 5,
7
+ autoFetch = true,
8
+ defaultSmallVariant = "small_panel_2",
9
+ defaultLargeVariant: _defaultLargeVariant = "large_slider_1"
10
+ } = options;
11
+ const [campaigns, setCampaigns] = useState([]);
12
+ const [loading, setLoading] = useState(autoFetch);
13
+ const [error, setError] = useState(null);
14
+ const fetchCampaigns = useCallback(async () => {
15
+ if (!isInitialized()) {
16
+ setError("shared-features not initialized");
17
+ setLoading(false);
18
+ return;
19
+ }
20
+ setLoading(true);
21
+ setError(null);
22
+ try {
23
+ const allCampaigns = await fetchActiveCampaigns(placement);
24
+ const eligibleCampaigns = [];
25
+ for (const campaign of allCampaigns) {
26
+ const eligible = await isEligibleForCampaign(
27
+ campaign.id,
28
+ campaign.frequencyDays
29
+ );
30
+ if (eligible) {
31
+ eligibleCampaigns.push(campaign);
32
+ if (eligibleCampaigns.length >= maxCampaigns) break;
33
+ }
34
+ }
35
+ setCampaigns(eligibleCampaigns);
36
+ } catch (err) {
37
+ const message = err instanceof Error ? err.message : "Failed to fetch campaigns";
38
+ setError(message);
39
+ console.error("[shared-features] Error fetching campaigns:", err);
40
+ } finally {
41
+ setLoading(false);
42
+ }
43
+ }, [placement, maxCampaigns]);
44
+ useEffect(() => {
45
+ if (autoFetch) {
46
+ fetchCampaigns();
47
+ }
48
+ }, [autoFetch, fetchCampaigns]);
49
+ const handleRecordImpression = useCallback(
50
+ async (campaign) => {
51
+ const variant = campaign.variant.startsWith("small_") ? campaign.variant : campaign.variant.startsWith("large_") ? campaign.variant : defaultSmallVariant;
52
+ await trackImpression(
53
+ campaign.id,
54
+ campaign.productId,
55
+ placement,
56
+ variant,
57
+ campaign.frequencyDays
58
+ );
59
+ },
60
+ [placement, defaultSmallVariant]
61
+ );
62
+ const handleRecordClick = useCallback(
63
+ async (campaign) => {
64
+ const variant = campaign.variant;
65
+ await trackClick(campaign.id, campaign.productId, placement, variant);
66
+ },
67
+ [placement]
68
+ );
69
+ const handleRecordClose = useCallback(
70
+ async (campaign) => {
71
+ const variant = campaign.variant;
72
+ await trackClose(campaign.id, campaign.productId, placement, variant);
73
+ },
74
+ [placement]
75
+ );
76
+ const refetch = useCallback(async () => {
77
+ clearCampaignsCache();
78
+ await fetchCampaigns();
79
+ }, [fetchCampaigns]);
80
+ return {
81
+ campaigns,
82
+ campaign: campaigns[0] || null,
83
+ loading,
84
+ error,
85
+ refetch,
86
+ recordImpression: handleRecordImpression,
87
+ recordClick: handleRecordClick,
88
+ recordClose: handleRecordClose
89
+ };
90
+ }
91
+ function useCampaign(options) {
92
+ return useCampaigns({ ...options, maxCampaigns: 1 });
93
+ }
94
+ const STORAGE_KEYS = {
95
+ oneTimeShown: "shared_features_onetime_ad_shown",
96
+ appVersion: "shared_features_app_version"
97
+ };
98
+ function useOneTimeAdModal() {
99
+ const [hasShown, setHasShown] = useState(false);
100
+ const [shouldShow, setShouldShow] = useState(false);
101
+ useEffect(() => {
102
+ const hasSeenModal = localStorage.getItem(STORAGE_KEYS.oneTimeShown);
103
+ if (!hasSeenModal) {
104
+ setShouldShow(true);
105
+ }
106
+ }, []);
107
+ const markAsShown = useCallback(() => {
108
+ localStorage.setItem(STORAGE_KEYS.oneTimeShown, "true");
109
+ setHasShown(true);
110
+ setShouldShow(false);
111
+ }, []);
112
+ return {
113
+ /** Whether the modal should be displayed */
114
+ shouldShow: shouldShow && !hasShown,
115
+ /** Mark the modal as shown (call when user dismisses) */
116
+ markAsShown
117
+ };
118
+ }
119
+ function useUpdateAdModal(currentVersion) {
120
+ const version = currentVersion || void 0 || "1.0.0";
121
+ const [shouldShow, setShouldShow] = useState(false);
122
+ const [previousVersion, setPreviousVersion] = useState(null);
123
+ useEffect(() => {
124
+ const storedVersion = localStorage.getItem(STORAGE_KEYS.appVersion);
125
+ if (!storedVersion) {
126
+ localStorage.setItem(STORAGE_KEYS.appVersion, version);
127
+ } else if (storedVersion !== version) {
128
+ setShouldShow(true);
129
+ setPreviousVersion(storedVersion);
130
+ }
131
+ }, [version]);
132
+ const markAsShown = useCallback(() => {
133
+ localStorage.setItem(STORAGE_KEYS.appVersion, version);
134
+ setShouldShow(false);
135
+ }, [version]);
136
+ return {
137
+ /** Whether the modal should be displayed */
138
+ shouldShow,
139
+ /** Previous app version (before update) */
140
+ previousVersion,
141
+ /** Current app version */
142
+ currentVersion: version,
143
+ /** Mark the modal as shown (call when user dismisses) */
144
+ markAsShown
145
+ };
146
+ }
147
+ function useContactInfo(options = {}) {
148
+ const { autoFetch = true, realtime = false } = options;
149
+ const [data, setData] = useState(null);
150
+ const [loading, setLoading] = useState(autoFetch);
151
+ const [error, setError] = useState(null);
152
+ const mountedRef = useRef(true);
153
+ const fetch = useCallback(async () => {
154
+ if (!isInitialized()) {
155
+ setError("shared-features not initialized");
156
+ setLoading(false);
157
+ return;
158
+ }
159
+ setLoading(true);
160
+ setError(null);
161
+ try {
162
+ const result = await fetchContactInfo();
163
+ if (mountedRef.current) setData(result);
164
+ } catch (err) {
165
+ if (mountedRef.current) {
166
+ setError(err instanceof Error ? err.message : "Failed to fetch contact info");
167
+ }
168
+ } finally {
169
+ if (mountedRef.current) setLoading(false);
170
+ }
171
+ }, []);
172
+ const refetch = useCallback(async () => {
173
+ clearContactInfoCache();
174
+ await fetch();
175
+ }, [fetch]);
176
+ useEffect(() => {
177
+ mountedRef.current = true;
178
+ if (realtime && isInitialized()) {
179
+ const unsubscribe = subscribeToContactInfo((result) => {
180
+ if (mountedRef.current) {
181
+ setData(result);
182
+ setLoading(false);
183
+ }
184
+ });
185
+ return () => {
186
+ mountedRef.current = false;
187
+ unsubscribe();
188
+ };
189
+ }
190
+ if (autoFetch) fetch();
191
+ return () => {
192
+ mountedRef.current = false;
193
+ };
194
+ }, [autoFetch, realtime, fetch]);
195
+ return { data, loading, error, refetch };
196
+ }
197
+ function useDeveloperInfo(options = {}) {
198
+ const { autoFetch = true, realtime = false } = options;
199
+ const [data, setData] = useState(null);
200
+ const [loading, setLoading] = useState(autoFetch);
201
+ const [error, setError] = useState(null);
202
+ const mountedRef = useRef(true);
203
+ const fetch = useCallback(async () => {
204
+ if (!isInitialized()) {
205
+ setError("shared-features not initialized");
206
+ setLoading(false);
207
+ return;
208
+ }
209
+ setLoading(true);
210
+ setError(null);
211
+ try {
212
+ const result = await fetchDeveloperInfo();
213
+ if (mountedRef.current) setData(result);
214
+ } catch (err) {
215
+ if (mountedRef.current) {
216
+ setError(err instanceof Error ? err.message : "Failed to fetch developer info");
217
+ }
218
+ } finally {
219
+ if (mountedRef.current) setLoading(false);
220
+ }
221
+ }, []);
222
+ const refetch = useCallback(async () => {
223
+ clearDeveloperInfoCache();
224
+ await fetch();
225
+ }, [fetch]);
226
+ useEffect(() => {
227
+ mountedRef.current = true;
228
+ if (realtime && isInitialized()) {
229
+ const unsubscribe = subscribeToDeveloperInfo((result) => {
230
+ if (mountedRef.current) {
231
+ setData(result);
232
+ setLoading(false);
233
+ }
234
+ });
235
+ return () => {
236
+ mountedRef.current = false;
237
+ unsubscribe();
238
+ };
239
+ }
240
+ if (autoFetch) fetch();
241
+ return () => {
242
+ mountedRef.current = false;
243
+ };
244
+ }, [autoFetch, realtime, fetch]);
245
+ return { data, loading, error, refetch };
246
+ }
247
+ function useAddressInfo(options = {}) {
248
+ const { autoFetch = true } = options;
249
+ const [data, setData] = useState(null);
250
+ const [loading, setLoading] = useState(autoFetch);
251
+ const [error, setError] = useState(null);
252
+ const mountedRef = useRef(true);
253
+ const fetch = useCallback(async () => {
254
+ if (!isInitialized()) {
255
+ setError("shared-features not initialized");
256
+ setLoading(false);
257
+ return;
258
+ }
259
+ setLoading(true);
260
+ setError(null);
261
+ try {
262
+ const result = await fetchAddressInfo();
263
+ if (mountedRef.current) setData(result);
264
+ } catch (err) {
265
+ if (mountedRef.current) {
266
+ setError(err instanceof Error ? err.message : "Failed to fetch address info");
267
+ }
268
+ } finally {
269
+ if (mountedRef.current) setLoading(false);
270
+ }
271
+ }, []);
272
+ const refetch = useCallback(async () => {
273
+ clearAddressInfoCache();
274
+ await fetch();
275
+ }, [fetch]);
276
+ useEffect(() => {
277
+ mountedRef.current = true;
278
+ if (autoFetch) fetch();
279
+ return () => {
280
+ mountedRef.current = false;
281
+ };
282
+ }, [autoFetch, fetch]);
283
+ return { data, loading, error, refetch };
284
+ }
285
+ function useSocialLinks(options = {}) {
286
+ const { autoFetch = true, showIn, activeOnly } = options;
287
+ const [data, setData] = useState([]);
288
+ const [loading, setLoading] = useState(autoFetch);
289
+ const [error, setError] = useState(null);
290
+ const mountedRef = useRef(true);
291
+ const fetch = useCallback(async () => {
292
+ if (!isInitialized()) {
293
+ setError("shared-features not initialized");
294
+ setLoading(false);
295
+ return;
296
+ }
297
+ setLoading(true);
298
+ setError(null);
299
+ try {
300
+ const result = await fetchSocialLinks({ showIn, activeOnly });
301
+ if (mountedRef.current) setData(result);
302
+ } catch (err) {
303
+ if (mountedRef.current) {
304
+ setError(err instanceof Error ? err.message : "Failed to fetch social links");
305
+ }
306
+ } finally {
307
+ if (mountedRef.current) setLoading(false);
308
+ }
309
+ }, [showIn, activeOnly]);
310
+ const refetch = useCallback(async () => {
311
+ clearSocialLinksCache();
312
+ await fetch();
313
+ }, [fetch]);
314
+ useEffect(() => {
315
+ mountedRef.current = true;
316
+ if (autoFetch) fetch();
317
+ return () => {
318
+ mountedRef.current = false;
319
+ };
320
+ }, [autoFetch, fetch]);
321
+ return { data, loading, error, refetch };
322
+ }
323
+ function usePaymentOptions(options = {}) {
324
+ const { autoFetch = true, activeOnly, type } = options;
325
+ const [data, setData] = useState([]);
326
+ const [loading, setLoading] = useState(autoFetch);
327
+ const [error, setError] = useState(null);
328
+ const mountedRef = useRef(true);
329
+ const fetch = useCallback(async () => {
330
+ if (!isInitialized()) {
331
+ setError("shared-features not initialized");
332
+ setLoading(false);
333
+ return;
334
+ }
335
+ setLoading(true);
336
+ setError(null);
337
+ try {
338
+ const result = await fetchPaymentOptions({ activeOnly, type });
339
+ if (mountedRef.current) setData(result);
340
+ } catch (err) {
341
+ if (mountedRef.current) {
342
+ setError(err instanceof Error ? err.message : "Failed to fetch payment options");
343
+ }
344
+ } finally {
345
+ if (mountedRef.current) setLoading(false);
346
+ }
347
+ }, [activeOnly, type]);
348
+ const refetch = useCallback(async () => {
349
+ clearPaymentOptionsCache();
350
+ await fetch();
351
+ }, [fetch]);
352
+ useEffect(() => {
353
+ mountedRef.current = true;
354
+ if (autoFetch) fetch();
355
+ return () => {
356
+ mountedRef.current = false;
357
+ };
358
+ }, [autoFetch, fetch]);
359
+ return { data, loading, error, refetch };
360
+ }
361
+ function useServices(options = {}) {
362
+ const { autoFetch = true, category, activeOnly, featuredOnly } = options;
363
+ const [data, setData] = useState([]);
364
+ const [loading, setLoading] = useState(autoFetch);
365
+ const [error, setError] = useState(null);
366
+ const mountedRef = useRef(true);
367
+ const fetch = useCallback(async () => {
368
+ if (!isInitialized()) {
369
+ setError("shared-features not initialized");
370
+ setLoading(false);
371
+ return;
372
+ }
373
+ setLoading(true);
374
+ setError(null);
375
+ try {
376
+ const result = await fetchServices({ category, activeOnly, featuredOnly });
377
+ if (mountedRef.current) setData(result);
378
+ } catch (err) {
379
+ if (mountedRef.current) {
380
+ setError(err instanceof Error ? err.message : "Failed to fetch services");
381
+ }
382
+ } finally {
383
+ if (mountedRef.current) setLoading(false);
384
+ }
385
+ }, [category, activeOnly, featuredOnly]);
386
+ const refetch = useCallback(async () => {
387
+ clearServicesCache();
388
+ await fetch();
389
+ }, [fetch]);
390
+ useEffect(() => {
391
+ mountedRef.current = true;
392
+ if (autoFetch) fetch();
393
+ return () => {
394
+ mountedRef.current = false;
395
+ };
396
+ }, [autoFetch, fetch]);
397
+ return { data, loading, error, refetch };
398
+ }
399
+ function useSkills(options = {}) {
400
+ const { autoFetch = true, category, activeOnly, featuredOnly } = options;
401
+ const [data, setData] = useState([]);
402
+ const [loading, setLoading] = useState(autoFetch);
403
+ const [error, setError] = useState(null);
404
+ const mountedRef = useRef(true);
405
+ const fetch = useCallback(async () => {
406
+ if (!isInitialized()) {
407
+ setError("shared-features not initialized");
408
+ setLoading(false);
409
+ return;
410
+ }
411
+ setLoading(true);
412
+ setError(null);
413
+ try {
414
+ const result = await fetchSkills({ category, activeOnly, featuredOnly });
415
+ if (mountedRef.current) setData(result);
416
+ } catch (err) {
417
+ if (mountedRef.current) {
418
+ setError(err instanceof Error ? err.message : "Failed to fetch skills");
419
+ }
420
+ } finally {
421
+ if (mountedRef.current) setLoading(false);
422
+ }
423
+ }, [category, activeOnly, featuredOnly]);
424
+ const refetch = useCallback(async () => {
425
+ clearSkillsCache();
426
+ await fetch();
427
+ }, [fetch]);
428
+ useEffect(() => {
429
+ mountedRef.current = true;
430
+ if (autoFetch) fetch();
431
+ return () => {
432
+ mountedRef.current = false;
433
+ };
434
+ }, [autoFetch, fetch]);
435
+ return { data, loading, error, refetch };
436
+ }
437
+ function useTestimonials(options = {}) {
438
+ const { autoFetch = true, activeOnly, featuredOnly, limit } = options;
439
+ const [data, setData] = useState([]);
440
+ const [loading, setLoading] = useState(autoFetch);
441
+ const [error, setError] = useState(null);
442
+ const mountedRef = useRef(true);
443
+ const fetch = useCallback(async () => {
444
+ if (!isInitialized()) {
445
+ setError("shared-features not initialized");
446
+ setLoading(false);
447
+ return;
448
+ }
449
+ setLoading(true);
450
+ setError(null);
451
+ try {
452
+ const result = await fetchTestimonials({ activeOnly, featuredOnly, limit });
453
+ if (mountedRef.current) setData(result);
454
+ } catch (err) {
455
+ if (mountedRef.current) {
456
+ setError(err instanceof Error ? err.message : "Failed to fetch testimonials");
457
+ }
458
+ } finally {
459
+ if (mountedRef.current) setLoading(false);
460
+ }
461
+ }, [activeOnly, featuredOnly, limit]);
462
+ const refetch = useCallback(async () => {
463
+ clearTestimonialsCache();
464
+ await fetch();
465
+ }, [fetch]);
466
+ useEffect(() => {
467
+ mountedRef.current = true;
468
+ if (autoFetch) fetch();
469
+ return () => {
470
+ mountedRef.current = false;
471
+ };
472
+ }, [autoFetch, fetch]);
473
+ return { data, loading, error, refetch };
474
+ }
475
+ export {
476
+ useCampaign as a,
477
+ useCampaigns as b,
478
+ useContactInfo as c,
479
+ useDeveloperInfo as d,
480
+ useOneTimeAdModal as e,
481
+ usePaymentOptions as f,
482
+ useServices as g,
483
+ useSkills as h,
484
+ useSocialLinks as i,
485
+ useTestimonials as j,
486
+ useUpdateAdModal as k,
487
+ useAddressInfo as u
488
+ };
489
+ //# sourceMappingURL=useCommonFeatures-DP2YyB7m.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCommonFeatures-DP2YyB7m.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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"}