@foxpixel/react 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/context/FoxPixelContext.tsx
2
- import { createContext, useContext, useMemo } from "react";
2
+ import React, { createContext, useContext, useMemo } from "react";
3
3
 
4
4
  // src/client/http.ts
5
5
  import axios from "axios";
@@ -111,15 +111,21 @@ var FoxPixelHttpClient = class {
111
111
 
112
112
  // src/context/FoxPixelContext.tsx
113
113
  import { jsx } from "react/jsx-runtime";
114
+ if (!React || typeof React.useMemo !== "function") {
115
+ throw new Error(
116
+ '@foxpixel/react: React is not available. Ensure your app uses a single React instance and the SDK is not bundled with a different React. In Next.js use transpilePackages: ["@foxpixel/react"] and ensure react/react-dom resolve to one module (see next.config.js).'
117
+ );
118
+ }
114
119
  var FoxPixelContext = createContext(null);
115
- function FoxPixelProvider({ children, config = {} }) {
120
+ function FoxPixelProvider({ children, config = {}, queryClient }) {
116
121
  const client = useMemo(() => {
117
122
  return new FoxPixelHttpClient(config);
118
123
  }, [config.apiUrl, config.apiKey, config.tenantId]);
119
124
  const value = useMemo(() => ({
120
125
  client,
121
- config
122
- }), [client, config]);
126
+ config,
127
+ queryClient: queryClient ?? null
128
+ }), [client, config, queryClient]);
123
129
  return /* @__PURE__ */ jsx(FoxPixelContext.Provider, { value, children });
124
130
  }
