@foxpixel/react 0.1.1 → 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.js CHANGED
@@ -31,10 +31,14 @@ 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,
38
42
  getBlogPostSchemaLd: () => getBlogPostSchemaLd,
39
43
  useAdminBlogAnalytics: () => useAdminBlogAnalytics,
40
44
  useAdminBlogCategories: () => useAdminBlogCategories,
@@ -55,11 +59,18 @@ __export(index_exports, {
55
59
  useBlogPosts: () => useBlogPosts,
56
60
  useBlogTags: () => useBlogTags,
57
61
  useContactCapture: () => useContactCapture,
62
+ useEditMode: () => useEditMode,
63
+ useEditModeMessaging: () => useEditModeMessaging,
58
64
  useFoxPixelContext: () => useFoxPixelContext,
59
65
  useLeadCapture: () => useLeadCapture,
60
66
  useNewsletterSubscribe: () => useNewsletterSubscribe,
61
67
  useNewsletterUnsubscribe: () => useNewsletterUnsubscribe,
68
+ useSendEditRequest: () => useSendEditRequest,
62
69
  useServices: () => useServices,
70
+ useSiteContent: () => useSiteContent,
71
+ useSiteContentQuery: () => useSiteContentQuery,
72
+ useSiteContentSection: () => useSiteContentSection,
73
+ useSiteContents: () => useSiteContents,
63
74
  withAuth: () => withAuth
64
75
  });
65
76
  module.exports = __toCommonJS(index_exports);
@@ -304,7 +315,8 @@ function AuthProvider({
304
315
  logout,
305
316
  register,
306
317
  updateProfile,
307
- refetch: fetchCurrentUser
318
+ refetch: fetchCurrentUser,
319
+ hasPermission: (permission) => user !== null && permission === "site:content:update"
308
320
  };
309
321
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AuthContext.Provider, { value, children });
310
322
  }
@@ -410,13 +422,332 @@ function withAuth(Component, options = {}) {
410
422
  };
411
423
  }
412
424
 
