@foxpixel/react 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +741 -2
- package/dist/index.d.ts +741 -2
- package/dist/index.js +1228 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1197 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -3
package/dist/index.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
|
|
668
|
+
import { useState as useState6, useEffect as useEffect6 } from "react";
|
|
349
669
|
function useServices(options = {}) {
|
|
350
670
|
const { client } = useFoxPixelContext();
|
|
351
|
-
const [services, setServices] =
|
|
352
|
-
const [isLoading, setIsLoading] =
|
|
353
|
-
const [error, setError] =
|
|
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
|
-
|
|
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
|
|
703
|
+
import { useState as useState7 } from "react";
|
|
384
704
|
function useLeadCapture() {
|
|
385
705
|
const { client } = useFoxPixelContext();
|
|
386
|
-
const [isLoading, setIsLoading] =
|
|
387
|
-
const [error, setError] =
|
|
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
|
|
730
|
+
import { useState as useState8 } from "react";
|
|
411
731
|
function useContactCapture() {
|
|
412
732
|
const { client } = useFoxPixelContext();
|
|
413
|
-
const [isLoading, setIsLoading] =
|
|
414
|
-
const [error, setError] =
|
|
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);
|
|
@@ -432,17 +752,882 @@ function useContactCapture() {
|
|
|
432
752
|
error
|
|
433
753
|
};
|
|
434
754
|
}
|
|
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
|
+
|
|
887
|
+
// src/blog/hooks.ts
|
|
888
|
+
import { useState as useState10, useEffect as useEffect8 } from "react";
|
|
889
|
+
function useBlogPosts(options = {}) {
|
|
890
|
+
const { client } = useFoxPixelContext();
|
|
891
|
+
const [data, setData] = useState10(null);
|
|
892
|
+
const [isLoading, setIsLoading] = useState10(true);
|
|
893
|
+
const [error, setError] = useState10(null);
|
|
894
|
+
const page = options.page ?? 0;
|
|
895
|
+
const limit = options.limit ?? 10;
|
|
896
|
+
const fetchPosts = async () => {
|
|
897
|
+
try {
|
|
898
|
+
setIsLoading(true);
|
|
899
|
+
setError(null);
|
|
900
|
+
const params = new URLSearchParams();
|
|
901
|
+
params.append("page", String(page));
|
|
902
|
+
params.append("size", String(limit));
|
|
903
|
+
const url = `/api/v1/blog/posts?${params.toString()}`;
|
|
904
|
+
const result = await client.get(url);
|
|
905
|
+
setData(result);
|
|
906
|
+
} catch (err) {
|
|
907
|
+
setError(err);
|
|
908
|
+
setData(null);
|
|
909
|
+
} finally {
|
|
910
|
+
setIsLoading(false);
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
useEffect8(() => {
|
|
914
|
+
fetchPosts();
|
|
915
|
+
}, [page, limit]);
|
|
916
|
+
return {
|
|
917
|
+
data,
|
|
918
|
+
isLoading,
|
|
919
|
+
error,
|
|
920
|
+
refetch: fetchPosts
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
function useBlogPost(slug) {
|
|
924
|
+
const { client } = useFoxPixelContext();
|
|
925
|
+
const [data, setData] = useState10(null);
|
|
926
|
+
const [isLoading, setIsLoading] = useState10(!!slug);
|
|
927
|
+
const [error, setError] = useState10(null);
|
|
928
|
+
const fetchPost = async () => {
|
|
929
|
+
if (!slug) {
|
|
930
|
+
setData(null);
|
|
931
|
+
setIsLoading(false);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
try {
|
|
935
|
+
setIsLoading(true);
|
|
936
|
+
setError(null);
|
|
937
|
+
const result = await client.get(`/api/v1/blog/posts/${encodeURIComponent(slug)}`);
|
|
938
|
+
setData(result);
|
|
939
|
+
} catch (err) {
|
|
940
|
+
setError(err);
|
|
941
|
+
setData(null);
|
|
942
|
+
} finally {
|
|
943
|
+
setIsLoading(false);
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
useEffect8(() => {
|
|
947
|
+
fetchPost();
|
|
948
|
+
}, [slug]);
|
|
949
|
+
return {
|
|
950
|
+
data,
|
|
951
|
+
isLoading,
|
|
952
|
+
error,
|
|
953
|
+
refetch: fetchPost
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
function useBlogCategories() {
|
|
957
|
+
const { client } = useFoxPixelContext();
|
|
958
|
+
const [data, setData] = useState10(null);
|
|
959
|
+
const [isLoading, setIsLoading] = useState10(true);
|
|
960
|
+
const [error, setError] = useState10(null);
|
|
961
|
+
const fetchCategories = async () => {
|
|
962
|
+
try {
|
|
963
|
+
setIsLoading(true);
|
|
964
|
+
setError(null);
|
|
965
|
+
const result = await client.get("/api/v1/blog/categories");
|
|
966
|
+
setData(Array.isArray(result) ? result : []);
|
|
967
|
+
} catch (err) {
|
|
968
|
+
setError(err);
|
|
969
|
+
setData(null);
|
|
970
|
+
} finally {
|
|
971
|
+
setIsLoading(false);
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
useEffect8(() => {
|
|
975
|
+
fetchCategories();
|
|
976
|
+
}, []);
|
|
977
|
+
return {
|
|
978
|
+
data,
|
|
979
|
+
isLoading,
|
|
980
|
+
error,
|
|
981
|
+
refetch: fetchCategories
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
function useBlogTags() {
|
|
985
|
+
const { client } = useFoxPixelContext();
|
|
986
|
+
const [data, setData] = useState10(null);
|
|
987
|
+
const [isLoading, setIsLoading] = useState10(true);
|
|
988
|
+
const [error, setError] = useState10(null);
|
|
989
|
+
const fetchTags = async () => {
|
|
990
|
+
try {
|
|
991
|
+
setIsLoading(true);
|
|
992
|
+
setError(null);
|
|
993
|
+
const result = await client.get("/api/v1/blog/tags");
|
|
994
|
+
setData(Array.isArray(result) ? result : []);
|
|
995
|
+
} catch (err) {
|
|
996
|
+
setError(err);
|
|
997
|
+
setData(null);
|
|
998
|
+
} finally {
|
|
999
|
+
setIsLoading(false);
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
useEffect8(() => {
|
|
1003
|
+
fetchTags();
|
|
1004
|
+
}, []);
|
|
1005
|
+
return {
|
|
1006
|
+
data,
|
|
1007
|
+
isLoading,
|
|
1008
|
+
error,
|
|
1009
|
+
refetch: fetchTags
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
function useBlogComments(slug) {
|
|
1013
|
+
const { client } = useFoxPixelContext();
|
|
1014
|
+
const [data, setData] = useState10(null);
|
|
1015
|
+
const [isLoading, setIsLoading] = useState10(!!slug);
|
|
1016
|
+
const [error, setError] = useState10(null);
|
|
1017
|
+
const fetchComments = async () => {
|
|
1018
|
+
if (!slug) {
|
|
1019
|
+
setData(null);
|
|
1020
|
+
setIsLoading(false);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
try {
|
|
1024
|
+
setIsLoading(true);
|
|
1025
|
+
setError(null);
|
|
1026
|
+
const result = await client.get(
|
|
1027
|
+
`/api/v1/blog/posts/${encodeURIComponent(slug)}/comments`
|
|
1028
|
+
);
|
|
1029
|
+
setData(Array.isArray(result) ? result : []);
|
|
1030
|
+
} catch (err) {
|
|
1031
|
+
setError(err);
|
|
1032
|
+
setData(null);
|
|
1033
|
+
} finally {
|
|
1034
|
+
setIsLoading(false);
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
useEffect8(() => {
|
|
1038
|
+
fetchComments();
|
|
1039
|
+
}, [slug]);
|
|
1040
|
+
return {
|
|
1041
|
+
data,
|
|
1042
|
+
isLoading,
|
|
1043
|
+
error,
|
|
1044
|
+
refetch: fetchComments
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function useBlogCommentSubmit(slug) {
|
|
1048
|
+
const { client } = useFoxPixelContext();
|
|
1049
|
+
const [isSubmitting, setIsSubmitting] = useState10(false);
|
|
1050
|
+
const [error, setError] = useState10(null);
|
|
1051
|
+
const submit = async (payload) => {
|
|
1052
|
+
if (!slug) return null;
|
|
1053
|
+
try {
|
|
1054
|
+
setIsSubmitting(true);
|
|
1055
|
+
setError(null);
|
|
1056
|
+
const result = await client.post(
|
|
1057
|
+
`/api/v1/blog/posts/${encodeURIComponent(slug)}/comments`,
|
|
1058
|
+
payload
|
|
1059
|
+
);
|
|
1060
|
+
return result;
|
|
1061
|
+
} catch (err) {
|
|
1062
|
+
setError(err);
|
|
1063
|
+
return null;
|
|
1064
|
+
} finally {
|
|
1065
|
+
setIsSubmitting(false);
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
const resetError = () => setError(null);
|
|
1069
|
+
return {
|
|
1070
|
+
submit,
|
|
1071
|
+
isSubmitting,
|
|
1072
|
+
error,
|
|
1073
|
+
resetError
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function useBlogFeaturedPosts(limit = 6) {
|
|
1077
|
+
const { client } = useFoxPixelContext();
|
|
1078
|
+
const [data, setData] = useState10(null);
|
|
1079
|
+
const [isLoading, setIsLoading] = useState10(true);
|
|
1080
|
+
const [error, setError] = useState10(null);
|
|
1081
|
+
const fetchFeatured = async () => {
|
|
1082
|
+
try {
|
|
1083
|
+
setIsLoading(true);
|
|
1084
|
+
setError(null);
|
|
1085
|
+
const params = new URLSearchParams();
|
|
1086
|
+
params.append("page", "0");
|
|
1087
|
+
params.append("size", String(limit));
|
|
1088
|
+
const url = `/api/v1/blog/posts/featured?${params.toString()}`;
|
|
1089
|
+
const result = await client.get(url);
|
|
1090
|
+
setData(result);
|
|
1091
|
+
} catch (err) {
|
|
1092
|
+
setError(err);
|
|
1093
|
+
setData(null);
|
|
1094
|
+
} finally {
|
|
1095
|
+
setIsLoading(false);
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
useEffect8(() => {
|
|
1099
|
+
fetchFeatured();
|
|
1100
|
+
}, [limit]);
|
|
1101
|
+
return {
|
|
1102
|
+
data,
|
|
1103
|
+
isLoading,
|
|
1104
|
+
error,
|
|
1105
|
+
refetch: fetchFeatured
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
function useNewsletterSubscribe() {
|
|
1109
|
+
const { client } = useFoxPixelContext();
|
|
1110
|
+
const [isSubmitting, setIsSubmitting] = useState10(false);
|
|
1111
|
+
const [error, setError] = useState10(null);
|
|
1112
|
+
const [success, setSuccess] = useState10(false);
|
|
1113
|
+
const subscribe = async (payload) => {
|
|
1114
|
+
try {
|
|
1115
|
+
setIsSubmitting(true);
|
|
1116
|
+
setError(null);
|
|
1117
|
+
setSuccess(false);
|
|
1118
|
+
const result = await client.post(
|
|
1119
|
+
"/api/v1/blog/newsletter/subscribe",
|
|
1120
|
+
payload
|
|
1121
|
+
);
|
|
1122
|
+
setSuccess(true);
|
|
1123
|
+
return result;
|
|
1124
|
+
} catch (err) {
|
|
1125
|
+
setError(err);
|
|
1126
|
+
return null;
|
|
1127
|
+
} finally {
|
|
1128
|
+
setIsSubmitting(false);
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
const reset = () => {
|
|
1132
|
+
setError(null);
|
|
1133
|
+
setSuccess(false);
|
|
1134
|
+
};
|
|
1135
|
+
return {
|
|
1136
|
+
subscribe,
|
|
1137
|
+
isSubmitting,
|
|
1138
|
+
error,
|
|
1139
|
+
success,
|
|
1140
|
+
reset
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
function useNewsletterUnsubscribe() {
|
|
1144
|
+
const { client } = useFoxPixelContext();
|
|
1145
|
+
const [isSubmitting, setIsSubmitting] = useState10(false);
|
|
1146
|
+
const [error, setError] = useState10(null);
|
|
1147
|
+
const [success, setSuccess] = useState10(false);
|
|
1148
|
+
const unsubscribe = async (email) => {
|
|
1149
|
+
try {
|
|
1150
|
+
setIsSubmitting(true);
|
|
1151
|
+
setError(null);
|
|
1152
|
+
setSuccess(false);
|
|
1153
|
+
await client.post("/api/v1/blog/newsletter/unsubscribe", null, {
|
|
1154
|
+
params: { email }
|
|
1155
|
+
});
|
|
1156
|
+
setSuccess(true);
|
|
1157
|
+
return true;
|
|
1158
|
+
} catch (err) {
|
|
1159
|
+
setError(err);
|
|
1160
|
+
return false;
|
|
1161
|
+
} finally {
|
|
1162
|
+
setIsSubmitting(false);
|
|
1163
|
+
}
|
|
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
|
+
};
|
|
1182
|
+
return {
|
|
1183
|
+
unsubscribe,
|
|
1184
|
+
unsubscribeByToken,
|
|
1185
|
+
isSubmitting,
|
|
1186
|
+
error,
|
|
1187
|
+
success
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// src/blog/admin-hooks.ts
|
|
1192
|
+
import { useState as useState11, useEffect as useEffect9, useCallback as useCallback5 } from "react";
|
|
1193
|
+
function useAdminBlogPosts(options = {}) {
|
|
1194
|
+
const { client } = useFoxPixelContext();
|
|
1195
|
+
const [data, setData] = useState11(null);
|
|
1196
|
+
const [isLoading, setIsLoading] = useState11(true);
|
|
1197
|
+
const [error, setError] = useState11(null);
|
|
1198
|
+
const page = options.page ?? 0;
|
|
1199
|
+
const size = options.size ?? 20;
|
|
1200
|
+
const fetchPosts = useCallback5(async () => {
|
|
1201
|
+
try {
|
|
1202
|
+
setIsLoading(true);
|
|
1203
|
+
setError(null);
|
|
1204
|
+
const params = new URLSearchParams();
|
|
1205
|
+
params.append("page", String(page));
|
|
1206
|
+
params.append("size", String(size));
|
|
1207
|
+
const result = await client.get(`/api/blog/posts?${params.toString()}`);
|
|
1208
|
+
setData(result);
|
|
1209
|
+
} catch (err) {
|
|
1210
|
+
setError(err);
|
|
1211
|
+
} finally {
|
|
1212
|
+
setIsLoading(false);
|
|
1213
|
+
}
|
|
1214
|
+
}, [client, page, size]);
|
|
1215
|
+
useEffect9(() => {
|
|
1216
|
+
fetchPosts();
|
|
1217
|
+
}, [fetchPosts]);
|
|
1218
|
+
return { data, isLoading, error, refetch: fetchPosts };
|
|
1219
|
+
}
|
|
1220
|
+
function useAdminBlogPost(id) {
|
|
1221
|
+
const { client } = useFoxPixelContext();
|
|
1222
|
+
const [data, setData] = useState11(null);
|
|
1223
|
+
const [isLoading, setIsLoading] = useState11(!!id);
|
|
1224
|
+
const [error, setError] = useState11(null);
|
|
1225
|
+
const fetchPost = useCallback5(async () => {
|
|
1226
|
+
if (!id) {
|
|
1227
|
+
setData(null);
|
|
1228
|
+
setIsLoading(false);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
try {
|
|
1232
|
+
setIsLoading(true);
|
|
1233
|
+
setError(null);
|
|
1234
|
+
const result = await client.get(`/api/blog/posts/${id}`);
|
|
1235
|
+
setData(result);
|
|
1236
|
+
} catch (err) {
|
|
1237
|
+
setError(err);
|
|
1238
|
+
} finally {
|
|
1239
|
+
setIsLoading(false);
|
|
1240
|
+
}
|
|
1241
|
+
}, [client, id]);
|
|
1242
|
+
useEffect9(() => {
|
|
1243
|
+
fetchPost();
|
|
1244
|
+
}, [fetchPost]);
|
|
1245
|
+
return { data, isLoading, error, refetch: fetchPost };
|
|
1246
|
+
}
|
|
1247
|
+
function useAdminBlogPostMutations() {
|
|
1248
|
+
const { client } = useFoxPixelContext();
|
|
1249
|
+
const [isLoading, setIsLoading] = useState11(false);
|
|
1250
|
+
const [error, setError] = useState11(null);
|
|
1251
|
+
const create = async (payload) => {
|
|
1252
|
+
try {
|
|
1253
|
+
setIsLoading(true);
|
|
1254
|
+
setError(null);
|
|
1255
|
+
const result = await client.post("/api/blog/posts", payload);
|
|
1256
|
+
return result;
|
|
1257
|
+
} catch (err) {
|
|
1258
|
+
setError(err);
|
|
1259
|
+
return null;
|
|
1260
|
+
} finally {
|
|
1261
|
+
setIsLoading(false);
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
const update = async (id, payload) => {
|
|
1265
|
+
try {
|
|
1266
|
+
setIsLoading(true);
|
|
1267
|
+
setError(null);
|
|
1268
|
+
const result = await client.put(`/api/blog/posts/${id}`, payload);
|
|
1269
|
+
return result;
|
|
1270
|
+
} catch (err) {
|
|
1271
|
+
setError(err);
|
|
1272
|
+
return null;
|
|
1273
|
+
} finally {
|
|
1274
|
+
setIsLoading(false);
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
const remove = async (id) => {
|
|
1278
|
+
try {
|
|
1279
|
+
setIsLoading(true);
|
|
1280
|
+
setError(null);
|
|
1281
|
+
await client.delete(`/api/blog/posts/${id}`);
|
|
1282
|
+
return true;
|
|
1283
|
+
} catch (err) {
|
|
1284
|
+
setError(err);
|
|
1285
|
+
return false;
|
|
1286
|
+
} finally {
|
|
1287
|
+
setIsLoading(false);
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
return { create, update, remove, isLoading, error };
|
|
1291
|
+
}
|
|
1292
|
+
function useAdminBlogCategories() {
|
|
1293
|
+
const { client } = useFoxPixelContext();
|
|
1294
|
+
const [data, setData] = useState11(null);
|
|
1295
|
+
const [isLoading, setIsLoading] = useState11(true);
|
|
1296
|
+
const [error, setError] = useState11(null);
|
|
1297
|
+
const fetchCategories = useCallback5(async () => {
|
|
1298
|
+
try {
|
|
1299
|
+
setIsLoading(true);
|
|
1300
|
+
setError(null);
|
|
1301
|
+
const result = await client.get("/api/blog/categories");
|
|
1302
|
+
setData(Array.isArray(result) ? result : []);
|
|
1303
|
+
} catch (err) {
|
|
1304
|
+
setError(err);
|
|
1305
|
+
} finally {
|
|
1306
|
+
setIsLoading(false);
|
|
1307
|
+
}
|
|
1308
|
+
}, [client]);
|
|
1309
|
+
useEffect9(() => {
|
|
1310
|
+
fetchCategories();
|
|
1311
|
+
}, [fetchCategories]);
|
|
1312
|
+
const create = async (payload) => {
|
|
1313
|
+
try {
|
|
1314
|
+
const result = await client.post("/api/blog/categories", payload);
|
|
1315
|
+
await fetchCategories();
|
|
1316
|
+
return result;
|
|
1317
|
+
} catch (err) {
|
|
1318
|
+
setError(err);
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
const update = async (id, payload) => {
|
|
1323
|
+
try {
|
|
1324
|
+
const result = await client.put(`/api/blog/categories/${id}`, payload);
|
|
1325
|
+
await fetchCategories();
|
|
1326
|
+
return result;
|
|
1327
|
+
} catch (err) {
|
|
1328
|
+
setError(err);
|
|
1329
|
+
return null;
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
const remove = async (id) => {
|
|
1333
|
+
try {
|
|
1334
|
+
await client.delete(`/api/blog/categories/${id}`);
|
|
1335
|
+
await fetchCategories();
|
|
1336
|
+
return true;
|
|
1337
|
+
} catch (err) {
|
|
1338
|
+
setError(err);
|
|
1339
|
+
return false;
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
return { data, isLoading, error, refetch: fetchCategories, create, update, remove };
|
|
1343
|
+
}
|
|
1344
|
+
function useAdminBlogTags() {
|
|
1345
|
+
const { client } = useFoxPixelContext();
|
|
1346
|
+
const [data, setData] = useState11(null);
|
|
1347
|
+
const [isLoading, setIsLoading] = useState11(true);
|
|
1348
|
+
const [error, setError] = useState11(null);
|
|
1349
|
+
const fetchTags = useCallback5(async () => {
|
|
1350
|
+
try {
|
|
1351
|
+
setIsLoading(true);
|
|
1352
|
+
setError(null);
|
|
1353
|
+
const result = await client.get("/api/blog/tags");
|
|
1354
|
+
setData(Array.isArray(result) ? result : []);
|
|
1355
|
+
} catch (err) {
|
|
1356
|
+
setError(err);
|
|
1357
|
+
} finally {
|
|
1358
|
+
setIsLoading(false);
|
|
1359
|
+
}
|
|
1360
|
+
}, [client]);
|
|
1361
|
+
useEffect9(() => {
|
|
1362
|
+
fetchTags();
|
|
1363
|
+
}, [fetchTags]);
|
|
1364
|
+
const create = async (payload) => {
|
|
1365
|
+
try {
|
|
1366
|
+
const result = await client.post("/api/blog/tags", payload);
|
|
1367
|
+
await fetchTags();
|
|
1368
|
+
return result;
|
|
1369
|
+
} catch (err) {
|
|
1370
|
+
setError(err);
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
const update = async (id, payload) => {
|
|
1375
|
+
try {
|
|
1376
|
+
const result = await client.put(`/api/blog/tags/${id}`, payload);
|
|
1377
|
+
await fetchTags();
|
|
1378
|
+
return result;
|
|
1379
|
+
} catch (err) {
|
|
1380
|
+
setError(err);
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
const remove = async (id) => {
|
|
1385
|
+
try {
|
|
1386
|
+
await client.delete(`/api/blog/tags/${id}`);
|
|
1387
|
+
await fetchTags();
|
|
1388
|
+
return true;
|
|
1389
|
+
} catch (err) {
|
|
1390
|
+
setError(err);
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
return { data, isLoading, error, refetch: fetchTags, create, update, remove };
|
|
1395
|
+
}
|
|
1396
|
+
function useAdminBlogComments(options = {}) {
|
|
1397
|
+
const { client } = useFoxPixelContext();
|
|
1398
|
+
const [data, setData] = useState11(null);
|
|
1399
|
+
const [isLoading, setIsLoading] = useState11(true);
|
|
1400
|
+
const [error, setError] = useState11(null);
|
|
1401
|
+
const { status, postId, page = 0, size = 20 } = options;
|
|
1402
|
+
const fetchComments = useCallback5(async () => {
|
|
1403
|
+
try {
|
|
1404
|
+
setIsLoading(true);
|
|
1405
|
+
setError(null);
|
|
1406
|
+
const params = new URLSearchParams();
|
|
1407
|
+
params.append("page", String(page));
|
|
1408
|
+
params.append("size", String(size));
|
|
1409
|
+
if (status) params.append("status", status);
|
|
1410
|
+
if (postId) params.append("postId", postId);
|
|
1411
|
+
const result = await client.get(`/api/blog/comments?${params.toString()}`);
|
|
1412
|
+
setData(result);
|
|
1413
|
+
} catch (err) {
|
|
1414
|
+
setError(err);
|
|
1415
|
+
} finally {
|
|
1416
|
+
setIsLoading(false);
|
|
1417
|
+
}
|
|
1418
|
+
}, [client, status, postId, page, size]);
|
|
1419
|
+
useEffect9(() => {
|
|
1420
|
+
fetchComments();
|
|
1421
|
+
}, [fetchComments]);
|
|
1422
|
+
const updateStatus = async (id, newStatus) => {
|
|
1423
|
+
try {
|
|
1424
|
+
await client.put(`/api/blog/comments/${id}/status`, { status: newStatus });
|
|
1425
|
+
await fetchComments();
|
|
1426
|
+
return true;
|
|
1427
|
+
} catch (err) {
|
|
1428
|
+
setError(err);
|
|
1429
|
+
return false;
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
const remove = async (id) => {
|
|
1433
|
+
try {
|
|
1434
|
+
await client.delete(`/api/blog/comments/${id}`);
|
|
1435
|
+
await fetchComments();
|
|
1436
|
+
return true;
|
|
1437
|
+
} catch (err) {
|
|
1438
|
+
setError(err);
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
return { data, isLoading, error, refetch: fetchComments, updateStatus, remove };
|
|
1443
|
+
}
|
|
1444
|
+
function useAdminNewsletterSubscribers(options = {}) {
|
|
1445
|
+
const { client } = useFoxPixelContext();
|
|
1446
|
+
const [data, setData] = useState11(null);
|
|
1447
|
+
const [isLoading, setIsLoading] = useState11(true);
|
|
1448
|
+
const [error, setError] = useState11(null);
|
|
1449
|
+
const { status, page = 0, size = 20 } = options;
|
|
1450
|
+
const fetchSubscribers = useCallback5(async () => {
|
|
1451
|
+
try {
|
|
1452
|
+
setIsLoading(true);
|
|
1453
|
+
setError(null);
|
|
1454
|
+
const params = new URLSearchParams();
|
|
1455
|
+
params.append("page", String(page));
|
|
1456
|
+
params.append("size", String(size));
|
|
1457
|
+
if (status) params.append("status", status);
|
|
1458
|
+
const result = await client.get(`/api/blog/newsletter/subscribers?${params.toString()}`);
|
|
1459
|
+
setData(result);
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
setError(err);
|
|
1462
|
+
} finally {
|
|
1463
|
+
setIsLoading(false);
|
|
1464
|
+
}
|
|
1465
|
+
}, [client, status, page, size]);
|
|
1466
|
+
useEffect9(() => {
|
|
1467
|
+
fetchSubscribers();
|
|
1468
|
+
}, [fetchSubscribers]);
|
|
1469
|
+
const remove = async (id) => {
|
|
1470
|
+
try {
|
|
1471
|
+
await client.delete(`/api/blog/newsletter/subscribers/${id}`);
|
|
1472
|
+
await fetchSubscribers();
|
|
1473
|
+
return true;
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
setError(err);
|
|
1476
|
+
return false;
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
return { data, isLoading, error, refetch: fetchSubscribers, remove };
|
|
1480
|
+
}
|
|
1481
|
+
function useAdminNewsletterStats() {
|
|
1482
|
+
const { client } = useFoxPixelContext();
|
|
1483
|
+
const [data, setData] = useState11(null);
|
|
1484
|
+
const [isLoading, setIsLoading] = useState11(true);
|
|
1485
|
+
const [error, setError] = useState11(null);
|
|
1486
|
+
const fetchStats = useCallback5(async () => {
|
|
1487
|
+
try {
|
|
1488
|
+
setIsLoading(true);
|
|
1489
|
+
setError(null);
|
|
1490
|
+
const result = await client.get("/api/blog/newsletter/stats");
|
|
1491
|
+
setData(result);
|
|
1492
|
+
} catch (err) {
|
|
1493
|
+
setError(err);
|
|
1494
|
+
} finally {
|
|
1495
|
+
setIsLoading(false);
|
|
1496
|
+
}
|
|
1497
|
+
}, [client]);
|
|
1498
|
+
useEffect9(() => {
|
|
1499
|
+
fetchStats();
|
|
1500
|
+
}, [fetchStats]);
|
|
1501
|
+
return { data, isLoading, error, refetch: fetchStats };
|
|
1502
|
+
}
|
|
1503
|
+
function useAdminBlogSettings() {
|
|
1504
|
+
const { client } = useFoxPixelContext();
|
|
1505
|
+
const [data, setData] = useState11(null);
|
|
1506
|
+
const [isLoading, setIsLoading] = useState11(true);
|
|
1507
|
+
const [error, setError] = useState11(null);
|
|
1508
|
+
const fetchSettings = useCallback5(async () => {
|
|
1509
|
+
try {
|
|
1510
|
+
setIsLoading(true);
|
|
1511
|
+
setError(null);
|
|
1512
|
+
const result = await client.get("/api/blog/settings");
|
|
1513
|
+
setData(result);
|
|
1514
|
+
} catch (err) {
|
|
1515
|
+
setError(err);
|
|
1516
|
+
} finally {
|
|
1517
|
+
setIsLoading(false);
|
|
1518
|
+
}
|
|
1519
|
+
}, [client]);
|
|
1520
|
+
useEffect9(() => {
|
|
1521
|
+
fetchSettings();
|
|
1522
|
+
}, [fetchSettings]);
|
|
1523
|
+
const update = async (settings) => {
|
|
1524
|
+
try {
|
|
1525
|
+
const result = await client.put("/api/blog/settings", settings);
|
|
1526
|
+
setData(result);
|
|
1527
|
+
return result;
|
|
1528
|
+
} catch (err) {
|
|
1529
|
+
setError(err);
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
return { data, isLoading, error, refetch: fetchSettings, update };
|
|
1534
|
+
}
|
|
1535
|
+
function useAdminBlogAnalytics() {
|
|
1536
|
+
const { client } = useFoxPixelContext();
|
|
1537
|
+
const [data, setData] = useState11(null);
|
|
1538
|
+
const [isLoading, setIsLoading] = useState11(true);
|
|
1539
|
+
const [error, setError] = useState11(null);
|
|
1540
|
+
const fetchAnalytics = useCallback5(async () => {
|
|
1541
|
+
try {
|
|
1542
|
+
setIsLoading(true);
|
|
1543
|
+
setError(null);
|
|
1544
|
+
const result = await client.get("/api/blog/analytics/summary");
|
|
1545
|
+
setData(result);
|
|
1546
|
+
} catch (err) {
|
|
1547
|
+
setError(err);
|
|
1548
|
+
} finally {
|
|
1549
|
+
setIsLoading(false);
|
|
1550
|
+
}
|
|
1551
|
+
}, [client]);
|
|
1552
|
+
useEffect9(() => {
|
|
1553
|
+
fetchAnalytics();
|
|
1554
|
+
}, [fetchAnalytics]);
|
|
1555
|
+
return { data, isLoading, error, refetch: fetchAnalytics };
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// src/blog/utils.ts
|
|
1559
|
+
function getBlogPostSchemaLd(post, options) {
|
|
1560
|
+
const { siteUrl, publisherName, publisherLogoUrl } = options;
|
|
1561
|
+
const postUrl = `${siteUrl.replace(/\/$/, "")}/blog/${post.slug}`;
|
|
1562
|
+
const schema = {
|
|
1563
|
+
"@context": "https://schema.org",
|
|
1564
|
+
"@type": "BlogPosting",
|
|
1565
|
+
headline: post.title,
|
|
1566
|
+
description: post.metaDescription || post.excerpt || void 0,
|
|
1567
|
+
image: post.coverImageUrl || void 0,
|
|
1568
|
+
datePublished: post.publishedAt || void 0,
|
|
1569
|
+
dateModified: post.updatedAt,
|
|
1570
|
+
mainEntityOfPage: {
|
|
1571
|
+
"@type": "WebPage",
|
|
1572
|
+
"@id": postUrl
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
if (publisherName || publisherLogoUrl) {
|
|
1576
|
+
schema.publisher = {
|
|
1577
|
+
"@type": "Organization",
|
|
1578
|
+
...publisherName && { name: publisherName },
|
|
1579
|
+
...publisherLogoUrl && {
|
|
1580
|
+
logo: {
|
|
1581
|
+
"@type": "ImageObject",
|
|
1582
|
+
url: publisherLogoUrl
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
return schema;
|
|
1588
|
+
}
|
|
435
1589
|
export {
|
|
436
1590
|
AuthProvider,
|
|
1591
|
+
Editable,
|
|
1592
|
+
EditableHTML,
|
|
1593
|
+
EditableImage,
|
|
437
1594
|
FoxPixelHttpClient,
|
|
438
1595
|
FoxPixelProvider,
|
|
439
1596
|
GuestOnlyRoute,
|
|
440
1597
|
ProtectedRoute,
|
|
1598
|
+
SITE_CONTENT_QUERY_KEY,
|
|
1599
|
+
getBlogPostSchemaLd,
|
|
1600
|
+
useAdminBlogAnalytics,
|
|
1601
|
+
useAdminBlogCategories,
|
|
1602
|
+
useAdminBlogComments,
|
|
1603
|
+
useAdminBlogPost,
|
|
1604
|
+
useAdminBlogPostMutations,
|
|
1605
|
+
useAdminBlogPosts,
|
|
1606
|
+
useAdminBlogSettings,
|
|
1607
|
+
useAdminBlogTags,
|
|
1608
|
+
useAdminNewsletterStats,
|
|
1609
|
+
useAdminNewsletterSubscribers,
|
|
441
1610
|
useAuth,
|
|
1611
|
+
useBlogCategories,
|
|
1612
|
+
useBlogCommentSubmit,
|
|
1613
|
+
useBlogComments,
|
|
1614
|
+
useBlogFeaturedPosts,
|
|
1615
|
+
useBlogPost,
|
|
1616
|
+
useBlogPosts,
|
|
1617
|
+
useBlogTags,
|
|
442
1618
|
useContactCapture,
|
|
1619
|
+
useEditMode,
|
|
1620
|
+
useEditModeMessaging,
|
|
443
1621
|
useFoxPixelContext,
|
|
444
1622
|
useLeadCapture,
|
|
1623
|
+
useNewsletterSubscribe,
|
|
1624
|
+
useNewsletterUnsubscribe,
|
|
1625
|
+
useSendEditRequest,
|
|
445
1626
|
useServices,
|
|
1627
|
+
useSiteContent,
|
|
1628
|
+
useSiteContentQuery,
|
|
1629
|
+
useSiteContentSection,
|
|
1630
|
+
useSiteContents,
|
|
446
1631
|
withAuth
|
|
447
1632
|
};
|
|
448
1633
|
//# sourceMappingURL=index.mjs.map
|