@crayonscodetech/cms-sdk 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,3 @@
1
- import React from "react";
2
- import { Activity, AlertCircle, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Award, BarChart, Battery, Bell, BookOpen, Bookmark, Brain, Briefcase, Building2, Calendar, Camera, Check, CheckCircle, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, ChevronsUpDown, Clipboard, Clock, Cloud, Code, Copy, CreditCard, Database, Download, Edit, ExternalLink, Eye, EyeOff, File, Filter, Flag, Folder, Gift, Globe, GraduationCap, Grid, Handshake, Heart, HeartHandshake, HelpCircle, Home, Image, Info, Layers, Layout, Lightbulb, Link, List, Lock, LogIn, LogOut, Mail, MapPin, Maximize, Menu, MessageSquare, Mic, Microscope, Minus, Moon, MoreHorizontal, MoreVertical, Music, Package, PauseCircle, Phone, PieChart, PlayCircle, Plus, RefreshCw, Save, Search, Send, Server, Settings, Share, ShoppingCart, Star, Stethoscope, Sun, Tag, Target, Trash, Trees, TrendingDown, TrendingUp, Truck, Unlock, Upload, User, UserMinus, UserPlus, Users, Video, Wifi, X, XCircle, Zap } from "lucide-react";
3
1
  //#region src/constants/pageTypes.ts