413
- // src/hooks/useServices.ts
425
+ // src/components/Editable.tsx
426
+ var import_react7 = require("react");
427
+
428
+ // src/hooks/useEditMode.ts
414
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");
415
746
  function useServices(options = {}) {
416
747
  const { client } = useFoxPixelContext();
417
- const [services, setServices] = (0, import_react6.useState)(null);
418
- const [isLoading, setIsLoading] = (0, import_react6.useState)(true);
419
- const [error, setError] = (0, import_react6.useState)(null);
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);
420
751
  const fetchServices = async () => {
421
752
  try {
422
753
  setIsLoading(true);
@@ -434,7 +765,7 @@ function useServices(options = {}) {
434
765
  setIsLoading(false);
435
766
  }
436
767
  };
437
- (0, import_react6.useEffect)(() => {
768
+ (0, import_react8.useEffect)(() => {
438
769
  fetchServices();
439
770
  }, [options.category, options.active]);
440
771
  return {
@@ -446,11 +777,11 @@ function useServices(options = {}) {
446
777
  }
447
778
 
448
779
  // src/hooks/useLeadCapture.ts
449
- var import_react7 = require("react");
780
+ var import_react9 = require("react");
450
781
  function useLeadCapture() {
451
782
  const { client } = useFoxPixelContext();
452
- const [isLoading, setIsLoading] = (0, import_react7.useState)(false);
453
- const [error, setError] = (0, import_react7.useState)(null);
783
+ const [isLoading, setIsLoading] = (0, import_react9.useState)(false);
784
+ const [error, setError] = (0, import_react9.useState)(null);
454
785
  const captureLead = async (data) => {
455
786
  try {
456
787
  setIsLoading(true);
@@ -473,11 +804,11 @@ function useLeadCapture() {
473
804
  }
474
805
 
475
806
  // src/hooks/useContactCapture.ts
476
- var import_react8 = require("react");
807
+ var import_react10 = require("react");
477
808
  function useContactCapture() {
478
809
  const { client } = useFoxPixelContext();
479
- const [isLoading, setIsLoading] = (0, import_react8.useState)(false);
480
- const [error, setError] = (0, import_react8.useState)(null);
810
+ const [isLoading, setIsLoading] = (0, import_react10.useState)(false);
811
+ const [error, setError] = (0, import_react10.useState)(null);
481
812
  const captureContact = async (data) => {
482
813
  try {
483
814
  setIsLoading(true);
@@ -499,13 +830,144 @@ function useContactCapture() {
499
830
  };
500
831
  }
501
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
+
502
964
  // src/blog/hooks.ts
503
- var import_react9 = require("react");
965
+ var import_react12 = require("react");
504
966
  function useBlogPosts(options = {}) {
505
967
  const { client } = useFoxPixelContext();
506
- const [data, setData] = (0, import_react9.useState)(null);
507
- const [isLoading, setIsLoading] = (0, import_react9.useState)(true);
508
- const [error, setError] = (0, import_react9.useState)(null);
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);
509
971
  const page = options.page ?? 0;
510
972
  const limit = options.limit ?? 10;
511
973
  const fetchPosts = async () => {
@@ -525,7 +987,7 @@ function useBlogPosts(options = {}) {
525
987
  setIsLoading(false);
526
988
  }
527
989
  };
528
- (0, import_react9.useEffect)(() => {
990
+ (0, import_react12.useEffect)(() => {
529
991
  fetchPosts();
530
992
  }, [page, limit]);
531
993
  return {
@@ -537,9 +999,9 @@ function useBlogPosts(options = {}) {
537
999
  }
538
1000
  function useBlogPost(slug) {
539
1001
  const { client } = useFoxPixelContext();
540
- const [data, setData] = (0, import_react9.useState)(null);
541
- const [isLoading, setIsLoading] = (0, import_react9.useState)(!!slug);
542
- const [error, setError] = (0, import_react9.useState)(null);
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);
543
1005
  const fetchPost = async () => {
544
1006
  if (!slug) {
545
1007
  setData(null);
@@ -558,7 +1020,7 @@ function useBlogPost(slug) {
558
1020
  setIsLoading(false);
559
1021
  }
560
1022
  };
561
- (0, import_react9.useEffect)(() => {
1023
+ (0, import_react12.useEffect)(() => {
562
1024
  fetchPost();
563
1025
  }, [slug]);
564
1026
  return {
@@ -570,9 +1032,9 @@ function useBlogPost(slug) {
570
1032
  }
571
1033
  function useBlogCategories() {
572
1034
  const { client } = useFoxPixelContext();
573
- const [data, setData] = (0, import_react9.useState)(null);
574
- const [isLoading, setIsLoading] = (0, import_react9.useState)(true);
575
- const [error, setError] = (0, import_react9.useState)(null);
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);
576
1038
  const fetchCategories = async () => {
577
1039
  try {
578
1040
  setIsLoading(true);
@@ -586,7 +1048,7 @@ function useBlogCategories() {
586
1048
  setIsLoading(false);
587
1049
  }
588
1050
  };
589
- (0, import_react9.useEffect)(() => {
1051
+ (0, import_react12.useEffect)(() => {
590
1052
  fetchCategories();
591
1053
  }, []);
592
1054
  return {
@@ -598,9 +1060,9 @@ function useBlogCategories() {
598
1060
  }
599
1061
  function useBlogTags() {
600
1062
  const { client } = useFoxPixelContext();
601
- const [data, setData] = (0, import_react9.useState)(null);
602
- const [isLoading, setIsLoading] = (0, import_react9.useState)(true);
603
- const [error, setError] = (0, import_react9.useState)(null);
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);
604
1066
  const fetchTags = async () => {
605
1067
  try {
606
1068
  setIsLoading(true);
@@ -614,7 +1076,7 @@ function useBlogTags() {
614
1076
  setIsLoading(false);
615
1077
  }
616
1078
  };
617
- (0, import_react9.useEffect)(() => {
1079
+ (0, import_react12.useEffect)(() => {
618
1080
  fetchTags();
619
1081
  }, []);
620
1082
  return {
@@ -626,9 +1088,9 @@ function useBlogTags() {
626
1088
  }
627
1089
  function useBlogComments(slug) {
628
1090
  const { client } = useFoxPixelContext();
629
- const [data, setData] = (0, import_react9.useState)(null);
630
- const [isLoading, setIsLoading] = (0, import_react9.useState)(!!slug);
631
- const [error, setError] = (0, import_react9.useState)(null);
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);
632
1094
  const fetchComments = async () => {
633
1095
  if (!slug) {
634
1096
  setData(null);
@@ -649,7 +1111,7 @@ function useBlogComments(slug) {
649
1111
  setIsLoading(false);
650
1112
  }
651
1113
  };
652
- (0, import_react9.useEffect)(() => {
1114
+ (0, import_react12.useEffect)(() => {
653
1115
  fetchComments();
654
1116
  }, [slug]);
655
1117
  return {
@@ -661,8 +1123,8 @@ function useBlogComments(slug) {
661
1123
  }
662
1124
  function useBlogCommentSubmit(slug) {
663
1125
  const { client } = useFoxPixelContext();
664
- const [isSubmitting, setIsSubmitting] = (0, import_react9.useState)(false);
665
- const [error, setError] = (0, import_react9.useState)(null);
1126
+ const [isSubmitting, setIsSubmitting] = (0, import_react12.useState)(false);
1127
+ const [error, setError] = (0, import_react12.useState)(null);
666
1128
  const submit = async (payload) => {
667
1129
  if (!slug) return null;
668
1130
  try {
@@ -690,9 +1152,9 @@ function useBlogCommentSubmit(slug) {
690
1152
  }
691
1153
  function useBlogFeaturedPosts(limit = 6) {
692
1154
  const { client } = useFoxPixelContext();
693
- const [data, setData] = (0, import_react9.useState)(null);
694
- const [isLoading, setIsLoading] = (0, import_react9.useState)(true);
695
- const [error, setError] = (0, import_react9.useState)(null);
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);
696
1158
  const fetchFeatured = async () => {
697
1159
  try {
698
1160
  setIsLoading(true);
@@ -710,7 +1172,7 @@ function useBlogFeaturedPosts(limit = 6) {
710
1172
  setIsLoading(false);
711
1173
  }
712
1174
  };
713
- (0, import_react9.useEffect)(() => {
1175
+ (0, import_react12.useEffect)(() => {
714
1176
  fetchFeatured();
715
1177
  }, [limit]);
716
1178
  return {
@@ -722,9 +1184,9 @@ function useBlogFeaturedPosts(limit = 6) {
722
1184
  }
723
1185
  function useNewsletterSubscribe() {
724
1186
  const { client } = useFoxPixelContext();
725
- const [isSubmitting, setIsSubmitting] = (0, import_react9.useState)(false);
726
- const [error, setError] = (0, import_react9.useState)(null);
727
- const [success, setSuccess] = (0, import_react9.useState)(false);
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);
728
1190
  const subscribe = async (payload) => {
729
1191
  try {
730
1192
  setIsSubmitting(true);
@@ -757,9 +1219,9 @@ function useNewsletterSubscribe() {
757
1219
  }
758
1220
  function useNewsletterUnsubscribe() {
759
1221
  const { client } = useFoxPixelContext();
760
- const [isSubmitting, setIsSubmitting] = (0, import_react9.useState)(false);
761
- const [error, setError] = (0, import_react9.useState)(null);
762
- const [success, setSuccess] = (0, import_react9.useState)(false);
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);
763
1225
  const unsubscribe = async (email) => {
764
1226
  try {
765
1227
  setIsSubmitting(true);
@@ -777,8 +1239,26 @@ function useNewsletterUnsubscribe() {
777
1239
  setIsSubmitting(false);
778
1240
  }
779
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
+ };
780
1259
  return {
781
1260
  unsubscribe,
1261
+ unsubscribeByToken,
782
1262
  isSubmitting,
783
1263
  error,
784
1264
  success
@@ -786,15 +1266,15 @@ function useNewsletterUnsubscribe() {
786
1266
  }
787
1267
 
788
1268
  // src/blog/admin-hooks.ts
789
- var import_react10 = require("react");
1269
+ var import_react13 = require("react");
790
1270
  function useAdminBlogPosts(options = {}) {
791
1271
  const { client } = useFoxPixelContext();
792
- const [data, setData] = (0, import_react10.useState)(null);
793
- const [isLoading, setIsLoading] = (0, import_react10.useState)(true);
794
- const [error, setError] = (0, import_react10.useState)(null);
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);
795
1275
  const page = options.page ?? 0;
796
1276
  const size = options.size ?? 20;
797
- const fetchPosts = (0, import_react10.useCallback)(async () => {
1277
+ const fetchPosts = (0, import_react13.useCallback)(async () => {
798
1278
  try {
799
1279
  setIsLoading(true);
800
1280
  setError(null);
@@ -809,17 +1289,17 @@ function useAdminBlogPosts(options = {}) {
809
1289
  setIsLoading(false);
810
1290
  }
811
1291
  }, [client, page, size]);
812
- (0, import_react10.useEffect)(() => {
1292
+ (0, import_react13.useEffect)(() => {
813
1293
  fetchPosts();
814
1294
  }, [fetchPosts]);
815
1295
  return { data, isLoading, error, refetch: fetchPosts };
816
1296
  }
817
1297
  function useAdminBlogPost(id) {
818
1298
  const { client } = useFoxPixelContext();
819
- const [data, setData] = (0, import_react10.useState)(null);
820
- const [isLoading, setIsLoading] = (0, import_react10.useState)(!!id);
821
- const [error, setError] = (0, import_react10.useState)(null);
822
- const fetchPost = (0, import_react10.useCallback)(async () => {
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 () => {
823
1303
  if (!id) {
824
1304
  setData(null);
825
1305
  setIsLoading(false);
@@ -836,15 +1316,15 @@ function useAdminBlogPost(id) {
836
1316
  setIsLoading(false);
837
1317
  }
838
1318
  }, [client, id]);
839
- (0, import_react10.useEffect)(() => {
1319
+ (0, import_react13.useEffect)(() => {
840
1320
  fetchPost();
841
1321
  }, [fetchPost]);
842
1322
  return { data, isLoading, error, refetch: fetchPost };
843
1323
  }
844
1324
  function useAdminBlogPostMutations() {
845
1325
  const { client } = useFoxPixelContext();
846
- const [isLoading, setIsLoading] = (0, import_react10.useState)(false);
847
- const [error, setError] = (0, import_react10.useState)(null);
1326
+ const [isLoading, setIsLoading] = (0, import_react13.useState)(false);
1327
+ const [error, setError] = (0, import_react13.useState)(null);
848
1328
  const create = async (payload) => {
849
1329
  try {
850
1330
  setIsLoading(true);
@@ -888,10 +1368,10 @@ function useAdminBlogPostMutations() {
888
1368
  }
889
1369
  function useAdminBlogCategories() {
890
1370
  const { client } = useFoxPixelContext();
891
- const [data, setData] = (0, import_react10.useState)(null);
892
- const [isLoading, setIsLoading] = (0, import_react10.useState)(true);
893
- const [error, setError] = (0, import_react10.useState)(null);
894
- const fetchCategories = (0, import_react10.useCallback)(async () => {
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 () => {
895
1375
  try {
896
1376
  setIsLoading(true);
897
1377
  setError(null);
@@ -903,7 +1383,7 @@ function useAdminBlogCategories() {
903
1383
  setIsLoading(false);
904
1384
  }
905
1385
  }, [client]);
906
- (0, import_react10.useEffect)(() => {
1386
+ (0, import_react13.useEffect)(() => {
907
1387
  fetchCategories();
908
1388
  }, [fetchCategories]);
909
1389
  const create = async (payload) => {
@@ -940,10 +1420,10 @@ function useAdminBlogCategories() {
940
1420
  }
941
1421
  function useAdminBlogTags() {
942
1422
  const { client } = useFoxPixelContext();
943
- const [data, setData] = (0, import_react10.useState)(null);
944
- const [isLoading, setIsLoading] = (0, import_react10.useState)(true);
945
- const [error, setError] = (0, import_react10.useState)(null);
946
- const fetchTags = (0, import_react10.useCallback)(async () => {
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 () => {
947
1427
  try {
948
1428
  setIsLoading(true);
949
1429
  setError(null);
@@ -955,7 +1435,7 @@ function useAdminBlogTags() {
955
1435
  setIsLoading(false);
956
1436
  }
957
1437
  }, [client]);
958
- (0, import_react10.useEffect)(() => {
1438
+ (0, import_react13.useEffect)(() => {
959
1439
  fetchTags();
960
1440
  }, [fetchTags]);
961
1441
  const create = async (payload) => {
@@ -992,11 +1472,11 @@ function useAdminBlogTags() {
992
1472
  }
993
1473
  function useAdminBlogComments(options = {}) {
994
1474
  const { client } = useFoxPixelContext();
995
- const [data, setData] = (0, import_react10.useState)(null);
996
- const [isLoading, setIsLoading] = (0, import_react10.useState)(true);
997
- const [error, setError] = (0, import_react10.useState)(null);
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);
998
1478
  const { status, postId, page = 0, size = 20 } = options;
999
- const fetchComments = (0, import_react10.useCallback)(async () => {
1479
+ const fetchComments = (0, import_react13.useCallback)(async () => {
1000
1480
  try {
1001
1481
  setIsLoading(true);
1002
1482
  setError(null);
@@ -1013,7 +1493,7 @@ function useAdminBlogComments(options = {}) {
1013
1493
  setIsLoading(false);
1014
1494
  }
1015
1495
  }, [client, status, postId, page, size]);
1016
- (0, import_react10.useEffect)(() => {
1496
+ (0, import_react13.useEffect)(() => {
1017
1497
  fetchComments();
1018
1498
  }, [fetchComments]);
1019
1499
  const updateStatus = async (id, newStatus) => {
@@ -1040,11 +1520,11 @@ function useAdminBlogComments(options = {}) {
1040
1520
  }
1041
1521
  function useAdminNewsletterSubscribers(options = {}) {
1042
1522
  const { client } = useFoxPixelContext();
1043
- const [data, setData] = (0, import_react10.useState)(null);
1044
- const [isLoading, setIsLoading] = (0, import_react10.useState)(true);
1045
- const [error, setError] = (0, import_react10.useState)(null);
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);
1046
1526
  const { status, page = 0, size = 20 } = options;
1047
- const fetchSubscribers = (0, import_react10.useCallback)(async () => {
1527
+ const fetchSubscribers = (0, import_react13.useCallback)(async () => {
1048
1528
  try {
1049
1529
  setIsLoading(true);
1050
1530
  setError(null);
@@ -1060,7 +1540,7 @@ function useAdminNewsletterSubscribers(options = {}) {
1060
1540
  setIsLoading(false);
1061
1541
  }
1062
1542
  }, [client, status, page, size]);
1063
- (0, import_react10.useEffect)(() => {
1543
+ (0, import_react13.useEffect)(() => {
1064
1544
  fetchSubscribers();
1065
1545
  }, [fetchSubscribers]);
1066
1546
  const remove = async (id) => {
@@ -1077,10 +1557,10 @@ function useAdminNewsletterSubscribers(options = {}) {
1077
1557
  }
1078
1558
  function useAdminNewsletterStats() {
1079
1559
  const { client } = useFoxPixelContext();
1080
- const [data, setData] = (0, import_react10.useState)(null);
1081
- const [isLoading, setIsLoading] = (0, import_react10.useState)(true);
1082
- const [error, setError] = (0, import_react10.useState)(null);
1083
- const fetchStats = (0, import_react10.useCallback)(async () => {
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 () => {
1084
1564
  try {
1085
1565
  setIsLoading(true);
1086
1566
  setError(null);
@@ -1092,17 +1572,17 @@ function useAdminNewsletterStats() {
1092
1572
  setIsLoading(false);
1093
1573
  }
1094
1574
  }, [client]);
1095
- (0, import_react10.useEffect)(() => {
1575
+ (0, import_react13.useEffect)(() => {
1096
1576
  fetchStats();
1097
1577
  }, [fetchStats]);
1098
1578
  return { data, isLoading, error, refetch: fetchStats };
1099
1579
  }
1100
1580
  function useAdminBlogSettings() {
1101
1581
  const { client } = useFoxPixelContext();
1102
- const [data, setData] = (0, import_react10.useState)(null);
1103
- const [isLoading, setIsLoading] = (0, import_react10.useState)(true);
1104
- const [error, setError] = (0, import_react10.useState)(null);
1105
- const fetchSettings = (0, import_react10.useCallback)(async () => {
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 () => {
1106
1586
  try {
1107
1587
  setIsLoading(true);
1108
1588
  setError(null);
@@ -1114,7 +1594,7 @@ function useAdminBlogSettings() {
1114
1594
  setIsLoading(false);
1115
1595
  }
1116
1596
  }, [client]);
1117
- (0, import_react10.useEffect)(() => {
1597
+ (0, import_react13.useEffect)(() => {
1118
1598
  fetchSettings();
1119
1599
  }, [fetchSettings]);
1120
1600
  const update = async (settings) => {
@@ -1131,10 +1611,10 @@ function useAdminBlogSettings() {
1131
1611
  }
1132
1612
  function useAdminBlogAnalytics() {
1133
1613
  const { client } = useFoxPixelContext();
1134
- const [data, setData] = (0, import_react10.useState)(null);
1135
- const [isLoading, setIsLoading] = (0, import_react10.useState)(true);
1136
- const [error, setError] = (0, import_react10.useState)(null);
1137
- const fetchAnalytics = (0, import_react10.useCallback)(async () => {
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 () => {
1138
1618
  try {
1139
1619
  setIsLoading(true);
1140
1620
  setError(null);
@@ -1146,7 +1626,7 @@ function useAdminBlogAnalytics() {
1146
1626
  setIsLoading(false);
1147
1627
  }
1148
1628
  }, [client]);
1149
- (0, import_react10.useEffect)(() => {
1629
+ (0, import_react13.useEffect)(() => {
1150
1630
  fetchAnalytics();
1151
1631
  }, [fetchAnalytics]);
1152
1632
  return { data, isLoading, error, refetch: fetchAnalytics };
@@ -1186,10 +1666,14 @@ function getBlogPostSchemaLd(post, options) {
1186
1666
  // Annotate the CommonJS export names for ESM import in node:
1187
1667
  0 && (module.exports = {
1188
1668
  AuthProvider,
1669
+ Editable,
1670
+ EditableHTML,
1671
+ EditableImage,
1189
1672
  FoxPixelHttpClient,
1190
1673
  FoxPixelProvider,
1191
1674
  GuestOnlyRoute,
1192
1675
  ProtectedRoute,
1676
+ SITE_CONTENT_QUERY_KEY,
1193
1677
  getBlogPostSchemaLd,
1194
1678
  useAdminBlogAnalytics,
1195
1679
  useAdminBlogCategories,
@@ -1210,11 +1694,18 @@ function getBlogPostSchemaLd(post, options) {
1210
1694
  useBlogPosts,
1211
1695
  useBlogTags,
1212
1696
  useContactCapture,
1697
+ useEditMode,
1698
+ useEditModeMessaging,
1213
1699
  useFoxPixelContext,
1214
1700
  useLeadCapture,
1215
1701
  useNewsletterSubscribe,
1216
1702
  useNewsletterUnsubscribe,
1703
+ useSendEditRequest,
1217
1704
  useServices,
1705
+ useSiteContent,
1706
+ useSiteContentQuery,
1707
+ useSiteContentSection,
1708
+ useSiteContents,
1218
1709
  withAuth
1219
1710
  });
1220
1711
  //# sourceMappingURL=index.js.map