@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.mjs CHANGED
@@ -238,7 +238,8 @@ function AuthProvider({
238
238
  logout,
239
239
  register,
240
240
  updateProfile,
241
- refetch: fetchCurrentUser
241
+ refetch: fetchCurrentUser,
242
+ hasPermission: (permission) => user !== null && permission === "site:content:update"
242
243
  };
243
244
  return /* @__PURE__ */ jsx2(AuthContext.Provider, { value, children });
244
245
  }
@@ -344,13 +345,332 @@ function withAuth(Component, options = {}) {
344
345
  };
345
346
  }
346
347
 
348
+ // src/components/Editable.tsx
349
+ import { createElement, useCallback as useCallback3 } from "react";
350
+
351
+ // src/hooks/useEditMode.ts
352
+ import { useEffect as useEffect5, useState as useState5, useCallback as useCallback2 } from "react";
353
+ import { useQueryClient } from "@tanstack/react-query";
354
+ var SITE_CONTENT_QUERY_KEY = "siteContent";
355
+ function useEditMode() {
356
+ const [isEditMode, setIsEditMode] = useState5(false);
357
+ useEffect5(() => {
358
+ if (typeof window === "undefined") return;
359
+ const params = new URLSearchParams(window.location.search);
360
+ setIsEditMode(params.get("edit-mode") === "true");
361
+ }, []);
362
+ return isEditMode;
363
+ }
364
+ function useEditModeMessaging() {
365
+ const queryClient = useQueryClient();
366
+ const isEditMode = useEditMode();
367
+ useEffect5(() => {
368
+ if (!isEditMode || typeof window === "undefined") return;
369
+ window.parent.postMessage({ type: "FOXPIXEL_READY" }, "*");
370
+ const handleMessage = (event) => {
371
+ const { type, payload } = event.data || {};
372
+ if (type === "FOXPIXEL_CONTENT_UPDATED" && payload?.contentKey) {
373
+ queryClient.invalidateQueries({
374
+ queryKey: [SITE_CONTENT_QUERY_KEY, payload.contentKey]
375
+ });
376
+ }
377
+ };
378
+ window.addEventListener("message", handleMessage);
379
+ return () => window.removeEventListener("message", handleMessage);
380
+ }, [isEditMode, queryClient]);
381
+ return isEditMode;
382
+ }
383
+ function useSendEditRequest() {
384
+ const isEditMode = useEditMode();
385
+ return useCallback2(
386
+ (contentKey, currentValue, contentType = "text", section, description) => {
387
+ if (!isEditMode) return;
388
+ if (typeof window !== "undefined" && window.parent !== window) {
389
+ window.parent.postMessage(
390
+ {
391
+ type: "FOXPIXEL_EDIT_CONTENT",
392
+ payload: {
393
+ contentKey,
394
+ currentValue,
395
+ contentType,
396
+ section,
397
+ description
398
+ }
399
+ },
400
+ "*"
401
+ );
402
+ }
403
+ },
404
+ [isEditMode]
405
+ );
406
+ }
407
+
408
+ // src/hooks/useSiteContentQuery.ts
409
+ import { useQuery } from "@tanstack/react-query";
410
+ function useSiteContentQuery(contentKey, options) {
411
+ const { defaultValue } = options;
412
+ const { client } = useFoxPixelContext();
413
+ const { data, isLoading } = useQuery({
414
+ queryKey: [SITE_CONTENT_QUERY_KEY, contentKey],
415
+ queryFn: async () => {
416
+ try {
417
+ const content = await client.get(
418
+ `/api/site/content/${encodeURIComponent(contentKey)}`
419
+ );
420
+ if (!content) return null;
421
+ return {
422
+ value: content.value ?? "",
423
+ contentType: content.contentType ?? "TEXT"
424
+ };
425
+ } catch (err) {
426
+ const status = err?.status;
427
+ if (status === 404) return null;
428
+ throw err;
429
+ }
430
+ },
431
+ staleTime: 1e3 * 60 * 5,
432
+ retry: 1
433
+ });
434
+ return {
435
+ value: data?.value ?? defaultValue,
436
+ isLoading,
437
+ contentType: data?.contentType ?? "TEXT"
438
+ };
439
+ }
440
+
441
+ // src/utils/sanitize.ts
442
+ import DOMPurify from "isomorphic-dompurify";
443
+ var DEFAULT_ALLOWED_TAGS = [
444
+ "p",
445
+ "br",
446
+ "strong",
447
+ "em",
448
+ "u",
449
+ "s",
450
+ "a",
451
+ "ul",
452
+ "ol",
453
+ "li",
454
+ "h1",
455
+ "h2",
456
+ "h3",
457
+ "h4",
458
+ "h5",
459
+ "h6",
460
+ "blockquote",
461
+ "code",
462
+ "pre",
463
+ "span",
464
+ "div",
465
+ "img",
466
+ "table",
467
+ "thead",
468
+ "tbody",
469
+ "tr",
470
+ "th",
471
+ "td"
472
+ ];
473
+ var DEFAULT_ALLOWED_ATTR = ["href", "target", "rel", "src", "alt", "title", "class"];
474
+ function sanitizeHtml(html) {
475
+ if (typeof html !== "string") return "";
476
+ return DOMPurify.sanitize(html, {
477
+ ALLOWED_TAGS: DEFAULT_ALLOWED_TAGS,
478
+ ALLOWED_ATTR: DEFAULT_ALLOWED_ATTR,
479
+ ADD_ATTR: ["target"]
480
+ });
481
+ }
482
+
483
+ // src/utils/cn.ts
484
+ function cn(...args) {
485
+ return args.filter(Boolean).join(" ");
486
+ }
487
+
488
+ // src/components/Editable.tsx
489
+ import { Fragment as Fragment3, jsx as jsx6, jsxs } from "react/jsx-runtime";
490
+ function Editable({
491
+ contentKey,
492
+ defaultValue,
493
+ as = "span",
494
+ multiline = false,
495
+ className
496
+ }) {
497
+ const isEditMode = useEditModeMessaging();
498
+ const sendEditRequest = useSendEditRequest();
499
+ const { value, isLoading, contentType } = useSiteContentQuery(contentKey, {
500
+ defaultValue
501
+ });
502
+ const section = contentKey.includes(".") ? contentKey.split(".")[0] : void 0;
503
+ const handleClick = useCallback3(
504
+ (e) => {
505
+ if (isEditMode) {
506
+ e.preventDefault();
507
+ e.stopPropagation();
508
+ sendEditRequest(
509
+ contentKey,
510
+ value,
511
+ contentType?.toLowerCase() || "text",
512
+ section
513
+ );
514
+ }
515
+ },
516
+ [isEditMode, contentKey, value, contentType, section, sendEditRequest]
517
+ );
518
+ if (isLoading) {
519
+ return createElement(as, {
520
+ className: cn(
521
+ "animate-pulse bg-muted rounded",
522
+ multiline ? "h-20" : "h-6",
523
+ "inline-block min-w-[100px]",
524
+ className
525
+ ),
526
+ "aria-busy": true,
527
+ "aria-label": "Loading content..."
528
+ });
529
+ }
530
+ const editModeStyles = isEditMode ? cn(
531
+ "cursor-pointer transition-all duration-200",
532
+ "hover:ring-2 hover:ring-blue-500 hover:ring-offset-2",
533
+ "hover:bg-blue-50/50 dark:hover:bg-blue-950/30",
534
+ "relative group"
535
+ ) : "";
536
+ if (multiline && value.includes("\n")) {
537
+ const safeBr = sanitizeHtml(value.replace(/\n/g, "<br />"));
538
+ return createElement(as, {
539
+ className: cn(className, editModeStyles),
540
+ "data-content-key": contentKey,
541
+ "data-editable": isEditMode ? "true" : void 0,
542
+ onClick: isEditMode ? handleClick : void 0,
543
+ dangerouslySetInnerHTML: { __html: safeBr },
544
+ title: isEditMode ? "Click to edit" : void 0
545
+ });
546
+ }
547
+ return createElement(
548
+ as,
549
+ {
550
+ className: cn(className, editModeStyles),
551
+ "data-content-key": contentKey,
552
+ "data-editable": isEditMode ? "true" : void 0,
553
+ onClick: isEditMode ? handleClick : void 0,
554
+ title: isEditMode ? "Click to edit" : void 0
555
+ },
556
+ /* @__PURE__ */ jsxs(Fragment3, { children: [
557
+ value,
558
+ isEditMode && /* @__PURE__ */ jsx6("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" })
559
+ ] })
560
+ );
561
+ }
562
+ function EditableHTML({
563
+ contentKey,
564
+ defaultValue,
565
+ as = "div",
566
+ className
567
+ }) {
568
+ const isEditMode = useEditModeMessaging();
569
+ const sendEditRequest = useSendEditRequest();
570
+ const { value, isLoading } = useSiteContentQuery(contentKey, {
571
+ defaultValue
572
+ });
573
+ const section = contentKey.includes(".") ? contentKey.split(".")[0] : void 0;
574
+ const handleClick = useCallback3(
575
+ (e) => {
576
+ if (isEditMode) {
577
+ e.preventDefault();
578
+ e.stopPropagation();
579
+ sendEditRequest(contentKey, value, "html", section);
580
+ }
581
+ },
582
+ [isEditMode, contentKey, value, section, sendEditRequest]
583
+ );
584
+ if (isLoading) {
585
+ return createElement(as, {
586
+ className: cn("animate-pulse bg-muted rounded h-32", className),
587
+ "aria-busy": true
588
+ });
589
+ }
590
+ const editModeStyles = isEditMode ? cn(
591
+ "cursor-pointer transition-all duration-200",
592
+ "hover:ring-2 hover:ring-blue-500 hover:ring-offset-2",
593
+ "hover:bg-blue-50/50 dark:hover:bg-blue-950/30",
594
+ "relative group"
595
+ ) : "";
596
+ const safeHtml = sanitizeHtml(value);
597
+ return createElement(as, {
598
+ className: cn("prose prose-slate dark:prose-invert", className, editModeStyles),
599
+ "data-content-key": contentKey,
600
+ "data-editable": isEditMode ? "true" : void 0,
601
+ onClick: isEditMode ? handleClick : void 0,
602
+ title: isEditMode ? "Click to edit" : void 0,
603
+ dangerouslySetInnerHTML: { __html: safeHtml }
604
+ });
605
+ }
606
+ function EditableImage({
607
+ contentKey,
608
+ defaultValue,
609
+ alt,
610
+ className,
611
+ width,
612
+ height,
613
+ priority = false
614
+ }) {
615
+ const isEditMode = useEditModeMessaging();
616
+ const sendEditRequest = useSendEditRequest();
617
+ const { value: src, isLoading } = useSiteContentQuery(contentKey, {
618
+ defaultValue
619
+ });
620
+ const section = contentKey.includes(".") ? contentKey.split(".")[0] : void 0;
621
+ const handleClick = useCallback3(
622
+ (e) => {
623
+ if (isEditMode) {
624
+ e.preventDefault();
625
+ e.stopPropagation();
626
+ sendEditRequest(contentKey, src, "image", section);
627
+ }
628
+ },
629
+ [isEditMode, contentKey, src, section, sendEditRequest]
630
+ );
631
+ if (isLoading) {
632
+ return /* @__PURE__ */ jsx6(
633
+ "div",
634
+ {
635
+ className: cn("animate-pulse bg-muted rounded", className),
636
+ style: { width, height },
637
+ "aria-busy": "true"
638
+ }
639
+ );
640
+ }
641
+ const editModeStyles = isEditMode ? cn(
642
+ "cursor-pointer transition-all duration-200",
643
+ "hover:ring-2 hover:ring-blue-500 hover:ring-offset-2",
644
+ "hover:opacity-90",
645
+ "relative group"
646
+ ) : "";
647
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative", isEditMode && "group"), children: [
648
+ /* @__PURE__ */ jsx6(
649
+ "img",
650
+ {
651
+ src,
652
+ alt,
653
+ className: cn(className, editModeStyles),
654
+ width,
655
+ height,
656
+ loading: priority ? "eager" : "lazy",
657
+ "data-content-key": contentKey,
658
+ "data-editable": isEditMode ? "true" : void 0,
659
+ onClick: isEditMode ? handleClick : void 0,
660
+ title: isEditMode ? "Click to edit image" : void 0
661
+ }
662
+ ),
663
+ isEditMode && /* @__PURE__ */ jsx6("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" })
664
+ ] });
665
+ }
666
+
347
667
  // src/hooks/useServices.ts