125
131
  function useFoxPixelContext() {
@@ -346,11 +352,10 @@ function withAuth(Component, options = {}) {
346
352
  }
347
353
 
348
354
  // src/components/Editable.tsx
349
- import { createElement, useCallback as useCallback3 } from "react";
355
+ import { createElement, useCallback as useCallback3, useState as useState7 } from "react";
350
356
 
351
357
  // src/hooks/useEditMode.ts
352
358
  import { useEffect as useEffect5, useState as useState5, useCallback as useCallback2 } from "react";
353
- import { useQueryClient } from "@tanstack/react-query";
354
359
  var SITE_CONTENT_QUERY_KEY = "siteContent";
355
360
  function useEditMode() {
356
361
  const [isEditMode, setIsEditMode] = useState5(false);
@@ -362,18 +367,29 @@ function useEditMode() {
362
367
  return isEditMode;
363
368
  }
364
369
  function useEditModeMessaging() {
365
- const queryClient = useQueryClient();
370
+ const ctx = useFoxPixelContext();
371
+ const queryClient = ctx?.queryClient ?? null;
366
372
  const isEditMode = useEditMode();
367
373
  useEffect5(() => {
368
374
  if (!isEditMode || typeof window === "undefined") return;
369
375
  window.parent.postMessage({ type: "FOXPIXEL_READY" }, "*");
370
376
  const handleMessage = (event) => {
377
+ if (!queryClient) return;
371
378
  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
- });
379
+ if (type !== "FOXPIXEL_CONTENT_UPDATED" || !payload?.contentKey) return;
380
+ const { contentKey, newValue } = payload;
381
+ if (typeof newValue === "string") {
382
+ queryClient.setQueryData(
383
+ [SITE_CONTENT_QUERY_KEY, contentKey],
384
+ (prev) => ({
385
+ value: newValue,
386
+ contentType: prev?.contentType ?? "TEXT"
387
+ })
388
+ );
376
389
  }
390
+ queryClient.invalidateQueries({
391
+ queryKey: [SITE_CONTENT_QUERY_KEY, contentKey]
392
+ });
377
393
  };
378
394
  window.addEventListener("message", handleMessage);
379
395
  return () => window.removeEventListener("message", handleMessage);
@@ -406,16 +422,40 @@ function useSendEditRequest() {
406
422
  }
407
423
 
408
424
  // src/hooks/useSiteContentQuery.ts
409
- import { useQuery } from "@tanstack/react-query";
425
+ import { useState as useState6, useEffect as useEffect6, useRef } from "react";
426
+ function getCached(queryClient, contentKey) {
427
+ const data = queryClient.getQueryData([
428
+ SITE_CONTENT_QUERY_KEY,
429
+ contentKey
430
+ ]);
431
+ if (data == null) return void 0;
432
+ return { value: data.value ?? "", contentType: data.contentType ?? "TEXT" };
433
+ }
410
434
  function useSiteContentQuery(contentKey, options) {
411
435
  const { defaultValue } = options;
412
- const { client } = useFoxPixelContext();
413
- const { data, isLoading } = useQuery({
414
- queryKey: [SITE_CONTENT_QUERY_KEY, contentKey],
415
- queryFn: async () => {
436
+ const { client, queryClient } = useFoxPixelContext();
437
+ const [state, setState] = useState6(() => {
438
+ if (queryClient) {
439
+ const cached = getCached(queryClient, contentKey);
440
+ if (cached) {
441
+ return { value: cached.value, isLoading: false, contentType: cached.contentType };
442
+ }
443
+ }
444
+ return { value: defaultValue, isLoading: true, contentType: "TEXT" };
445
+ });
446
+ const contentKeyRef = useRef(contentKey);
447
+ contentKeyRef.current = contentKey;
448
+ useEffect6(() => {
449
+ if (!queryClient) {
450
+ setState((s) => ({ ...s, value: defaultValue, isLoading: false }));
451
+ return;
452
+ }
453
+ const key = contentKeyRef.current;
454
+ const queryKey = [SITE_CONTENT_QUERY_KEY, key];
455
+ const queryFn = async () => {
416
456
  try {
417
457
  const content = await client.get(
418
- `/api/site/content/${encodeURIComponent(contentKey)}`
458
+ `/api/site/content/${encodeURIComponent(key)}`
419
459
  );
420
460
  if (!content) return null;
421
461
  return {
@@ -423,23 +463,50 @@ function useSiteContentQuery(contentKey, options) {
423
463
  contentType: content.contentType ?? "TEXT"
424
464
  };
425
465
  } catch (err) {
426
- const status = err?.status;
466
+ const status = err?.response?.status;
427
467
  if (status === 404) return null;
428
468
  throw err;
429
469
  }
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
- };
470
+ };
471
+ let cancelled = false;
472
+ queryClient.fetchQuery({
473
+ queryKey,
474
+ queryFn,
475
+ staleTime: 1e3 * 60 * 5,
476
+ retry: 1
477
+ }).then((data) => {
478
+ if (cancelled) return;
479
+ setState({
480
+ value: data?.value ?? defaultValue,
481
+ isLoading: false,
482
+ contentType: data?.contentType ?? "TEXT"
483
+ });
484
+ }).catch(() => {
485
+ if (cancelled) return;
486
+ setState((s) => ({ ...s, value: defaultValue, isLoading: false }));
487
+ });
488
+ const unsub = queryClient.getQueryCache().subscribe((event) => {
489
+ if (event?.type === "updated" && event?.query?.queryKey[1] === key) {
490
+ const cached = getCached(queryClient, key);
491
+ if (cached && !cancelled) {
492
+ setState({
493
+ value: cached.value,
494
+ isLoading: false,
495
+ contentType: cached.contentType
496
+ });
497
+ }
498
+ }
499
+ });
500
+ return () => {
501
+ cancelled = true;
502
+ unsub();
503
+ };
504
+ }, [queryClient, contentKey, defaultValue, client]);
505
+ return state;
439
506
  }
440
507
 
441
508
  // src/utils/sanitize.ts
442
- import DOMPurify from "isomorphic-dompurify";
509
+ import sanitizeHtmlLib from "sanitize-html";
443
510
  var DEFAULT_ALLOWED_TAGS = [
444
511
  "p",
445
512
  "br",
@@ -470,13 +537,16 @@ var DEFAULT_ALLOWED_TAGS = [
470
537
  "th",
471
538
  "td"
472
539
  ];
473
- var DEFAULT_ALLOWED_ATTR = ["href", "target", "rel", "src", "alt", "title", "class"];
540
+ var DEFAULT_ALLOWED_ATTR = {
541
+ a: ["href", "target", "rel", "title"],
542
+ img: ["src", "alt", "title", "width", "height"],
543
+ "*": ["class"]
544
+ };
474
545
  function sanitizeHtml(html) {
475
546
  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"]
547
+ return sanitizeHtmlLib(html, {
548
+ allowedTags: DEFAULT_ALLOWED_TAGS,
549
+ allowedAttributes: DEFAULT_ALLOWED_ATTR
480
550
  });
481
551
  }
482
552
 
@@ -486,7 +556,24 @@ function cn(...args) {
486
556
  }
487
557
 
488
558
  // src/components/Editable.tsx
489
- import { Fragment as Fragment3, jsx as jsx6, jsxs } from "react/jsx-runtime";
559
+ import { jsx as jsx6, jsxs } from "react/jsx-runtime";
560
+ var EDIT_MODE_TOOLTIP_STYLE = {
561
+ position: "absolute",
562
+ top: "-28px",
563
+ left: "50%",
564
+ transform: "translateX(-50%)",
565
+ fontSize: "10px",
566
+ fontFamily: "system-ui, sans-serif",
567
+ padding: "4px 8px",
568
+ backgroundColor: "rgb(37 99 235)",
569
+ color: "white",
570
+ borderRadius: "4px",
571
+ whiteSpace: "nowrap",
572
+ pointerEvents: "none",
573
+ zIndex: 100,
574
+ boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
575
+ transition: "opacity 0.15s ease"
576
+ };
490
577
  function Editable({
491
578
  contentKey,
492
579
  defaultValue,
@@ -494,6 +581,7 @@ function Editable({
494
581
  multiline = false,
495
582
  className
496
583
  }) {
584
+ const [isHovered, setIsHovered] = useState7(false);
497
585
  const isEditMode = useEditModeMessaging();
498
586
  const sendEditRequest = useSendEditRequest();
499
587
  const { value, isLoading, contentType } = useSiteContentQuery(contentKey, {
@@ -527,36 +615,59 @@ function Editable({
527
615
  "aria-label": "Loading content..."
528
616
  });
529
617
  }
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
- ) : "";
618
+ const editModeWrapperStyle = isEditMode ? {
619
+ position: "relative",
620
+ display: "inline",
621
+ cursor: "pointer",
622
+ borderRadius: "2px",
623
+ outline: isHovered ? "2px solid rgb(59 130 246)" : "none",
624
+ outlineOffset: "2px",
625
+ backgroundColor: isHovered ? "rgba(59 130 246 / 0.08)" : void 0
626
+ } : {};
627
+ const tooltipSpan = isEditMode ? /* @__PURE__ */ jsx6(
628
+ "span",
629
+ {
630
+ style: {
631
+ ...EDIT_MODE_TOOLTIP_STYLE,
632
+ opacity: isHovered ? 1 : 0
633
+ },
634
+ "aria-hidden": true,
635
+ children: "Click to edit"
636
+ }
637
+ ) : null;
638
+ const hoverHandlers = isEditMode ? {
639
+ onMouseEnter: () => setIsHovered(true),
640
+ onMouseLeave: () => setIsHovered(false)
641
+ } : {};
536
642
  if (multiline && value.includes("\n")) {
537
643
  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
- });
644
+ return createElement(
645
+ "span",
646
+ { style: editModeWrapperStyle, ...hoverHandlers },
647
+ createElement(as, {
648
+ className,
649
+ "data-content-key": contentKey,
650
+ "data-editable": isEditMode ? "true" : void 0,
651
+ onClick: isEditMode ? handleClick : void 0,
652
+ dangerouslySetInnerHTML: { __html: safeBr }
653
+ }),
654
+ tooltipSpan
655
+ );
546
656
  }
547
657
  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
- ] })
658
+ "span",
659
+ { style: editModeWrapperStyle, ...hoverHandlers },
660
+ createElement(
661
+ as,
662
+ {
663
+ className,
664
+ "data-content-key": contentKey,
665
+ "data-editable": isEditMode ? "true" : void 0,
666
+ onClick: isEditMode ? handleClick : void 0
667
+ },
668
+ value
669
+ ),
670
+ tooltipSpan
560
671
  );
561
672
  }
562
673
  function EditableHTML({
@@ -565,6 +676,7 @@ function EditableHTML({
565
676
  as = "div",
566
677
  className
567
678
  }) {
679
+ const [isHovered, setIsHovered] = useState7(false);
568
680
  const isEditMode = useEditModeMessaging();
569
681
  const sendEditRequest = useSendEditRequest();
570
682
  const { value, isLoading } = useSiteContentQuery(contentKey, {
@@ -587,21 +699,39 @@ function EditableHTML({
587
699
  "aria-busy": true
588
700
  });
589
701
  }
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
- ) : "";
702
+ const wrapperStyle = isEditMode ? {
703
+ position: "relative",
704
+ cursor: "pointer",
705
+ borderRadius: "2px",
706
+ outline: isHovered ? "2px solid rgb(59 130 246)" : "none",
707
+ outlineOffset: "2px",
708
+ backgroundColor: isHovered ? "rgba(59 130 246 / 0.08)" : void 0
709
+ } : {};
596
710
  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
- });
711
+ const hoverHandlers = isEditMode ? { onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false) } : {};
712
+ return createElement(
713
+ "div",
714
+ { style: wrapperStyle, ...hoverHandlers },
715
+ createElement(as, {
716
+ className: cn("prose prose-slate dark:prose-invert", className),
717
+ "data-content-key": contentKey,
718
+ "data-editable": isEditMode ? "true" : void 0,
719
+ onClick: isEditMode ? handleClick : void 0,
720
+ dangerouslySetInnerHTML: { __html: safeHtml }
721
+ }),
722
+ isEditMode && /* @__PURE__ */ jsx6(
723
+ "span",
724
+ {
725
+ style: {
726
+ ...EDIT_MODE_TOOLTIP_STYLE,
727
+ top: "-24px",
728
+ opacity: isHovered ? 1 : 0
729
+ },
730
+ "aria-hidden": true,
731
+ children: "Click to edit"
732
+ }
733
+ )
734
+ );
605
735
  }
606
736
  function EditableImage({
607
737
  contentKey,
@@ -612,6 +742,7 @@ function EditableImage({
612
742
  height,
613
743
  priority = false
614
744
  }) {
745
+ const [isHovered, setIsHovered] = useState7(false);
615
746
  const isEditMode = useEditModeMessaging();
616
747
  const sendEditRequest = useSendEditRequest();
617
748
  const { value: src, isLoading } = useSiteContentQuery(contentKey, {
@@ -638,39 +769,55 @@ function EditableImage({
638
769
  }
639
770
  );
640
771
  }
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: [
772
+ const wrapperStyle = isEditMode ? {
773
+ position: "relative",
774
+ display: "inline-block",
775
+ cursor: "pointer",
776
+ borderRadius: "2px",
777
+ outline: isHovered ? "2px solid rgb(59 130 246)" : "none",
778
+ outlineOffset: "2px"
779
+ } : {};
780
+ const hoverHandlers = isEditMode ? { onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false) } : {};
781
+ return /* @__PURE__ */ jsxs("div", { style: wrapperStyle, ...hoverHandlers, children: [
648
782
  /* @__PURE__ */ jsx6(
649
783
  "img",
650
784
  {
651
785
  src,
652
786
  alt,
653
- className: cn(className, editModeStyles),
787
+ className,
788
+ style: isEditMode && isHovered ? { opacity: 0.95 } : void 0,
654
789
  width,
655
790
  height,
656
791
  loading: priority ? "eager" : "lazy",
657
792
  "data-content-key": contentKey,
658
793
  "data-editable": isEditMode ? "true" : void 0,
659
- onClick: isEditMode ? handleClick : void 0,
660
- title: isEditMode ? "Click to edit image" : void 0
794
+ onClick: isEditMode ? handleClick : void 0
661
795
  }
662
796
  ),
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" })
797
+ isEditMode && /* @__PURE__ */ jsx6(
798
+ "span",
799
+ {
800
+ style: {
801
+ ...EDIT_MODE_TOOLTIP_STYLE,
802
+ top: "8px",
803
+ left: "8px",
804
+ transform: "none",
805
+ opacity: isHovered ? 1 : 0
806
+ },
807
+ "aria-hidden": true,
808
+ children: "Click to edit image"
809
+ }
810
+ )
664
811
  ] });
665
812
  }
666
813
 
667
814
  // src/hooks/useServices.ts
668
- import { useState as useState6, useEffect as useEffect6 } from "react";
815
+ import { useState as useState8, useEffect as useEffect7 } from "react";
669
816
  function useServices(options = {}) {
670
817
  const { client } = useFoxPixelContext();
671
- const [services, setServices] = useState6(null);
672
- const [isLoading, setIsLoading] = useState6(true);
673
- const [error, setError] = useState6(null);
818
+ const [services, setServices] = useState8(null);
819
+ const [isLoading, setIsLoading] = useState8(true);
820
+ const [error, setError] = useState8(null);
674
821
  const fetchServices = async () => {
675
822
  try {
676
823
  setIsLoading(true);
@@ -688,7 +835,7 @@ function useServices(options = {}) {
688
835
  setIsLoading(false);
689
836
  }
690
837
  };
691
- useEffect6(() => {
838
+ useEffect7(() => {
692
839
  fetchServices();
693
840
  }, [options.category, options.active]);
694
841
  return {
@@ -700,11 +847,11 @@ function useServices(options = {}) {
700
847
  }
701
848
 
702
849
  // src/hooks/useLeadCapture.ts
703
- import { useState as useState7 } from "react";
850
+ import { useState as useState9 } from "react";
704
851
  function useLeadCapture() {
705
852
  const { client } = useFoxPixelContext();
706
- const [isLoading, setIsLoading] = useState7(false);
707
- const [error, setError] = useState7(null);
853
+ const [isLoading, setIsLoading] = useState9(false);
854
+ const [error, setError] = useState9(null);
708
855
  const captureLead = async (data) => {
709
856
  try {
710
857
  setIsLoading(true);
@@ -727,11 +874,11 @@ function useLeadCapture() {
727
874
  }
728
875
 
729
876
  // src/hooks/useContactCapture.ts
730
- import { useState as useState8 } from "react";
877
+ import { useState as useState10 } from "react";
731
878
  function useContactCapture() {
732
879
  const { client } = useFoxPixelContext();
733
- const [isLoading, setIsLoading] = useState8(false);
734
- const [error, setError] = useState8(null);
880
+ const [isLoading, setIsLoading] = useState10(false);
881
+ const [error, setError] = useState10(null);
735
882
  const captureContact = async (data) => {
736
883
  try {
737
884
  setIsLoading(true);
@@ -754,14 +901,14 @@ function useContactCapture() {
754
901
  }
755
902
 
756
903
  // src/hooks/useSiteContent.ts
757
- import { useState as useState9, useEffect as useEffect7, useCallback as useCallback4 } from "react";
904
+ import { useState as useState11, useEffect as useEffect8, useCallback as useCallback4 } from "react";
758
905
  function useSiteContent(contentKey, options = {}) {
759
906
  const { defaultValue = "", fetchOnMount = true } = options;
760
907
  const { client } = useFoxPixelContext();
761
908
  const { user, hasPermission } = useAuth();
762
- const [data, setData] = useState9(null);
763
- const [isLoading, setIsLoading] = useState9(fetchOnMount);
764
- const [error, setError] = useState9(null);
909
+ const [data, setData] = useState11(null);
910
+ const [isLoading, setIsLoading] = useState11(fetchOnMount);
911
+ const [error, setError] = useState11(null);
765
912
  const canEdit = user !== null && hasPermission("site:content:update");
766
913
  const fetchContent = useCallback4(async () => {
767
914
  try {
@@ -794,7 +941,7 @@ function useSiteContent(contentKey, options = {}) {
794
941
  throw err;
795
942
  }
796
943
  }, [client, contentKey]);
797
- useEffect7(() => {
944
+ useEffect8(() => {
798
945
  if (fetchOnMount) {
799
946
  fetchContent();
800
947
  }
@@ -813,9 +960,9 @@ function useSiteContent(contentKey, options = {}) {
813
960
  function useSiteContents(contentKeys, options = {}) {
814
961
  const { defaults = {} } = options;
815
962
  const { client } = useFoxPixelContext();
816
- const [data, setData] = useState9({});
817
- const [isLoading, setIsLoading] = useState9(true);
818
- const [error, setError] = useState9(null);
963
+ const [data, setData] = useState11({});
964
+ const [isLoading, setIsLoading] = useState11(true);
965
+ const [error, setError] = useState11(null);
819
966
  const fetchContents = useCallback4(async () => {
820
967
  if (contentKeys.length === 0) {
821
968
  setData({});
@@ -836,7 +983,7 @@ function useSiteContents(contentKeys, options = {}) {
836
983
  setIsLoading(false);
837
984
  }
838
985
  }, [client, contentKeys.join(",")]);
839
- useEffect7(() => {
986
+ useEffect8(() => {
840
987
  fetchContents();
841
988
  }, [fetchContents]);
842
989
  const getValue = useCallback4((key, defaultValue) => {
@@ -856,9 +1003,9 @@ function useSiteContents(contentKeys, options = {}) {
856
1003
  }
857
1004
  function useSiteContentSection(section) {
858
1005
  const { client } = useFoxPixelContext();
859
- const [contents, setContents] = useState9([]);
860
- const [isLoading, setIsLoading] = useState9(true);
861
- const [error, setError] = useState9(null);
1006
+ const [contents, setContents] = useState11([]);
1007
+ const [isLoading, setIsLoading] = useState11(true);
1008
+ const [error, setError] = useState11(null);
862
1009
  const fetchContents = useCallback4(async () => {
863
1010
  try {
864
1011
  setIsLoading(true);
@@ -873,7 +1020,7 @@ function useSiteContentSection(section) {
873
1020
  setIsLoading(false);
874
1021
  }
875
1022
  }, [client, section]);
876
- useEffect7(() => {
1023
+ useEffect8(() => {
877
1024
  fetchContents();
878
1025
  }, [fetchContents]);
879
1026
  return {
@@ -884,13 +1031,43 @@ function useSiteContentSection(section) {
884
1031
  };
885
1032
  }
886
1033
 
1034
+ // src/prefetchSiteContent.ts
1035
+ async function prefetchSiteContent(queryClient, options) {
1036
+ const { apiUrl, apiKey, tenantId, contentKeys } = options;
1037
+ const client = new FoxPixelHttpClient({ apiUrl, apiKey, tenantId });
1038
+ await Promise.all(
1039
+ contentKeys.map(async (contentKey) => {
1040
+ try {
1041
+ const content = await client.get(
1042
+ `/api/site/content/${encodeURIComponent(contentKey)}`
1043
+ );
1044
+ queryClient.setQueryData(
1045
+ [SITE_CONTENT_QUERY_KEY, contentKey],
1046
+ {
1047
+ value: content?.value ?? "",
1048
+ contentType: content?.contentType ?? "TEXT"
1049
+ }
1050
+ );
1051
+ } catch (err) {
1052
+ const status = err?.response?.status;
1053
+ if (status === 404) {
1054
+ queryClient.setQueryData([SITE_CONTENT_QUERY_KEY, contentKey], {
1055
+ value: "",
1056
+ contentType: "TEXT"
1057
+ });
1058
+ }
1059
+ }
1060
+ })
1061
+ );
1062
+ }
1063
+
887
1064
  // src/blog/hooks.ts
888
- import { useState as useState10, useEffect as useEffect8 } from "react";
1065
+ import { useState as useState12, useEffect as useEffect9 } from "react";
889
1066
  function useBlogPosts(options = {}) {
890
1067
  const { client } = useFoxPixelContext();
891
- const [data, setData] = useState10(null);
892
- const [isLoading, setIsLoading] = useState10(true);
893
- const [error, setError] = useState10(null);
1068
+ const [data, setData] = useState12(null);
1069
+ const [isLoading, setIsLoading] = useState12(true);
1070
+ const [error, setError] = useState12(null);
894
1071
  const page = options.page ?? 0;
895
1072
  const limit = options.limit ?? 10;
896
1073
  const fetchPosts = async () => {
@@ -910,7 +1087,7 @@ function useBlogPosts(options = {}) {
910
1087
  setIsLoading(false);
911
1088
  }
912
1089
  };
913
- useEffect8(() => {
1090
+ useEffect9(() => {
914
1091
  fetchPosts();
915
1092
  }, [page, limit]);
916
1093
  return {
@@ -922,9 +1099,9 @@ function useBlogPosts(options = {}) {
922
1099
  }
923
1100
  function useBlogPost(slug) {
924
1101
  const { client } = useFoxPixelContext();
925
- const [data, setData] = useState10(null);
926
- const [isLoading, setIsLoading] = useState10(!!slug);
927
- const [error, setError] = useState10(null);
1102
+ const [data, setData] = useState12(null);
1103
+ const [isLoading, setIsLoading] = useState12(!!slug);
1104
+ const [error, setError] = useState12(null);
928
1105
  const fetchPost = async () => {
929
1106
  if (!slug) {
930
1107
  setData(null);
@@ -943,7 +1120,7 @@ function useBlogPost(slug) {
943
1120
  setIsLoading(false);
944
1121
  }
945
1122
  };
946
- useEffect8(() => {
1123
+ useEffect9(() => {
947
1124
  fetchPost();
948
1125
  }, [slug]);
949
1126
  return {
@@ -955,9 +1132,9 @@ function useBlogPost(slug) {
955
1132
  }
956
1133
  function useBlogCategories() {
957
1134
  const { client } = useFoxPixelContext();
958
- const [data, setData] = useState10(null);
959
- const [isLoading, setIsLoading] = useState10(true);
960
- const [error, setError] = useState10(null);
1135
+ const [data, setData] = useState12(null);
1136
+ const [isLoading, setIsLoading] = useState12(true);
1137
+ const [error, setError] = useState12(null);
961
1138
  const fetchCategories = async () => {
962
1139
  try {
963
1140
  setIsLoading(true);
@@ -971,7 +1148,7 @@ function useBlogCategories() {
971
1148
  setIsLoading(false);
972
1149
  }
973
1150
  };
974
- useEffect8(() => {
1151
+ useEffect9(() => {
975
1152
  fetchCategories();
976
1153
  }, []);
977
1154
  return {
@@ -983,9 +1160,9 @@ function useBlogCategories() {
983
1160
  }
984
1161
  function useBlogTags() {
985
1162
  const { client } = useFoxPixelContext();
986
- const [data, setData] = useState10(null);
987
- const [isLoading, setIsLoading] = useState10(true);
988
- const [error, setError] = useState10(null);
1163
+ const [data, setData] = useState12(null);
1164
+ const [isLoading, setIsLoading] = useState12(true);
1165
+ const [error, setError] = useState12(null);
989
1166
  const fetchTags = async () => {
990
1167
  try {
991
1168
  setIsLoading(true);
@@ -999,7 +1176,7 @@ function useBlogTags() {
999
1176
  setIsLoading(false);
1000
1177
  }
1001
1178
  };
1002
- useEffect8(() => {
1179
+ useEffect9(() => {
1003
1180
  fetchTags();
1004
1181
  }, []);
1005
1182
  return {
@@ -1011,9 +1188,9 @@ function useBlogTags() {
1011
1188
  }
1012
1189
  function useBlogComments(slug) {
1013
1190
  const { client } = useFoxPixelContext();
1014
- const [data, setData] = useState10(null);
1015
- const [isLoading, setIsLoading] = useState10(!!slug);
1016
- const [error, setError] = useState10(null);
1191
+ const [data, setData] = useState12(null);
1192
+ const [isLoading, setIsLoading] = useState12(!!slug);
1193
+ const [error, setError] = useState12(null);
1017
1194
  const fetchComments = async () => {
1018
1195
  if (!slug) {
1019
1196
  setData(null);
@@ -1034,7 +1211,7 @@ function useBlogComments(slug) {
1034
1211
  setIsLoading(false);
1035
1212
  }
1036
1213
  };
1037
- useEffect8(() => {
1214
+ useEffect9(() => {
1038
1215
  fetchComments();
1039
1216
  }, [slug]);
1040
1217
  return {
@@ -1046,8 +1223,8 @@ function useBlogComments(slug) {
1046
1223
  }
1047
1224
  function useBlogCommentSubmit(slug) {
1048
1225
  const { client } = useFoxPixelContext();
1049
- const [isSubmitting, setIsSubmitting] = useState10(false);
1050
- const [error, setError] = useState10(null);
1226
+ const [isSubmitting, setIsSubmitting] = useState12(false);
1227
+ const [error, setError] = useState12(null);
1051
1228
  const submit = async (payload) => {
1052
1229
  if (!slug) return null;
1053
1230
  try {
@@ -1075,9 +1252,9 @@ function useBlogCommentSubmit(slug) {
1075
1252
  }
1076
1253
  function useBlogFeaturedPosts(limit = 6) {
1077
1254
  const { client } = useFoxPixelContext();
1078
- const [data, setData] = useState10(null);
1079
- const [isLoading, setIsLoading] = useState10(true);
1080
- const [error, setError] = useState10(null);
1255
+ const [data, setData] = useState12(null);
1256
+ const [isLoading, setIsLoading] = useState12(true);
1257
+ const [error, setError] = useState12(null);
1081
1258
  const fetchFeatured = async () => {
1082
1259
  try {
1083
1260
  setIsLoading(true);
@@ -1095,7 +1272,7 @@ function useBlogFeaturedPosts(limit = 6) {
1095
1272
  setIsLoading(false);
1096
1273
  }
1097
1274
  };
1098
- useEffect8(() => {
1275
+ useEffect9(() => {
1099
1276
  fetchFeatured();
1100
1277
  }, [limit]);
1101
1278
  return {
@@ -1107,9 +1284,9 @@ function useBlogFeaturedPosts(limit = 6) {
1107
1284
  }
1108
1285
  function useNewsletterSubscribe() {
1109
1286
  const { client } = useFoxPixelContext();
1110
- const [isSubmitting, setIsSubmitting] = useState10(false);
1111
- const [error, setError] = useState10(null);
1112
- const [success, setSuccess] = useState10(false);
1287
+ const [isSubmitting, setIsSubmitting] = useState12(false);
1288
+ const [error, setError] = useState12(null);
1289
+ const [success, setSuccess] = useState12(false);
1113
1290
  const subscribe = async (payload) => {
1114
1291
  try {
1115
1292
  setIsSubmitting(true);
@@ -1142,9 +1319,9 @@ function useNewsletterSubscribe() {
1142
1319
  }
1143
1320
  function useNewsletterUnsubscribe() {
1144
1321
  const { client } = useFoxPixelContext();
1145
- const [isSubmitting, setIsSubmitting] = useState10(false);
1146
- const [error, setError] = useState10(null);
1147
- const [success, setSuccess] = useState10(false);
1322
+ const [isSubmitting, setIsSubmitting] = useState12(false);
1323
+ const [error, setError] = useState12(null);
1324
+ const [success, setSuccess] = useState12(false);
1148
1325
  const unsubscribe = async (email) => {
1149
1326
  try {
1150
1327
  setIsSubmitting(true);
@@ -1189,12 +1366,12 @@ function useNewsletterUnsubscribe() {
1189
1366
  }
1190
1367
 
1191
1368
  // src/blog/admin-hooks.ts
1192
- import { useState as useState11, useEffect as useEffect9, useCallback as useCallback5 } from "react";
1369
+ import { useState as useState13, useEffect as useEffect10, useCallback as useCallback5 } from "react";
1193
1370
  function useAdminBlogPosts(options = {}) {
1194
1371
  const { client } = useFoxPixelContext();
1195
- const [data, setData] = useState11(null);
1196
- const [isLoading, setIsLoading] = useState11(true);
1197
- const [error, setError] = useState11(null);
1372
+ const [data, setData] = useState13(null);
1373
+ const [isLoading, setIsLoading] = useState13(true);
1374
+ const [error, setError] = useState13(null);
1198
1375
  const page = options.page ?? 0;
1199
1376
  const size = options.size ?? 20;
1200
1377
  const fetchPosts = useCallback5(async () => {
@@ -1212,16 +1389,16 @@ function useAdminBlogPosts(options = {}) {
1212
1389
  setIsLoading(false);
1213
1390
  }
1214
1391
  }, [client, page, size]);
1215
- useEffect9(() => {
1392
+ useEffect10(() => {
1216
1393
  fetchPosts();
1217
1394
  }, [fetchPosts]);
1218
1395
  return { data, isLoading, error, refetch: fetchPosts };
1219
1396
  }
1220
1397
  function useAdminBlogPost(id) {
1221
1398
  const { client } = useFoxPixelContext();
1222
- const [data, setData] = useState11(null);
1223
- const [isLoading, setIsLoading] = useState11(!!id);
1224
- const [error, setError] = useState11(null);
1399
+ const [data, setData] = useState13(null);
1400
+ const [isLoading, setIsLoading] = useState13(!!id);
1401
+ const [error, setError] = useState13(null);
1225
1402
  const fetchPost = useCallback5(async () => {
1226
1403
  if (!id) {
1227
1404
  setData(null);
@@ -1239,15 +1416,15 @@ function useAdminBlogPost(id) {
1239
1416
  setIsLoading(false);
1240
1417
  }
1241
1418
  }, [client, id]);
1242
- useEffect9(() => {
1419
+ useEffect10(() => {
1243
1420
  fetchPost();
1244
1421
  }, [fetchPost]);
1245
1422
  return { data, isLoading, error, refetch: fetchPost };
1246
1423
  }
1247
1424
  function useAdminBlogPostMutations() {
1248
1425
  const { client } = useFoxPixelContext();
1249
- const [isLoading, setIsLoading] = useState11(false);
1250
- const [error, setError] = useState11(null);
1426
+ const [isLoading, setIsLoading] = useState13(false);
1427
+ const [error, setError] = useState13(null);
1251
1428
  const create = async (payload) => {
1252
1429
  try {
1253
1430
  setIsLoading(true);
@@ -1291,9 +1468,9 @@ function useAdminBlogPostMutations() {
1291
1468
  }
1292
1469
  function useAdminBlogCategories() {
1293
1470
  const { client } = useFoxPixelContext();
1294
- const [data, setData] = useState11(null);
1295
- const [isLoading, setIsLoading] = useState11(true);
1296
- const [error, setError] = useState11(null);
1471
+ const [data, setData] = useState13(null);
1472
+ const [isLoading, setIsLoading] = useState13(true);
1473
+ const [error, setError] = useState13(null);
1297
1474
  const fetchCategories = useCallback5(async () => {
1298
1475
  try {
1299
1476
  setIsLoading(true);
@@ -1306,7 +1483,7 @@ function useAdminBlogCategories() {
1306
1483
  setIsLoading(false);
1307
1484
  }
1308
1485
  }, [client]);
1309
- useEffect9(() => {
1486
+ useEffect10(() => {
1310
1487
  fetchCategories();
1311
1488
  }, [fetchCategories]);
1312
1489
  const create = async (payload) => {
@@ -1343,9 +1520,9 @@ function useAdminBlogCategories() {
1343
1520
  }
1344
1521
  function useAdminBlogTags() {
1345
1522
  const { client } = useFoxPixelContext();
1346
- const [data, setData] = useState11(null);
1347
- const [isLoading, setIsLoading] = useState11(true);
1348
- const [error, setError] = useState11(null);
1523
+ const [data, setData] = useState13(null);
1524
+ const [isLoading, setIsLoading] = useState13(true);
1525
+ const [error, setError] = useState13(null);
1349
1526
  const fetchTags = useCallback5(async () => {
1350
1527
  try {
1351
1528
  setIsLoading(true);
@@ -1358,7 +1535,7 @@ function useAdminBlogTags() {
1358
1535
  setIsLoading(false);
1359
1536
  }
1360
1537
  }, [client]);
1361
- useEffect9(() => {
1538
+ useEffect10(() => {
1362
1539
  fetchTags();
1363
1540
  }, [fetchTags]);
1364
1541
  const create = async (payload) => {
@@ -1395,9 +1572,9 @@ function useAdminBlogTags() {
1395
1572
  }
1396
1573
  function useAdminBlogComments(options = {}) {
1397
1574
  const { client } = useFoxPixelContext();
1398
- const [data, setData] = useState11(null);
1399
- const [isLoading, setIsLoading] = useState11(true);
1400
- const [error, setError] = useState11(null);
1575
+ const [data, setData] = useState13(null);
1576
+ const [isLoading, setIsLoading] = useState13(true);
1577
+ const [error, setError] = useState13(null);
1401
1578
  const { status, postId, page = 0, size = 20 } = options;
1402
1579
  const fetchComments = useCallback5(async () => {
1403
1580
  try {
@@ -1416,7 +1593,7 @@ function useAdminBlogComments(options = {}) {
1416
1593
  setIsLoading(false);
1417
1594
  }
1418
1595
  }, [client, status, postId, page, size]);
1419
- useEffect9(() => {
1596
+ useEffect10(() => {
1420
1597
  fetchComments();
1421
1598
  }, [fetchComments]);
1422
1599
  const updateStatus = async (id, newStatus) => {
@@ -1443,9 +1620,9 @@ function useAdminBlogComments(options = {}) {
1443
1620
  }
1444
1621
  function useAdminNewsletterSubscribers(options = {}) {
1445
1622
  const { client } = useFoxPixelContext();
1446
- const [data, setData] = useState11(null);
1447
- const [isLoading, setIsLoading] = useState11(true);
1448
- const [error, setError] = useState11(null);
1623
+ const [data, setData] = useState13(null);
1624
+ const [isLoading, setIsLoading] = useState13(true);
1625
+ const [error, setError] = useState13(null);
1449
1626
  const { status, page = 0, size = 20 } = options;
1450
1627
  const fetchSubscribers = useCallback5(async () => {
1451
1628
  try {
@@ -1463,7 +1640,7 @@ function useAdminNewsletterSubscribers(options = {}) {
1463
1640
  setIsLoading(false);
1464
1641
  }
1465
1642
  }, [client, status, page, size]);
1466
- useEffect9(() => {
1643
+ useEffect10(() => {
1467
1644
  fetchSubscribers();
1468
1645
  }, [fetchSubscribers]);
1469
1646
  const remove = async (id) => {
@@ -1480,9 +1657,9 @@ function useAdminNewsletterSubscribers(options = {}) {
1480
1657
  }
1481
1658
  function useAdminNewsletterStats() {
1482
1659
  const { client } = useFoxPixelContext();
1483
- const [data, setData] = useState11(null);
1484
- const [isLoading, setIsLoading] = useState11(true);
1485
- const [error, setError] = useState11(null);
1660
+ const [data, setData] = useState13(null);
1661
+ const [isLoading, setIsLoading] = useState13(true);
1662
+ const [error, setError] = useState13(null);
1486
1663
  const fetchStats = useCallback5(async () => {
1487
1664
  try {
1488
1665
  setIsLoading(true);
@@ -1495,16 +1672,16 @@ function useAdminNewsletterStats() {
1495
1672
  setIsLoading(false);
1496
1673
  }
1497
1674
  }, [client]);
1498
- useEffect9(() => {
1675
+ useEffect10(() => {
1499
1676
  fetchStats();
1500
1677
  }, [fetchStats]);
1501
1678
  return { data, isLoading, error, refetch: fetchStats };
1502
1679
  }
1503
1680
  function useAdminBlogSettings() {
1504
1681
  const { client } = useFoxPixelContext();
1505
- const [data, setData] = useState11(null);
1506
- const [isLoading, setIsLoading] = useState11(true);
1507
- const [error, setError] = useState11(null);
1682
+ const [data, setData] = useState13(null);
1683
+ const [isLoading, setIsLoading] = useState13(true);
1684
+ const [error, setError] = useState13(null);
1508
1685
  const fetchSettings = useCallback5(async () => {
1509
1686
  try {
1510
1687
  setIsLoading(true);
@@ -1517,7 +1694,7 @@ function useAdminBlogSettings() {
1517
1694
  setIsLoading(false);
1518
1695
  }
1519
1696
  }, [client]);
1520
- useEffect9(() => {
1697
+ useEffect10(() => {
1521
1698
  fetchSettings();
1522
1699
  }, [fetchSettings]);
1523
1700
  const update = async (settings) => {
@@ -1534,9 +1711,9 @@ function useAdminBlogSettings() {
1534
1711
  }
1535
1712
  function useAdminBlogAnalytics() {
1536
1713
  const { client } = useFoxPixelContext();
1537
- const [data, setData] = useState11(null);
1538
- const [isLoading, setIsLoading] = useState11(true);
1539
- const [error, setError] = useState11(null);
1714
+ const [data, setData] = useState13(null);
1715
+ const [isLoading, setIsLoading] = useState13(true);
1716
+ const [error, setError] = useState13(null);
1540
1717
  const fetchAnalytics = useCallback5(async () => {
1541
1718
  try {
1542
1719
  setIsLoading(true);
@@ -1549,7 +1726,7 @@ function useAdminBlogAnalytics() {
1549
1726
  setIsLoading(false);
1550
1727
  }
1551
1728
  }, [client]);
1552
- useEffect9(() => {
1729
+ useEffect10(() => {
1553
1730
  fetchAnalytics();
1554
1731
  }, [fetchAnalytics]);
1555
1732
  return { data, isLoading, error, refetch: fetchAnalytics };
@@ -1597,6 +1774,7 @@ export {
1597
1774
  ProtectedRoute,
1598
1775
  SITE_CONTENT_QUERY_KEY,
1599
1776
  getBlogPostSchemaLd,
1777
+ prefetchSiteContent,
1600
1778
  useAdminBlogAnalytics,
1601
1779
  useAdminBlogCategories,
1602
1780
  useAdminBlogComments,