@foxpixel/react 0.1.0 → 0.2.0
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.
- package/dist/index.d.mts +741 -2
- package/dist/index.d.ts +741 -2
- package/dist/index.js +1228 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1197 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -3
package/dist/index.js
CHANGED
|
@@ -31,15 +31,46 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AuthProvider: () => AuthProvider,
|
|
34
|
+
Editable: () => Editable,
|
|
35
|
+
EditableHTML: () => EditableHTML,
|
|
36
|
+
EditableImage: () => EditableImage,
|
|
34
37
|
FoxPixelHttpClient: () => FoxPixelHttpClient,
|
|
35
38
|
FoxPixelProvider: () => FoxPixelProvider,
|
|
36
39
|
GuestOnlyRoute: () => GuestOnlyRoute,
|
|
37
40
|
ProtectedRoute: () => ProtectedRoute,
|
|
41
|
+
SITE_CONTENT_QUERY_KEY: () => SITE_CONTENT_QUERY_KEY,
|
|
42
|
+
getBlogPostSchemaLd: () => getBlogPostSchemaLd,
|
|
43
|
+
useAdminBlogAnalytics: () => useAdminBlogAnalytics,
|
|
44
|
+
useAdminBlogCategories: () => useAdminBlogCategories,
|
|
45
|
+
useAdminBlogComments: () => useAdminBlogComments,
|
|
46
|
+
useAdminBlogPost: () => useAdminBlogPost,
|
|
47
|
+
useAdminBlogPostMutations: () => useAdminBlogPostMutations,
|
|
48
|
+
useAdminBlogPosts: () => useAdminBlogPosts,
|
|
49
|
+
useAdminBlogSettings: () => useAdminBlogSettings,
|
|
50
|
+
useAdminBlogTags: () => useAdminBlogTags,
|
|
51
|
+
useAdminNewsletterStats: () => useAdminNewsletterStats,
|
|
52
|
+
useAdminNewsletterSubscribers: () => useAdminNewsletterSubscribers,
|
|
38
53
|
useAuth: () => useAuth,
|
|
54
|
+
useBlogCategories: () => useBlogCategories,
|
|
55
|
+
useBlogCommentSubmit: () => useBlogCommentSubmit,
|
|
56
|
+
useBlogComments: () => useBlogComments,
|
|
57
|
+
useBlogFeaturedPosts: () => useBlogFeaturedPosts,
|
|
58
|
+
useBlogPost: () => useBlogPost,
|
|
59
|
+
useBlogPosts: () => useBlogPosts,
|
|
60
|
+
useBlogTags: () => useBlogTags,
|
|
39
61
|
useContactCapture: () => useContactCapture,
|
|
62
|
+
useEditMode: () => useEditMode,
|
|
63
|
+
useEditModeMessaging: () => useEditModeMessaging,
|
|
40
64
|
useFoxPixelContext: () => useFoxPixelContext,
|
|
41
65
|
useLeadCapture: () => useLeadCapture,
|
|
66
|
+
useNewsletterSubscribe: () => useNewsletterSubscribe,
|
|
67
|
+
useNewsletterUnsubscribe: () => useNewsletterUnsubscribe,
|
|
68
|
+
useSendEditRequest: () => useSendEditRequest,
|
|
42
69
|
useServices: () => useServices,
|
|
70
|
+
useSiteContent: () => useSiteContent,
|
|
71
|
+
useSiteContentQuery: () => useSiteContentQuery,
|
|
72
|
+
useSiteContentSection: () => useSiteContentSection,
|
|
73
|
+
useSiteContents: () => useSiteContents,
|
|
43
74
|
withAuth: () => withAuth
|
|
44
75
|
});
|
|
45
76
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -284,7 +315,8 @@ function AuthProvider({
|
|
|
284
315
|
logout,
|
|
285
316
|
register,
|
|
286
317
|
updateProfile,
|
|
287
|
-
refetch: fetchCurrentUser
|
|
318
|
+
refetch: fetchCurrentUser,
|
|
319
|
+
hasPermission: (permission) => user !== null && permission === "site:content:update"
|
|
288
320
|
};
|
|
289
321
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AuthContext.Provider, { value, children });
|
|
290
322
|
}
|
|
@@ -390,13 +422,332 @@ function withAuth(Component, options = {}) {
|
|
|
390
422
|
};
|
|
391
423
|
}
|
|
392
424
|
|
|
393
|
-
// src/
|
|
425
|
+
// src/components/Editable.tsx
|
|
426
|
+
var import_react7 = require("react");
|
|
427
|
+
|
|
428
|
+
// src/hooks/useEditMode.ts
|
|
394
429
|
var import_react6 = require("react");
|
|
430
|
+
var import_react_query = require("@tanstack/react-query");
|
|
431
|
+
var SITE_CONTENT_QUERY_KEY = "siteContent";
|
|
432
|
+
function useEditMode() {
|
|
433
|
+
const [isEditMode, setIsEditMode] = (0, import_react6.useState)(false);
|
|
434
|
+
(0, import_react6.useEffect)(() => {
|
|
435
|
+
if (typeof window === "undefined") return;
|
|
436
|
+
const params = new URLSearchParams(window.location.search);
|
|
437
|
+
setIsEditMode(params.get("edit-mode") === "true");
|
|
438
|
+
}, []);
|
|
439
|
+
return isEditMode;
|
|
440
|
+
}
|
|
441
|
+
function useEditModeMessaging() {
|
|
442
|
+
const queryClient = (0, import_react_query.useQueryClient)();
|
|
443
|
+
const isEditMode = useEditMode();
|
|
444
|
+
(0, import_react6.useEffect)(() => {
|
|
445
|
+
if (!isEditMode || typeof window === "undefined") return;
|
|
446
|
+
window.parent.postMessage({ type: "FOXPIXEL_READY" }, "*");
|
|
447
|
+
const handleMessage = (event) => {
|
|
448
|
+
const { type, payload } = event.data || {};
|
|
449
|
+
if (type === "FOXPIXEL_CONTENT_UPDATED" && payload?.contentKey) {
|
|
450
|
+
queryClient.invalidateQueries({
|
|
451
|
+
queryKey: [SITE_CONTENT_QUERY_KEY, payload.contentKey]
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
window.addEventListener("message", handleMessage);
|
|
456
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
457
|
+
}, [isEditMode, queryClient]);
|
|
458
|
+
return isEditMode;
|
|
459
|
+
}
|
|
460
|
+
function useSendEditRequest() {
|
|
461
|
+
const isEditMode = useEditMode();
|
|
462
|
+
return (0, import_react6.useCallback)(
|
|
463
|
+
(contentKey, currentValue, contentType = "text", section, description) => {
|
|
464
|
+
if (!isEditMode) return;
|
|
465
|
+
if (typeof window !== "undefined" && window.parent !== window) {
|
|
466
|
+
window.parent.postMessage(
|
|
467
|
+
{
|
|
468
|
+
type: "FOXPIXEL_EDIT_CONTENT",
|
|
469
|
+
payload: {
|
|
470
|
+
contentKey,
|
|
471
|
+
currentValue,
|
|
472
|
+
contentType,
|
|
473
|
+
section,
|
|
474
|
+
description
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
"*"
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
[isEditMode]
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/hooks/useSiteContentQuery.ts
|
|
486
|
+
var import_react_query2 = require("@tanstack/react-query");
|
|
487
|
+
function useSiteContentQuery(contentKey, options) {
|
|
488
|
+
const { defaultValue } = options;
|
|
489
|
+
const { client } = useFoxPixelContext();
|
|
490
|
+
const { data, isLoading } = (0, import_react_query2.useQuery)({
|
|
491
|
+
queryKey: [SITE_CONTENT_QUERY_KEY, contentKey],
|
|
492
|
+
queryFn: async () => {
|
|
493
|
+
try {
|
|
494
|
+
const content = await client.get(
|
|
495
|
+
`/api/site/content/${encodeURIComponent(contentKey)}`
|
|
496
|
+
);
|
|
497
|
+
if (!content) return null;
|
|
498
|
+
return {
|
|
499
|
+
value: content.value ?? "",
|
|
500
|
+
contentType: content.contentType ?? "TEXT"
|
|
501
|
+
};
|
|
502
|
+
} catch (err) {
|
|
503
|
+
const status = err?.status;
|
|
504
|
+
if (status === 404) return null;
|
|
505
|
+
throw err;
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
staleTime: 1e3 * 60 * 5,
|
|
509
|
+
retry: 1
|
|
510
|
+
});
|
|
511
|
+
return {
|
|
512
|
+
value: data?.value ?? defaultValue,
|
|
513
|
+
isLoading,
|
|
514
|
+
contentType: data?.contentType ?? "TEXT"
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/utils/sanitize.ts
|
|
519
|
+
var import_isomorphic_dompurify = __toESM(require("isomorphic-dompurify"));
|
|
520
|
+
var DEFAULT_ALLOWED_TAGS = [
|
|
521
|
+
"p",
|
|
522
|
+
"br",
|
|
523
|
+
"strong",
|
|
524
|
+
"em",
|
|
525
|
+
"u",
|
|
526
|
+
"s",
|
|
527
|
+
"a",
|
|
528
|
+
"ul",
|
|
529
|
+
"ol",
|
|
530
|
+
"li",
|
|
531
|
+
"h1",
|
|
532
|
+
"h2",
|
|
533
|
+
"h3",
|
|
534
|
+
"h4",
|
|
535
|
+
"h5",
|
|
536
|
+
"h6",
|
|
537
|
+
"blockquote",
|
|
538
|
+
"code",
|
|
539
|
+
"pre",
|
|
540
|
+
"span",
|
|
541
|
+
"div",
|
|
542
|
+
"img",
|
|
543
|
+
"table",
|
|
544
|
+
"thead",
|
|
545
|
+
"tbody",
|
|
546
|
+
"tr",
|
|
547
|
+
"th",
|
|
548
|
+
"td"
|
|
549
|
+
];
|
|
550
|
+
var DEFAULT_ALLOWED_ATTR = ["href", "target", "rel", "src", "alt", "title", "class"];
|
|
551
|
+
function sanitizeHtml(html) {
|
|
552
|
+
if (typeof html !== "string") return "";
|
|
553
|
+
return import_isomorphic_dompurify.default.sanitize(html, {
|
|
554
|
+
ALLOWED_TAGS: DEFAULT_ALLOWED_TAGS,
|
|
555
|
+
ALLOWED_ATTR: DEFAULT_ALLOWED_ATTR,
|
|
556
|
+
ADD_ATTR: ["target"]
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// src/utils/cn.ts
|
|
561
|
+
function cn(...args) {
|
|
562
|
+
return args.filter(Boolean).join(" ");
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/components/Editable.tsx
|
|
566
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
567
|
+
function Editable({
|
|
568
|
+
contentKey,
|
|
569
|
+
defaultValue,
|
|
570
|
+
as = "span",
|
|
571
|
+
multiline = false,
|
|
572
|
+
className
|
|
573
|
+
}) {
|
|
574
|
+
const isEditMode = useEditModeMessaging();
|
|
575
|
+
const sendEditRequest = useSendEditRequest();
|
|
576
|
+
const { value, isLoading, contentType } = useSiteContentQuery(contentKey, {
|
|
577
|
+
defaultValue
|
|
578
|
+
});
|
|
579
|
+
const section = contentKey.includes(".") ? contentKey.split(".")[0] : void 0;
|
|
580
|
+
const handleClick = (0, import_react7.useCallback)(
|
|
581
|
+
(e) => {
|
|
582
|
+
if (isEditMode) {
|
|
583
|
+
e.preventDefault();
|
|
584
|
+
e.stopPropagation();
|
|
585
|
+
sendEditRequest(
|
|
586
|
+
contentKey,
|
|
587
|
+
value,
|
|
588
|
+
contentType?.toLowerCase() || "text",
|
|
589
|
+
section
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
[isEditMode, contentKey, value, contentType, section, sendEditRequest]
|
|
594
|
+
);
|
|
595
|
+
if (isLoading) {
|
|
596
|
+
return (0, import_react7.createElement)(as, {
|
|
597
|
+
className: cn(
|
|
598
|
+
"animate-pulse bg-muted rounded",
|
|
599
|
+
multiline ? "h-20" : "h-6",
|
|
600
|
+
"inline-block min-w-[100px]",
|
|
601
|
+
className
|
|
602
|
+
),
|
|
603
|
+
"aria-busy": true,
|
|
604
|
+
"aria-label": "Loading content..."
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
const editModeStyles = isEditMode ? cn(
|
|
608
|
+
"cursor-pointer transition-all duration-200",
|
|
609
|
+
"hover:ring-2 hover:ring-blue-500 hover:ring-offset-2",
|
|
610
|
+
"hover:bg-blue-50/50 dark:hover:bg-blue-950/30",
|
|
611
|
+
"relative group"
|
|
612
|
+
) : "";
|
|
613
|
+
if (multiline && value.includes("\n")) {
|
|
614
|
+
const safeBr = sanitizeHtml(value.replace(/\n/g, "<br />"));
|
|
615
|
+
return (0, import_react7.createElement)(as, {
|
|
616
|
+
className: cn(className, editModeStyles),
|
|
617
|
+
"data-content-key": contentKey,
|
|
618
|
+
"data-editable": isEditMode ? "true" : void 0,
|
|
619
|
+
onClick: isEditMode ? handleClick : void 0,
|
|
620
|
+
dangerouslySetInnerHTML: { __html: safeBr },
|
|
621
|
+
title: isEditMode ? "Click to edit" : void 0
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
return (0, import_react7.createElement)(
|
|
625
|
+
as,
|
|
626
|
+
{
|
|
627
|
+
className: cn(className, editModeStyles),
|
|
628
|
+
"data-content-key": contentKey,
|
|
629
|
+
"data-editable": isEditMode ? "true" : void 0,
|
|
630
|
+
onClick: isEditMode ? handleClick : void 0,
|
|
631
|
+
title: isEditMode ? "Click to edit" : void 0
|
|
632
|
+
},
|
|
633
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
634
|
+
value,
|
|
635
|
+
isEditMode && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "absolute -top-6 left-1/2 -translate-x-1/2 px-2 py-0.5 bg-blue-600 text-white text-[10px] rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none z-50", children: "Click to edit" })
|
|
636
|
+
] })
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
function EditableHTML({
|
|
640
|
+
contentKey,
|
|
641
|
+
defaultValue,
|
|
642
|
+
as = "div",
|
|
643
|
+
className
|
|
644
|
+
}) {
|
|
645
|
+
const isEditMode = useEditModeMessaging();
|
|
646
|
+
const sendEditRequest = useSendEditRequest();
|
|
647
|
+
const { value, isLoading } = useSiteContentQuery(contentKey, {
|
|
648
|
+
defaultValue
|
|
649
|
+
});
|
|
650
|
+
const section = contentKey.includes(".") ? contentKey.split(".")[0] : void 0;
|
|
651
|
+
const handleClick = (0, import_react7.useCallback)(
|
|
652
|
+
(e) => {
|
|
653
|
+
if (isEditMode) {
|
|
654
|
+
e.preventDefault();
|
|
655
|
+
e.stopPropagation();
|
|
656
|
+
sendEditRequest(contentKey, value, "html", section);
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
[isEditMode, contentKey, value, section, sendEditRequest]
|
|
660
|
+
);
|
|
661
|
+
if (isLoading) {
|
|
662
|
+
return (0, import_react7.createElement)(as, {
|
|
663
|
+
className: cn("animate-pulse bg-muted rounded h-32", className),
|
|
664
|
+
"aria-busy": true
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
const editModeStyles = isEditMode ? cn(
|
|
668
|
+
"cursor-pointer transition-all duration-200",
|
|
669
|
+
"hover:ring-2 hover:ring-blue-500 hover:ring-offset-2",
|
|
670
|
+
"hover:bg-blue-50/50 dark:hover:bg-blue-950/30",
|
|
671
|
+
"relative group"
|
|
672
|
+
) : "";
|
|
673
|
+
const safeHtml = sanitizeHtml(value);
|
|
674
|
+
return (0, import_react7.createElement)(as, {
|
|
675
|
+
className: cn("prose prose-slate dark:prose-invert", className, editModeStyles),
|
|
676
|
+
"data-content-key": contentKey,
|
|
677
|
+
"data-editable": isEditMode ? "true" : void 0,
|
|
678
|
+
onClick: isEditMode ? handleClick : void 0,
|
|
679
|
+
title: isEditMode ? "Click to edit" : void 0,
|
|
680
|
+
dangerouslySetInnerHTML: { __html: safeHtml }
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
function EditableImage({
|
|
684
|
+
contentKey,
|
|
685
|
+
defaultValue,
|
|
686
|
+
alt,
|
|
687
|
+
className,
|
|
688
|
+
width,
|
|
689
|
+
height,
|
|
690
|
+
priority = false
|
|
691
|
+
}) {
|
|
692
|
+
const isEditMode = useEditModeMessaging();
|
|
693
|
+
const sendEditRequest = useSendEditRequest();
|
|
694
|
+
const { value: src, isLoading } = useSiteContentQuery(contentKey, {
|
|
695
|
+
defaultValue
|
|
696
|
+
});
|
|
697
|
+
const section = contentKey.includes(".") ? contentKey.split(".")[0] : void 0;
|
|
698
|
+
const handleClick = (0, import_react7.useCallback)(
|
|
699
|
+
(e) => {
|
|
700
|
+
if (isEditMode) {
|
|
701
|
+
e.preventDefault();
|
|
702
|
+
e.stopPropagation();
|
|
703
|
+
sendEditRequest(contentKey, src, "image", section);
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
[isEditMode, contentKey, src, section, sendEditRequest]
|
|
707
|
+
);
|
|
708
|
+
if (isLoading) {
|
|
709
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
710
|
+
"div",
|
|
711
|
+
{
|
|
712
|
+
className: cn("animate-pulse bg-muted rounded", className),
|
|
713
|
+
style: { width, height },
|
|
714
|
+
"aria-busy": "true"
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
const editModeStyles = isEditMode ? cn(
|
|
719
|
+
"cursor-pointer transition-all duration-200",
|
|
720
|
+
"hover:ring-2 hover:ring-blue-500 hover:ring-offset-2",
|
|
721
|
+
"hover:opacity-90",
|
|
722
|
+
"relative group"
|
|
723
|
+
) : "";
|
|
724
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: cn("relative", isEditMode && "group"), children: [
|
|
725
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
726
|
+
"img",
|
|
727
|
+
{
|
|
728
|
+
src,
|
|
729
|
+
alt,
|
|
730
|
+
className: cn(className, editModeStyles),
|
|
731
|
+
width,
|
|
732
|
+
height,
|
|
733
|
+
loading: priority ? "eager" : "lazy",
|
|
734
|
+
"data-content-key": contentKey,
|
|
735
|
+
"data-editable": isEditMode ? "true" : void 0,
|
|
736
|
+
onClick: isEditMode ? handleClick : void 0,
|
|
737
|
+
title: isEditMode ? "Click to edit image" : void 0
|
|
738
|
+
}
|
|
739
|
+
),
|
|
740
|
+
isEditMode && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "absolute top-2 left-2 px-2 py-0.5 bg-blue-600 text-white text-[10px] rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none", children: "Click to edit image" })
|
|
741
|
+
] });
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/hooks/useServices.ts
|
|
745
|
+
var import_react8 = require("react");
|
|
395
746
|
function useServices(options = {}) {
|
|
396
747
|
const { client } = useFoxPixelContext();
|
|
397
|
-
const [services, setServices] = (0,
|
|
398
|
-
const [isLoading, setIsLoading] = (0,
|
|
399
|
-
const [error, setError] = (0,
|
|
748
|
+
const [services, setServices] = (0, import_react8.useState)(null);
|
|
749
|
+
const [isLoading, setIsLoading] = (0, import_react8.useState)(true);
|
|
750
|
+
const [error, setError] = (0, import_react8.useState)(null);
|
|
400
751
|
const fetchServices = async () => {
|
|
401
752
|
try {
|
|
402
753
|
setIsLoading(true);
|
|
@@ -414,7 +765,7 @@ function useServices(options = {}) {
|
|
|
414
765
|
setIsLoading(false);
|
|
415
766
|
}
|
|
416
767
|
};
|
|
417
|
-
(0,
|
|
768
|
+
(0, import_react8.useEffect)(() => {
|
|
418
769
|
fetchServices();
|
|
419
770
|
}, [options.category, options.active]);
|
|
420
771
|
return {
|
|
@@ -426,11 +777,11 @@ function useServices(options = {}) {
|
|
|
426
777
|
}
|
|
427
778
|
|
|
428
779
|
// src/hooks/useLeadCapture.ts
|
|
429
|
-
var
|
|
780
|
+
var import_react9 = require("react");
|
|
430
781
|
function useLeadCapture() {
|
|
431
782
|
const { client } = useFoxPixelContext();
|
|
432
|
-
const [isLoading, setIsLoading] = (0,
|
|
433
|
-
const [error, setError] = (0,
|
|
783
|
+
const [isLoading, setIsLoading] = (0, import_react9.useState)(false);
|
|
784
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
434
785
|
const captureLead = async (data) => {
|
|
435
786
|
try {
|
|
436
787
|
setIsLoading(true);
|
|
@@ -453,11 +804,11 @@ function useLeadCapture() {
|
|
|
453
804
|
}
|
|
454
805
|
|
|
455
806
|
// src/hooks/useContactCapture.ts
|
|
456
|
-
var
|
|
807
|
+
var import_react10 = require("react");
|
|
457
808
|
function useContactCapture() {
|
|
458
809
|
const { client } = useFoxPixelContext();
|
|
459
|
-
const [isLoading, setIsLoading] = (0,
|
|
460
|
-
const [error, setError] = (0,
|
|
810
|
+
const [isLoading, setIsLoading] = (0, import_react10.useState)(false);
|
|
811
|
+
const [error, setError] = (0, import_react10.useState)(null);
|
|
461
812
|
const captureContact = async (data) => {
|
|
462
813
|
try {
|
|
463
814
|
setIsLoading(true);
|
|
@@ -478,18 +829,883 @@ function useContactCapture() {
|
|
|
478
829
|
error
|
|
479
830
|
};
|
|
480
831
|
}
|
|
832
|
+
|
|
833
|
+
// src/hooks/useSiteContent.ts
|
|
834
|
+
var import_react11 = require("react");
|
|
835
|
+
function useSiteContent(contentKey, options = {}) {
|
|
836
|
+
const { defaultValue = "", fetchOnMount = true } = options;
|
|
837
|
+
const { client } = useFoxPixelContext();
|
|
838
|
+
const { user, hasPermission } = useAuth();
|
|
839
|
+
const [data, setData] = (0, import_react11.useState)(null);
|
|
840
|
+
const [isLoading, setIsLoading] = (0, import_react11.useState)(fetchOnMount);
|
|
841
|
+
const [error, setError] = (0, import_react11.useState)(null);
|
|
842
|
+
const canEdit = user !== null && hasPermission("site:content:update");
|
|
843
|
+
const fetchContent = (0, import_react11.useCallback)(async () => {
|
|
844
|
+
try {
|
|
845
|
+
setIsLoading(true);
|
|
846
|
+
setError(null);
|
|
847
|
+
const content = await client.get(
|
|
848
|
+
`/api/site/content/${encodeURIComponent(contentKey)}`
|
|
849
|
+
);
|
|
850
|
+
setData(content);
|
|
851
|
+
} catch (err) {
|
|
852
|
+
if (err?.status === 404) {
|
|
853
|
+
setData(null);
|
|
854
|
+
} else {
|
|
855
|
+
setError(err);
|
|
856
|
+
}
|
|
857
|
+
} finally {
|
|
858
|
+
setIsLoading(false);
|
|
859
|
+
}
|
|
860
|
+
}, [client, contentKey]);
|
|
861
|
+
const updateContent = (0, import_react11.useCallback)(async (newValue) => {
|
|
862
|
+
try {
|
|
863
|
+
setError(null);
|
|
864
|
+
const updated = await client.put(
|
|
865
|
+
`/api/site/content/${encodeURIComponent(contentKey)}`,
|
|
866
|
+
{ value: newValue }
|
|
867
|
+
);
|
|
868
|
+
setData(updated);
|
|
869
|
+
} catch (err) {
|
|
870
|
+
setError(err);
|
|
871
|
+
throw err;
|
|
872
|
+
}
|
|
873
|
+
}, [client, contentKey]);
|
|
874
|
+
(0, import_react11.useEffect)(() => {
|
|
875
|
+
if (fetchOnMount) {
|
|
876
|
+
fetchContent();
|
|
877
|
+
}
|
|
878
|
+
}, [contentKey, fetchOnMount]);
|
|
879
|
+
const value = data?.value ?? defaultValue;
|
|
880
|
+
return {
|
|
881
|
+
data,
|
|
882
|
+
value,
|
|
883
|
+
isLoading,
|
|
884
|
+
error,
|
|
885
|
+
canEdit,
|
|
886
|
+
update: updateContent,
|
|
887
|
+
refetch: fetchContent
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function useSiteContents(contentKeys, options = {}) {
|
|
891
|
+
const { defaults = {} } = options;
|
|
892
|
+
const { client } = useFoxPixelContext();
|
|
893
|
+
const [data, setData] = (0, import_react11.useState)({});
|
|
894
|
+
const [isLoading, setIsLoading] = (0, import_react11.useState)(true);
|
|
895
|
+
const [error, setError] = (0, import_react11.useState)(null);
|
|
896
|
+
const fetchContents = (0, import_react11.useCallback)(async () => {
|
|
897
|
+
if (contentKeys.length === 0) {
|
|
898
|
+
setData({});
|
|
899
|
+
setIsLoading(false);
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
try {
|
|
903
|
+
setIsLoading(true);
|
|
904
|
+
setError(null);
|
|
905
|
+
const contents = await client.post(
|
|
906
|
+
"/api/site/content/batch",
|
|
907
|
+
contentKeys
|
|
908
|
+
);
|
|
909
|
+
setData(contents);
|
|
910
|
+
} catch (err) {
|
|
911
|
+
setError(err);
|
|
912
|
+
} finally {
|
|
913
|
+
setIsLoading(false);
|
|
914
|
+
}
|
|
915
|
+
}, [client, contentKeys.join(",")]);
|
|
916
|
+
(0, import_react11.useEffect)(() => {
|
|
917
|
+
fetchContents();
|
|
918
|
+
}, [fetchContents]);
|
|
919
|
+
const getValue = (0, import_react11.useCallback)((key, defaultValue) => {
|
|
920
|
+
const content = data[key];
|
|
921
|
+
if (content?.value) {
|
|
922
|
+
return content.value;
|
|
923
|
+
}
|
|
924
|
+
return defaultValue ?? defaults[key] ?? "";
|
|
925
|
+
}, [data, defaults]);
|
|
926
|
+
return {
|
|
927
|
+
data,
|
|
928
|
+
getValue,
|
|
929
|
+
isLoading,
|
|
930
|
+
error,
|
|
931
|
+
refetch: fetchContents
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function useSiteContentSection(section) {
|
|
935
|
+
const { client } = useFoxPixelContext();
|
|
936
|
+
const [contents, setContents] = (0, import_react11.useState)([]);
|
|
937
|
+
const [isLoading, setIsLoading] = (0, import_react11.useState)(true);
|
|
938
|
+
const [error, setError] = (0, import_react11.useState)(null);
|
|
939
|
+
const fetchContents = (0, import_react11.useCallback)(async () => {
|
|
940
|
+
try {
|
|
941
|
+
setIsLoading(true);
|
|
942
|
+
setError(null);
|
|
943
|
+
const data = await client.get(
|
|
944
|
+
`/api/site/content/section/${encodeURIComponent(section)}`
|
|
945
|
+
);
|
|
946
|
+
setContents(data);
|
|
947
|
+
} catch (err) {
|
|
948
|
+
setError(err);
|
|
949
|
+
} finally {
|
|
950
|
+
setIsLoading(false);
|
|
951
|
+
}
|
|
952
|
+
}, [client, section]);
|
|
953
|
+
(0, import_react11.useEffect)(() => {
|
|
954
|
+
fetchContents();
|
|
955
|
+
}, [fetchContents]);
|
|
956
|
+
return {
|
|
957
|
+
contents,
|
|
958
|
+
isLoading,
|
|
959
|
+
error,
|
|
960
|
+
refetch: fetchContents
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/blog/hooks.ts
|
|
965
|
+
var import_react12 = require("react");
|
|
966
|
+
function useBlogPosts(options = {}) {
|
|
967
|
+
const { client } = useFoxPixelContext();
|
|
968
|
+
const [data, setData] = (0, import_react12.useState)(null);
|
|
969
|
+
const [isLoading, setIsLoading] = (0, import_react12.useState)(true);
|
|
970
|
+
const [error, setError] = (0, import_react12.useState)(null);
|
|
971
|
+
const page = options.page ?? 0;
|
|
972
|
+
const limit = options.limit ?? 10;
|
|
973
|
+
const fetchPosts = async () => {
|
|
974
|
+
try {
|
|
975
|
+
setIsLoading(true);
|
|
976
|
+
setError(null);
|
|
977
|
+
const params = new URLSearchParams();
|
|
978
|
+
params.append("page", String(page));
|
|
979
|
+
params.append("size", String(limit));
|
|
980
|
+
const url = `/api/v1/blog/posts?${params.toString()}`;
|
|
981
|
+
const result = await client.get(url);
|
|
982
|
+
setData(result);
|
|
983
|
+
} catch (err) {
|
|
984
|
+
setError(err);
|
|
985
|
+
setData(null);
|
|
986
|
+
} finally {
|
|
987
|
+
setIsLoading(false);
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
(0, import_react12.useEffect)(() => {
|
|
991
|
+
fetchPosts();
|
|
992
|
+
}, [page, limit]);
|
|
993
|
+
return {
|
|
994
|
+
data,
|
|
995
|
+
isLoading,
|
|
996
|
+
error,
|
|
997
|
+
refetch: fetchPosts
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
function useBlogPost(slug) {
|
|
1001
|
+
const { client } = useFoxPixelContext();
|
|
1002
|
+
const [data, setData] = (0, import_react12.useState)(null);
|
|
1003
|
+
const [isLoading, setIsLoading] = (0, import_react12.useState)(!!slug);
|
|
1004
|
+
const [error, setError] = (0, import_react12.useState)(null);
|
|
1005
|
+
const fetchPost = async () => {
|
|
1006
|
+
if (!slug) {
|
|
1007
|
+
setData(null);
|
|
1008
|
+
setIsLoading(false);
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
try {
|
|
1012
|
+
setIsLoading(true);
|
|
1013
|
+
setError(null);
|
|
1014
|
+
const result = await client.get(`/api/v1/blog/posts/${encodeURIComponent(slug)}`);
|
|
1015
|
+
setData(result);
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
setError(err);
|
|
1018
|
+
setData(null);
|
|
1019
|
+
} finally {
|
|
1020
|
+
setIsLoading(false);
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
(0, import_react12.useEffect)(() => {
|
|
1024
|
+
fetchPost();
|
|
1025
|
+
}, [slug]);
|
|
1026
|
+
return {
|
|
1027
|
+
data,
|
|
1028
|
+
isLoading,
|
|
1029
|
+
error,
|
|
1030
|
+
refetch: fetchPost
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
function useBlogCategories() {
|
|
1034
|
+
const { client } = useFoxPixelContext();
|
|
1035
|
+
const [data, setData] = (0, import_react12.useState)(null);
|
|
1036
|
+
const [isLoading, setIsLoading] = (0, import_react12.useState)(true);
|
|
1037
|
+
const [error, setError] = (0, import_react12.useState)(null);
|
|
1038
|
+
const fetchCategories = async () => {
|
|
1039
|
+
try {
|
|
1040
|
+
setIsLoading(true);
|
|
1041
|
+
setError(null);
|
|
1042
|
+
const result = await client.get("/api/v1/blog/categories");
|
|
1043
|
+
setData(Array.isArray(result) ? result : []);
|
|
1044
|
+
} catch (err) {
|
|
1045
|
+
setError(err);
|
|
1046
|
+
setData(null);
|
|
1047
|
+
} finally {
|
|
1048
|
+
setIsLoading(false);
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
(0, import_react12.useEffect)(() => {
|
|
1052
|
+
fetchCategories();
|
|
1053
|
+
}, []);
|
|
1054
|
+
return {
|
|
1055
|
+
data,
|
|
1056
|
+
isLoading,
|
|
1057
|
+
error,
|
|
1058
|
+
refetch: fetchCategories
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function useBlogTags() {
|
|
1062
|
+
const { client } = useFoxPixelContext();
|
|
1063
|
+
const [data, setData] = (0, import_react12.useState)(null);
|
|
1064
|
+
const [isLoading, setIsLoading] = (0, import_react12.useState)(true);
|
|
1065
|
+
const [error, setError] = (0, import_react12.useState)(null);
|
|
1066
|
+
const fetchTags = async () => {
|
|
1067
|
+
try {
|
|
1068
|
+
setIsLoading(true);
|
|
1069
|
+
setError(null);
|
|
1070
|
+
const result = await client.get("/api/v1/blog/tags");
|
|
1071
|
+
setData(Array.isArray(result) ? result : []);
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
setError(err);
|
|
1074
|
+
setData(null);
|
|
1075
|
+
} finally {
|
|
1076
|
+
setIsLoading(false);
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
(0, import_react12.useEffect)(() => {
|
|
1080
|
+
fetchTags();
|
|
1081
|
+
}, []);
|
|
1082
|
+
return {
|
|
1083
|
+
data,
|
|
1084
|
+
isLoading,
|
|
1085
|
+
error,
|
|
1086
|
+
refetch: fetchTags
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
function useBlogComments(slug) {
|
|
1090
|
+
const { client } = useFoxPixelContext();
|
|
1091
|
+
const [data, setData] = (0, import_react12.useState)(null);
|
|
1092
|
+
const [isLoading, setIsLoading] = (0, import_react12.useState)(!!slug);
|
|
1093
|
+
const [error, setError] = (0, import_react12.useState)(null);
|
|
1094
|
+
const fetchComments = async () => {
|
|
1095
|
+
if (!slug) {
|
|
1096
|
+
setData(null);
|
|
1097
|
+
setIsLoading(false);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
try {
|
|
1101
|
+
setIsLoading(true);
|
|
1102
|
+
setError(null);
|
|
1103
|
+
const result = await client.get(
|
|
1104
|
+
`/api/v1/blog/posts/${encodeURIComponent(slug)}/comments`
|
|
1105
|
+
);
|
|
1106
|
+
setData(Array.isArray(result) ? result : []);
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
setError(err);
|
|
1109
|
+
setData(null);
|
|
1110
|
+
} finally {
|
|
1111
|
+
setIsLoading(false);
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
(0, import_react12.useEffect)(() => {
|
|
1115
|
+
fetchComments();
|
|
1116
|
+
}, [slug]);
|
|
1117
|
+
return {
|
|
1118
|
+
data,
|
|
1119
|
+
isLoading,
|
|
1120
|
+
error,
|
|
1121
|
+
refetch: fetchComments
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
function useBlogCommentSubmit(slug) {
|
|
1125
|
+
const { client } = useFoxPixelContext();
|
|
1126
|
+
const [isSubmitting, setIsSubmitting] = (0, import_react12.useState)(false);
|
|
1127
|
+
const [error, setError] = (0, import_react12.useState)(null);
|
|
1128
|
+
const submit = async (payload) => {
|
|
1129
|
+
if (!slug) return null;
|
|
1130
|
+
try {
|
|
1131
|
+
setIsSubmitting(true);
|
|
1132
|
+
setError(null);
|
|
1133
|
+
const result = await client.post(
|
|
1134
|
+
`/api/v1/blog/posts/${encodeURIComponent(slug)}/comments`,
|
|
1135
|
+
payload
|
|
1136
|
+
);
|
|
1137
|
+
return result;
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
setError(err);
|
|
1140
|
+
return null;
|
|
1141
|
+
} finally {
|
|
1142
|
+
setIsSubmitting(false);
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
const resetError = () => setError(null);
|
|
1146
|
+
return {
|
|
1147
|
+
submit,
|
|
1148
|
+
isSubmitting,
|
|
1149
|
+
error,
|
|
1150
|
+
resetError
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
function useBlogFeaturedPosts(limit = 6) {
|
|
1154
|
+
const { client } = useFoxPixelContext();
|
|
1155
|
+
const [data, setData] = (0, import_react12.useState)(null);
|
|
1156
|
+
const [isLoading, setIsLoading] = (0, import_react12.useState)(true);
|
|
1157
|
+
const [error, setError] = (0, import_react12.useState)(null);
|
|
1158
|
+
const fetchFeatured = async () => {
|
|
1159
|
+
try {
|
|
1160
|
+
setIsLoading(true);
|
|
1161
|
+
setError(null);
|
|
1162
|
+
const params = new URLSearchParams();
|
|
1163
|
+
params.append("page", "0");
|
|
1164
|
+
params.append("size", String(limit));
|
|
1165
|
+
const url = `/api/v1/blog/posts/featured?${params.toString()}`;
|
|
1166
|
+
const result = await client.get(url);
|
|
1167
|
+
setData(result);
|
|
1168
|
+
} catch (err) {
|
|
1169
|
+
setError(err);
|
|
1170
|
+
setData(null);
|
|
1171
|
+
} finally {
|
|
1172
|
+
setIsLoading(false);
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
(0, import_react12.useEffect)(() => {
|
|
1176
|
+
fetchFeatured();
|
|
1177
|
+
}, [limit]);
|
|
1178
|
+
return {
|
|
1179
|
+
data,
|
|
1180
|
+
isLoading,
|
|
1181
|
+
error,
|
|
1182
|
+
refetch: fetchFeatured
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
function useNewsletterSubscribe() {
|
|
1186
|
+
const { client } = useFoxPixelContext();
|
|
1187
|
+
const [isSubmitting, setIsSubmitting] = (0, import_react12.useState)(false);
|
|
1188
|
+
const [error, setError] = (0, import_react12.useState)(null);
|
|
1189
|
+
const [success, setSuccess] = (0, import_react12.useState)(false);
|
|
1190
|
+
const subscribe = async (payload) => {
|
|
1191
|
+
try {
|
|
1192
|
+
setIsSubmitting(true);
|
|
1193
|
+
setError(null);
|
|
1194
|
+
setSuccess(false);
|
|
1195
|
+
const result = await client.post(
|
|
1196
|
+
"/api/v1/blog/newsletter/subscribe",
|
|
1197
|
+
payload
|
|
1198
|
+
);
|
|
1199
|
+
setSuccess(true);
|
|
1200
|
+
return result;
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
setError(err);
|
|
1203
|
+
return null;
|
|
1204
|
+
} finally {
|
|
1205
|
+
setIsSubmitting(false);
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
const reset = () => {
|
|
1209
|
+
setError(null);
|
|
1210
|
+
setSuccess(false);
|
|
1211
|
+
};
|
|
1212
|
+
return {
|
|
1213
|
+
subscribe,
|
|
1214
|
+
isSubmitting,
|
|
1215
|
+
error,
|
|
1216
|
+
success,
|
|
1217
|
+
reset
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function useNewsletterUnsubscribe() {
|
|
1221
|
+
const { client } = useFoxPixelContext();
|
|
1222
|
+
const [isSubmitting, setIsSubmitting] = (0, import_react12.useState)(false);
|
|
1223
|
+
const [error, setError] = (0, import_react12.useState)(null);
|
|
1224
|
+
const [success, setSuccess] = (0, import_react12.useState)(false);
|
|
1225
|
+
const unsubscribe = async (email) => {
|
|
1226
|
+
try {
|
|
1227
|
+
setIsSubmitting(true);
|
|
1228
|
+
setError(null);
|
|
1229
|
+
setSuccess(false);
|
|
1230
|
+
await client.post("/api/v1/blog/newsletter/unsubscribe", null, {
|
|
1231
|
+
params: { email }
|
|
1232
|
+
});
|
|
1233
|
+
setSuccess(true);
|
|
1234
|
+
return true;
|
|
1235
|
+
} catch (err) {
|
|
1236
|
+
setError(err);
|
|
1237
|
+
return false;
|
|
1238
|
+
} finally {
|
|
1239
|
+
setIsSubmitting(false);
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
const unsubscribeByToken = async (token) => {
|
|
1243
|
+
try {
|
|
1244
|
+
setIsSubmitting(true);
|
|
1245
|
+
setError(null);
|
|
1246
|
+
setSuccess(false);
|
|
1247
|
+
await client.get("/api/v1/blog/newsletter/unsubscribe", {
|
|
1248
|
+
params: { token }
|
|
1249
|
+
});
|
|
1250
|
+
setSuccess(true);
|
|
1251
|
+
return true;
|
|
1252
|
+
} catch (err) {
|
|
1253
|
+
setError(err);
|
|
1254
|
+
return false;
|
|
1255
|
+
} finally {
|
|
1256
|
+
setIsSubmitting(false);
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
return {
|
|
1260
|
+
unsubscribe,
|
|
1261
|
+
unsubscribeByToken,
|
|
1262
|
+
isSubmitting,
|
|
1263
|
+
error,
|
|
1264
|
+
success
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// src/blog/admin-hooks.ts
|
|
1269
|
+
var import_react13 = require("react");
|
|
1270
|
+
function useAdminBlogPosts(options = {}) {
|
|
1271
|
+
const { client } = useFoxPixelContext();
|
|
1272
|
+
const [data, setData] = (0, import_react13.useState)(null);
|
|
1273
|
+
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
1274
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1275
|
+
const page = options.page ?? 0;
|
|
1276
|
+
const size = options.size ?? 20;
|
|
1277
|
+
const fetchPosts = (0, import_react13.useCallback)(async () => {
|
|
1278
|
+
try {
|
|
1279
|
+
setIsLoading(true);
|
|
1280
|
+
setError(null);
|
|
1281
|
+
const params = new URLSearchParams();
|
|
1282
|
+
params.append("page", String(page));
|
|
1283
|
+
params.append("size", String(size));
|
|
1284
|
+
const result = await client.get(`/api/blog/posts?${params.toString()}`);
|
|
1285
|
+
setData(result);
|
|
1286
|
+
} catch (err) {
|
|
1287
|
+
setError(err);
|
|
1288
|
+
} finally {
|
|
1289
|
+
setIsLoading(false);
|
|
1290
|
+
}
|
|
1291
|
+
}, [client, page, size]);
|
|
1292
|
+
(0, import_react13.useEffect)(() => {
|
|
1293
|
+
fetchPosts();
|
|
1294
|
+
}, [fetchPosts]);
|
|
1295
|
+
return { data, isLoading, error, refetch: fetchPosts };
|
|
1296
|
+
}
|
|
1297
|
+
function useAdminBlogPost(id) {
|
|
1298
|
+
const { client } = useFoxPixelContext();
|
|
1299
|
+
const [data, setData] = (0, import_react13.useState)(null);
|
|
1300
|
+
const [isLoading, setIsLoading] = (0, import_react13.useState)(!!id);
|
|
1301
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1302
|
+
const fetchPost = (0, import_react13.useCallback)(async () => {
|
|
1303
|
+
if (!id) {
|
|
1304
|
+
setData(null);
|
|
1305
|
+
setIsLoading(false);
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
try {
|
|
1309
|
+
setIsLoading(true);
|
|
1310
|
+
setError(null);
|
|
1311
|
+
const result = await client.get(`/api/blog/posts/${id}`);
|
|
1312
|
+
setData(result);
|
|
1313
|
+
} catch (err) {
|
|
1314
|
+
setError(err);
|
|
1315
|
+
} finally {
|
|
1316
|
+
setIsLoading(false);
|
|
1317
|
+
}
|
|
1318
|
+
}, [client, id]);
|
|
1319
|
+
(0, import_react13.useEffect)(() => {
|
|
1320
|
+
fetchPost();
|
|
1321
|
+
}, [fetchPost]);
|
|
1322
|
+
return { data, isLoading, error, refetch: fetchPost };
|
|
1323
|
+
}
|
|
1324
|
+
function useAdminBlogPostMutations() {
|
|
1325
|
+
const { client } = useFoxPixelContext();
|
|
1326
|
+
const [isLoading, setIsLoading] = (0, import_react13.useState)(false);
|
|
1327
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1328
|
+
const create = async (payload) => {
|
|
1329
|
+
try {
|
|
1330
|
+
setIsLoading(true);
|
|
1331
|
+
setError(null);
|
|
1332
|
+
const result = await client.post("/api/blog/posts", payload);
|
|
1333
|
+
return result;
|
|
1334
|
+
} catch (err) {
|
|
1335
|
+
setError(err);
|
|
1336
|
+
return null;
|
|
1337
|
+
} finally {
|
|
1338
|
+
setIsLoading(false);
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
const update = async (id, payload) => {
|
|
1342
|
+
try {
|
|
1343
|
+
setIsLoading(true);
|
|
1344
|
+
setError(null);
|
|
1345
|
+
const result = await client.put(`/api/blog/posts/${id}`, payload);
|
|
1346
|
+
return result;
|
|
1347
|
+
} catch (err) {
|
|
1348
|
+
setError(err);
|
|
1349
|
+
return null;
|
|
1350
|
+
} finally {
|
|
1351
|
+
setIsLoading(false);
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
const remove = async (id) => {
|
|
1355
|
+
try {
|
|
1356
|
+
setIsLoading(true);
|
|
1357
|
+
setError(null);
|
|
1358
|
+
await client.delete(`/api/blog/posts/${id}`);
|
|
1359
|
+
return true;
|
|
1360
|
+
} catch (err) {
|
|
1361
|
+
setError(err);
|
|
1362
|
+
return false;
|
|
1363
|
+
} finally {
|
|
1364
|
+
setIsLoading(false);
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
return { create, update, remove, isLoading, error };
|
|
1368
|
+
}
|
|
1369
|
+
function useAdminBlogCategories() {
|
|
1370
|
+
const { client } = useFoxPixelContext();
|
|
1371
|
+
const [data, setData] = (0, import_react13.useState)(null);
|
|
1372
|
+
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
1373
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1374
|
+
const fetchCategories = (0, import_react13.useCallback)(async () => {
|
|
1375
|
+
try {
|
|
1376
|
+
setIsLoading(true);
|
|
1377
|
+
setError(null);
|
|
1378
|
+
const result = await client.get("/api/blog/categories");
|
|
1379
|
+
setData(Array.isArray(result) ? result : []);
|
|
1380
|
+
} catch (err) {
|
|
1381
|
+
setError(err);
|
|
1382
|
+
} finally {
|
|
1383
|
+
setIsLoading(false);
|
|
1384
|
+
}
|
|
1385
|
+
}, [client]);
|
|
1386
|
+
(0, import_react13.useEffect)(() => {
|
|
1387
|
+
fetchCategories();
|
|
1388
|
+
}, [fetchCategories]);
|
|
1389
|
+
const create = async (payload) => {
|
|
1390
|
+
try {
|
|
1391
|
+
const result = await client.post("/api/blog/categories", payload);
|
|
1392
|
+
await fetchCategories();
|
|
1393
|
+
return result;
|
|
1394
|
+
} catch (err) {
|
|
1395
|
+
setError(err);
|
|
1396
|
+
return null;
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
const update = async (id, payload) => {
|
|
1400
|
+
try {
|
|
1401
|
+
const result = await client.put(`/api/blog/categories/${id}`, payload);
|
|
1402
|
+
await fetchCategories();
|
|
1403
|
+
return result;
|
|
1404
|
+
} catch (err) {
|
|
1405
|
+
setError(err);
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
const remove = async (id) => {
|
|
1410
|
+
try {
|
|
1411
|
+
await client.delete(`/api/blog/categories/${id}`);
|
|
1412
|
+
await fetchCategories();
|
|
1413
|
+
return true;
|
|
1414
|
+
} catch (err) {
|
|
1415
|
+
setError(err);
|
|
1416
|
+
return false;
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
return { data, isLoading, error, refetch: fetchCategories, create, update, remove };
|
|
1420
|
+
}
|
|
1421
|
+
function useAdminBlogTags() {
|
|
1422
|
+
const { client } = useFoxPixelContext();
|
|
1423
|
+
const [data, setData] = (0, import_react13.useState)(null);
|
|
1424
|
+
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
1425
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1426
|
+
const fetchTags = (0, import_react13.useCallback)(async () => {
|
|
1427
|
+
try {
|
|
1428
|
+
setIsLoading(true);
|
|
1429
|
+
setError(null);
|
|
1430
|
+
const result = await client.get("/api/blog/tags");
|
|
1431
|
+
setData(Array.isArray(result) ? result : []);
|
|
1432
|
+
} catch (err) {
|
|
1433
|
+
setError(err);
|
|
1434
|
+
} finally {
|
|
1435
|
+
setIsLoading(false);
|
|
1436
|
+
}
|
|
1437
|
+
}, [client]);
|
|
1438
|
+
(0, import_react13.useEffect)(() => {
|
|
1439
|
+
fetchTags();
|
|
1440
|
+
}, [fetchTags]);
|
|
1441
|
+
const create = async (payload) => {
|
|
1442
|
+
try {
|
|
1443
|
+
const result = await client.post("/api/blog/tags", payload);
|
|
1444
|
+
await fetchTags();
|
|
1445
|
+
return result;
|
|
1446
|
+
} catch (err) {
|
|
1447
|
+
setError(err);
|
|
1448
|
+
return null;
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
const update = async (id, payload) => {
|
|
1452
|
+
try {
|
|
1453
|
+
const result = await client.put(`/api/blog/tags/${id}`, payload);
|
|
1454
|
+
await fetchTags();
|
|
1455
|
+
return result;
|
|
1456
|
+
} catch (err) {
|
|
1457
|
+
setError(err);
|
|
1458
|
+
return null;
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
const remove = async (id) => {
|
|
1462
|
+
try {
|
|
1463
|
+
await client.delete(`/api/blog/tags/${id}`);
|
|
1464
|
+
await fetchTags();
|
|
1465
|
+
return true;
|
|
1466
|
+
} catch (err) {
|
|
1467
|
+
setError(err);
|
|
1468
|
+
return false;
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
return { data, isLoading, error, refetch: fetchTags, create, update, remove };
|
|
1472
|
+
}
|
|
1473
|
+
function useAdminBlogComments(options = {}) {
|
|
1474
|
+
const { client } = useFoxPixelContext();
|
|
1475
|
+
const [data, setData] = (0, import_react13.useState)(null);
|
|
1476
|
+
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
1477
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1478
|
+
const { status, postId, page = 0, size = 20 } = options;
|
|
1479
|
+
const fetchComments = (0, import_react13.useCallback)(async () => {
|
|
1480
|
+
try {
|
|
1481
|
+
setIsLoading(true);
|
|
1482
|
+
setError(null);
|
|
1483
|
+
const params = new URLSearchParams();
|
|
1484
|
+
params.append("page", String(page));
|
|
1485
|
+
params.append("size", String(size));
|
|
1486
|
+
if (status) params.append("status", status);
|
|
1487
|
+
if (postId) params.append("postId", postId);
|
|
1488
|
+
const result = await client.get(`/api/blog/comments?${params.toString()}`);
|
|
1489
|
+
setData(result);
|
|
1490
|
+
} catch (err) {
|
|
1491
|
+
setError(err);
|
|
1492
|
+
} finally {
|
|
1493
|
+
setIsLoading(false);
|
|
1494
|
+
}
|
|
1495
|
+
}, [client, status, postId, page, size]);
|
|
1496
|
+
(0, import_react13.useEffect)(() => {
|
|
1497
|
+
fetchComments();
|
|
1498
|
+
}, [fetchComments]);
|
|
1499
|
+
const updateStatus = async (id, newStatus) => {
|
|
1500
|
+
try {
|
|
1501
|
+
await client.put(`/api/blog/comments/${id}/status`, { status: newStatus });
|
|
1502
|
+
await fetchComments();
|
|
1503
|
+
return true;
|
|
1504
|
+
} catch (err) {
|
|
1505
|
+
setError(err);
|
|
1506
|
+
return false;
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
const remove = async (id) => {
|
|
1510
|
+
try {
|
|
1511
|
+
await client.delete(`/api/blog/comments/${id}`);
|
|
1512
|
+
await fetchComments();
|
|
1513
|
+
return true;
|
|
1514
|
+
} catch (err) {
|
|
1515
|
+
setError(err);
|
|
1516
|
+
return false;
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
return { data, isLoading, error, refetch: fetchComments, updateStatus, remove };
|
|
1520
|
+
}
|
|
1521
|
+
function useAdminNewsletterSubscribers(options = {}) {
|
|
1522
|
+
const { client } = useFoxPixelContext();
|
|
1523
|
+
const [data, setData] = (0, import_react13.useState)(null);
|
|
1524
|
+
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
1525
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1526
|
+
const { status, page = 0, size = 20 } = options;
|
|
1527
|
+
const fetchSubscribers = (0, import_react13.useCallback)(async () => {
|
|
1528
|
+
try {
|
|
1529
|
+
setIsLoading(true);
|
|
1530
|
+
setError(null);
|
|
1531
|
+
const params = new URLSearchParams();
|
|
1532
|
+
params.append("page", String(page));
|
|
1533
|
+
params.append("size", String(size));
|
|
1534
|
+
if (status) params.append("status", status);
|
|
1535
|
+
const result = await client.get(`/api/blog/newsletter/subscribers?${params.toString()}`);
|
|
1536
|
+
setData(result);
|
|
1537
|
+
} catch (err) {
|
|
1538
|
+
setError(err);
|
|
1539
|
+
} finally {
|
|
1540
|
+
setIsLoading(false);
|
|
1541
|
+
}
|
|
1542
|
+
}, [client, status, page, size]);
|
|
1543
|
+
(0, import_react13.useEffect)(() => {
|
|
1544
|
+
fetchSubscribers();
|
|
1545
|
+
}, [fetchSubscribers]);
|
|
1546
|
+
const remove = async (id) => {
|
|
1547
|
+
try {
|
|
1548
|
+
await client.delete(`/api/blog/newsletter/subscribers/${id}`);
|
|
1549
|
+
await fetchSubscribers();
|
|
1550
|
+
return true;
|
|
1551
|
+
} catch (err) {
|
|
1552
|
+
setError(err);
|
|
1553
|
+
return false;
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
return { data, isLoading, error, refetch: fetchSubscribers, remove };
|
|
1557
|
+
}
|
|
1558
|
+
function useAdminNewsletterStats() {
|
|
1559
|
+
const { client } = useFoxPixelContext();
|
|
1560
|
+
const [data, setData] = (0, import_react13.useState)(null);
|
|
1561
|
+
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
1562
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1563
|
+
const fetchStats = (0, import_react13.useCallback)(async () => {
|
|
1564
|
+
try {
|
|
1565
|
+
setIsLoading(true);
|
|
1566
|
+
setError(null);
|
|
1567
|
+
const result = await client.get("/api/blog/newsletter/stats");
|
|
1568
|
+
setData(result);
|
|
1569
|
+
} catch (err) {
|
|
1570
|
+
setError(err);
|
|
1571
|
+
} finally {
|
|
1572
|
+
setIsLoading(false);
|
|
1573
|
+
}
|
|
1574
|
+
}, [client]);
|
|
1575
|
+
(0, import_react13.useEffect)(() => {
|
|
1576
|
+
fetchStats();
|
|
1577
|
+
}, [fetchStats]);
|
|
1578
|
+
return { data, isLoading, error, refetch: fetchStats };
|
|
1579
|
+
}
|
|
1580
|
+
function useAdminBlogSettings() {
|
|
1581
|
+
const { client } = useFoxPixelContext();
|
|
1582
|
+
const [data, setData] = (0, import_react13.useState)(null);
|
|
1583
|
+
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
1584
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1585
|
+
const fetchSettings = (0, import_react13.useCallback)(async () => {
|
|
1586
|
+
try {
|
|
1587
|
+
setIsLoading(true);
|
|
1588
|
+
setError(null);
|
|
1589
|
+
const result = await client.get("/api/blog/settings");
|
|
1590
|
+
setData(result);
|
|
1591
|
+
} catch (err) {
|
|
1592
|
+
setError(err);
|
|
1593
|
+
} finally {
|
|
1594
|
+
setIsLoading(false);
|
|
1595
|
+
}
|
|
1596
|
+
}, [client]);
|
|
1597
|
+
(0, import_react13.useEffect)(() => {
|
|
1598
|
+
fetchSettings();
|
|
1599
|
+
}, [fetchSettings]);
|
|
1600
|
+
const update = async (settings) => {
|
|
1601
|
+
try {
|
|
1602
|
+
const result = await client.put("/api/blog/settings", settings);
|
|
1603
|
+
setData(result);
|
|
1604
|
+
return result;
|
|
1605
|
+
} catch (err) {
|
|
1606
|
+
setError(err);
|
|
1607
|
+
return null;
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
return { data, isLoading, error, refetch: fetchSettings, update };
|
|
1611
|
+
}
|
|
1612
|
+
function useAdminBlogAnalytics() {
|
|
1613
|
+
const { client } = useFoxPixelContext();
|
|
1614
|
+
const [data, setData] = (0, import_react13.useState)(null);
|
|
1615
|
+
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
1616
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1617
|
+
const fetchAnalytics = (0, import_react13.useCallback)(async () => {
|
|
1618
|
+
try {
|
|
1619
|
+
setIsLoading(true);
|
|
1620
|
+
setError(null);
|
|
1621
|
+
const result = await client.get("/api/blog/analytics/summary");
|
|
1622
|
+
setData(result);
|
|
1623
|
+
} catch (err) {
|
|
1624
|
+
setError(err);
|
|
1625
|
+
} finally {
|
|
1626
|
+
setIsLoading(false);
|
|
1627
|
+
}
|
|
1628
|
+
}, [client]);
|
|
1629
|
+
(0, import_react13.useEffect)(() => {
|
|
1630
|
+
fetchAnalytics();
|
|
1631
|
+
}, [fetchAnalytics]);
|
|
1632
|
+
return { data, isLoading, error, refetch: fetchAnalytics };
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
// src/blog/utils.ts
|
|
1636
|
+
function getBlogPostSchemaLd(post, options) {
|
|
1637
|
+
const { siteUrl, publisherName, publisherLogoUrl } = options;
|
|
1638
|
+
const postUrl = `${siteUrl.replace(/\/$/, "")}/blog/${post.slug}`;
|
|
1639
|
+
const schema = {
|
|
1640
|
+
"@context": "https://schema.org",
|
|
1641
|
+
"@type": "BlogPosting",
|
|
1642
|
+
headline: post.title,
|
|
1643
|
+
description: post.metaDescription || post.excerpt || void 0,
|
|
1644
|
+
image: post.coverImageUrl || void 0,
|
|
1645
|
+
datePublished: post.publishedAt || void 0,
|
|
1646
|
+
dateModified: post.updatedAt,
|
|
1647
|
+
mainEntityOfPage: {
|
|
1648
|
+
"@type": "WebPage",
|
|
1649
|
+
"@id": postUrl
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
if (publisherName || publisherLogoUrl) {
|
|
1653
|
+
schema.publisher = {
|
|
1654
|
+
"@type": "Organization",
|
|
1655
|
+
...publisherName && { name: publisherName },
|
|
1656
|
+
...publisherLogoUrl && {
|
|
1657
|
+
logo: {
|
|
1658
|
+
"@type": "ImageObject",
|
|
1659
|
+
url: publisherLogoUrl
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
return schema;
|
|
1665
|
+
}
|
|
481
1666
|
// Annotate the CommonJS export names for ESM import in node:
|
|
482
1667
|
0 && (module.exports = {
|
|
483
1668
|
AuthProvider,
|
|
1669
|
+
Editable,
|
|
1670
|
+
EditableHTML,
|
|
1671
|
+
EditableImage,
|
|
484
1672
|
FoxPixelHttpClient,
|
|
485
1673
|
FoxPixelProvider,
|
|
486
1674
|
GuestOnlyRoute,
|
|
487
1675
|
ProtectedRoute,
|
|
1676
|
+
SITE_CONTENT_QUERY_KEY,
|
|
1677
|
+
getBlogPostSchemaLd,
|
|
1678
|
+
useAdminBlogAnalytics,
|
|
1679
|
+
useAdminBlogCategories,
|
|
1680
|
+
useAdminBlogComments,
|
|
1681
|
+
useAdminBlogPost,
|
|
1682
|
+
useAdminBlogPostMutations,
|
|
1683
|
+
useAdminBlogPosts,
|
|
1684
|
+
useAdminBlogSettings,
|
|
1685
|
+
useAdminBlogTags,
|
|
1686
|
+
useAdminNewsletterStats,
|
|
1687
|
+
useAdminNewsletterSubscribers,
|
|
488
1688
|
useAuth,
|
|
1689
|
+
useBlogCategories,
|
|
1690
|
+
useBlogCommentSubmit,
|
|
1691
|
+
useBlogComments,
|
|
1692
|
+
useBlogFeaturedPosts,
|
|
1693
|
+
useBlogPost,
|
|
1694
|
+
useBlogPosts,
|
|
1695
|
+
useBlogTags,
|
|
489
1696
|
useContactCapture,
|
|
1697
|
+
useEditMode,
|
|
1698
|
+
useEditModeMessaging,
|
|
490
1699
|
useFoxPixelContext,
|
|
491
1700
|
useLeadCapture,
|
|
1701
|
+
useNewsletterSubscribe,
|
|
1702
|
+
useNewsletterUnsubscribe,
|
|
1703
|
+
useSendEditRequest,
|
|
492
1704
|
useServices,
|
|
1705
|
+
useSiteContent,
|
|
1706
|
+
useSiteContentQuery,
|
|
1707
|
+
useSiteContentSection,
|
|
1708
|
+
useSiteContents,
|
|
493
1709
|
withAuth
|
|
494
1710
|
});
|
|
495
1711
|
//# sourceMappingURL=index.js.map
|