@lodashventure/medusa-campaign 1.5.0 → 1.5.3
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/.medusa/server/src/admin/index.js +1355 -1355
- package/.medusa/server/src/admin/index.mjs +1357 -1357
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
|
|
3
|
-
import { Container, Heading, Text, Alert, Badge, Button, createDataTableColumnHelper, useDataTable, DataTable, Label, Input, Textarea, Select, toast,
|
|
4
|
-
import { Sparkles, PhotoSolid, PencilSquare, Eye, Tag, InformationCircle, CommandLine, X, CloudArrowUp
|
|
3
|
+
import { Container, Heading, Text, Alert, Badge, Button, createDataTableColumnHelper, useDataTable, DataTable, Label, Input, Textarea, Select, toast, Table, FocusModal, Tabs, clx } from "@medusajs/ui";
|
|
4
|
+
import { Sparkles, PhotoSolid, PencilSquare, Eye, Tag, Trash, InformationCircle, CommandLine, X, CloudArrowUp } from "@medusajs/icons";
|
|
5
5
|
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
6
6
|
import { useNavigate, useParams } from "react-router-dom";
|
|
7
7
|
import dayjs from "dayjs";
|
|
@@ -692,1364 +692,1360 @@ const CouponForm = ({
|
|
|
692
692
|
)
|
|
693
693
|
] });
|
|
694
694
|
};
|
|
695
|
-
const
|
|
696
|
-
const
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
const fetchCoupon = async () => {
|
|
700
|
-
if (!id) {
|
|
701
|
-
return;
|
|
702
|
-
}
|
|
703
|
-
setIsLoading(true);
|
|
704
|
-
setError(null);
|
|
695
|
+
const CouponCreate = () => {
|
|
696
|
+
const navigate = useNavigate();
|
|
697
|
+
const handleSubmit = async (formData) => {
|
|
698
|
+
var _a, _b;
|
|
705
699
|
try {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
700
|
+
await axios.post("/admin/coupons", {
|
|
701
|
+
...formData,
|
|
702
|
+
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
703
|
+
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
704
|
+
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
705
|
+
});
|
|
706
|
+
toast.success("Coupon created successfully");
|
|
707
|
+
navigate("/coupons");
|
|
708
|
+
} catch (error) {
|
|
709
|
+
let message = "Failed to create coupon";
|
|
710
|
+
if (axios.isAxiosError(error)) {
|
|
711
|
+
message = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) ?? message;
|
|
712
|
+
} else if (error instanceof Error) {
|
|
713
|
+
message = error.message;
|
|
714
|
+
}
|
|
715
|
+
toast.error("Failed to create coupon", {
|
|
716
|
+
description: message
|
|
717
|
+
});
|
|
712
718
|
}
|
|
713
719
|
};
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
refetch: fetchCoupon
|
|
722
|
-
};
|
|
720
|
+
return /* @__PURE__ */ jsx(
|
|
721
|
+
CouponForm,
|
|
722
|
+
{
|
|
723
|
+
onSubmit: handleSubmit,
|
|
724
|
+
onCancel: () => navigate("/coupons")
|
|
725
|
+
}
|
|
726
|
+
);
|
|
723
727
|
};
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
728
|
+
const sdk = new Medusa({
|
|
729
|
+
baseUrl: "/",
|
|
730
|
+
debug: false,
|
|
731
|
+
auth: {
|
|
732
|
+
type: "session"
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
const columnHelper = createDataTableColumnHelper();
|
|
736
|
+
const columns = [
|
|
737
|
+
columnHelper.accessor("title", {
|
|
738
|
+
header: "Title",
|
|
739
|
+
cell: (info) => /* @__PURE__ */ jsx(Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
740
|
+
}),
|
|
741
|
+
columnHelper.accessor("description", {
|
|
742
|
+
header: "Description",
|
|
743
|
+
cell: (info) => /* @__PURE__ */ jsx(Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
744
|
+
})
|
|
745
|
+
];
|
|
746
|
+
const ProductSelector = ({
|
|
747
|
+
selectedProductIds,
|
|
748
|
+
onSelectProduct
|
|
732
749
|
}) => {
|
|
733
|
-
const [
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
750
|
+
const [search, setSearch] = useState("");
|
|
751
|
+
const debouncedSearchHandler = debounce((value) => {
|
|
752
|
+
setSearch(value);
|
|
753
|
+
}, 500);
|
|
754
|
+
const [products, setProducts] = useState(null);
|
|
755
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
756
|
+
useEffect(() => {
|
|
757
|
+
const fetchProducts = async () => {
|
|
758
|
+
setIsLoading(true);
|
|
759
|
+
try {
|
|
760
|
+
const result = await sdk.admin.product.list({
|
|
761
|
+
q: search,
|
|
762
|
+
limit: 100
|
|
763
|
+
});
|
|
764
|
+
setProducts(result);
|
|
765
|
+
} catch (error) {
|
|
766
|
+
console.error("Failed to fetch products:", error);
|
|
767
|
+
} finally {
|
|
768
|
+
setIsLoading(false);
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
fetchProducts();
|
|
772
|
+
}, [selectedProductIds, search]);
|
|
773
|
+
const selectableProducts = useMemo(() => {
|
|
774
|
+
var _a;
|
|
775
|
+
return (_a = products == null ? void 0 : products.products) == null ? void 0 : _a.filter(
|
|
776
|
+
(product) => !selectedProductIds.includes(product.id)
|
|
737
777
|
);
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
778
|
+
}, [products == null ? void 0 : products.products, selectedProductIds]);
|
|
779
|
+
const table = useDataTable({
|
|
780
|
+
data: selectableProducts || [],
|
|
781
|
+
columns,
|
|
782
|
+
getRowId: (product) => product.id,
|
|
783
|
+
onRowClick: (_, row) => {
|
|
784
|
+
onSelectProduct == null ? void 0 : onSelectProduct(row.original);
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
788
|
+
/* @__PURE__ */ jsx(
|
|
789
|
+
Input,
|
|
790
|
+
{
|
|
791
|
+
className: "text-lg py-2",
|
|
792
|
+
placeholder: "Search products...",
|
|
793
|
+
onChange: (e) => debouncedSearchHandler(e.target.value)
|
|
794
|
+
}
|
|
795
|
+
),
|
|
796
|
+
/* @__PURE__ */ jsx(DataTable, { instance: table, children: /* @__PURE__ */ jsx(DataTable.Table, {}) })
|
|
797
|
+
] });
|
|
798
|
+
};
|
|
799
|
+
const customCampaignSchema = z.object({
|
|
800
|
+
name: z.string().min(1, "Name is required"),
|
|
801
|
+
description: z.string().min(1, "Description is required"),
|
|
802
|
+
type: z.literal("flash-sale"),
|
|
803
|
+
starts_at: z.string().min(1, "Start date is required"),
|
|
804
|
+
ends_at: z.string().min(1, "End date is required")
|
|
805
|
+
});
|
|
806
|
+
const campaignProductSchema = z.object({
|
|
807
|
+
product: z.object({
|
|
808
|
+
id: z.string(),
|
|
809
|
+
title: z.string()
|
|
810
|
+
}),
|
|
811
|
+
discountType: z.enum([
|
|
812
|
+
"percentage"
|
|
813
|
+
// , "fixed"
|
|
814
|
+
]),
|
|
815
|
+
discountValue: z.number().min(1),
|
|
816
|
+
limit: z.number().min(1),
|
|
817
|
+
maxQty: z.number().min(1)
|
|
818
|
+
});
|
|
819
|
+
const campaignDataSchema = customCampaignSchema.extend({
|
|
820
|
+
products: z.array(campaignProductSchema).min(1, "At least one product is required")
|
|
821
|
+
}).refine(
|
|
822
|
+
(data) => new Date(data.starts_at) < new Date(data.ends_at),
|
|
823
|
+
"End date must be after start date"
|
|
824
|
+
);
|
|
825
|
+
const FlashSaleForm = ({
|
|
826
|
+
initialData,
|
|
827
|
+
initialProducts,
|
|
828
|
+
onSubmit,
|
|
829
|
+
onCancel,
|
|
830
|
+
disabled = false
|
|
831
|
+
}) => {
|
|
832
|
+
var _a;
|
|
833
|
+
const {
|
|
834
|
+
control,
|
|
835
|
+
register,
|
|
836
|
+
handleSubmit,
|
|
837
|
+
watch,
|
|
838
|
+
setValue,
|
|
839
|
+
formState: { errors }
|
|
840
|
+
} = useForm({
|
|
841
|
+
resolver: zodResolver(campaignDataSchema),
|
|
842
|
+
defaultValues: {
|
|
843
|
+
name: (initialData == null ? void 0 : initialData.name) || "",
|
|
844
|
+
description: (initialData == null ? void 0 : initialData.description) || "",
|
|
845
|
+
type: "flash-sale",
|
|
846
|
+
starts_at: (initialData == null ? void 0 : initialData.starts_at) || "",
|
|
847
|
+
ends_at: (initialData == null ? void 0 : initialData.ends_at) || "",
|
|
848
|
+
products: initialProducts ? Array.from(initialProducts.values()) : []
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
const { fields, append, remove, update } = useFieldArray({
|
|
852
|
+
control,
|
|
853
|
+
name: "products"
|
|
854
|
+
});
|
|
855
|
+
const [openProductModal, setOpenProductModal] = useState(false);
|
|
856
|
+
const startsAt = watch("starts_at");
|
|
857
|
+
const endsAt = watch("ends_at");
|
|
858
|
+
const handleDateTimeChange = (field, type, value) => {
|
|
859
|
+
const currentValue = watch(field);
|
|
860
|
+
if (type === "date") {
|
|
861
|
+
const time = currentValue ? dayjs(currentValue).format("HH:mm") : field === "starts_at" ? "00:00" : "23:59";
|
|
862
|
+
setValue(field, `${value}T${time}`);
|
|
863
|
+
} else {
|
|
864
|
+
const date = currentValue ? dayjs(currentValue).format("YYYY-MM-DD") : dayjs().format("YYYY-MM-DD");
|
|
865
|
+
setValue(field, `${date}T${value}`);
|
|
866
|
+
}
|
|
751
867
|
};
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
|
|
755
|
-
html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
|
|
756
|
-
html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
|
|
757
|
-
html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
758
|
-
html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
|
|
759
|
-
html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
|
|
760
|
-
html = html.replace(/_(.+?)_/g, "<em>$1</em>");
|
|
761
|
-
html = html.replace(
|
|
762
|
-
/```(\w+)?\n([\s\S]+?)```/g,
|
|
763
|
-
'<pre class="bg-ui-bg-subtle p-4 rounded-lg overflow-x-auto"><code class="language-$1">$2</code></pre>'
|
|
764
|
-
);
|
|
765
|
-
html = html.replace(
|
|
766
|
-
/`([^`]+)`/g,
|
|
767
|
-
'<code class="bg-ui-bg-subtle px-1 py-0.5 rounded text-sm">$1</code>'
|
|
768
|
-
);
|
|
769
|
-
html = html.replace(
|
|
770
|
-
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
771
|
-
'<a href="$2" class="text-ui-fg-interactive underline">$1</a>'
|
|
772
|
-
);
|
|
773
|
-
html = html.replace(
|
|
774
|
-
/!\[([^\]]*)\]\(([^)]+)\)/g,
|
|
775
|
-
'<img src="$2" alt="$1" class="max-w-full h-auto rounded-lg my-2" />'
|
|
776
|
-
);
|
|
777
|
-
html = html.replace(/^\* (.+)$/gim, "<li>$1</li>");
|
|
778
|
-
html = html.replace(/^\- (.+)$/gim, "<li>$1</li>");
|
|
779
|
-
html = html.replace(
|
|
780
|
-
/(<li>.*<\/li>)/s,
|
|
781
|
-
"<ul class='list-disc ml-6'>$1</ul>"
|
|
782
|
-
);
|
|
783
|
-
html = html.replace(/\n\n/g, "</p><p>");
|
|
784
|
-
html = `<p>${html}</p>`;
|
|
785
|
-
return html;
|
|
868
|
+
const onFormSubmit = (data) => {
|
|
869
|
+
onSubmit(data);
|
|
786
870
|
};
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
] }),
|
|
795
|
-
showPreview ? /* @__PURE__ */ jsxs(Tabs, { value: activeTab, onValueChange: (v) => setActiveTab(v), children: [
|
|
796
|
-
/* @__PURE__ */ jsxs(Tabs.List, { children: [
|
|
797
|
-
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "write", children: "Write" }),
|
|
798
|
-
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "preview", children: "Preview" })
|
|
799
|
-
] }),
|
|
800
|
-
/* @__PURE__ */ jsx(Tabs.Content, { value: "write", className: "mt-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
801
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
|
|
802
|
-
/* @__PURE__ */ jsx(
|
|
803
|
-
Button,
|
|
804
|
-
{
|
|
805
|
-
type: "button",
|
|
806
|
-
variant: "secondary",
|
|
807
|
-
size: "small",
|
|
808
|
-
onClick: () => insertMarkdown("**", "**"),
|
|
809
|
-
title: "Bold",
|
|
810
|
-
children: /* @__PURE__ */ jsx("strong", { children: "B" })
|
|
811
|
-
}
|
|
812
|
-
),
|
|
813
|
-
/* @__PURE__ */ jsx(
|
|
814
|
-
Button,
|
|
815
|
-
{
|
|
816
|
-
type: "button",
|
|
817
|
-
variant: "secondary",
|
|
818
|
-
size: "small",
|
|
819
|
-
onClick: () => insertMarkdown("*", "*"),
|
|
820
|
-
title: "Italic",
|
|
821
|
-
children: /* @__PURE__ */ jsx("em", { children: "I" })
|
|
822
|
-
}
|
|
823
|
-
),
|
|
824
|
-
/* @__PURE__ */ jsx(
|
|
825
|
-
Button,
|
|
826
|
-
{
|
|
827
|
-
type: "button",
|
|
828
|
-
variant: "secondary",
|
|
829
|
-
size: "small",
|
|
830
|
-
onClick: () => insertMarkdown("`", "`"),
|
|
831
|
-
title: "Inline Code",
|
|
832
|
-
children: /* @__PURE__ */ jsx(CommandLine, { className: "h-4 w-4" })
|
|
833
|
-
}
|
|
834
|
-
),
|
|
835
|
-
/* @__PURE__ */ jsx(
|
|
836
|
-
Button,
|
|
837
|
-
{
|
|
838
|
-
type: "button",
|
|
839
|
-
variant: "secondary",
|
|
840
|
-
size: "small",
|
|
841
|
-
onClick: () => insertMarkdown("## "),
|
|
842
|
-
title: "Heading",
|
|
843
|
-
children: "H2"
|
|
844
|
-
}
|
|
845
|
-
),
|
|
846
|
-
/* @__PURE__ */ jsx(
|
|
847
|
-
Button,
|
|
848
|
-
{
|
|
849
|
-
type: "button",
|
|
850
|
-
variant: "secondary",
|
|
851
|
-
size: "small",
|
|
852
|
-
onClick: () => insertMarkdown("### "),
|
|
853
|
-
title: "Heading",
|
|
854
|
-
children: "H3"
|
|
855
|
-
}
|
|
856
|
-
),
|
|
857
|
-
/* @__PURE__ */ jsx(
|
|
858
|
-
Button,
|
|
859
|
-
{
|
|
860
|
-
type: "button",
|
|
861
|
-
variant: "secondary",
|
|
862
|
-
size: "small",
|
|
863
|
-
onClick: () => insertMarkdown("[", "](url)"),
|
|
864
|
-
title: "Link",
|
|
865
|
-
children: "Link"
|
|
866
|
-
}
|
|
867
|
-
),
|
|
868
|
-
/* @__PURE__ */ jsx(
|
|
869
|
-
Button,
|
|
870
|
-
{
|
|
871
|
-
type: "button",
|
|
872
|
-
variant: "secondary",
|
|
873
|
-
size: "small",
|
|
874
|
-
onClick: () => insertMarkdown(""),
|
|
875
|
-
title: "Image",
|
|
876
|
-
children: "Image"
|
|
877
|
-
}
|
|
878
|
-
),
|
|
879
|
-
/* @__PURE__ */ jsx(
|
|
880
|
-
Button,
|
|
881
|
-
{
|
|
882
|
-
type: "button",
|
|
883
|
-
variant: "secondary",
|
|
884
|
-
size: "small",
|
|
885
|
-
onClick: () => insertMarkdown("```\n", "\n```"),
|
|
886
|
-
title: "Code Block",
|
|
887
|
-
children: "Code"
|
|
888
|
-
}
|
|
889
|
-
),
|
|
890
|
-
/* @__PURE__ */ jsx(
|
|
891
|
-
Button,
|
|
892
|
-
{
|
|
893
|
-
type: "button",
|
|
894
|
-
variant: "secondary",
|
|
895
|
-
size: "small",
|
|
896
|
-
onClick: () => insertMarkdown("- "),
|
|
897
|
-
title: "List",
|
|
898
|
-
children: "List"
|
|
899
|
-
}
|
|
900
|
-
)
|
|
901
|
-
] }),
|
|
902
|
-
/* @__PURE__ */ jsx(
|
|
903
|
-
Textarea,
|
|
904
|
-
{
|
|
905
|
-
id: `markdown-${label}`,
|
|
906
|
-
value,
|
|
907
|
-
onChange: (e) => onChange(e.target.value),
|
|
908
|
-
placeholder,
|
|
909
|
-
rows,
|
|
910
|
-
className: "font-mono text-sm"
|
|
911
|
-
}
|
|
912
|
-
)
|
|
913
|
-
] }) }),
|
|
914
|
-
/* @__PURE__ */ jsx(Tabs.Content, { value: "preview", className: "mt-4", children: /* @__PURE__ */ jsx(
|
|
915
|
-
"div",
|
|
916
|
-
{
|
|
917
|
-
className: clx(
|
|
918
|
-
"min-h-[200px] rounded-lg border border-ui-border-base bg-ui-bg-subtle p-4",
|
|
919
|
-
"prose prose-sm max-w-none"
|
|
920
|
-
),
|
|
921
|
-
dangerouslySetInnerHTML: {
|
|
922
|
-
__html: value ? renderMarkdownPreview(value) : '<p class="text-ui-fg-subtle">Nothing to preview</p>'
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
) })
|
|
926
|
-
] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
927
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
|
|
928
|
-
/* @__PURE__ */ jsx(
|
|
929
|
-
Button,
|
|
930
|
-
{
|
|
931
|
-
type: "button",
|
|
932
|
-
variant: "secondary",
|
|
933
|
-
size: "small",
|
|
934
|
-
onClick: () => insertMarkdown("**", "**"),
|
|
935
|
-
title: "Bold",
|
|
936
|
-
children: /* @__PURE__ */ jsx("strong", { children: "B" })
|
|
937
|
-
}
|
|
938
|
-
),
|
|
939
|
-
/* @__PURE__ */ jsx(
|
|
940
|
-
Button,
|
|
941
|
-
{
|
|
942
|
-
type: "button",
|
|
943
|
-
variant: "secondary",
|
|
944
|
-
size: "small",
|
|
945
|
-
onClick: () => insertMarkdown("*", "*"),
|
|
946
|
-
title: "Italic",
|
|
947
|
-
children: /* @__PURE__ */ jsx("em", { children: "I" })
|
|
948
|
-
}
|
|
949
|
-
),
|
|
950
|
-
/* @__PURE__ */ jsx(
|
|
951
|
-
Button,
|
|
952
|
-
{
|
|
953
|
-
type: "button",
|
|
954
|
-
variant: "secondary",
|
|
955
|
-
size: "small",
|
|
956
|
-
onClick: () => insertMarkdown("`", "`"),
|
|
957
|
-
title: "Inline Code",
|
|
958
|
-
children: /* @__PURE__ */ jsx(CommandLine, { className: "h-4 w-4" })
|
|
959
|
-
}
|
|
960
|
-
),
|
|
961
|
-
/* @__PURE__ */ jsx(
|
|
962
|
-
Button,
|
|
963
|
-
{
|
|
964
|
-
type: "button",
|
|
965
|
-
variant: "secondary",
|
|
966
|
-
size: "small",
|
|
967
|
-
onClick: () => insertMarkdown("```\n", "\n```"),
|
|
968
|
-
title: "Code Block",
|
|
969
|
-
children: "Code"
|
|
970
|
-
}
|
|
971
|
-
)
|
|
972
|
-
] }),
|
|
973
|
-
/* @__PURE__ */ jsx(
|
|
974
|
-
Textarea,
|
|
975
|
-
{
|
|
976
|
-
id: `markdown-${label}`,
|
|
977
|
-
value,
|
|
978
|
-
onChange: (e) => onChange(e.target.value),
|
|
979
|
-
placeholder,
|
|
980
|
-
rows,
|
|
981
|
-
className: "font-mono text-sm"
|
|
982
|
-
}
|
|
983
|
-
)
|
|
984
|
-
] }),
|
|
985
|
-
/* @__PURE__ */ jsx("div", { className: "text-xs text-ui-fg-subtle", children: "Supports Markdown formatting: **bold**, *italic*, `code`, ```code blocks```, [links](url), , and lists" })
|
|
986
|
-
] });
|
|
987
|
-
};
|
|
988
|
-
const CampaignImageUploader = ({
|
|
989
|
-
campaignId,
|
|
990
|
-
imageType,
|
|
991
|
-
currentImageUrl,
|
|
992
|
-
onClose,
|
|
993
|
-
onSuccess
|
|
994
|
-
}) => {
|
|
995
|
-
const [displayImage, setDisplayImage] = useState(currentImageUrl || null);
|
|
996
|
-
const [isUploading, setIsUploading] = useState(false);
|
|
997
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
998
|
-
const [error, setError] = useState(null);
|
|
999
|
-
const [previewUrl, setPreviewUrl] = useState(null);
|
|
1000
|
-
const isThumbnail = imageType === "thumbnail";
|
|
1001
|
-
const maxSize = 5;
|
|
1002
|
-
const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
1003
|
-
const formatFileTypes = () => {
|
|
1004
|
-
return "JPEG, PNG, GIF, or WebP";
|
|
1005
|
-
};
|
|
1006
|
-
const validateFile = (file) => {
|
|
1007
|
-
if (!allowedTypes.includes(file.type)) {
|
|
1008
|
-
setError(`Please upload a valid image file (${formatFileTypes()})`);
|
|
1009
|
-
return false;
|
|
1010
|
-
}
|
|
1011
|
-
if (file.size > maxSize * 1024 * 1024) {
|
|
1012
|
-
setError(`File size must be less than ${maxSize}MB`);
|
|
1013
|
-
return false;
|
|
1014
|
-
}
|
|
1015
|
-
return true;
|
|
1016
|
-
};
|
|
1017
|
-
const handleFileUpload = async (file) => {
|
|
1018
|
-
var _a, _b;
|
|
1019
|
-
if (!validateFile(file)) return;
|
|
1020
|
-
setError(null);
|
|
1021
|
-
const reader = new FileReader();
|
|
1022
|
-
reader.onloadend = () => {
|
|
1023
|
-
setPreviewUrl(reader.result);
|
|
1024
|
-
};
|
|
1025
|
-
reader.readAsDataURL(file);
|
|
1026
|
-
setIsUploading(true);
|
|
1027
|
-
const formData = new FormData();
|
|
1028
|
-
formData.append("file", file);
|
|
1029
|
-
try {
|
|
1030
|
-
const response = await fetch(
|
|
1031
|
-
`/admin/campaigns/${campaignId}/${imageType}`,
|
|
1032
|
-
{
|
|
1033
|
-
method: "POST",
|
|
1034
|
-
body: formData,
|
|
1035
|
-
credentials: "include"
|
|
1036
|
-
}
|
|
1037
|
-
);
|
|
1038
|
-
if (response.ok) {
|
|
1039
|
-
const result = await response.json();
|
|
1040
|
-
const newImageUrl = imageType === "image" ? (_a = result.campaign_detail) == null ? void 0 : _a.image_url : (_b = result.campaign_detail) == null ? void 0 : _b.thumbnail_url;
|
|
1041
|
-
setDisplayImage(newImageUrl);
|
|
1042
|
-
setPreviewUrl(null);
|
|
1043
|
-
onSuccess == null ? void 0 : onSuccess();
|
|
1044
|
-
setTimeout(() => {
|
|
1045
|
-
onClose();
|
|
1046
|
-
}, 1e3);
|
|
1047
|
-
} else {
|
|
1048
|
-
const errorData = await response.json();
|
|
1049
|
-
setError(errorData.error || `Failed to upload ${imageType}`);
|
|
1050
|
-
setPreviewUrl(null);
|
|
1051
|
-
}
|
|
1052
|
-
} catch (err) {
|
|
1053
|
-
setError(`Error uploading ${imageType}`);
|
|
1054
|
-
setPreviewUrl(null);
|
|
1055
|
-
console.error(`Error uploading ${imageType}:`, err);
|
|
1056
|
-
} finally {
|
|
1057
|
-
setIsUploading(false);
|
|
1058
|
-
}
|
|
1059
|
-
};
|
|
1060
|
-
const handleDelete = async () => {
|
|
1061
|
-
if (!confirm(`Are you sure you want to delete the ${imageType}?`)) return;
|
|
1062
|
-
setIsUploading(true);
|
|
1063
|
-
setError(null);
|
|
1064
|
-
try {
|
|
1065
|
-
const response = await fetch(
|
|
1066
|
-
`/admin/campaigns/${campaignId}/${imageType}`,
|
|
1067
|
-
{
|
|
1068
|
-
method: "DELETE",
|
|
1069
|
-
credentials: "include"
|
|
1070
|
-
}
|
|
1071
|
-
);
|
|
1072
|
-
if (response.ok) {
|
|
1073
|
-
setDisplayImage(null);
|
|
1074
|
-
setPreviewUrl(null);
|
|
1075
|
-
onSuccess == null ? void 0 : onSuccess();
|
|
1076
|
-
setTimeout(() => {
|
|
1077
|
-
onClose();
|
|
1078
|
-
}, 500);
|
|
1079
|
-
} else {
|
|
1080
|
-
const errorData = await response.json();
|
|
1081
|
-
setError(errorData.error || `Failed to delete ${imageType}`);
|
|
1082
|
-
}
|
|
1083
|
-
} catch (err) {
|
|
1084
|
-
setError(`Error deleting ${imageType}`);
|
|
1085
|
-
console.error(`Error deleting ${imageType}:`, err);
|
|
1086
|
-
} finally {
|
|
1087
|
-
setIsUploading(false);
|
|
1088
|
-
}
|
|
1089
|
-
};
|
|
1090
|
-
const handleDragEnter = useCallback((e) => {
|
|
1091
|
-
e.preventDefault();
|
|
1092
|
-
e.stopPropagation();
|
|
1093
|
-
setIsDragging(true);
|
|
1094
|
-
}, []);
|
|
1095
|
-
const handleDragLeave = useCallback((e) => {
|
|
1096
|
-
e.preventDefault();
|
|
1097
|
-
e.stopPropagation();
|
|
1098
|
-
setIsDragging(false);
|
|
1099
|
-
}, []);
|
|
1100
|
-
const handleDragOver = useCallback((e) => {
|
|
1101
|
-
e.preventDefault();
|
|
1102
|
-
e.stopPropagation();
|
|
1103
|
-
}, []);
|
|
1104
|
-
const handleDrop = useCallback((e) => {
|
|
1105
|
-
var _a;
|
|
1106
|
-
e.preventDefault();
|
|
1107
|
-
e.stopPropagation();
|
|
1108
|
-
setIsDragging(false);
|
|
1109
|
-
const file = (_a = e.dataTransfer.files) == null ? void 0 : _a[0];
|
|
1110
|
-
if (file) {
|
|
1111
|
-
handleFileUpload(file);
|
|
1112
|
-
}
|
|
1113
|
-
}, []);
|
|
1114
|
-
const imageToDisplay = previewUrl || displayImage;
|
|
1115
|
-
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-2xl rounded-lg bg-ui-bg-base p-6", children: [
|
|
1116
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
1117
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
1118
|
-
/* @__PURE__ */ jsxs(Heading, { level: "h2", children: [
|
|
1119
|
-
"Upload Campaign ",
|
|
1120
|
-
isThumbnail ? "Thumbnail" : "Image"
|
|
1121
|
-
] }),
|
|
1122
|
-
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: isThumbnail ? "Thumbnail for campaign listing" : "Main campaign image" })
|
|
1123
|
-
] }),
|
|
1124
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsx(X, {}) })
|
|
1125
|
-
] }),
|
|
1126
|
-
error && /* @__PURE__ */ jsx(Alert, { variant: "error", dismissible: true, className: "mb-4", children: error }),
|
|
1127
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1128
|
-
imageToDisplay ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1129
|
-
/* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-subtle", children: [
|
|
1130
|
-
/* @__PURE__ */ jsx(
|
|
1131
|
-
"img",
|
|
1132
|
-
{
|
|
1133
|
-
src: imageToDisplay,
|
|
1134
|
-
alt: `Campaign ${imageType}`,
|
|
1135
|
-
className: clx(
|
|
1136
|
-
"w-full object-contain",
|
|
1137
|
-
isThumbnail ? "h-32" : "h-64"
|
|
1138
|
-
)
|
|
1139
|
-
}
|
|
1140
|
-
),
|
|
1141
|
-
isUploading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsx("div", { className: "text-white", children: "Uploading..." }) })
|
|
1142
|
-
] }),
|
|
1143
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1144
|
-
/* @__PURE__ */ jsxs(
|
|
1145
|
-
Button,
|
|
1146
|
-
{
|
|
1147
|
-
variant: "secondary",
|
|
1148
|
-
disabled: isUploading,
|
|
1149
|
-
onClick: () => {
|
|
1150
|
-
var _a;
|
|
1151
|
-
return (_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click();
|
|
1152
|
-
},
|
|
1153
|
-
children: [
|
|
1154
|
-
/* @__PURE__ */ jsx(CloudArrowUp, { className: "mr-2" }),
|
|
1155
|
-
"Replace"
|
|
1156
|
-
]
|
|
1157
|
-
}
|
|
1158
|
-
),
|
|
1159
|
-
/* @__PURE__ */ jsxs(
|
|
1160
|
-
Button,
|
|
1161
|
-
{
|
|
1162
|
-
variant: "danger",
|
|
1163
|
-
disabled: isUploading,
|
|
1164
|
-
onClick: handleDelete,
|
|
1165
|
-
children: [
|
|
1166
|
-
/* @__PURE__ */ jsx(Trash, { className: "mr-2" }),
|
|
1167
|
-
"Delete"
|
|
1168
|
-
]
|
|
1169
|
-
}
|
|
1170
|
-
)
|
|
1171
|
-
] })
|
|
1172
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
1173
|
-
"div",
|
|
1174
|
-
{
|
|
1175
|
-
className: clx(
|
|
1176
|
-
"flex flex-col items-center justify-center rounded-lg border-2 border-dashed transition-colors",
|
|
1177
|
-
isThumbnail ? "h-48" : "h-64",
|
|
1178
|
-
isDragging ? "border-ui-border-interactive bg-ui-bg-highlight" : "border-ui-border-base bg-ui-bg-subtle",
|
|
1179
|
-
!isUploading && "cursor-pointer"
|
|
1180
|
-
),
|
|
1181
|
-
onDragEnter: handleDragEnter,
|
|
1182
|
-
onDragLeave: handleDragLeave,
|
|
1183
|
-
onDragOver: handleDragOver,
|
|
1184
|
-
onDrop: handleDrop,
|
|
1185
|
-
onClick: () => {
|
|
1186
|
-
var _a;
|
|
1187
|
-
return !isUploading && ((_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click());
|
|
1188
|
-
},
|
|
1189
|
-
children: [
|
|
1190
|
-
/* @__PURE__ */ jsx(PhotoSolid, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }),
|
|
1191
|
-
/* @__PURE__ */ jsx(Text, { className: "mb-2 text-lg font-medium", children: isDragging ? `Drop ${imageType} here` : `Upload ${isThumbnail ? "Thumbnail" : "Image"}` }),
|
|
1192
|
-
/* @__PURE__ */ jsx(Text, { className: "mb-4 text-center text-ui-fg-subtle", children: "Drag and drop an image here, or click to select" }),
|
|
1193
|
-
/* @__PURE__ */ jsxs(Text, { className: "text-sm text-ui-fg-subtle", children: [
|
|
1194
|
-
formatFileTypes(),
|
|
1195
|
-
" • Max ",
|
|
1196
|
-
maxSize,
|
|
1197
|
-
"MB"
|
|
1198
|
-
] }),
|
|
1199
|
-
isThumbnail && /* @__PURE__ */ jsx(Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: 16:9 aspect ratio, minimum 400x225px" }),
|
|
1200
|
-
!isThumbnail && /* @__PURE__ */ jsx(Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: High resolution, minimum 1200x600px" }),
|
|
1201
|
-
isUploading && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(Text, { children: "Uploading..." }) })
|
|
1202
|
-
]
|
|
1203
|
-
}
|
|
1204
|
-
),
|
|
1205
|
-
/* @__PURE__ */ jsx(
|
|
1206
|
-
"input",
|
|
1207
|
-
{
|
|
1208
|
-
id: `${imageType}-file-input`,
|
|
1209
|
-
type: "file",
|
|
1210
|
-
accept: allowedTypes.join(","),
|
|
1211
|
-
onChange: (e) => {
|
|
1212
|
-
var _a;
|
|
1213
|
-
const file = (_a = e.target.files) == null ? void 0 : _a[0];
|
|
1214
|
-
if (file) handleFileUpload(file);
|
|
1215
|
-
e.target.value = "";
|
|
1216
|
-
},
|
|
1217
|
-
className: "hidden"
|
|
1218
|
-
}
|
|
1219
|
-
)
|
|
1220
|
-
] }),
|
|
1221
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-between border-t pt-4", children: [
|
|
1222
|
-
/* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: isThumbnail ? "Thumbnail will be displayed in campaign listings" : "Main image for the campaign detail page" }),
|
|
1223
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: "Close" })
|
|
1224
|
-
] })
|
|
1225
|
-
] }) });
|
|
1226
|
-
};
|
|
1227
|
-
const CampaignDetailForm = ({
|
|
1228
|
-
campaignId,
|
|
1229
|
-
campaignName
|
|
1230
|
-
}) => {
|
|
1231
|
-
const [campaignDetail, setCampaignDetail] = useState(
|
|
1232
|
-
null
|
|
1233
|
-
);
|
|
1234
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
1235
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
1236
|
-
const [showImageUploader, setShowImageUploader] = useState(null);
|
|
1237
|
-
const [formData, setFormData] = useState({
|
|
1238
|
-
detail_content: "",
|
|
1239
|
-
terms_and_conditions: "",
|
|
1240
|
-
meta_title: "",
|
|
1241
|
-
meta_description: "",
|
|
1242
|
-
meta_keywords: "",
|
|
1243
|
-
link_url: "",
|
|
1244
|
-
link_text: "",
|
|
1245
|
-
display_order: 0
|
|
1246
|
-
});
|
|
1247
|
-
useEffect(() => {
|
|
1248
|
-
fetchCampaignDetail();
|
|
1249
|
-
}, [campaignId]);
|
|
1250
|
-
const fetchCampaignDetail = async () => {
|
|
1251
|
-
setIsLoading(true);
|
|
1252
|
-
try {
|
|
1253
|
-
const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
|
|
1254
|
-
credentials: "include"
|
|
1255
|
-
});
|
|
1256
|
-
if (response.ok) {
|
|
1257
|
-
const data = await response.json();
|
|
1258
|
-
if (data.campaign_detail) {
|
|
1259
|
-
console.log("Campaign detail loaded:", {
|
|
1260
|
-
image_url: data.campaign_detail.image_url,
|
|
1261
|
-
thumbnail_url: data.campaign_detail.thumbnail_url,
|
|
1262
|
-
decoded_image: data.campaign_detail.image_url ? decodeURIComponent(data.campaign_detail.image_url) : null,
|
|
1263
|
-
decoded_thumbnail: data.campaign_detail.thumbnail_url ? decodeURIComponent(data.campaign_detail.thumbnail_url) : null
|
|
1264
|
-
});
|
|
1265
|
-
setCampaignDetail(data.campaign_detail);
|
|
1266
|
-
setFormData({
|
|
1267
|
-
detail_content: data.campaign_detail.detail_content || "",
|
|
1268
|
-
terms_and_conditions: data.campaign_detail.terms_and_conditions || "",
|
|
1269
|
-
meta_title: data.campaign_detail.meta_title || "",
|
|
1270
|
-
meta_description: data.campaign_detail.meta_description || "",
|
|
1271
|
-
meta_keywords: data.campaign_detail.meta_keywords || "",
|
|
1272
|
-
link_url: data.campaign_detail.link_url || "",
|
|
1273
|
-
link_text: data.campaign_detail.link_text || "",
|
|
1274
|
-
display_order: data.campaign_detail.display_order || 0
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
} else if (response.status !== 404) {
|
|
1278
|
-
console.error("Failed to fetch campaign detail");
|
|
1279
|
-
}
|
|
1280
|
-
} catch (error) {
|
|
1281
|
-
console.error("Error fetching campaign detail:", error);
|
|
1282
|
-
} finally {
|
|
1283
|
-
setIsLoading(false);
|
|
1284
|
-
}
|
|
1285
|
-
};
|
|
1286
|
-
const handleSave = async () => {
|
|
1287
|
-
setIsSaving(true);
|
|
1288
|
-
try {
|
|
1289
|
-
const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
|
|
1290
|
-
method: "POST",
|
|
1291
|
-
headers: {
|
|
1292
|
-
"Content-Type": "application/json"
|
|
1293
|
-
},
|
|
1294
|
-
body: JSON.stringify(formData),
|
|
1295
|
-
credentials: "include"
|
|
1296
|
-
});
|
|
1297
|
-
if (response.ok) {
|
|
1298
|
-
const data = await response.json();
|
|
1299
|
-
setCampaignDetail(data.campaign_detail);
|
|
1300
|
-
toast.success("Campaign details saved successfully");
|
|
1301
|
-
} else {
|
|
1302
|
-
const errorData = await response.json();
|
|
1303
|
-
toast.error(errorData.error || "Failed to save campaign details");
|
|
871
|
+
const onFormError = () => {
|
|
872
|
+
const errorMessages = Object.entries(errors).map(([key, value]) => {
|
|
873
|
+
var _a2;
|
|
874
|
+
if (key === "products" && Array.isArray(value)) {
|
|
875
|
+
return value.map(
|
|
876
|
+
(item, index) => item ? `Product ${index + 1}: ${Object.values(item).join(", ")}` : ""
|
|
877
|
+
).filter(Boolean).join(", ");
|
|
1304
878
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
}
|
|
879
|
+
return (value == null ? void 0 : value.message) || ((_a2 = value == null ? void 0 : value.root) == null ? void 0 : _a2.message);
|
|
880
|
+
}).filter(Boolean).join(", ");
|
|
881
|
+
toast.error("Invalid data", {
|
|
882
|
+
description: errorMessages
|
|
883
|
+
});
|
|
1311
884
|
};
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
885
|
+
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
886
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
887
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "Flash sale campaign" }),
|
|
888
|
+
/* @__PURE__ */ jsx(Button, { variant: "transparent", onClick: onCancel, children: "Cancel" })
|
|
889
|
+
] }),
|
|
890
|
+
/* @__PURE__ */ jsxs(
|
|
891
|
+
"form",
|
|
892
|
+
{
|
|
893
|
+
onSubmit: handleSubmit(onFormSubmit, onFormError),
|
|
894
|
+
className: "space-y-4 my-8",
|
|
895
|
+
children: [
|
|
896
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
897
|
+
/* @__PURE__ */ jsx(Label, { children: "Name" }),
|
|
898
|
+
/* @__PURE__ */ jsx(Input, { ...register("name"), disabled }),
|
|
899
|
+
errors.name && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.name.message })
|
|
900
|
+
] }),
|
|
901
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
902
|
+
/* @__PURE__ */ jsx(Label, { children: "Description" }),
|
|
903
|
+
/* @__PURE__ */ jsx(Textarea, { ...register("description"), disabled }),
|
|
904
|
+
errors.description && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
|
|
905
|
+
] }),
|
|
906
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
907
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
908
|
+
/* @__PURE__ */ jsx(Label, { children: "Start Date" }),
|
|
909
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
910
|
+
/* @__PURE__ */ jsx(
|
|
911
|
+
Input,
|
|
912
|
+
{
|
|
913
|
+
type: "date",
|
|
914
|
+
value: startsAt ? dayjs(startsAt).format("YYYY-MM-DD") : "",
|
|
915
|
+
onChange: (e) => handleDateTimeChange("starts_at", "date", e.target.value),
|
|
916
|
+
disabled,
|
|
917
|
+
className: "flex-1"
|
|
918
|
+
}
|
|
919
|
+
),
|
|
920
|
+
/* @__PURE__ */ jsx(
|
|
921
|
+
Input,
|
|
922
|
+
{
|
|
923
|
+
type: "time",
|
|
924
|
+
value: startsAt ? dayjs(startsAt).format("HH:mm") : "",
|
|
925
|
+
onChange: (e) => handleDateTimeChange("starts_at", "time", e.target.value),
|
|
926
|
+
disabled,
|
|
927
|
+
className: "w-32"
|
|
928
|
+
}
|
|
929
|
+
)
|
|
930
|
+
] }),
|
|
931
|
+
errors.starts_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
|
|
932
|
+
] }),
|
|
933
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
934
|
+
/* @__PURE__ */ jsx(Label, { children: "End Date" }),
|
|
935
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
936
|
+
/* @__PURE__ */ jsx(
|
|
937
|
+
Input,
|
|
938
|
+
{
|
|
939
|
+
type: "date",
|
|
940
|
+
value: endsAt ? dayjs(endsAt).format("YYYY-MM-DD") : "",
|
|
941
|
+
onChange: (e) => handleDateTimeChange("ends_at", "date", e.target.value),
|
|
942
|
+
disabled,
|
|
943
|
+
className: "flex-1"
|
|
944
|
+
}
|
|
945
|
+
),
|
|
946
|
+
/* @__PURE__ */ jsx(
|
|
947
|
+
Input,
|
|
948
|
+
{
|
|
949
|
+
type: "time",
|
|
950
|
+
value: endsAt ? dayjs(endsAt).format("HH:mm") : "",
|
|
951
|
+
onChange: (e) => handleDateTimeChange("ends_at", "time", e.target.value),
|
|
952
|
+
disabled,
|
|
953
|
+
className: "w-32"
|
|
954
|
+
}
|
|
955
|
+
)
|
|
956
|
+
] }),
|
|
957
|
+
errors.ends_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
|
|
958
|
+
] })
|
|
959
|
+
] }),
|
|
960
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
961
|
+
/* @__PURE__ */ jsx(Label, { children: "Products" }),
|
|
1329
962
|
/* @__PURE__ */ jsx(
|
|
1330
|
-
|
|
963
|
+
Button,
|
|
1331
964
|
{
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
children:
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
965
|
+
type: "button",
|
|
966
|
+
variant: "secondary",
|
|
967
|
+
onClick: () => setOpenProductModal(true),
|
|
968
|
+
disabled,
|
|
969
|
+
children: "Add Product"
|
|
970
|
+
}
|
|
971
|
+
)
|
|
972
|
+
] }),
|
|
973
|
+
((_a = errors.products) == null ? void 0 : _a.root) && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm", children: errors.products.root.message }),
|
|
974
|
+
/* @__PURE__ */ jsxs(Table, { children: [
|
|
975
|
+
/* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
976
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Product" }),
|
|
977
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Discount Type" }),
|
|
978
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Discount Value" }),
|
|
979
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Limit" }),
|
|
980
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Max Qty per Order" }),
|
|
981
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Actions" })
|
|
982
|
+
] }) }),
|
|
983
|
+
/* @__PURE__ */ jsx(Table.Body, { children: fields.map((field, index) => /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
984
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: field.product.title }),
|
|
985
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
986
|
+
Controller,
|
|
987
|
+
{
|
|
988
|
+
name: `products.${index}.discountType`,
|
|
989
|
+
control,
|
|
990
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsxs(
|
|
991
|
+
Select,
|
|
1354
992
|
{
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
onClick: () => setShowImageUploader("image"),
|
|
993
|
+
value: field2.value,
|
|
994
|
+
onValueChange: field2.onChange,
|
|
995
|
+
disabled,
|
|
1359
996
|
children: [
|
|
1360
|
-
/* @__PURE__ */ jsx(
|
|
1361
|
-
"
|
|
997
|
+
/* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select discount type" }) }),
|
|
998
|
+
/* @__PURE__ */ jsx(Select.Content, { children: /* @__PURE__ */ jsx(Select.Item, { value: "percentage", children: "Percentage" }) })
|
|
1362
999
|
]
|
|
1363
1000
|
}
|
|
1364
1001
|
)
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
}
|
|
1375
|
-
)
|
|
1376
|
-
}
|
|
1377
|
-
)
|
|
1378
|
-
] }),
|
|
1379
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1380
|
-
/* @__PURE__ */ jsx(Label, { children: "Thumbnail" }),
|
|
1381
|
-
/* @__PURE__ */ jsx(
|
|
1382
|
-
"div",
|
|
1383
|
-
{
|
|
1384
|
-
className: clx(
|
|
1385
|
-
"relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
|
|
1386
|
-
(campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
|
|
1387
|
-
),
|
|
1388
|
-
children: (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1389
|
-
/* @__PURE__ */ jsx(
|
|
1390
|
-
"img",
|
|
1002
|
+
}
|
|
1003
|
+
) }),
|
|
1004
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
1005
|
+
Controller,
|
|
1006
|
+
{
|
|
1007
|
+
name: `products.${index}.discountValue`,
|
|
1008
|
+
control,
|
|
1009
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsx(
|
|
1010
|
+
Input,
|
|
1391
1011
|
{
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
console.error(
|
|
1397
|
-
"Failed to load thumbnail:",
|
|
1398
|
-
campaignDetail.thumbnail_url
|
|
1399
|
-
);
|
|
1400
|
-
e.currentTarget.style.display = "none";
|
|
1401
|
-
}
|
|
1012
|
+
type: "number",
|
|
1013
|
+
value: field2.value,
|
|
1014
|
+
onChange: (e) => field2.onChange(Number(e.target.value)),
|
|
1015
|
+
disabled
|
|
1402
1016
|
}
|
|
1403
|
-
)
|
|
1404
|
-
|
|
1405
|
-
|
|
1017
|
+
)
|
|
1018
|
+
}
|
|
1019
|
+
) }),
|
|
1020
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
1021
|
+
Controller,
|
|
1022
|
+
{
|
|
1023
|
+
name: `products.${index}.limit`,
|
|
1024
|
+
control,
|
|
1025
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsx(
|
|
1026
|
+
Input,
|
|
1406
1027
|
{
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
children: [
|
|
1412
|
-
/* @__PURE__ */ jsx(PencilSquare, { className: "mr-1" }),
|
|
1413
|
-
"Change"
|
|
1414
|
-
]
|
|
1028
|
+
type: "number",
|
|
1029
|
+
value: field2.value,
|
|
1030
|
+
onChange: (e) => field2.onChange(Number(e.target.value)),
|
|
1031
|
+
disabled
|
|
1415
1032
|
}
|
|
1416
1033
|
)
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1034
|
+
}
|
|
1035
|
+
) }),
|
|
1036
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
1037
|
+
Controller,
|
|
1038
|
+
{
|
|
1039
|
+
name: `products.${index}.maxQty`,
|
|
1040
|
+
control,
|
|
1041
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsx(
|
|
1042
|
+
Input,
|
|
1043
|
+
{
|
|
1044
|
+
type: "number",
|
|
1045
|
+
value: field2.value,
|
|
1046
|
+
onChange: (e) => field2.onChange(Number(e.target.value)),
|
|
1047
|
+
disabled
|
|
1048
|
+
}
|
|
1049
|
+
)
|
|
1050
|
+
}
|
|
1051
|
+
) }),
|
|
1052
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
1053
|
+
Button,
|
|
1054
|
+
{
|
|
1055
|
+
type: "button",
|
|
1056
|
+
variant: "danger",
|
|
1057
|
+
onClick: () => remove(index),
|
|
1058
|
+
disabled,
|
|
1059
|
+
children: /* @__PURE__ */ jsx(Trash, {})
|
|
1060
|
+
}
|
|
1061
|
+
) })
|
|
1062
|
+
] }, field.id)) })
|
|
1063
|
+
] }),
|
|
1064
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", disabled, children: "Save" })
|
|
1065
|
+
]
|
|
1066
|
+
}
|
|
1067
|
+
),
|
|
1068
|
+
/* @__PURE__ */ jsx(FocusModal, { open: openProductModal, onOpenChange: setOpenProductModal, children: /* @__PURE__ */ jsxs(FocusModal.Content, { children: [
|
|
1069
|
+
/* @__PURE__ */ jsx(FocusModal.Header, { title: "Add Product" }),
|
|
1070
|
+
/* @__PURE__ */ jsx(FocusModal.Body, { children: /* @__PURE__ */ jsx(
|
|
1071
|
+
ProductSelector,
|
|
1072
|
+
{
|
|
1073
|
+
selectedProductIds: fields.map((f) => f.product.id),
|
|
1074
|
+
onSelectProduct: (product) => {
|
|
1075
|
+
append({
|
|
1076
|
+
product,
|
|
1077
|
+
discountType: "percentage",
|
|
1078
|
+
discountValue: 10,
|
|
1079
|
+
limit: 10,
|
|
1080
|
+
maxQty: 1
|
|
1081
|
+
});
|
|
1082
|
+
setOpenProductModal(false);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
) })
|
|
1086
|
+
] }) })
|
|
1087
|
+
] });
|
|
1088
|
+
};
|
|
1089
|
+
const useFlashSaleById = (id) => {
|
|
1090
|
+
const [data, setData] = useState(null);
|
|
1091
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
1092
|
+
const [error, setError] = useState(null);
|
|
1093
|
+
const fetchFlashSale = async () => {
|
|
1094
|
+
if (!id) {
|
|
1095
|
+
setIsLoading(false);
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
setIsLoading(true);
|
|
1099
|
+
setError(null);
|
|
1100
|
+
try {
|
|
1101
|
+
const response = await axios.get(`/admin/flash-sales/${id}`);
|
|
1102
|
+
setData(response.data);
|
|
1103
|
+
} catch (err) {
|
|
1104
|
+
setError(
|
|
1105
|
+
err instanceof Error ? err : new Error("Failed to fetch flash sale")
|
|
1106
|
+
);
|
|
1107
|
+
} finally {
|
|
1108
|
+
setIsLoading(false);
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
useEffect(() => {
|
|
1112
|
+
fetchFlashSale();
|
|
1113
|
+
}, [id]);
|
|
1114
|
+
return {
|
|
1115
|
+
data,
|
|
1116
|
+
isLoading,
|
|
1117
|
+
error,
|
|
1118
|
+
refetch: fetchFlashSale
|
|
1119
|
+
};
|
|
1120
|
+
};
|
|
1121
|
+
const MarkdownEditor = ({
|
|
1122
|
+
label,
|
|
1123
|
+
value,
|
|
1124
|
+
onChange,
|
|
1125
|
+
placeholder,
|
|
1126
|
+
helpText,
|
|
1127
|
+
rows = 10,
|
|
1128
|
+
showPreview = true
|
|
1129
|
+
}) => {
|
|
1130
|
+
const [activeTab, setActiveTab] = useState("write");
|
|
1131
|
+
const insertMarkdown = (before, after = "") => {
|
|
1132
|
+
const textarea = document.getElementById(
|
|
1133
|
+
`markdown-${label}`
|
|
1134
|
+
);
|
|
1135
|
+
if (!textarea) return;
|
|
1136
|
+
const start = textarea.selectionStart;
|
|
1137
|
+
const end = textarea.selectionEnd;
|
|
1138
|
+
const selectedText = value.substring(start, end);
|
|
1139
|
+
const newText = value.substring(0, start) + before + selectedText + after + value.substring(end);
|
|
1140
|
+
onChange(newText);
|
|
1141
|
+
setTimeout(() => {
|
|
1142
|
+
textarea.focus();
|
|
1143
|
+
textarea.setSelectionRange(
|
|
1144
|
+
start + before.length,
|
|
1145
|
+
start + before.length + selectedText.length
|
|
1146
|
+
);
|
|
1147
|
+
}, 0);
|
|
1148
|
+
};
|
|
1149
|
+
const renderMarkdownPreview = (markdown) => {
|
|
1150
|
+
let html = markdown;
|
|
1151
|
+
html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
|
|
1152
|
+
html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
|
|
1153
|
+
html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
|
|
1154
|
+
html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
1155
|
+
html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
|
|
1156
|
+
html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
|
|
1157
|
+
html = html.replace(/_(.+?)_/g, "<em>$1</em>");
|
|
1158
|
+
html = html.replace(
|
|
1159
|
+
/```(\w+)?\n([\s\S]+?)```/g,
|
|
1160
|
+
'<pre class="bg-ui-bg-subtle p-4 rounded-lg overflow-x-auto"><code class="language-$1">$2</code></pre>'
|
|
1161
|
+
);
|
|
1162
|
+
html = html.replace(
|
|
1163
|
+
/`([^`]+)`/g,
|
|
1164
|
+
'<code class="bg-ui-bg-subtle px-1 py-0.5 rounded text-sm">$1</code>'
|
|
1165
|
+
);
|
|
1166
|
+
html = html.replace(
|
|
1167
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
1168
|
+
'<a href="$2" class="text-ui-fg-interactive underline">$1</a>'
|
|
1169
|
+
);
|
|
1170
|
+
html = html.replace(
|
|
1171
|
+
/!\[([^\]]*)\]\(([^)]+)\)/g,
|
|
1172
|
+
'<img src="$2" alt="$1" class="max-w-full h-auto rounded-lg my-2" />'
|
|
1173
|
+
);
|
|
1174
|
+
html = html.replace(/^\* (.+)$/gim, "<li>$1</li>");
|
|
1175
|
+
html = html.replace(/^\- (.+)$/gim, "<li>$1</li>");
|
|
1176
|
+
html = html.replace(
|
|
1177
|
+
/(<li>.*<\/li>)/s,
|
|
1178
|
+
"<ul class='list-disc ml-6'>$1</ul>"
|
|
1179
|
+
);
|
|
1180
|
+
html = html.replace(/\n\n/g, "</p><p>");
|
|
1181
|
+
html = `<p>${html}</p>`;
|
|
1182
|
+
return html;
|
|
1183
|
+
};
|
|
1184
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1185
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1186
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `markdown-${label}`, className: "font-medium", children: label }),
|
|
1187
|
+
helpText && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-ui-fg-subtle", children: [
|
|
1188
|
+
/* @__PURE__ */ jsx(InformationCircle, { className: "h-4 w-4" }),
|
|
1189
|
+
/* @__PURE__ */ jsx("span", { children: helpText })
|
|
1190
|
+
] })
|
|
1191
|
+
] }),
|
|
1192
|
+
showPreview ? /* @__PURE__ */ jsxs(Tabs, { value: activeTab, onValueChange: (v) => setActiveTab(v), children: [
|
|
1193
|
+
/* @__PURE__ */ jsxs(Tabs.List, { children: [
|
|
1194
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "write", children: "Write" }),
|
|
1195
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "preview", children: "Preview" })
|
|
1432
1196
|
] }),
|
|
1433
|
-
/* @__PURE__ */ jsx("
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1197
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "write", className: "mt-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1198
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
|
|
1199
|
+
/* @__PURE__ */ jsx(
|
|
1200
|
+
Button,
|
|
1201
|
+
{
|
|
1202
|
+
type: "button",
|
|
1203
|
+
variant: "secondary",
|
|
1204
|
+
size: "small",
|
|
1205
|
+
onClick: () => insertMarkdown("**", "**"),
|
|
1206
|
+
title: "Bold",
|
|
1207
|
+
children: /* @__PURE__ */ jsx("strong", { children: "B" })
|
|
1208
|
+
}
|
|
1209
|
+
),
|
|
1210
|
+
/* @__PURE__ */ jsx(
|
|
1211
|
+
Button,
|
|
1212
|
+
{
|
|
1213
|
+
type: "button",
|
|
1214
|
+
variant: "secondary",
|
|
1215
|
+
size: "small",
|
|
1216
|
+
onClick: () => insertMarkdown("*", "*"),
|
|
1217
|
+
title: "Italic",
|
|
1218
|
+
children: /* @__PURE__ */ jsx("em", { children: "I" })
|
|
1219
|
+
}
|
|
1220
|
+
),
|
|
1221
|
+
/* @__PURE__ */ jsx(
|
|
1222
|
+
Button,
|
|
1223
|
+
{
|
|
1224
|
+
type: "button",
|
|
1225
|
+
variant: "secondary",
|
|
1226
|
+
size: "small",
|
|
1227
|
+
onClick: () => insertMarkdown("`", "`"),
|
|
1228
|
+
title: "Inline Code",
|
|
1229
|
+
children: /* @__PURE__ */ jsx(CommandLine, { className: "h-4 w-4" })
|
|
1230
|
+
}
|
|
1231
|
+
),
|
|
1232
|
+
/* @__PURE__ */ jsx(
|
|
1233
|
+
Button,
|
|
1234
|
+
{
|
|
1235
|
+
type: "button",
|
|
1236
|
+
variant: "secondary",
|
|
1237
|
+
size: "small",
|
|
1238
|
+
onClick: () => insertMarkdown("## "),
|
|
1239
|
+
title: "Heading",
|
|
1240
|
+
children: "H2"
|
|
1241
|
+
}
|
|
1242
|
+
),
|
|
1243
|
+
/* @__PURE__ */ jsx(
|
|
1244
|
+
Button,
|
|
1245
|
+
{
|
|
1246
|
+
type: "button",
|
|
1247
|
+
variant: "secondary",
|
|
1248
|
+
size: "small",
|
|
1249
|
+
onClick: () => insertMarkdown("### "),
|
|
1250
|
+
title: "Heading",
|
|
1251
|
+
children: "H3"
|
|
1252
|
+
}
|
|
1253
|
+
),
|
|
1254
|
+
/* @__PURE__ */ jsx(
|
|
1255
|
+
Button,
|
|
1256
|
+
{
|
|
1257
|
+
type: "button",
|
|
1258
|
+
variant: "secondary",
|
|
1259
|
+
size: "small",
|
|
1260
|
+
onClick: () => insertMarkdown("[", "](url)"),
|
|
1261
|
+
title: "Link",
|
|
1262
|
+
children: "Link"
|
|
1263
|
+
}
|
|
1264
|
+
),
|
|
1265
|
+
/* @__PURE__ */ jsx(
|
|
1266
|
+
Button,
|
|
1267
|
+
{
|
|
1268
|
+
type: "button",
|
|
1269
|
+
variant: "secondary",
|
|
1270
|
+
size: "small",
|
|
1271
|
+
onClick: () => insertMarkdown(""),
|
|
1272
|
+
title: "Image",
|
|
1273
|
+
children: "Image"
|
|
1274
|
+
}
|
|
1275
|
+
),
|
|
1276
|
+
/* @__PURE__ */ jsx(
|
|
1277
|
+
Button,
|
|
1278
|
+
{
|
|
1279
|
+
type: "button",
|
|
1280
|
+
variant: "secondary",
|
|
1281
|
+
size: "small",
|
|
1282
|
+
onClick: () => insertMarkdown("```\n", "\n```"),
|
|
1283
|
+
title: "Code Block",
|
|
1284
|
+
children: "Code"
|
|
1285
|
+
}
|
|
1286
|
+
),
|
|
1287
|
+
/* @__PURE__ */ jsx(
|
|
1288
|
+
Button,
|
|
1289
|
+
{
|
|
1290
|
+
type: "button",
|
|
1291
|
+
variant: "secondary",
|
|
1292
|
+
size: "small",
|
|
1293
|
+
onClick: () => insertMarkdown("- "),
|
|
1294
|
+
title: "List",
|
|
1295
|
+
children: "List"
|
|
1296
|
+
}
|
|
1297
|
+
)
|
|
1298
|
+
] }),
|
|
1299
|
+
/* @__PURE__ */ jsx(
|
|
1300
|
+
Textarea,
|
|
1301
|
+
{
|
|
1302
|
+
id: `markdown-${label}`,
|
|
1303
|
+
value,
|
|
1304
|
+
onChange: (e) => onChange(e.target.value),
|
|
1305
|
+
placeholder,
|
|
1306
|
+
rows,
|
|
1307
|
+
className: "font-mono text-sm"
|
|
1308
|
+
}
|
|
1309
|
+
)
|
|
1310
|
+
] }) }),
|
|
1311
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "preview", className: "mt-4", children: /* @__PURE__ */ jsx(
|
|
1312
|
+
"div",
|
|
1447
1313
|
{
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
}
|
|
1456
|
-
) })
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
/* @__PURE__ */
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "meta-description", children: "Meta Description" }),
|
|
1504
|
-
/* @__PURE__ */ jsx(
|
|
1505
|
-
Input,
|
|
1506
|
-
{
|
|
1507
|
-
id: "meta-description",
|
|
1508
|
-
value: formData.meta_description,
|
|
1509
|
-
onChange: (e) => setFormData({
|
|
1510
|
-
...formData,
|
|
1511
|
-
meta_description: e.target.value
|
|
1512
|
-
}),
|
|
1513
|
-
placeholder: "Campaign meta description for SEO"
|
|
1514
|
-
}
|
|
1515
|
-
)
|
|
1516
|
-
] }),
|
|
1517
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1518
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "meta-keywords", children: "Meta Keywords" }),
|
|
1519
|
-
/* @__PURE__ */ jsx(
|
|
1520
|
-
Input,
|
|
1521
|
-
{
|
|
1522
|
-
id: "meta-keywords",
|
|
1523
|
-
value: formData.meta_keywords,
|
|
1524
|
-
onChange: (e) => setFormData({
|
|
1525
|
-
...formData,
|
|
1526
|
-
meta_keywords: e.target.value
|
|
1527
|
-
}),
|
|
1528
|
-
placeholder: "keyword1, keyword2, keyword3"
|
|
1529
|
-
}
|
|
1530
|
-
),
|
|
1531
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Comma-separated keywords for SEO" })
|
|
1532
|
-
] }),
|
|
1533
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1534
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "display-order", children: "Display Order" }),
|
|
1535
|
-
/* @__PURE__ */ jsx(
|
|
1536
|
-
Input,
|
|
1537
|
-
{
|
|
1538
|
-
id: "display-order",
|
|
1539
|
-
type: "number",
|
|
1540
|
-
value: formData.display_order,
|
|
1541
|
-
onChange: (e) => setFormData({
|
|
1542
|
-
...formData,
|
|
1543
|
-
display_order: parseInt(e.target.value) || 0
|
|
1544
|
-
}),
|
|
1545
|
-
placeholder: "0"
|
|
1546
|
-
}
|
|
1547
|
-
),
|
|
1548
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Lower numbers appear first in listings" })
|
|
1549
|
-
] })
|
|
1550
|
-
] })
|
|
1314
|
+
className: clx(
|
|
1315
|
+
"min-h-[200px] rounded-lg border border-ui-border-base bg-ui-bg-subtle p-4",
|
|
1316
|
+
"prose prose-sm max-w-none"
|
|
1317
|
+
),
|
|
1318
|
+
dangerouslySetInnerHTML: {
|
|
1319
|
+
__html: value ? renderMarkdownPreview(value) : '<p class="text-ui-fg-subtle">Nothing to preview</p>'
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
) })
|
|
1323
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1324
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
|
|
1325
|
+
/* @__PURE__ */ jsx(
|
|
1326
|
+
Button,
|
|
1327
|
+
{
|
|
1328
|
+
type: "button",
|
|
1329
|
+
variant: "secondary",
|
|
1330
|
+
size: "small",
|
|
1331
|
+
onClick: () => insertMarkdown("**", "**"),
|
|
1332
|
+
title: "Bold",
|
|
1333
|
+
children: /* @__PURE__ */ jsx("strong", { children: "B" })
|
|
1334
|
+
}
|
|
1335
|
+
),
|
|
1336
|
+
/* @__PURE__ */ jsx(
|
|
1337
|
+
Button,
|
|
1338
|
+
{
|
|
1339
|
+
type: "button",
|
|
1340
|
+
variant: "secondary",
|
|
1341
|
+
size: "small",
|
|
1342
|
+
onClick: () => insertMarkdown("*", "*"),
|
|
1343
|
+
title: "Italic",
|
|
1344
|
+
children: /* @__PURE__ */ jsx("em", { children: "I" })
|
|
1345
|
+
}
|
|
1346
|
+
),
|
|
1347
|
+
/* @__PURE__ */ jsx(
|
|
1348
|
+
Button,
|
|
1349
|
+
{
|
|
1350
|
+
type: "button",
|
|
1351
|
+
variant: "secondary",
|
|
1352
|
+
size: "small",
|
|
1353
|
+
onClick: () => insertMarkdown("`", "`"),
|
|
1354
|
+
title: "Inline Code",
|
|
1355
|
+
children: /* @__PURE__ */ jsx(CommandLine, { className: "h-4 w-4" })
|
|
1356
|
+
}
|
|
1357
|
+
),
|
|
1358
|
+
/* @__PURE__ */ jsx(
|
|
1359
|
+
Button,
|
|
1360
|
+
{
|
|
1361
|
+
type: "button",
|
|
1362
|
+
variant: "secondary",
|
|
1363
|
+
size: "small",
|
|
1364
|
+
onClick: () => insertMarkdown("```\n", "\n```"),
|
|
1365
|
+
title: "Code Block",
|
|
1366
|
+
children: "Code"
|
|
1367
|
+
}
|
|
1368
|
+
)
|
|
1551
1369
|
] }),
|
|
1552
|
-
/* @__PURE__ */ jsx(
|
|
1370
|
+
/* @__PURE__ */ jsx(
|
|
1371
|
+
Textarea,
|
|
1372
|
+
{
|
|
1373
|
+
id: `markdown-${label}`,
|
|
1374
|
+
value,
|
|
1375
|
+
onChange: (e) => onChange(e.target.value),
|
|
1376
|
+
placeholder,
|
|
1377
|
+
rows,
|
|
1378
|
+
className: "font-mono text-sm"
|
|
1379
|
+
}
|
|
1380
|
+
)
|
|
1553
1381
|
] }),
|
|
1554
|
-
|
|
1555
|
-
CampaignImageUploader,
|
|
1556
|
-
{
|
|
1557
|
-
campaignId,
|
|
1558
|
-
imageType: showImageUploader,
|
|
1559
|
-
currentImageUrl: showImageUploader === "image" ? campaignDetail == null ? void 0 : campaignDetail.image_url : campaignDetail == null ? void 0 : campaignDetail.thumbnail_url,
|
|
1560
|
-
onClose: () => setShowImageUploader(null),
|
|
1561
|
-
onSuccess: fetchCampaignDetail
|
|
1562
|
-
}
|
|
1563
|
-
)
|
|
1382
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-ui-fg-subtle", children: "Supports Markdown formatting: **bold**, *italic*, `code`, ```code blocks```, [links](url), , and lists" })
|
|
1564
1383
|
] });
|
|
1565
1384
|
};
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
const
|
|
1579
|
-
const
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
type: "coupon",
|
|
1584
|
-
code: (promotion == null ? void 0 : promotion.code) ?? "",
|
|
1585
|
-
discount_type: ((_b = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _b.type) ?? "percentage",
|
|
1586
|
-
discount_value: Number(((_c = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _c.value) ?? 0),
|
|
1587
|
-
currency_code: ((_d = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _d.currency_code) ?? void 0,
|
|
1588
|
-
starts_at: campaign.starts_at ? dayjs(campaign.starts_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
1589
|
-
ends_at: campaign.ends_at ? dayjs(campaign.ends_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
1590
|
-
allocation: ((_e = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _e.allocation) === "total" ? "across" : ((_f = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _f.allocation) ?? "across",
|
|
1591
|
-
target_type: ((_g = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _g.target_type) ?? "order"
|
|
1385
|
+
const CampaignImageUploader = ({
|
|
1386
|
+
campaignId,
|
|
1387
|
+
imageType,
|
|
1388
|
+
currentImageUrl,
|
|
1389
|
+
onClose,
|
|
1390
|
+
onSuccess
|
|
1391
|
+
}) => {
|
|
1392
|
+
const [displayImage, setDisplayImage] = useState(currentImageUrl || null);
|
|
1393
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
1394
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
1395
|
+
const [error, setError] = useState(null);
|
|
1396
|
+
const [previewUrl, setPreviewUrl] = useState(null);
|
|
1397
|
+
const isThumbnail = imageType === "thumbnail";
|
|
1398
|
+
const maxSize = 5;
|
|
1399
|
+
const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
1400
|
+
const formatFileTypes = () => {
|
|
1401
|
+
return "JPEG, PNG, GIF, or WebP";
|
|
1592
1402
|
};
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
return;
|
|
1403
|
+
const validateFile = (file) => {
|
|
1404
|
+
if (!allowedTypes.includes(file.type)) {
|
|
1405
|
+
setError(`Please upload a valid image file (${formatFileTypes()})`);
|
|
1406
|
+
return false;
|
|
1597
1407
|
}
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
1602
|
-
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
1603
|
-
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
1604
|
-
});
|
|
1605
|
-
toast.success("Coupon updated successfully");
|
|
1606
|
-
setIsEditing(false);
|
|
1607
|
-
refetch();
|
|
1608
|
-
} catch (error) {
|
|
1609
|
-
let message = "Failed to update coupon";
|
|
1610
|
-
if (axios.isAxiosError(error)) {
|
|
1611
|
-
message = ((_b2 = (_a2 = error.response) == null ? void 0 : _a2.data) == null ? void 0 : _b2.message) ?? message;
|
|
1612
|
-
} else if (error instanceof Error) {
|
|
1613
|
-
message = error.message;
|
|
1614
|
-
}
|
|
1615
|
-
toast.error("Failed to update coupon", {
|
|
1616
|
-
description: message
|
|
1617
|
-
});
|
|
1408
|
+
if (file.size > maxSize * 1024 * 1024) {
|
|
1409
|
+
setError(`File size must be less than ${maxSize}MB`);
|
|
1410
|
+
return false;
|
|
1618
1411
|
}
|
|
1412
|
+
return true;
|
|
1619
1413
|
};
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
] }),
|
|
1636
|
-
/* @__PURE__ */ jsx(Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
1637
|
-
CouponForm,
|
|
1414
|
+
const handleFileUpload = async (file) => {
|
|
1415
|
+
var _a, _b;
|
|
1416
|
+
if (!validateFile(file)) return;
|
|
1417
|
+
setError(null);
|
|
1418
|
+
const reader = new FileReader();
|
|
1419
|
+
reader.onloadend = () => {
|
|
1420
|
+
setPreviewUrl(reader.result);
|
|
1421
|
+
};
|
|
1422
|
+
reader.readAsDataURL(file);
|
|
1423
|
+
setIsUploading(true);
|
|
1424
|
+
const formData = new FormData();
|
|
1425
|
+
formData.append("file", file);
|
|
1426
|
+
try {
|
|
1427
|
+
const response = await fetch(
|
|
1428
|
+
`/admin/campaigns/${campaignId}/${imageType}`,
|
|
1638
1429
|
{
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
disabled: !isEditing
|
|
1430
|
+
method: "POST",
|
|
1431
|
+
body: formData,
|
|
1432
|
+
credentials: "include"
|
|
1643
1433
|
}
|
|
1644
|
-
)
|
|
1645
|
-
|
|
1646
|
-
|
|
1434
|
+
);
|
|
1435
|
+
if (response.ok) {
|
|
1436
|
+
const result = await response.json();
|
|
1437
|
+
const newImageUrl = imageType === "image" ? (_a = result.campaign_detail) == null ? void 0 : _a.image_url : (_b = result.campaign_detail) == null ? void 0 : _b.thumbnail_url;
|
|
1438
|
+
setDisplayImage(newImageUrl);
|
|
1439
|
+
setPreviewUrl(null);
|
|
1440
|
+
onSuccess == null ? void 0 : onSuccess();
|
|
1441
|
+
setTimeout(() => {
|
|
1442
|
+
onClose();
|
|
1443
|
+
}, 1e3);
|
|
1444
|
+
} else {
|
|
1445
|
+
const errorData = await response.json();
|
|
1446
|
+
setError(errorData.error || `Failed to upload ${imageType}`);
|
|
1447
|
+
setPreviewUrl(null);
|
|
1448
|
+
}
|
|
1449
|
+
} catch (err) {
|
|
1450
|
+
setError(`Error uploading ${imageType}`);
|
|
1451
|
+
setPreviewUrl(null);
|
|
1452
|
+
console.error(`Error uploading ${imageType}:`, err);
|
|
1453
|
+
} finally {
|
|
1454
|
+
setIsUploading(false);
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
const handleDelete = async () => {
|
|
1458
|
+
if (!confirm(`Are you sure you want to delete the ${imageType}?`)) return;
|
|
1459
|
+
setIsUploading(true);
|
|
1460
|
+
setError(null);
|
|
1461
|
+
try {
|
|
1462
|
+
const response = await fetch(
|
|
1463
|
+
`/admin/campaigns/${campaignId}/${imageType}`,
|
|
1647
1464
|
{
|
|
1648
|
-
|
|
1649
|
-
|
|
1465
|
+
method: "DELETE",
|
|
1466
|
+
credentials: "include"
|
|
1650
1467
|
}
|
|
1651
|
-
)
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
});
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
var _a, _b;
|
|
1663
|
-
try {
|
|
1664
|
-
await axios.post("/admin/coupons", {
|
|
1665
|
-
...formData,
|
|
1666
|
-
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
1667
|
-
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
1668
|
-
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
1669
|
-
});
|
|
1670
|
-
toast.success("Coupon created successfully");
|
|
1671
|
-
navigate("/coupons");
|
|
1672
|
-
} catch (error) {
|
|
1673
|
-
let message = "Failed to create coupon";
|
|
1674
|
-
if (axios.isAxiosError(error)) {
|
|
1675
|
-
message = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) ?? message;
|
|
1676
|
-
} else if (error instanceof Error) {
|
|
1677
|
-
message = error.message;
|
|
1468
|
+
);
|
|
1469
|
+
if (response.ok) {
|
|
1470
|
+
setDisplayImage(null);
|
|
1471
|
+
setPreviewUrl(null);
|
|
1472
|
+
onSuccess == null ? void 0 : onSuccess();
|
|
1473
|
+
setTimeout(() => {
|
|
1474
|
+
onClose();
|
|
1475
|
+
}, 500);
|
|
1476
|
+
} else {
|
|
1477
|
+
const errorData = await response.json();
|
|
1478
|
+
setError(errorData.error || `Failed to delete ${imageType}`);
|
|
1678
1479
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
});
|
|
1480
|
+
} catch (err) {
|
|
1481
|
+
setError(`Error deleting ${imageType}`);
|
|
1482
|
+
console.error(`Error deleting ${imageType}:`, err);
|
|
1483
|
+
} finally {
|
|
1484
|
+
setIsUploading(false);
|
|
1682
1485
|
}
|
|
1683
1486
|
};
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
const columnHelper = createDataTableColumnHelper();
|
|
1700
|
-
const columns = [
|
|
1701
|
-
columnHelper.accessor("title", {
|
|
1702
|
-
header: "Title",
|
|
1703
|
-
cell: (info) => /* @__PURE__ */ jsx(Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
1704
|
-
}),
|
|
1705
|
-
columnHelper.accessor("description", {
|
|
1706
|
-
header: "Description",
|
|
1707
|
-
cell: (info) => /* @__PURE__ */ jsx(Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
1708
|
-
})
|
|
1709
|
-
];
|
|
1710
|
-
const ProductSelector = ({
|
|
1711
|
-
selectedProductIds,
|
|
1712
|
-
onSelectProduct
|
|
1713
|
-
}) => {
|
|
1714
|
-
const [search, setSearch] = useState("");
|
|
1715
|
-
const debouncedSearchHandler = debounce((value) => {
|
|
1716
|
-
setSearch(value);
|
|
1717
|
-
}, 500);
|
|
1718
|
-
const [products, setProducts] = useState(null);
|
|
1719
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
1720
|
-
useEffect(() => {
|
|
1721
|
-
const fetchProducts = async () => {
|
|
1722
|
-
setIsLoading(true);
|
|
1723
|
-
try {
|
|
1724
|
-
const result = await sdk.admin.product.list({
|
|
1725
|
-
q: search,
|
|
1726
|
-
limit: 100
|
|
1727
|
-
});
|
|
1728
|
-
setProducts(result);
|
|
1729
|
-
} catch (error) {
|
|
1730
|
-
console.error("Failed to fetch products:", error);
|
|
1731
|
-
} finally {
|
|
1732
|
-
setIsLoading(false);
|
|
1733
|
-
}
|
|
1734
|
-
};
|
|
1735
|
-
fetchProducts();
|
|
1736
|
-
}, [selectedProductIds, search]);
|
|
1737
|
-
const selectableProducts = useMemo(() => {
|
|
1487
|
+
const handleDragEnter = useCallback((e) => {
|
|
1488
|
+
e.preventDefault();
|
|
1489
|
+
e.stopPropagation();
|
|
1490
|
+
setIsDragging(true);
|
|
1491
|
+
}, []);
|
|
1492
|
+
const handleDragLeave = useCallback((e) => {
|
|
1493
|
+
e.preventDefault();
|
|
1494
|
+
e.stopPropagation();
|
|
1495
|
+
setIsDragging(false);
|
|
1496
|
+
}, []);
|
|
1497
|
+
const handleDragOver = useCallback((e) => {
|
|
1498
|
+
e.preventDefault();
|
|
1499
|
+
e.stopPropagation();
|
|
1500
|
+
}, []);
|
|
1501
|
+
const handleDrop = useCallback((e) => {
|
|
1738
1502
|
var _a;
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
);
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
columns,
|
|
1746
|
-
getRowId: (product) => product.id,
|
|
1747
|
-
onRowClick: (_, row) => {
|
|
1748
|
-
onSelectProduct == null ? void 0 : onSelectProduct(row.original);
|
|
1503
|
+
e.preventDefault();
|
|
1504
|
+
e.stopPropagation();
|
|
1505
|
+
setIsDragging(false);
|
|
1506
|
+
const file = (_a = e.dataTransfer.files) == null ? void 0 : _a[0];
|
|
1507
|
+
if (file) {
|
|
1508
|
+
handleFileUpload(file);
|
|
1749
1509
|
}
|
|
1750
|
-
});
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
{
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
);
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1510
|
+
}, []);
|
|
1511
|
+
const imageToDisplay = previewUrl || displayImage;
|
|
1512
|
+
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-2xl rounded-lg bg-ui-bg-base p-6", children: [
|
|
1513
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
1514
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1515
|
+
/* @__PURE__ */ jsxs(Heading, { level: "h2", children: [
|
|
1516
|
+
"Upload Campaign ",
|
|
1517
|
+
isThumbnail ? "Thumbnail" : "Image"
|
|
1518
|
+
] }),
|
|
1519
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: isThumbnail ? "Thumbnail for campaign listing" : "Main campaign image" })
|
|
1520
|
+
] }),
|
|
1521
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsx(X, {}) })
|
|
1522
|
+
] }),
|
|
1523
|
+
error && /* @__PURE__ */ jsx(Alert, { variant: "error", dismissible: true, className: "mb-4", children: error }),
|
|
1524
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1525
|
+
imageToDisplay ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1526
|
+
/* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-subtle", children: [
|
|
1527
|
+
/* @__PURE__ */ jsx(
|
|
1528
|
+
"img",
|
|
1529
|
+
{
|
|
1530
|
+
src: imageToDisplay,
|
|
1531
|
+
alt: `Campaign ${imageType}`,
|
|
1532
|
+
className: clx(
|
|
1533
|
+
"w-full object-contain",
|
|
1534
|
+
isThumbnail ? "h-32" : "h-64"
|
|
1535
|
+
)
|
|
1536
|
+
}
|
|
1537
|
+
),
|
|
1538
|
+
isUploading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsx("div", { className: "text-white", children: "Uploading..." }) })
|
|
1539
|
+
] }),
|
|
1540
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1541
|
+
/* @__PURE__ */ jsxs(
|
|
1542
|
+
Button,
|
|
1543
|
+
{
|
|
1544
|
+
variant: "secondary",
|
|
1545
|
+
disabled: isUploading,
|
|
1546
|
+
onClick: () => {
|
|
1547
|
+
var _a;
|
|
1548
|
+
return (_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click();
|
|
1549
|
+
},
|
|
1550
|
+
children: [
|
|
1551
|
+
/* @__PURE__ */ jsx(CloudArrowUp, { className: "mr-2" }),
|
|
1552
|
+
"Replace"
|
|
1553
|
+
]
|
|
1554
|
+
}
|
|
1555
|
+
),
|
|
1556
|
+
/* @__PURE__ */ jsxs(
|
|
1557
|
+
Button,
|
|
1558
|
+
{
|
|
1559
|
+
variant: "danger",
|
|
1560
|
+
disabled: isUploading,
|
|
1561
|
+
onClick: handleDelete,
|
|
1562
|
+
children: [
|
|
1563
|
+
/* @__PURE__ */ jsx(Trash, { className: "mr-2" }),
|
|
1564
|
+
"Delete"
|
|
1565
|
+
]
|
|
1566
|
+
}
|
|
1567
|
+
)
|
|
1568
|
+
] })
|
|
1569
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
1570
|
+
"div",
|
|
1571
|
+
{
|
|
1572
|
+
className: clx(
|
|
1573
|
+
"flex flex-col items-center justify-center rounded-lg border-2 border-dashed transition-colors",
|
|
1574
|
+
isThumbnail ? "h-48" : "h-64",
|
|
1575
|
+
isDragging ? "border-ui-border-interactive bg-ui-bg-highlight" : "border-ui-border-base bg-ui-bg-subtle",
|
|
1576
|
+
!isUploading && "cursor-pointer"
|
|
1577
|
+
),
|
|
1578
|
+
onDragEnter: handleDragEnter,
|
|
1579
|
+
onDragLeave: handleDragLeave,
|
|
1580
|
+
onDragOver: handleDragOver,
|
|
1581
|
+
onDrop: handleDrop,
|
|
1582
|
+
onClick: () => {
|
|
1583
|
+
var _a;
|
|
1584
|
+
return !isUploading && ((_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click());
|
|
1585
|
+
},
|
|
1586
|
+
children: [
|
|
1587
|
+
/* @__PURE__ */ jsx(PhotoSolid, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }),
|
|
1588
|
+
/* @__PURE__ */ jsx(Text, { className: "mb-2 text-lg font-medium", children: isDragging ? `Drop ${imageType} here` : `Upload ${isThumbnail ? "Thumbnail" : "Image"}` }),
|
|
1589
|
+
/* @__PURE__ */ jsx(Text, { className: "mb-4 text-center text-ui-fg-subtle", children: "Drag and drop an image here, or click to select" }),
|
|
1590
|
+
/* @__PURE__ */ jsxs(Text, { className: "text-sm text-ui-fg-subtle", children: [
|
|
1591
|
+
formatFileTypes(),
|
|
1592
|
+
" • Max ",
|
|
1593
|
+
maxSize,
|
|
1594
|
+
"MB"
|
|
1595
|
+
] }),
|
|
1596
|
+
isThumbnail && /* @__PURE__ */ jsx(Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: 16:9 aspect ratio, minimum 400x225px" }),
|
|
1597
|
+
!isThumbnail && /* @__PURE__ */ jsx(Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: High resolution, minimum 1200x600px" }),
|
|
1598
|
+
isUploading && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(Text, { children: "Uploading..." }) })
|
|
1599
|
+
]
|
|
1600
|
+
}
|
|
1601
|
+
),
|
|
1602
|
+
/* @__PURE__ */ jsx(
|
|
1603
|
+
"input",
|
|
1604
|
+
{
|
|
1605
|
+
id: `${imageType}-file-input`,
|
|
1606
|
+
type: "file",
|
|
1607
|
+
accept: allowedTypes.join(","),
|
|
1608
|
+
onChange: (e) => {
|
|
1609
|
+
var _a;
|
|
1610
|
+
const file = (_a = e.target.files) == null ? void 0 : _a[0];
|
|
1611
|
+
if (file) handleFileUpload(file);
|
|
1612
|
+
e.target.value = "";
|
|
1613
|
+
},
|
|
1614
|
+
className: "hidden"
|
|
1615
|
+
}
|
|
1616
|
+
)
|
|
1617
|
+
] }),
|
|
1618
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-between border-t pt-4", children: [
|
|
1619
|
+
/* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: isThumbnail ? "Thumbnail will be displayed in campaign listings" : "Main image for the campaign detail page" }),
|
|
1620
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: "Close" })
|
|
1621
|
+
] })
|
|
1622
|
+
] }) });
|
|
1623
|
+
};
|
|
1624
|
+
const CampaignDetailForm = ({
|
|
1625
|
+
campaignId,
|
|
1626
|
+
campaignName
|
|
1795
1627
|
}) => {
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
ends_at: (initialData == null ? void 0 : initialData.ends_at) || "",
|
|
1812
|
-
products: initialProducts ? Array.from(initialProducts.values()) : []
|
|
1813
|
-
}
|
|
1814
|
-
});
|
|
1815
|
-
const { fields, append, remove, update } = useFieldArray({
|
|
1816
|
-
control,
|
|
1817
|
-
name: "products"
|
|
1628
|
+
const [campaignDetail, setCampaignDetail] = useState(
|
|
1629
|
+
null
|
|
1630
|
+
);
|
|
1631
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
1632
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
1633
|
+
const [showImageUploader, setShowImageUploader] = useState(null);
|
|
1634
|
+
const [formData, setFormData] = useState({
|
|
1635
|
+
detail_content: "",
|
|
1636
|
+
terms_and_conditions: "",
|
|
1637
|
+
meta_title: "",
|
|
1638
|
+
meta_description: "",
|
|
1639
|
+
meta_keywords: "",
|
|
1640
|
+
link_url: "",
|
|
1641
|
+
link_text: "",
|
|
1642
|
+
display_order: 0
|
|
1818
1643
|
});
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
const
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
const
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1644
|
+
useEffect(() => {
|
|
1645
|
+
fetchCampaignDetail();
|
|
1646
|
+
}, [campaignId]);
|
|
1647
|
+
const fetchCampaignDetail = async () => {
|
|
1648
|
+
setIsLoading(true);
|
|
1649
|
+
try {
|
|
1650
|
+
const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
|
|
1651
|
+
credentials: "include"
|
|
1652
|
+
});
|
|
1653
|
+
if (response.ok) {
|
|
1654
|
+
const data = await response.json();
|
|
1655
|
+
if (data.campaign_detail) {
|
|
1656
|
+
console.log("Campaign detail loaded:", {
|
|
1657
|
+
image_url: data.campaign_detail.image_url,
|
|
1658
|
+
thumbnail_url: data.campaign_detail.thumbnail_url,
|
|
1659
|
+
decoded_image: data.campaign_detail.image_url ? decodeURIComponent(data.campaign_detail.image_url) : null,
|
|
1660
|
+
decoded_thumbnail: data.campaign_detail.thumbnail_url ? decodeURIComponent(data.campaign_detail.thumbnail_url) : null
|
|
1661
|
+
});
|
|
1662
|
+
setCampaignDetail(data.campaign_detail);
|
|
1663
|
+
setFormData({
|
|
1664
|
+
detail_content: data.campaign_detail.detail_content || "",
|
|
1665
|
+
terms_and_conditions: data.campaign_detail.terms_and_conditions || "",
|
|
1666
|
+
meta_title: data.campaign_detail.meta_title || "",
|
|
1667
|
+
meta_description: data.campaign_detail.meta_description || "",
|
|
1668
|
+
meta_keywords: data.campaign_detail.meta_keywords || "",
|
|
1669
|
+
link_url: data.campaign_detail.link_url || "",
|
|
1670
|
+
link_text: data.campaign_detail.link_text || "",
|
|
1671
|
+
display_order: data.campaign_detail.display_order || 0
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
} else if (response.status !== 404) {
|
|
1675
|
+
console.error("Failed to fetch campaign detail");
|
|
1676
|
+
}
|
|
1677
|
+
} catch (error) {
|
|
1678
|
+
console.error("Error fetching campaign detail:", error);
|
|
1679
|
+
} finally {
|
|
1680
|
+
setIsLoading(false);
|
|
1830
1681
|
}
|
|
1831
1682
|
};
|
|
1832
|
-
const
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1683
|
+
const handleSave = async () => {
|
|
1684
|
+
setIsSaving(true);
|
|
1685
|
+
try {
|
|
1686
|
+
const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
|
|
1687
|
+
method: "POST",
|
|
1688
|
+
headers: {
|
|
1689
|
+
"Content-Type": "application/json"
|
|
1690
|
+
},
|
|
1691
|
+
body: JSON.stringify(formData),
|
|
1692
|
+
credentials: "include"
|
|
1693
|
+
});
|
|
1694
|
+
if (response.ok) {
|
|
1695
|
+
const data = await response.json();
|
|
1696
|
+
setCampaignDetail(data.campaign_detail);
|
|
1697
|
+
toast.success("Campaign details saved successfully");
|
|
1698
|
+
} else {
|
|
1699
|
+
const errorData = await response.json();
|
|
1700
|
+
toast.error(errorData.error || "Failed to save campaign details");
|
|
1842
1701
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
console.error("Error saving campaign detail:", error);
|
|
1704
|
+
toast.error("Error saving campaign details");
|
|
1705
|
+
} finally {
|
|
1706
|
+
setIsSaving(false);
|
|
1707
|
+
}
|
|
1848
1708
|
};
|
|
1849
|
-
|
|
1850
|
-
/* @__PURE__ */
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
children:
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
/* @__PURE__ */ jsx(Label, { children: "Description" }),
|
|
1867
|
-
/* @__PURE__ */ jsx(Textarea, { ...register("description"), disabled }),
|
|
1868
|
-
errors.description && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
|
|
1869
|
-
] }),
|
|
1870
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1871
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
1872
|
-
/* @__PURE__ */ jsx(Label, { children: "Start Date" }),
|
|
1873
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1874
|
-
/* @__PURE__ */ jsx(
|
|
1875
|
-
Input,
|
|
1876
|
-
{
|
|
1877
|
-
type: "date",
|
|
1878
|
-
value: startsAt ? dayjs(startsAt).format("YYYY-MM-DD") : "",
|
|
1879
|
-
onChange: (e) => handleDateTimeChange("starts_at", "date", e.target.value),
|
|
1880
|
-
disabled,
|
|
1881
|
-
className: "flex-1"
|
|
1882
|
-
}
|
|
1883
|
-
),
|
|
1884
|
-
/* @__PURE__ */ jsx(
|
|
1885
|
-
Input,
|
|
1886
|
-
{
|
|
1887
|
-
type: "time",
|
|
1888
|
-
value: startsAt ? dayjs(startsAt).format("HH:mm") : "",
|
|
1889
|
-
onChange: (e) => handleDateTimeChange("starts_at", "time", e.target.value),
|
|
1890
|
-
disabled,
|
|
1891
|
-
className: "w-32"
|
|
1892
|
-
}
|
|
1893
|
-
)
|
|
1894
|
-
] }),
|
|
1895
|
-
errors.starts_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
|
|
1896
|
-
] }),
|
|
1897
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
1898
|
-
/* @__PURE__ */ jsx(Label, { children: "End Date" }),
|
|
1899
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1900
|
-
/* @__PURE__ */ jsx(
|
|
1901
|
-
Input,
|
|
1902
|
-
{
|
|
1903
|
-
type: "date",
|
|
1904
|
-
value: endsAt ? dayjs(endsAt).format("YYYY-MM-DD") : "",
|
|
1905
|
-
onChange: (e) => handleDateTimeChange("ends_at", "date", e.target.value),
|
|
1906
|
-
disabled,
|
|
1907
|
-
className: "flex-1"
|
|
1908
|
-
}
|
|
1909
|
-
),
|
|
1910
|
-
/* @__PURE__ */ jsx(
|
|
1911
|
-
Input,
|
|
1912
|
-
{
|
|
1913
|
-
type: "time",
|
|
1914
|
-
value: endsAt ? dayjs(endsAt).format("HH:mm") : "",
|
|
1915
|
-
onChange: (e) => handleDateTimeChange("ends_at", "time", e.target.value),
|
|
1916
|
-
disabled,
|
|
1917
|
-
className: "w-32"
|
|
1918
|
-
}
|
|
1919
|
-
)
|
|
1920
|
-
] }),
|
|
1921
|
-
errors.ends_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
|
|
1922
|
-
] })
|
|
1923
|
-
] }),
|
|
1924
|
-
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
1925
|
-
/* @__PURE__ */ jsx(Label, { children: "Products" }),
|
|
1709
|
+
if (isLoading) {
|
|
1710
|
+
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { className: "py-8 text-center", children: "Loading campaign details..." }) });
|
|
1711
|
+
}
|
|
1712
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1713
|
+
/* @__PURE__ */ jsxs(Container, { className: "space-y-6", children: [
|
|
1714
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1715
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1716
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Campaign Details" }),
|
|
1717
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-ui-fg-subtle", children: campaignName })
|
|
1718
|
+
] }),
|
|
1719
|
+
/* @__PURE__ */ jsx(Button, { onClick: handleSave, isLoading: isSaving, children: "Save Details" })
|
|
1720
|
+
] }),
|
|
1721
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1722
|
+
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "Images" }),
|
|
1723
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1724
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1725
|
+
/* @__PURE__ */ jsx(Label, { children: "Main Campaign Image" }),
|
|
1926
1726
|
/* @__PURE__ */ jsx(
|
|
1927
|
-
|
|
1727
|
+
"div",
|
|
1928
1728
|
{
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
children:
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
] }),
|
|
1937
|
-
((_a = errors.products) == null ? void 0 : _a.root) && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm", children: errors.products.root.message }),
|
|
1938
|
-
/* @__PURE__ */ jsxs(Table, { children: [
|
|
1939
|
-
/* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
1940
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Product" }),
|
|
1941
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Discount Type" }),
|
|
1942
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Discount Value" }),
|
|
1943
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Limit" }),
|
|
1944
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Max Qty per Order" }),
|
|
1945
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Actions" })
|
|
1946
|
-
] }) }),
|
|
1947
|
-
/* @__PURE__ */ jsx(Table.Body, { children: fields.map((field, index) => /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
1948
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: field.product.title }),
|
|
1949
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
1950
|
-
Controller,
|
|
1951
|
-
{
|
|
1952
|
-
name: `products.${index}.discountType`,
|
|
1953
|
-
control,
|
|
1954
|
-
render: ({ field: field2 }) => /* @__PURE__ */ jsxs(
|
|
1955
|
-
Select,
|
|
1729
|
+
className: clx(
|
|
1730
|
+
"relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
|
|
1731
|
+
(campaignDetail == null ? void 0 : campaignDetail.image_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
|
|
1732
|
+
),
|
|
1733
|
+
children: (campaignDetail == null ? void 0 : campaignDetail.image_url) ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1734
|
+
/* @__PURE__ */ jsx(
|
|
1735
|
+
"img",
|
|
1956
1736
|
{
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1737
|
+
src: campaignDetail.image_url,
|
|
1738
|
+
alt: "Campaign",
|
|
1739
|
+
className: "h-full w-full object-cover",
|
|
1740
|
+
onError: (e) => {
|
|
1741
|
+
console.error(
|
|
1742
|
+
"Failed to load image:",
|
|
1743
|
+
campaignDetail.image_url
|
|
1744
|
+
);
|
|
1745
|
+
e.currentTarget.style.display = "none";
|
|
1746
|
+
}
|
|
1964
1747
|
}
|
|
1965
|
-
)
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
1969
|
-
Controller,
|
|
1970
|
-
{
|
|
1971
|
-
name: `products.${index}.discountValue`,
|
|
1972
|
-
control,
|
|
1973
|
-
render: ({ field: field2 }) => /* @__PURE__ */ jsx(
|
|
1974
|
-
Input,
|
|
1748
|
+
),
|
|
1749
|
+
/* @__PURE__ */ jsxs(
|
|
1750
|
+
Button,
|
|
1975
1751
|
{
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1752
|
+
variant: "secondary",
|
|
1753
|
+
size: "small",
|
|
1754
|
+
className: "absolute bottom-2 right-2",
|
|
1755
|
+
onClick: () => setShowImageUploader("image"),
|
|
1756
|
+
children: [
|
|
1757
|
+
/* @__PURE__ */ jsx(PencilSquare, { className: "mr-1" }),
|
|
1758
|
+
"Change"
|
|
1759
|
+
]
|
|
1980
1760
|
}
|
|
1981
1761
|
)
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1762
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
1763
|
+
Button,
|
|
1764
|
+
{
|
|
1765
|
+
variant: "secondary",
|
|
1766
|
+
onClick: () => setShowImageUploader("image"),
|
|
1767
|
+
children: [
|
|
1768
|
+
/* @__PURE__ */ jsx(PhotoSolid, { className: "mr-2" }),
|
|
1769
|
+
"Upload Image"
|
|
1770
|
+
]
|
|
1771
|
+
}
|
|
1772
|
+
)
|
|
1773
|
+
}
|
|
1774
|
+
)
|
|
1775
|
+
] }),
|
|
1776
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1777
|
+
/* @__PURE__ */ jsx(Label, { children: "Thumbnail" }),
|
|
1778
|
+
/* @__PURE__ */ jsx(
|
|
1779
|
+
"div",
|
|
1780
|
+
{
|
|
1781
|
+
className: clx(
|
|
1782
|
+
"relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
|
|
1783
|
+
(campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
|
|
1784
|
+
),
|
|
1785
|
+
children: (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1786
|
+
/* @__PURE__ */ jsx(
|
|
1787
|
+
"img",
|
|
1991
1788
|
{
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1789
|
+
src: campaignDetail.thumbnail_url,
|
|
1790
|
+
alt: "Thumbnail",
|
|
1791
|
+
className: "h-full w-full object-cover",
|
|
1792
|
+
onError: (e) => {
|
|
1793
|
+
console.error(
|
|
1794
|
+
"Failed to load thumbnail:",
|
|
1795
|
+
campaignDetail.thumbnail_url
|
|
1796
|
+
);
|
|
1797
|
+
e.currentTarget.style.display = "none";
|
|
1798
|
+
}
|
|
1996
1799
|
}
|
|
1997
|
-
)
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
2001
|
-
Controller,
|
|
2002
|
-
{
|
|
2003
|
-
name: `products.${index}.maxQty`,
|
|
2004
|
-
control,
|
|
2005
|
-
render: ({ field: field2 }) => /* @__PURE__ */ jsx(
|
|
2006
|
-
Input,
|
|
1800
|
+
),
|
|
1801
|
+
/* @__PURE__ */ jsxs(
|
|
1802
|
+
Button,
|
|
2007
1803
|
{
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
1804
|
+
variant: "secondary",
|
|
1805
|
+
size: "small",
|
|
1806
|
+
className: "absolute bottom-2 right-2",
|
|
1807
|
+
onClick: () => setShowImageUploader("thumbnail"),
|
|
1808
|
+
children: [
|
|
1809
|
+
/* @__PURE__ */ jsx(PencilSquare, { className: "mr-1" }),
|
|
1810
|
+
"Change"
|
|
1811
|
+
]
|
|
2012
1812
|
}
|
|
2013
1813
|
)
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
1814
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
1815
|
+
Button,
|
|
1816
|
+
{
|
|
1817
|
+
variant: "secondary",
|
|
1818
|
+
onClick: () => setShowImageUploader("thumbnail"),
|
|
1819
|
+
children: [
|
|
1820
|
+
/* @__PURE__ */ jsx(PhotoSolid, { className: "mr-2" }),
|
|
1821
|
+
"Upload Thumbnail"
|
|
1822
|
+
]
|
|
1823
|
+
}
|
|
1824
|
+
)
|
|
1825
|
+
}
|
|
1826
|
+
)
|
|
1827
|
+
] })
|
|
1828
|
+
] })
|
|
1829
|
+
] }),
|
|
1830
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
1831
|
+
MarkdownEditor,
|
|
1832
|
+
{
|
|
1833
|
+
label: "Campaign Detail Content",
|
|
1834
|
+
value: formData.detail_content,
|
|
1835
|
+
onChange: (value) => setFormData({ ...formData, detail_content: value }),
|
|
1836
|
+
placeholder: "Enter campaign details in Markdown format. You can include images, links, code blocks, and more...",
|
|
1837
|
+
helpText: "Supports Markdown and code syntax",
|
|
1838
|
+
rows: 12,
|
|
1839
|
+
showPreview: true
|
|
1840
|
+
}
|
|
1841
|
+
) }),
|
|
1842
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
1843
|
+
MarkdownEditor,
|
|
1844
|
+
{
|
|
1845
|
+
label: "Terms and Conditions",
|
|
1846
|
+
value: formData.terms_and_conditions,
|
|
1847
|
+
onChange: (value) => setFormData({ ...formData, terms_and_conditions: value }),
|
|
1848
|
+
placeholder: "Enter terms and conditions for this campaign...",
|
|
1849
|
+
helpText: "Optional - Campaign specific terms",
|
|
1850
|
+
rows: 8,
|
|
1851
|
+
showPreview: true
|
|
1852
|
+
}
|
|
1853
|
+
) }),
|
|
1854
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1855
|
+
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "Call to Action Link" }),
|
|
1856
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1857
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1858
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "link-url", children: "Link URL" }),
|
|
1859
|
+
/* @__PURE__ */ jsx(
|
|
1860
|
+
Input,
|
|
1861
|
+
{
|
|
1862
|
+
id: "link-url",
|
|
1863
|
+
type: "url",
|
|
1864
|
+
value: formData.link_url,
|
|
1865
|
+
onChange: (e) => setFormData({ ...formData, link_url: e.target.value }),
|
|
1866
|
+
placeholder: "https://example.com/campaign"
|
|
1867
|
+
}
|
|
1868
|
+
)
|
|
2027
1869
|
] }),
|
|
2028
|
-
/* @__PURE__ */
|
|
2029
|
-
|
|
1870
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1871
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "link-text", children: "Link Text" }),
|
|
1872
|
+
/* @__PURE__ */ jsx(
|
|
1873
|
+
Input,
|
|
1874
|
+
{
|
|
1875
|
+
id: "link-text",
|
|
1876
|
+
value: formData.link_text,
|
|
1877
|
+
onChange: (e) => setFormData({ ...formData, link_text: e.target.value }),
|
|
1878
|
+
placeholder: "Shop Now"
|
|
1879
|
+
}
|
|
1880
|
+
)
|
|
1881
|
+
] })
|
|
1882
|
+
] })
|
|
1883
|
+
] }),
|
|
1884
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1885
|
+
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "SEO & Metadata" }),
|
|
1886
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1887
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1888
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "meta-title", children: "Meta Title" }),
|
|
1889
|
+
/* @__PURE__ */ jsx(
|
|
1890
|
+
Input,
|
|
1891
|
+
{
|
|
1892
|
+
id: "meta-title",
|
|
1893
|
+
value: formData.meta_title,
|
|
1894
|
+
onChange: (e) => setFormData({ ...formData, meta_title: e.target.value }),
|
|
1895
|
+
placeholder: "Campaign meta title for SEO"
|
|
1896
|
+
}
|
|
1897
|
+
)
|
|
1898
|
+
] }),
|
|
1899
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1900
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "meta-description", children: "Meta Description" }),
|
|
1901
|
+
/* @__PURE__ */ jsx(
|
|
1902
|
+
Input,
|
|
1903
|
+
{
|
|
1904
|
+
id: "meta-description",
|
|
1905
|
+
value: formData.meta_description,
|
|
1906
|
+
onChange: (e) => setFormData({
|
|
1907
|
+
...formData,
|
|
1908
|
+
meta_description: e.target.value
|
|
1909
|
+
}),
|
|
1910
|
+
placeholder: "Campaign meta description for SEO"
|
|
1911
|
+
}
|
|
1912
|
+
)
|
|
1913
|
+
] }),
|
|
1914
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1915
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "meta-keywords", children: "Meta Keywords" }),
|
|
1916
|
+
/* @__PURE__ */ jsx(
|
|
1917
|
+
Input,
|
|
1918
|
+
{
|
|
1919
|
+
id: "meta-keywords",
|
|
1920
|
+
value: formData.meta_keywords,
|
|
1921
|
+
onChange: (e) => setFormData({
|
|
1922
|
+
...formData,
|
|
1923
|
+
meta_keywords: e.target.value
|
|
1924
|
+
}),
|
|
1925
|
+
placeholder: "keyword1, keyword2, keyword3"
|
|
1926
|
+
}
|
|
1927
|
+
),
|
|
1928
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Comma-separated keywords for SEO" })
|
|
1929
|
+
] }),
|
|
1930
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1931
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "display-order", children: "Display Order" }),
|
|
1932
|
+
/* @__PURE__ */ jsx(
|
|
1933
|
+
Input,
|
|
1934
|
+
{
|
|
1935
|
+
id: "display-order",
|
|
1936
|
+
type: "number",
|
|
1937
|
+
value: formData.display_order,
|
|
1938
|
+
onChange: (e) => setFormData({
|
|
1939
|
+
...formData,
|
|
1940
|
+
display_order: parseInt(e.target.value) || 0
|
|
1941
|
+
}),
|
|
1942
|
+
placeholder: "0"
|
|
1943
|
+
}
|
|
1944
|
+
),
|
|
1945
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Lower numbers appear first in listings" })
|
|
1946
|
+
] })
|
|
1947
|
+
] })
|
|
1948
|
+
] }),
|
|
1949
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end border-t pt-4", children: /* @__PURE__ */ jsx(Button, { onClick: handleSave, isLoading: isSaving, children: "Save Campaign Details" }) })
|
|
1950
|
+
] }),
|
|
1951
|
+
showImageUploader && /* @__PURE__ */ jsx(
|
|
1952
|
+
CampaignImageUploader,
|
|
1953
|
+
{
|
|
1954
|
+
campaignId,
|
|
1955
|
+
imageType: showImageUploader,
|
|
1956
|
+
currentImageUrl: showImageUploader === "image" ? campaignDetail == null ? void 0 : campaignDetail.image_url : campaignDetail == null ? void 0 : campaignDetail.thumbnail_url,
|
|
1957
|
+
onClose: () => setShowImageUploader(null),
|
|
1958
|
+
onSuccess: fetchCampaignDetail
|
|
2030
1959
|
}
|
|
2031
|
-
)
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
1960
|
+
)
|
|
1961
|
+
] });
|
|
1962
|
+
};
|
|
1963
|
+
const FlashSaleDetail = () => {
|
|
1964
|
+
const { id } = useParams();
|
|
1965
|
+
const navigate = useNavigate();
|
|
1966
|
+
const { data, isLoading, refetch } = useFlashSaleById(id || "");
|
|
1967
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
1968
|
+
async function handleSubmit(campaignData2) {
|
|
1969
|
+
var _a;
|
|
1970
|
+
try {
|
|
1971
|
+
await axios.put(`/admin/flash-sales/${id}`, {
|
|
1972
|
+
...campaignData2,
|
|
1973
|
+
starts_at: new Date(campaignData2.starts_at).toUTCString(),
|
|
1974
|
+
ends_at: new Date(campaignData2.ends_at).toUTCString()
|
|
1975
|
+
});
|
|
1976
|
+
toast.success("Flash sale updated successfully");
|
|
1977
|
+
refetch();
|
|
1978
|
+
navigate("/flash-sales");
|
|
1979
|
+
} catch (error) {
|
|
1980
|
+
let message;
|
|
1981
|
+
if (axios.isAxiosError(error)) {
|
|
1982
|
+
console.log(error);
|
|
1983
|
+
message = (_a = error.response) == null ? void 0 : _a.data.message;
|
|
1984
|
+
} else if (error instanceof Error) {
|
|
1985
|
+
message = error.message;
|
|
1986
|
+
} else {
|
|
1987
|
+
message = "Failed to update flash sale";
|
|
1988
|
+
}
|
|
1989
|
+
toast.error("Failed to update flash sale", {
|
|
1990
|
+
description: message
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
if (isLoading) {
|
|
1995
|
+
return /* @__PURE__ */ jsx(Container, { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx("div", { className: "animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent" }) });
|
|
1996
|
+
}
|
|
1997
|
+
if (!data) {
|
|
1998
|
+
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("p", { children: "Flash sale not found" }) });
|
|
1999
|
+
}
|
|
2000
|
+
const campaignData = {
|
|
2001
|
+
name: data.name || "",
|
|
2002
|
+
description: data.description || "",
|
|
2003
|
+
type: "flash-sale",
|
|
2004
|
+
starts_at: data.starts_at,
|
|
2005
|
+
ends_at: data.ends_at
|
|
2006
|
+
};
|
|
2007
|
+
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
2008
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
2009
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2010
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold mb-1", children: data.name }),
|
|
2011
|
+
/* @__PURE__ */ jsx("p", { className: "text-ui-fg-subtle", children: "Manage campaign settings and content" })
|
|
2012
|
+
] }),
|
|
2013
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2014
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/flash-sales"), children: "Back to Campaigns" }),
|
|
2015
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
|
|
2016
|
+
] })
|
|
2017
|
+
] }),
|
|
2018
|
+
/* @__PURE__ */ jsxs(Tabs, { defaultValue: "settings", children: [
|
|
2019
|
+
/* @__PURE__ */ jsxs(Tabs.List, { children: [
|
|
2020
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "settings", children: "Campaign Settings" }),
|
|
2021
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "details", children: "Campaign Details" })
|
|
2022
|
+
] }),
|
|
2023
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
2024
|
+
FlashSaleForm,
|
|
2036
2025
|
{
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2026
|
+
initialData: campaignData,
|
|
2027
|
+
initialProducts: new Map(
|
|
2028
|
+
data.products.map((product) => [product.product.id, product])
|
|
2029
|
+
),
|
|
2030
|
+
onSubmit: handleSubmit,
|
|
2031
|
+
onCancel: () => navigate("/flash-sales"),
|
|
2032
|
+
disabled: !isEditing
|
|
2033
|
+
}
|
|
2034
|
+
) }),
|
|
2035
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
2036
|
+
CampaignDetailForm,
|
|
2037
|
+
{
|
|
2038
|
+
campaignId: id || "",
|
|
2039
|
+
campaignName: data.name || ""
|
|
2048
2040
|
}
|
|
2049
2041
|
) })
|
|
2050
|
-
] })
|
|
2042
|
+
] })
|
|
2051
2043
|
] });
|
|
2052
2044
|
};
|
|
2045
|
+
const config$1 = defineRouteConfig({
|
|
2046
|
+
label: "Flash Sale Detail",
|
|
2047
|
+
icon: Sparkles
|
|
2048
|
+
});
|
|
2053
2049
|
const FlashSaleCreate = () => {
|
|
2054
2050
|
const navigate = useNavigate();
|
|
2055
2051
|
async function handleSubmit(campaignData) {
|
|
@@ -2085,123 +2081,127 @@ const FlashSaleCreate = () => {
|
|
|
2085
2081
|
}
|
|
2086
2082
|
);
|
|
2087
2083
|
};
|
|
2088
|
-
const
|
|
2084
|
+
const useCouponById = (id) => {
|
|
2089
2085
|
const [data, setData] = useState(null);
|
|
2090
2086
|
const [isLoading, setIsLoading] = useState(true);
|
|
2091
2087
|
const [error, setError] = useState(null);
|
|
2092
|
-
const
|
|
2088
|
+
const fetchCoupon = async () => {
|
|
2093
2089
|
if (!id) {
|
|
2094
|
-
setIsLoading(false);
|
|
2095
2090
|
return;
|
|
2096
2091
|
}
|
|
2097
2092
|
setIsLoading(true);
|
|
2098
2093
|
setError(null);
|
|
2099
2094
|
try {
|
|
2100
|
-
const response = await axios.get(`/admin/
|
|
2095
|
+
const response = await axios.get(`/admin/coupons/${id}`);
|
|
2101
2096
|
setData(response.data);
|
|
2102
2097
|
} catch (err) {
|
|
2103
|
-
setError(
|
|
2104
|
-
err instanceof Error ? err : new Error("Failed to fetch flash sale")
|
|
2105
|
-
);
|
|
2098
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch coupon"));
|
|
2106
2099
|
} finally {
|
|
2107
2100
|
setIsLoading(false);
|
|
2108
2101
|
}
|
|
2109
2102
|
};
|
|
2110
2103
|
useEffect(() => {
|
|
2111
|
-
|
|
2104
|
+
fetchCoupon();
|
|
2112
2105
|
}, [id]);
|
|
2113
2106
|
return {
|
|
2114
2107
|
data,
|
|
2115
2108
|
isLoading,
|
|
2116
2109
|
error,
|
|
2117
|
-
refetch:
|
|
2110
|
+
refetch: fetchCoupon
|
|
2118
2111
|
};
|
|
2119
2112
|
};
|
|
2120
|
-
const
|
|
2113
|
+
const CouponDetail = () => {
|
|
2114
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
2121
2115
|
const { id } = useParams();
|
|
2122
2116
|
const navigate = useNavigate();
|
|
2123
|
-
const { data, isLoading, refetch } = useFlashSaleById(id || "");
|
|
2124
2117
|
const [isEditing, setIsEditing] = useState(false);
|
|
2125
|
-
|
|
2126
|
-
|
|
2118
|
+
const { data, isLoading, refetch } = useCouponById(id ?? "");
|
|
2119
|
+
if (isLoading) {
|
|
2120
|
+
return /* @__PURE__ */ jsx(Container, { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx("div", { className: "animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent" }) });
|
|
2121
|
+
}
|
|
2122
|
+
if (!(data == null ? void 0 : data.campaign)) {
|
|
2123
|
+
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("p", { children: "Coupon not found" }) });
|
|
2124
|
+
}
|
|
2125
|
+
const campaign = data.campaign;
|
|
2126
|
+
const promotion = (_a = campaign.promotions) == null ? void 0 : _a[0];
|
|
2127
|
+
const initialValues = {
|
|
2128
|
+
name: campaign.name ?? "",
|
|
2129
|
+
description: campaign.description ?? "",
|
|
2130
|
+
type: "coupon",
|
|
2131
|
+
code: (promotion == null ? void 0 : promotion.code) ?? "",
|
|
2132
|
+
discount_type: ((_b = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _b.type) ?? "percentage",
|
|
2133
|
+
discount_value: Number(((_c = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _c.value) ?? 0),
|
|
2134
|
+
currency_code: ((_d = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _d.currency_code) ?? void 0,
|
|
2135
|
+
starts_at: campaign.starts_at ? dayjs(campaign.starts_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
2136
|
+
ends_at: campaign.ends_at ? dayjs(campaign.ends_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
2137
|
+
allocation: ((_e = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _e.allocation) === "total" ? "across" : ((_f = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _f.allocation) ?? "across",
|
|
2138
|
+
target_type: ((_g = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _g.target_type) ?? "order"
|
|
2139
|
+
};
|
|
2140
|
+
const handleSubmit = async (formData) => {
|
|
2141
|
+
var _a2, _b2;
|
|
2142
|
+
if (!id) {
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2127
2145
|
try {
|
|
2128
|
-
await axios.put(`/admin/
|
|
2129
|
-
...
|
|
2130
|
-
starts_at: new Date(
|
|
2131
|
-
ends_at: new Date(
|
|
2146
|
+
await axios.put(`/admin/coupons/${id}`, {
|
|
2147
|
+
...formData,
|
|
2148
|
+
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
2149
|
+
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
2150
|
+
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
2132
2151
|
});
|
|
2133
|
-
toast.success("
|
|
2152
|
+
toast.success("Coupon updated successfully");
|
|
2153
|
+
setIsEditing(false);
|
|
2134
2154
|
refetch();
|
|
2135
|
-
navigate("/flash-sales");
|
|
2136
2155
|
} catch (error) {
|
|
2137
|
-
let message;
|
|
2156
|
+
let message = "Failed to update coupon";
|
|
2138
2157
|
if (axios.isAxiosError(error)) {
|
|
2139
|
-
|
|
2140
|
-
message = (_a = error.response) == null ? void 0 : _a.data.message;
|
|
2158
|
+
message = ((_b2 = (_a2 = error.response) == null ? void 0 : _a2.data) == null ? void 0 : _b2.message) ?? message;
|
|
2141
2159
|
} else if (error instanceof Error) {
|
|
2142
2160
|
message = error.message;
|
|
2143
|
-
} else {
|
|
2144
|
-
message = "Failed to update flash sale";
|
|
2145
2161
|
}
|
|
2146
|
-
toast.error("Failed to update
|
|
2162
|
+
toast.error("Failed to update coupon", {
|
|
2147
2163
|
description: message
|
|
2148
2164
|
});
|
|
2149
2165
|
}
|
|
2150
|
-
}
|
|
2151
|
-
if (isLoading) {
|
|
2152
|
-
return /* @__PURE__ */ jsx(Container, { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx("div", { className: "animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent" }) });
|
|
2153
|
-
}
|
|
2154
|
-
if (!data) {
|
|
2155
|
-
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("p", { children: "Flash sale not found" }) });
|
|
2156
|
-
}
|
|
2157
|
-
const campaignData = {
|
|
2158
|
-
name: data.name || "",
|
|
2159
|
-
description: data.description || "",
|
|
2160
|
-
type: "flash-sale",
|
|
2161
|
-
starts_at: data.starts_at,
|
|
2162
|
-
ends_at: data.ends_at
|
|
2163
2166
|
};
|
|
2164
2167
|
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
2165
2168
|
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
2166
2169
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
2167
|
-
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold mb-1", children:
|
|
2168
|
-
/* @__PURE__ */ jsx("p", { className: "text-ui-fg-subtle", children: "Manage
|
|
2170
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold mb-1", children: campaign.name }),
|
|
2171
|
+
/* @__PURE__ */ jsx("p", { className: "text-ui-fg-subtle", children: "Manage coupon configuration and content" })
|
|
2169
2172
|
] }),
|
|
2170
2173
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2171
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/
|
|
2174
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/coupons"), children: "Back to Coupons" }),
|
|
2172
2175
|
/* @__PURE__ */ jsx(Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
|
|
2173
2176
|
] })
|
|
2174
2177
|
] }),
|
|
2175
2178
|
/* @__PURE__ */ jsxs(Tabs, { defaultValue: "settings", children: [
|
|
2176
2179
|
/* @__PURE__ */ jsxs(Tabs.List, { children: [
|
|
2177
|
-
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "settings", children: "
|
|
2178
|
-
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "details", children: "
|
|
2180
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "settings", children: "Coupon Settings" }),
|
|
2181
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "details", children: "Coupon Details" })
|
|
2179
2182
|
] }),
|
|
2180
2183
|
/* @__PURE__ */ jsx(Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
2181
|
-
|
|
2184
|
+
CouponForm,
|
|
2182
2185
|
{
|
|
2183
|
-
initialData:
|
|
2184
|
-
initialProducts: new Map(
|
|
2185
|
-
data.products.map((product) => [product.product.id, product])
|
|
2186
|
-
),
|
|
2186
|
+
initialData: initialValues,
|
|
2187
2187
|
onSubmit: handleSubmit,
|
|
2188
|
-
onCancel: () => navigate("/
|
|
2188
|
+
onCancel: () => navigate("/coupons"),
|
|
2189
2189
|
disabled: !isEditing
|
|
2190
2190
|
}
|
|
2191
2191
|
) }),
|
|
2192
2192
|
/* @__PURE__ */ jsx(Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
2193
2193
|
CampaignDetailForm,
|
|
2194
2194
|
{
|
|
2195
|
-
campaignId: id
|
|
2196
|
-
campaignName:
|
|
2195
|
+
campaignId: campaign.id,
|
|
2196
|
+
campaignName: campaign.name ?? ""
|
|
2197
2197
|
}
|
|
2198
2198
|
) })
|
|
2199
2199
|
] })
|
|
2200
2200
|
] });
|
|
2201
2201
|
};
|
|
2202
2202
|
const config = defineRouteConfig({
|
|
2203
|
-
label: "
|
|
2204
|
-
icon:
|
|
2203
|
+
label: "Coupon Detail",
|
|
2204
|
+
icon: Tag
|
|
2205
2205
|
});
|
|
2206
2206
|
const widgetModule = { widgets: [
|
|
2207
2207
|
{
|
|
@@ -2219,21 +2219,21 @@ const routeModule = {
|
|
|
2219
2219
|
Component: FlashSale,
|
|
2220
2220
|
path: "/flash-sales"
|
|
2221
2221
|
},
|
|
2222
|
-
{
|
|
2223
|
-
Component: CouponDetail,
|
|
2224
|
-
path: "/coupons/:id"
|
|
2225
|
-
},
|
|
2226
2222
|
{
|
|
2227
2223
|
Component: CouponCreate,
|
|
2228
2224
|
path: "/coupons/create"
|
|
2229
2225
|
},
|
|
2226
|
+
{
|
|
2227
|
+
Component: FlashSaleDetail,
|
|
2228
|
+
path: "/flash-sales/:id"
|
|
2229
|
+
},
|
|
2230
2230
|
{
|
|
2231
2231
|
Component: FlashSaleCreate,
|
|
2232
2232
|
path: "/flash-sales/create"
|
|
2233
2233
|
},
|
|
2234
2234
|
{
|
|
2235
|
-
Component:
|
|
2236
|
-
path: "/
|
|
2235
|
+
Component: CouponDetail,
|
|
2236
|
+
path: "/coupons/:id"
|
|
2237
2237
|
}
|
|
2238
2238
|
]
|
|
2239
2239
|
};
|
|
@@ -2252,14 +2252,14 @@ const menuItemModule = {
|
|
|
2252
2252
|
nested: void 0
|
|
2253
2253
|
},
|
|
2254
2254
|
{
|
|
2255
|
-
label: config
|
|
2256
|
-
icon: config
|
|
2255
|
+
label: config.label,
|
|
2256
|
+
icon: config.icon,
|
|
2257
2257
|
path: "/coupons/:id",
|
|
2258
2258
|
nested: void 0
|
|
2259
2259
|
},
|
|
2260
2260
|
{
|
|
2261
|
-
label: config.label,
|
|
2262
|
-
icon: config.icon,
|
|
2261
|
+
label: config$1.label,
|
|
2262
|
+
icon: config$1.icon,
|
|
2263
2263
|
path: "/flash-sales/:id",
|
|
2264
2264
|
nested: void 0
|
|
2265
2265
|
}
|