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