4
2
  const pageTypes = [
5
3
  "home",
@@ -82,6 +80,21 @@ const MEDIA = {
82
80
  MAX_IMAGE_SIZE: 5242880
83
81
  };
84
82
  //#endregion
83
+ //#region src/types/contact.ts
84
+ /** Attachment caps enforced by the API; mirrored here to fail fast client-side. */
85
+ const MAX_CONTACT_ATTACHMENTS = 3;
86
+ const MAX_CONTACT_ATTACHMENTS_TOTAL_BYTES = 4194304;
87
+ const CONTACT_ATTACHMENT_MIME_ALLOWLIST = [
88
+ "application/pdf",
89
+ "image/png",
90
+ "image/jpeg",
91
+ "image/webp",
92
+ "image/gif",
93
+ "text/plain",
94
+ "application/msword",
95
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
96
+ ];
97
+ //#endregion
85
98
  //#region src/core/client.ts
86
99
  var CmsError = class extends Error {
87
100
  message;
@@ -98,9 +111,9 @@ var CmsError = class extends Error {
98
111
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
99
112
  function createCmsClient(config) {
100
113
  const { baseUrl, defaultOptions = {} } = config;
101
- async function cmsFetch(endpoint, options = {}, retries = 2) {
114
+ async function cmsFetch(endpoint, options = {}) {
102
115
  const url = `${baseUrl}${endpoint}`;
103
- const { revalidate = CACHE.NO_CACHE, tags, ...rest } = {
116
+ const { revalidate = CACHE.NO_CACHE, tags, retries = 1, ...rest } = {
104
117
  ...defaultOptions,
105
118
  ...options
106
119
  };
@@ -111,12 +124,9 @@ function createCmsClient(config) {
111
124
  ...tags && { tags }
112
125
  }
113
126
  };
114
- let lastError = null;
115
127
  for (let attempt = 0; attempt <= retries; attempt++) try {
116
128
  const response = await fetch(url, fetchConfig);
117
129
  if (!response.ok) {
118
- const errorMsg = `API request failed: ${response.status} ${url}`;
119
- console.error(errorMsg);
120
130
  if (attempt < retries && [
121
131
  502,
122
132
  503,
@@ -125,32 +135,29 @@ function createCmsClient(config) {
125
135
  await sleep(Math.pow(2, attempt) * 1e3);
126
136
  continue;
127
137
  }
128
- return null;
138
+ let errorMsg = `API request failed: ${response.status} ${url}`;
139
+ try {
140
+ const body = await response.json();
141
+ if (body?.message) errorMsg = body.message;
142
+ } catch {}
143
+ throw new CmsError(errorMsg, response.status, url);
129
144
  }
130
145
  if (response.status === 204) return null;
131
146
  const result = await response.json();
132
147
  return result.data ?? result;
133
148
  } catch (error) {
134
- lastError = error instanceof Error ? error : new Error(String(error));
135
- console.error(`API request error (attempt ${attempt + 1}/${retries + 1}): ${url}`, lastError);
149
+ if (error instanceof CmsError) throw error;
136
150
  if (attempt < retries) {
137
151
  await sleep(Math.pow(2, attempt) * 1e3);
138
152
  continue;
139
153
  }
154
+ throw new CmsError(`Request failed: ${error instanceof Error ? error.message : String(error)}`, void 0, url);
140
155
  }
141
- return null;
142
- }
143
- async function cmsFetchPaginated(endpoint, options = {}, retries = 2) {
144
- const emptyResponse = {
145
- data: [],
146
- pagination: {
147
- page: 1,
148
- limit: PAGINATION.DEFAULT_LIMIT,
149
- total: 0
150
- }
151
- };
156
+ throw new CmsError(`Request failed after ${retries + 1} attempts`, void 0, url);
157
+ }
158
+ async function cmsFetchPaginated(endpoint, options = {}) {
152
159
  const url = `${baseUrl}${endpoint}`;
153
- const { revalidate = CACHE.NO_CACHE, tags, ...rest } = {
160
+ const { revalidate = CACHE.NO_CACHE, tags, retries = 1, ...rest } = {
154
161
  ...defaultOptions,
155
162
  ...options
156
163
  };
@@ -164,7 +171,6 @@ function createCmsClient(config) {
164
171
  for (let attempt = 0; attempt <= retries; attempt++) try {
165
172
  const response = await fetch(url, fetchConfig);
166
173
  if (!response.ok) {
167
- console.error(`API request failed: ${response.status} ${url}`);
168
174
  if (attempt < retries && [
169
175
  502,
170
176
  503,
@@ -173,17 +179,23 @@ function createCmsClient(config) {
173
179
  await sleep(Math.pow(2, attempt) * 1e3);
174
180
  continue;
175
181
  }
176
- return emptyResponse;
182
+ let errorMsg = `API request failed: ${response.status} ${url}`;
183
+ try {
184
+ const body = await response.json();
185
+ if (body?.message) errorMsg = body.message;
186
+ } catch {}
187
+ throw new CmsError(errorMsg, response.status, url);
177
188
  }
178
189
  return await response.json();
179
190
  } catch (error) {
180
- console.error(`API request error (attempt ${attempt + 1}/${retries + 1}): ${url}`, error);
191
+ if (error instanceof CmsError) throw error;
181
192
  if (attempt < retries) {
182
193
  await sleep(Math.pow(2, attempt) * 1e3);
183
194
  continue;
184
195
  }
196
+ throw new CmsError(`Request failed: ${error instanceof Error ? error.message : String(error)}`, void 0, url);
185
197
  }
186
- return emptyResponse;
198
+ throw new CmsError(`Request failed after ${retries + 1} attempts`, void 0, url);
187
199
  }
188
200
  function buildQueryString(params) {
189
201
  const query = new URLSearchParams();
@@ -225,17 +237,12 @@ function createCmsClient(config) {
225
237
  });
226
238
  }
227
239
  async function fetchPageByUrl(siteId, urlPath, options) {
228
- try {
229
- const targetUrl = urlPath === "/" || urlPath === "" ? "/" : `/${urlPath.replace(/^\/|\/$/g, "")}`;
230
- return await cmsFetch(`/api/public/cms/${siteId}/page/by-url/?url=${encodeURIComponent(targetUrl)}`, {
231
- revalidate: CACHE.SHORT,
232
- tags: ["pages", `page-${targetUrl || "root"}`],
233
- ...options
234
- });
235
- } catch (error) {
236
- console.error(`Error in fetchPageByUrl for ${urlPath}:`, error);
237
- return null;
238
- }
240
+ const targetUrl = urlPath === "/" || urlPath === "" ? "/" : `/${urlPath.replace(/^\/|\/$/g, "")}`;
241
+ return cmsFetch(`/api/public/cms/${siteId}/page/by-url/?url=${encodeURIComponent(targetUrl)}`, {
242
+ revalidate: CACHE.SHORT,
243
+ tags: ["pages", `page-${targetUrl || "root"}`],
244
+ ...options
245
+ });
239
246
  }
240
247
  function fetchAboutUs(siteId, options) {
241
248
  return cmsFetch(`/api/public/cms/${siteId}/about-us/`, {
@@ -361,14 +368,73 @@ function createCmsClient(config) {
361
368
  ...options
362
369
  });
363
370
  }
364
- async function submitContactForm(siteId, payload, options) {
365
- return cmsFetch(`/api/public/cms/${siteId}/contact/`, {
366
- method: "POST",
367
- headers: { "Content-Type": "application/json" },
368
- body: JSON.stringify(payload),
369
- revalidate: CACHE.NO_CACHE,
370
- ...options
371
- });
371
+ /**
372
+ * Public Turnstile settings for the site's contact form.
373
+ *
374
+ * Call this before rendering the form: when `turnstile.enabled` is true the
375
+ * widget must be rendered with `turnstile.site_key` and its token passed to
376
+ * `submitContactForm`, or every submission is rejected.
377
+ */
378
+ function fetchContactConfig(siteId, options) {
379
+ return cmsFetch(`/api/public/cms/${siteId}/contact-config/`, {
380
+ revalidate: CACHE.SHORT,
381
+ tags: ["contact-config"],
382
+ ...options
383
+ });
384
+ }
385
+ /**
386
+ * Submit the contact form.
387
+ *
388
+ * Unlike the read methods, this THROWS `CmsError` on a rejection rather than
389
+ * returning null — a form needs to tell "captcha failed, reset the widget"
390
+ * (403) apart from "rate limited" (429) and a network blip, and a single
391
+ * `null` cannot carry that. `error.status` holds the HTTP status.
392
+ *
393
+ * Pass `attachments` to send files; the request then goes out as multipart
394
+ * instead of JSON. Caps are enforced server-side and pre-checked here.
395
+ */
396
+ async function submitContactForm(siteId, payload, attachments, options) {
397
+ const url = `${baseUrl}/api/public/cms/${siteId}/contact/`;
398
+ if (attachments?.length) {
399
+ if (attachments.length > 3) throw new CmsError(`At most 3 attachments are allowed`, 400, url);
400
+ if (attachments.reduce((sum, f) => sum + f.size, 0) > 4194304) throw new CmsError("Attachments exceed the 4 MiB total size limit", 400, url);
401
+ for (const f of attachments) if (!CONTACT_ATTACHMENT_MIME_ALLOWLIST.includes(f.type)) throw new CmsError(`Attachment type not allowed: ${f.type || "unknown"}`, 400, url);
402
+ }
403
+ let body;
404
+ const headers = {};
405
+ if (attachments?.length) {
406
+ const form = new FormData();
407
+ for (const [key, value] of Object.entries(payload)) if (value !== void 0 && value !== null) form.append(key, String(value));
408
+ for (const file of attachments) form.append("attachments", file, file.name);
409
+ body = form;
410
+ } else {
411
+ headers["Content-Type"] = "application/json";
412
+ body = JSON.stringify(payload);
413
+ }
414
+ let response;
415
+ try {
416
+ response = await fetch(url, {
417
+ ...options,
418
+ method: "POST",
419
+ headers: {
420
+ ...headers,
421
+ ...options?.headers
422
+ },
423
+ body
424
+ });
425
+ } catch (error) {
426
+ throw new CmsError(error instanceof Error ? error.message : "Network request failed", void 0, url);
427
+ }
428
+ if (!response.ok) {
429
+ let message = `Request failed with status ${response.status}`;
430
+ try {
431
+ const parsed = await response.json();
432
+ if (parsed?.message) message = parsed.message;
433
+ } catch {}
434
+ throw new CmsError(message, response.status, url);
435
+ }
436
+ const result = await response.json();
437
+ return result.data ?? result;
372
438
  }
373
439
  function fetchFaqs(siteId, params, options) {
374
440
  return cmsFetchPaginated(`/api/public/cms/${siteId}/faqs/?${buildQueryString({
@@ -409,8 +475,9 @@ function createCmsClient(config) {
409
475
  referrer
410
476
  }),
411
477
  revalidate: CACHE.NO_CACHE,
412
- tags: ["redirects", "redirect-404-logs"]
413
- }, 0);
478
+ tags: ["redirects", "redirect-404-logs"],
479
+ retries: 0
480
+ });
414
481
  }
415
482
  function fetchStoreSettings(siteId, options) {
416
483
  return cmsFetch(`/api/public/store/${siteId}/settings/`, {
@@ -490,6 +557,7 @@ function createCmsClient(config) {
490
557
  headers: { "Content-Type": "application/json" },
491
558
  body: JSON.stringify(payload),
492
559
  revalidate: CACHE.NO_CACHE,
560
+ retries: 0,
493
561
  ...options
494
562
  });
495
563
  }
@@ -525,6 +593,70 @@ function createCmsClient(config) {
525
593
  ...options
526
594
  });
527
595
  }
596
+ const SITEMAP_ROUTES = {
597
+ blogs: {
598
+ segment: "blogs",
599
+ store: false
600
+ },
601
+ pages: {
602
+ segment: "pages",
603
+ store: false
604
+ },
605
+ services: {
606
+ segment: "services",
607
+ store: false
608
+ },
609
+ events: {
610
+ segment: "events",
611
+ store: false
612
+ },
613
+ albums: {
614
+ segment: "albums",
615
+ store: false
616
+ },
617
+ "team-members": {
618
+ segment: "team-members",
619
+ store: false
620
+ },
621
+ "team-categories": {
622
+ segment: "team-categories",
623
+ store: false
624
+ },
625
+ "brand-groups": {
626
+ segment: "brand-groups",
627
+ store: false
628
+ },
629
+ products: {
630
+ segment: "products",
631
+ store: true
632
+ },
633
+ collections: {
634
+ segment: "collections",
635
+ store: true
636
+ },
637
+ "product-categories": {
638
+ segment: "categories",
639
+ store: true
640
+ },
641
+ "product-brands": {
642
+ segment: "brands",
643
+ store: true
644
+ }
645
+ };
646
+ /**
647
+ * Fetch any sitemap resource. Returns slug/url, title/name, image (where
648
+ * the content type has one) and `updatedAt` for `lastModified`.
649
+ */
650
+ function fetchSitemap(siteId, resource, params = {}, options) {
651
+ const { segment, store } = SITEMAP_ROUTES[resource];
652
+ const namespace = store ? "store" : "cms";
653
+ const query = buildQueryString(params);
654
+ return cmsFetchPaginated(`/api/public/${namespace}/${siteId}/sitemap/${segment}/${query ? `?${query}` : ""}`, {
655
+ revalidate: CACHE.STATIC,
656
+ tags: ["sitemap", `sitemap-${resource}`],
657
+ ...options
658
+ });
659
+ }
528
660
  return {
529
661
  LIST_DEFAULT_LIMIT,
530
662
  fetchHeader,
@@ -554,6 +686,7 @@ function createCmsClient(config) {
554
686
  resolveRedirect,
555
687
  reportRedirect404,
556
688
  submitContactForm,
689
+ fetchContactConfig,
557
690
  fetchStoreSettings,
558
691
  fetchProductCategories,
559
692
  fetchProductBrands,
@@ -564,6 +697,7 @@ function createCmsClient(config) {
564
697
  fetchCollectionDetail,
565
698
  fetchCollectionDetailById,
566
699
  placeOrder,
700
+ fetchSitemap,
567
701
  fetchSitemapBlogs,
568
702
  fetchSitemapPages,
569
703
  fetchSitemapProducts,
@@ -574,127 +708,116 @@ function createCmsClient(config) {
574
708
  };
575
709
  }
576
710
  //#endregion
577
- //#region src/components/Icon.tsx
578
- /**
579
- * A exhaustive map of Lucide icons commonly used in the CMS.
580
- */
581
- const ICON_MAP = {
582
- Home,
583
- User,
584
- Settings,
585
- Mail,
586
- Bell,
587
- Calendar,
588
- Star,
589
- Heart,
590
- Bookmark,
591
- Search,
592
- Filter,
593
- Download,
594
- Upload,
595
- Trash,
596
- Edit,
597
- Plus,
598
- Minus,
599
- Check,
600
- X,
601
- ChevronRight,
602
- ChevronLeft,
603
- ChevronUp,
604
- ChevronDown,
605
- ChevronsUpDown,
606
- ArrowRight,
607
- ArrowLeft,
608
- ArrowUp,
609
- ArrowDown,
610
- File,
611
- Folder,
612
- Image,
613
- Video,
614
- Music,
615
- Lock,
616
- Unlock,
617
- Eye,
618
- EyeOff,
619
- Sun,
620
- Moon,
621
- Cloud,
622
- Zap,
623
- Activity,
624
- TrendingUp,
625
- TrendingDown,
626
- BarChart,
627
- PieChart,
628
- Database,
629
- Server,
630
- Wifi,
631
- Battery,
632
- Camera,
633
- Mic,
634
- Phone,
635
- MessageSquare,
636
- Send,
637
- Share,
638
- Link,
639
- ExternalLink,
640
- Globe,
641
- MapPin,
642
- Users,
643
- ShoppingCart,
644
- CreditCard,
645
- Package,
646
- Briefcase,
647
- Target,
648
- Flag,
649
- Clipboard,
650
- List,
651
- Grid,
652
- Layout,
653
- Layers,
654
- Copy,
655
- Save,
656
- RefreshCw,
657
- Maximize,
658
- Code,
659
- AlertCircle,
660
- Info,
661
- HelpCircle,
662
- CheckCircle,
663
- XCircle,
664
- Menu,
665
- MoreVertical,
666
- MoreHorizontal,
667
- PlayCircle,
668
- PauseCircle,
669
- LogIn,
670
- LogOut,
671
- UserPlus,
672
- UserMinus,
673
- Tag,
674
- Award,
675
- Gift,
676
- Truck,
677
- Clock,
678
- Handshake,
679
- Stethoscope,
680
- Microscope,
681
- GraduationCap,
682
- BookOpen,
683
- Building2,
684
- Trees,
685
- HeartHandshake,
686
- Brain,
687
- Lightbulb
688
- };
689
- /**
690
- * Dynamic Icon component that renders Lucide icons by name.
691
- * Useful for CMS-driven icons where names are stored as strings.
692
- */
693
- function Icon({ name, fallback = "HelpCircle", ...props }) {
694
- const IconComponent = ICON_MAP[name] ?? ICON_MAP[fallback] ?? HelpCircle;
695
- return React.createElement(IconComponent, props);
696
- }
711
+ //#region src/types/icon.ts
712
+ const ICON_NAMES = [
713
+ "Home",
714
+ "User",
715
+ "Settings",
716
+ "Mail",
717
+ "Bell",
718
+ "Calendar",
719
+ "Star",
720
+ "Heart",
721
+ "Bookmark",
722
+ "Search",
723
+ "Filter",
724
+ "Download",
725
+ "Upload",
726
+ "Trash",
727
+ "Edit",
728
+ "Plus",
729
+ "Minus",
730
+ "Check",
731
+ "X",
732
+ "ChevronRight",
733
+ "ChevronLeft",
734
+ "ChevronUp",
735
+ "ChevronDown",
736
+ "ChevronsUpDown",
737
+ "ArrowRight",
738
+ "ArrowLeft",
739
+ "ArrowUp",
740
+ "ArrowDown",
741
+ "File",
742
+ "Folder",
743
+ "Image",
744
+ "Video",
745
+ "Music",
746
+ "Lock",
747
+ "Unlock",
748
+ "Eye",
749
+ "EyeOff",
750
+ "Sun",
751
+ "Moon",
752
+ "Cloud",
753
+ "Zap",
754
+ "Activity",
755
+ "TrendingUp",
756
+ "TrendingDown",
757
+ "BarChart",
758
+ "PieChart",
759
+ "Database",
760
+ "Server",
761
+ "Wifi",
762
+ "Battery",
763
+ "Camera",
764
+ "Mic",
765
+ "Phone",
766
+ "MessageSquare",
767
+ "Send",
768
+ "Share",
769
+ "Link",
770
+ "ExternalLink",
771
+ "Globe",
772
+ "MapPin",
773
+ "Users",
774
+ "ShoppingCart",
775
+ "CreditCard",
776
+ "Package",
777
+ "Briefcase",
778
+ "Target",
779
+ "Flag",
780
+ "Clipboard",
781
+ "List",
782
+ "Grid",
783
+ "Layout",
784
+ "Layers",
785
+ "Copy",
786
+ "Save",
787
+ "RefreshCw",
788
+ "Maximize",
789
+ "Code",
790
+ "AlertCircle",
791
+ "Info",
792
+ "HelpCircle",
793
+ "CheckCircle",
794
+ "XCircle",
795
+ "Menu",
796
+ "MoreVertical",
797
+ "MoreHorizontal",
798
+ "PlayCircle",
799
+ "PauseCircle",
800
+ "LogIn",
801
+ "LogOut",
802
+ "UserPlus",
803
+ "UserMinus",
804
+ "Tag",
805
+ "Award",
806
+ "Gift",
807
+ "Truck",
808
+ "Clock",
809
+ "Handshake",
810
+ "Stethoscope",
811
+ "Microscope",
812
+ "GraduationCap",
813
+ "BookOpen",
814
+ "Building2",
815
+ "Trees",
816
+ "HeartHandshake",
817
+ "Brain",
818
+ "Lightbulb"
819
+ ];
697
820
  //#endregion
698
- export { ANIMATION, CACHE, CONTACT_SUBJECTS, CmsError, HTTP_STATUS, ICON_MAP, Icon, MEDIA, PAGINATION, SEARCH, allPageTypes, createCmsClient, defaultPageTypes, pageTypes };
821
+ export { ANIMATION, CACHE, CONTACT_ATTACHMENT_MIME_ALLOWLIST, CONTACT_SUBJECTS, CmsError, HTTP_STATUS, ICON_NAMES, MAX_CONTACT_ATTACHMENTS, MAX_CONTACT_ATTACHMENTS_TOTAL_BYTES, MEDIA, PAGINATION, SEARCH, allPageTypes, createCmsClient, defaultPageTypes, pageTypes };
699
822
 
700
823
  //# sourceMappingURL=index.mjs.map