348
- import { useState as useState5, useEffect as useEffect5 } from "react";
668
+ import { useState as useState6, useEffect as useEffect6 } from "react";
349
669
  function useServices(options = {}) {
350
670
  const { client } = useFoxPixelContext();
351
- const [services, setServices] = useState5(null);
352
- const [isLoading, setIsLoading] = useState5(true);
353
- const [error, setError] = useState5(null);
671
+ const [services, setServices] = useState6(null);
672
+ const [isLoading, setIsLoading] = useState6(true);
673
+ const [error, setError] = useState6(null);
354
674
  const fetchServices = async () => {
355
675
  try {
356
676
  setIsLoading(true);
@@ -368,7 +688,7 @@ function useServices(options = {}) {
368
688
  setIsLoading(false);
369
689
  }
370
690
  };
371
- useEffect5(() => {
691
+ useEffect6(() => {
372
692
  fetchServices();
373
693
  }, [options.category, options.active]);
374
694
  return {
@@ -380,11 +700,11 @@ function useServices(options = {}) {
380
700
  }
381
701
 
382
702
  // src/hooks/useLeadCapture.ts
383
- import { useState as useState6 } from "react";
703
+ import { useState as useState7 } from "react";
384
704
  function useLeadCapture() {
385
705
  const { client } = useFoxPixelContext();
386
- const [isLoading, setIsLoading] = useState6(false);
387
- const [error, setError] = useState6(null);
706
+ const [isLoading, setIsLoading] = useState7(false);
707
+ const [error, setError] = useState7(null);
388
708
  const captureLead = async (data) => {
389
709
  try {
390
710
  setIsLoading(true);
@@ -407,11 +727,11 @@ function useLeadCapture() {
407
727
  }
408
728
 
409
729
  // src/hooks/useContactCapture.ts
410
- import { useState as useState7 } from "react";
730
+ import { useState as useState8 } from "react";
411
731
  function useContactCapture() {
412
732
  const { client } = useFoxPixelContext();
413
- const [isLoading, setIsLoading] = useState7(false);
414
- const [error, setError] = useState7(null);
733
+ const [isLoading, setIsLoading] = useState8(false);
734
+ const [error, setError] = useState8(null);
415
735
  const captureContact = async (data) => {
416
736
  try {
417
737
  setIsLoading(true);
@@ -433,13 +753,144 @@ function useContactCapture() {
433
753
  };
434
754
  }
435
755
 
756
+ // src/hooks/useSiteContent.ts
757
+ import { useState as useState9, useEffect as useEffect7, useCallback as useCallback4 } from "react";
758
+ function useSiteContent(contentKey, options = {}) {
759
+ const { defaultValue = "", fetchOnMount = true } = options;
760
+ const { client } = useFoxPixelContext();
761
+ const { user, hasPermission } = useAuth();
762
+ const [data, setData] = useState9(null);
763
+ const [isLoading, setIsLoading] = useState9(fetchOnMount);
764
+ const [error, setError] = useState9(null);
765
+ const canEdit = user !== null && hasPermission("site:content:update");
766
+ const fetchContent = useCallback4(async () => {
767
+ try {
768
+ setIsLoading(true);
769
+ setError(null);
770
+ const content = await client.get(
771
+ `/api/site/content/${encodeURIComponent(contentKey)}`
772
+ );
773
+ setData(content);
774
+ } catch (err) {
775
+ if (err?.status === 404) {
776
+ setData(null);
777
+ } else {
778
+ setError(err);
779
+ }
780
+ } finally {
781
+ setIsLoading(false);
782
+ }
783
+ }, [client, contentKey]);
784
+ const updateContent = useCallback4(async (newValue) => {
785
+ try {
786
+ setError(null);
787
+ const updated = await client.put(
788
+ `/api/site/content/${encodeURIComponent(contentKey)}`,
789
+ { value: newValue }
790
+ );
791
+ setData(updated);
792
+ } catch (err) {
793
+ setError(err);
794
+ throw err;
795
+ }
796
+ }, [client, contentKey]);
797
+ useEffect7(() => {
798
+ if (fetchOnMount) {
799
+ fetchContent();
800
+ }
801
+ }, [contentKey, fetchOnMount]);
802
+ const value = data?.value ?? defaultValue;
803
+ return {
804
+ data,
805
+ value,
806
+ isLoading,
807
+ error,
808
+ canEdit,
809
+ update: updateContent,
810
+ refetch: fetchContent
811
+ };
812
+ }
813
+ function useSiteContents(contentKeys, options = {}) {
814
+ const { defaults = {} } = options;
815
+ const { client } = useFoxPixelContext();
816
+ const [data, setData] = useState9({});
817
+ const [isLoading, setIsLoading] = useState9(true);
818
+ const [error, setError] = useState9(null);
819
+ const fetchContents = useCallback4(async () => {
820
+ if (contentKeys.length === 0) {
821
+ setData({});
822
+ setIsLoading(false);
823
+ return;
824
+ }
825
+ try {
826
+ setIsLoading(true);
827
+ setError(null);
828
+ const contents = await client.post(
829
+ "/api/site/content/batch",
830
+ contentKeys
831
+ );
832
+ setData(contents);
833
+ } catch (err) {
834
+ setError(err);
835
+ } finally {
836
+ setIsLoading(false);
837
+ }
838
+ }, [client, contentKeys.join(",")]);
839
+ useEffect7(() => {
840
+ fetchContents();
841
+ }, [fetchContents]);
842
+ const getValue = useCallback4((key, defaultValue) => {
843
+ const content = data[key];
844
+ if (content?.value) {
845
+ return content.value;
846
+ }
847
+ return defaultValue ?? defaults[key] ?? "";
848
+ }, [data, defaults]);
849
+ return {
850
+ data,
851
+ getValue,
852
+ isLoading,
853
+ error,
854
+ refetch: fetchContents
855
+ };
856
+ }
857
+ function useSiteContentSection(section) {
858
+ const { client } = useFoxPixelContext();
859
+ const [contents, setContents] = useState9([]);
860
+ const [isLoading, setIsLoading] = useState9(true);
861
+ const [error, setError] = useState9(null);
862
+ const fetchContents = useCallback4(async () => {
863
+ try {
864
+ setIsLoading(true);
865
+ setError(null);
866
+ const data = await client.get(
867
+ `/api/site/content/section/${encodeURIComponent(section)}`
868
+ );
869
+ setContents(data);
870
+ } catch (err) {
871
+ setError(err);
872
+ } finally {
873
+ setIsLoading(false);
874
+ }
875
+ }, [client, section]);
876
+ useEffect7(() => {
877
+ fetchContents();
878
+ }, [fetchContents]);
879
+ return {
880
+ contents,
881
+ isLoading,
882
+ error,
883
+ refetch: fetchContents
884
+ };
885
+ }
886
+
436
887
  // src/blog/hooks.ts
437
- import { useState as useState8, useEffect as useEffect6 } from "react";
888
+ import { useState as useState10, useEffect as useEffect8 } from "react";
438
889
  function useBlogPosts(options = {}) {
439
890
  const { client } = useFoxPixelContext();
440
- const [data, setData] = useState8(null);
441
- const [isLoading, setIsLoading] = useState8(true);
442
- const [error, setError] = useState8(null);
891
+ const [data, setData] = useState10(null);
892
+ const [isLoading, setIsLoading] = useState10(true);
893
+ const [error, setError] = useState10(null);
443
894
  const page = options.page ?? 0;
444
895
  const limit = options.limit ?? 10;
445
896
  const fetchPosts = async () => {
@@ -459,7 +910,7 @@ function useBlogPosts(options = {}) {
459
910
  setIsLoading(false);
460
911
  }
461
912
  };
462
- useEffect6(() => {
913
+ useEffect8(() => {
463
914
  fetchPosts();
464
915
  }, [page, limit]);
465
916
  return {
@@ -471,9 +922,9 @@ function useBlogPosts(options = {}) {
471
922
  }
472
923
  function useBlogPost(slug) {
473
924
  const { client } = useFoxPixelContext();
474
- const [data, setData] = useState8(null);
475
- const [isLoading, setIsLoading] = useState8(!!slug);
476
- const [error, setError] = useState8(null);
925
+ const [data, setData] = useState10(null);
926
+ const [isLoading, setIsLoading] = useState10(!!slug);
927
+ const [error, setError] = useState10(null);
477
928
  const fetchPost = async () => {
478
929
  if (!slug) {
479
930
  setData(null);
@@ -492,7 +943,7 @@ function useBlogPost(slug) {
492
943
  setIsLoading(false);
493
944
  }
494
945
  };
495
- useEffect6(() => {
946
+ useEffect8(() => {
496
947
  fetchPost();
497
948
  }, [slug]);
498
949
  return {
@@ -504,9 +955,9 @@ function useBlogPost(slug) {
504
955
  }
505
956
  function useBlogCategories() {
506
957
  const { client } = useFoxPixelContext();
507
- const [data, setData] = useState8(null);
508
- const [isLoading, setIsLoading] = useState8(true);
509
- const [error, setError] = useState8(null);
958
+ const [data, setData] = useState10(null);
959
+ const [isLoading, setIsLoading] = useState10(true);
960
+ const [error, setError] = useState10(null);
510
961
  const fetchCategories = async () => {
511
962
  try {
512
963
  setIsLoading(true);
@@ -520,7 +971,7 @@ function useBlogCategories() {
520
971
  setIsLoading(false);
521
972
  }
522
973
  };
523
- useEffect6(() => {
974
+ useEffect8(() => {
524
975
  fetchCategories();
525
976
  }, []);
526
977
  return {
@@ -532,9 +983,9 @@ function useBlogCategories() {
532
983
  }
533
984
  function useBlogTags() {
534
985
  const { client } = useFoxPixelContext();
535
- const [data, setData] = useState8(null);
536
- const [isLoading, setIsLoading] = useState8(true);
537
- const [error, setError] = useState8(null);
986
+ const [data, setData] = useState10(null);
987
+ const [isLoading, setIsLoading] = useState10(true);
988
+ const [error, setError] = useState10(null);
538
989
  const fetchTags = async () => {
539
990
  try {
540
991
  setIsLoading(true);
@@ -548,7 +999,7 @@ function useBlogTags() {
548
999
  setIsLoading(false);
549
1000
  }
550
1001
  };
551
- useEffect6(() => {
1002
+ useEffect8(() => {
552
1003
  fetchTags();
553
1004
  }, []);
554
1005
  return {
@@ -560,9 +1011,9 @@ function useBlogTags() {
560
1011
  }
561
1012
  function useBlogComments(slug) {
562
1013
  const { client } = useFoxPixelContext();
563
- const [data, setData] = useState8(null);
564
- const [isLoading, setIsLoading] = useState8(!!slug);
565
- const [error, setError] = useState8(null);
1014
+ const [data, setData] = useState10(null);
1015
+ const [isLoading, setIsLoading] = useState10(!!slug);
1016
+ const [error, setError] = useState10(null);
566
1017
  const fetchComments = async () => {
567
1018
  if (!slug) {
568
1019
  setData(null);
@@ -583,7 +1034,7 @@ function useBlogComments(slug) {
583
1034
  setIsLoading(false);
584
1035
  }
585
1036
  };
586
- useEffect6(() => {
1037
+ useEffect8(() => {
587
1038
  fetchComments();
588
1039
  }, [slug]);
589
1040
  return {
@@ -595,8 +1046,8 @@ function useBlogComments(slug) {
595
1046
  }
596
1047
  function useBlogCommentSubmit(slug) {
597
1048
  const { client } = useFoxPixelContext();
598
- const [isSubmitting, setIsSubmitting] = useState8(false);
599
- const [error, setError] = useState8(null);
1049
+ const [isSubmitting, setIsSubmitting] = useState10(false);
1050
+ const [error, setError] = useState10(null);
600
1051
  const submit = async (payload) => {
601
1052
  if (!slug) return null;
602
1053
  try {
@@ -624,9 +1075,9 @@ function useBlogCommentSubmit(slug) {
624
1075
  }
625
1076
  function useBlogFeaturedPosts(limit = 6) {
626
1077
  const { client } = useFoxPixelContext();
627
- const [data, setData] = useState8(null);
628
- const [isLoading, setIsLoading] = useState8(true);
629
- const [error, setError] = useState8(null);
1078
+ const [data, setData] = useState10(null);
1079
+ const [isLoading, setIsLoading] = useState10(true);
1080
+ const [error, setError] = useState10(null);
630
1081
  const fetchFeatured = async () => {
631
1082
  try {
632
1083
  setIsLoading(true);
@@ -644,7 +1095,7 @@ function useBlogFeaturedPosts(limit = 6) {
644
1095
  setIsLoading(false);
645
1096
  }
646
1097
  };
647
- useEffect6(() => {
1098
+ useEffect8(() => {
648
1099
  fetchFeatured();
649
1100
  }, [limit]);
650
1101
  return {
@@ -656,9 +1107,9 @@ function useBlogFeaturedPosts(limit = 6) {
656
1107
  }
657
1108
  function useNewsletterSubscribe() {
658
1109
  const { client } = useFoxPixelContext();
659
- const [isSubmitting, setIsSubmitting] = useState8(false);
660
- const [error, setError] = useState8(null);
661
- const [success, setSuccess] = useState8(false);
1110
+ const [isSubmitting, setIsSubmitting] = useState10(false);
1111
+ const [error, setError] = useState10(null);
1112
+ const [success, setSuccess] = useState10(false);
662
1113
  const subscribe = async (payload) => {
663
1114
  try {
664
1115
  setIsSubmitting(true);
@@ -691,9 +1142,9 @@ function useNewsletterSubscribe() {
691
1142
  }
692
1143
  function useNewsletterUnsubscribe() {
693
1144
  const { client } = useFoxPixelContext();
694
- const [isSubmitting, setIsSubmitting] = useState8(false);
695
- const [error, setError] = useState8(null);
696
- const [success, setSuccess] = useState8(false);
1145
+ const [isSubmitting, setIsSubmitting] = useState10(false);
1146
+ const [error, setError] = useState10(null);
1147
+ const [success, setSuccess] = useState10(false);
697
1148
  const unsubscribe = async (email) => {
698
1149
  try {
699
1150
  setIsSubmitting(true);
@@ -711,8 +1162,26 @@ function useNewsletterUnsubscribe() {
711
1162
  setIsSubmitting(false);
712
1163
  }
713
1164
  };
1165
+ const unsubscribeByToken = async (token) => {
1166
+ try {
1167
+ setIsSubmitting(true);
1168
+ setError(null);
1169
+ setSuccess(false);
1170
+ await client.get("/api/v1/blog/newsletter/unsubscribe", {
1171
+ params: { token }
1172
+ });
1173
+ setSuccess(true);
1174
+ return true;
1175
+ } catch (err) {
1176
+ setError(err);
1177
+ return false;
1178
+ } finally {
1179
+ setIsSubmitting(false);
1180
+ }
1181
+ };
714
1182
  return {
715
1183
  unsubscribe,
1184
+ unsubscribeByToken,
716
1185
  isSubmitting,
717
1186
  error,
718
1187
  success
@@ -720,15 +1189,15 @@ function useNewsletterUnsubscribe() {
720
1189
  }
721
1190
 
722
1191
  // src/blog/admin-hooks.ts
723
- import { useState as useState9, useEffect as useEffect7, useCallback as useCallback2 } from "react";
1192
+ import { useState as useState11, useEffect as useEffect9, useCallback as useCallback5 } from "react";
724
1193
  function useAdminBlogPosts(options = {}) {
725
1194
  const { client } = useFoxPixelContext();
726
- const [data, setData] = useState9(null);
727
- const [isLoading, setIsLoading] = useState9(true);
728
- const [error, setError] = useState9(null);
1195
+ const [data, setData] = useState11(null);
1196
+ const [isLoading, setIsLoading] = useState11(true);
1197
+ const [error, setError] = useState11(null);
729
1198
  const page = options.page ?? 0;
730
1199
  const size = options.size ?? 20;
731
- const fetchPosts = useCallback2(async () => {
1200
+ const fetchPosts = useCallback5(async () => {
732
1201
  try {
733
1202
  setIsLoading(true);
734
1203
  setError(null);
@@ -743,17 +1212,17 @@ function useAdminBlogPosts(options = {}) {
743
1212
  setIsLoading(false);
744
1213
  }
745
1214
  }, [client, page, size]);
746
- useEffect7(() => {
1215
+ useEffect9(() => {
747
1216
  fetchPosts();
748
1217
  }, [fetchPosts]);
749
1218
  return { data, isLoading, error, refetch: fetchPosts };
750
1219
  }
751
1220
  function useAdminBlogPost(id) {
752
1221
  const { client } = useFoxPixelContext();
753
- const [data, setData] = useState9(null);
754
- const [isLoading, setIsLoading] = useState9(!!id);
755
- const [error, setError] = useState9(null);
756
- const fetchPost = useCallback2(async () => {
1222
+ const [data, setData] = useState11(null);
1223
+ const [isLoading, setIsLoading] = useState11(!!id);
1224
+ const [error, setError] = useState11(null);
1225
+ const fetchPost = useCallback5(async () => {
757
1226
  if (!id) {
758
1227
  setData(null);
759
1228
  setIsLoading(false);
@@ -770,15 +1239,15 @@ function useAdminBlogPost(id) {
770
1239
  setIsLoading(false);
771
1240
  }
772
1241
  }, [client, id]);
773
- useEffect7(() => {
1242
+ useEffect9(() => {
774
1243
  fetchPost();
775
1244
  }, [fetchPost]);
776
1245
  return { data, isLoading, error, refetch: fetchPost };
777
1246
  }
778
1247
  function useAdminBlogPostMutations() {
779
1248
  const { client } = useFoxPixelContext();
780
- const [isLoading, setIsLoading] = useState9(false);
781
- const [error, setError] = useState9(null);
1249
+ const [isLoading, setIsLoading] = useState11(false);
1250
+ const [error, setError] = useState11(null);
782
1251
  const create = async (payload) => {
783
1252
  try {
784
1253
  setIsLoading(true);
@@ -822,10 +1291,10 @@ function useAdminBlogPostMutations() {
822
1291
  }
823
1292
  function useAdminBlogCategories() {
824
1293
  const { client } = useFoxPixelContext();
825
- const [data, setData] = useState9(null);
826
- const [isLoading, setIsLoading] = useState9(true);
827
- const [error, setError] = useState9(null);
828
- const fetchCategories = useCallback2(async () => {
1294
+ const [data, setData] = useState11(null);
1295
+ const [isLoading, setIsLoading] = useState11(true);
1296
+ const [error, setError] = useState11(null);
1297
+ const fetchCategories = useCallback5(async () => {
829
1298
  try {
830
1299
  setIsLoading(true);
831
1300
  setError(null);
@@ -837,7 +1306,7 @@ function useAdminBlogCategories() {
837
1306
  setIsLoading(false);
838
1307
  }
839
1308
  }, [client]);
840
- useEffect7(() => {
1309
+ useEffect9(() => {
841
1310
  fetchCategories();
842
1311
  }, [fetchCategories]);
843
1312
  const create = async (payload) => {
@@ -874,10 +1343,10 @@ function useAdminBlogCategories() {
874
1343
  }
875
1344
  function useAdminBlogTags() {
876
1345
  const { client } = useFoxPixelContext();
877
- const [data, setData] = useState9(null);
878
- const [isLoading, setIsLoading] = useState9(true);
879
- const [error, setError] = useState9(null);
880
- const fetchTags = useCallback2(async () => {
1346
+ const [data, setData] = useState11(null);
1347
+ const [isLoading, setIsLoading] = useState11(true);
1348
+ const [error, setError] = useState11(null);
1349
+ const fetchTags = useCallback5(async () => {
881
1350
  try {
882
1351
  setIsLoading(true);
883
1352
  setError(null);
@@ -889,7 +1358,7 @@ function useAdminBlogTags() {
889
1358
  setIsLoading(false);
890
1359
  }
891
1360
  }, [client]);
892
- useEffect7(() => {
1361
+ useEffect9(() => {
893
1362
  fetchTags();
894
1363
  }, [fetchTags]);
895
1364
  const create = async (payload) => {
@@ -926,11 +1395,11 @@ function useAdminBlogTags() {
926
1395
  }
927
1396
  function useAdminBlogComments(options = {}) {
928
1397
  const { client } = useFoxPixelContext();
929
- const [data, setData] = useState9(null);
930
- const [isLoading, setIsLoading] = useState9(true);
931
- const [error, setError] = useState9(null);
1398
+ const [data, setData] = useState11(null);
1399
+ const [isLoading, setIsLoading] = useState11(true);
1400
+ const [error, setError] = useState11(null);
932
1401
  const { status, postId, page = 0, size = 20 } = options;
933
- const fetchComments = useCallback2(async () => {
1402
+ const fetchComments = useCallback5(async () => {
934
1403
  try {
935
1404
  setIsLoading(true);
936
1405
  setError(null);
@@ -947,7 +1416,7 @@ function useAdminBlogComments(options = {}) {
947
1416
  setIsLoading(false);
948
1417
  }
949
1418
  }, [client, status, postId, page, size]);
950
- useEffect7(() => {
1419
+ useEffect9(() => {
951
1420
  fetchComments();
952
1421
  }, [fetchComments]);
953
1422
  const updateStatus = async (id, newStatus) => {
@@ -974,11 +1443,11 @@ function useAdminBlogComments(options = {}) {
974
1443
  }
975
1444
  function useAdminNewsletterSubscribers(options = {}) {
976
1445
  const { client } = useFoxPixelContext();
977
- const [data, setData] = useState9(null);
978
- const [isLoading, setIsLoading] = useState9(true);
979
- const [error, setError] = useState9(null);
1446
+ const [data, setData] = useState11(null);
1447
+ const [isLoading, setIsLoading] = useState11(true);
1448
+ const [error, setError] = useState11(null);
980
1449
  const { status, page = 0, size = 20 } = options;
981
- const fetchSubscribers = useCallback2(async () => {
1450
+ const fetchSubscribers = useCallback5(async () => {
982
1451
  try {
983
1452
  setIsLoading(true);
984
1453
  setError(null);
@@ -994,7 +1463,7 @@ function useAdminNewsletterSubscribers(options = {}) {
994
1463
  setIsLoading(false);
995
1464
  }
996
1465
  }, [client, status, page, size]);
997
- useEffect7(() => {
1466
+ useEffect9(() => {
998
1467
  fetchSubscribers();
999
1468
  }, [fetchSubscribers]);
1000
1469
  const remove = async (id) => {
@@ -1011,10 +1480,10 @@ function useAdminNewsletterSubscribers(options = {}) {
1011
1480
  }
1012
1481
  function useAdminNewsletterStats() {
1013
1482
  const { client } = useFoxPixelContext();
1014
- const [data, setData] = useState9(null);
1015
- const [isLoading, setIsLoading] = useState9(true);
1016
- const [error, setError] = useState9(null);
1017
- const fetchStats = useCallback2(async () => {
1483
+ const [data, setData] = useState11(null);
1484
+ const [isLoading, setIsLoading] = useState11(true);
1485
+ const [error, setError] = useState11(null);
1486
+ const fetchStats = useCallback5(async () => {
1018
1487
  try {
1019
1488
  setIsLoading(true);
1020
1489
  setError(null);
@@ -1026,17 +1495,17 @@ function useAdminNewsletterStats() {
1026
1495
  setIsLoading(false);
1027
1496
  }
1028
1497
  }, [client]);
1029
- useEffect7(() => {
1498
+ useEffect9(() => {
1030
1499
  fetchStats();
1031
1500
  }, [fetchStats]);
1032
1501
  return { data, isLoading, error, refetch: fetchStats };
1033
1502
  }
1034
1503
  function useAdminBlogSettings() {
1035
1504
  const { client } = useFoxPixelContext();
1036
- const [data, setData] = useState9(null);
1037
- const [isLoading, setIsLoading] = useState9(true);
1038
- const [error, setError] = useState9(null);
1039
- const fetchSettings = useCallback2(async () => {
1505
+ const [data, setData] = useState11(null);
1506
+ const [isLoading, setIsLoading] = useState11(true);
1507
+ const [error, setError] = useState11(null);
1508
+ const fetchSettings = useCallback5(async () => {
1040
1509
  try {
1041
1510
  setIsLoading(true);
1042
1511
  setError(null);
@@ -1048,7 +1517,7 @@ function useAdminBlogSettings() {
1048
1517
  setIsLoading(false);
1049
1518
  }
1050
1519
  }, [client]);
1051
- useEffect7(() => {
1520
+ useEffect9(() => {
1052
1521
  fetchSettings();
1053
1522
  }, [fetchSettings]);
1054
1523
  const update = async (settings) => {
@@ -1065,10 +1534,10 @@ function useAdminBlogSettings() {
1065
1534
  }
1066
1535
  function useAdminBlogAnalytics() {
1067
1536
  const { client } = useFoxPixelContext();
1068
- const [data, setData] = useState9(null);
1069
- const [isLoading, setIsLoading] = useState9(true);
1070
- const [error, setError] = useState9(null);
1071
- const fetchAnalytics = useCallback2(async () => {
1537
+ const [data, setData] = useState11(null);
1538
+ const [isLoading, setIsLoading] = useState11(true);
1539
+ const [error, setError] = useState11(null);
1540
+ const fetchAnalytics = useCallback5(async () => {
1072
1541
  try {
1073
1542
  setIsLoading(true);
1074
1543
  setError(null);
@@ -1080,7 +1549,7 @@ function useAdminBlogAnalytics() {
1080
1549
  setIsLoading(false);
1081
1550
  }
1082
1551
  }, [client]);
1083
- useEffect7(() => {
1552
+ useEffect9(() => {
1084
1553
  fetchAnalytics();
1085
1554
  }, [fetchAnalytics]);
1086
1555
  return { data, isLoading, error, refetch: fetchAnalytics };
@@ -1119,10 +1588,14 @@ function getBlogPostSchemaLd(post, options) {
1119
1588
  }
1120
1589
  export {
1121
1590
  AuthProvider,
1591
+ Editable,
1592
+ EditableHTML,
1593
+ EditableImage,
1122
1594
  FoxPixelHttpClient,
1123
1595
  FoxPixelProvider,
1124
1596
  GuestOnlyRoute,
1125
1597
  ProtectedRoute,
1598
+ SITE_CONTENT_QUERY_KEY,
1126
1599
  getBlogPostSchemaLd,
1127
1600
  useAdminBlogAnalytics,
1128
1601
  useAdminBlogCategories,
@@ -1143,11 +1616,18 @@ export {
1143
1616
  useBlogPosts,
1144
1617
  useBlogTags,
1145
1618
  useContactCapture,
1619
+ useEditMode,
1620
+ useEditModeMessaging,
1146
1621
  useFoxPixelContext,
1147
1622
  useLeadCapture,
1148
1623
  useNewsletterSubscribe,
1149
1624
  useNewsletterUnsubscribe,
1625
+ useSendEditRequest,
1150
1626
  useServices,
1627
+ useSiteContent,
1628
+ useSiteContentQuery,
1629
+ useSiteContentSection,
1630
+ useSiteContents,
1151
1631
  withAuth
1152
1632
  };
1153
1633
  //# sourceMappingURL=index.mjs.map