@crayonscodetech/cms-sdk 1.0.7 → 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",
@@ -79,9 +77,24 @@ const MEDIA = {
79
77
  ".webm",
80
78
  ".ogg"
81
79
  ],
82
- MAX_IMAGE_SIZE: 5 * 1024 * 1024
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,31 +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
  }
145
+ if (response.status === 204) return null;
130
146
  const result = await response.json();
131
147
  return result.data ?? result;
132
148
  } catch (error) {
133
- lastError = error instanceof Error ? error : new Error(String(error));
134
- console.error(`API request error (attempt ${attempt + 1}/${retries + 1}): ${url}`, lastError);
149
+ if (error instanceof CmsError) throw error;
135
150
  if (attempt < retries) {
136
151
  await sleep(Math.pow(2, attempt) * 1e3);
137
152
  continue;
138
153
  }
154
+ throw new CmsError(`Request failed: ${error instanceof Error ? error.message : String(error)}`, void 0, url);
139
155
  }
140
- return null;
141
- }
142
- async function cmsFetchPaginated(endpoint, options = {}, retries = 2) {
143
- const emptyResponse = {
144
- data: [],
145
- pagination: {
146
- page: 1,
147
- limit: PAGINATION.DEFAULT_LIMIT,
148
- total: 0
149
- }
150
- };
156
+ throw new CmsError(`Request failed after ${retries + 1} attempts`, void 0, url);
157
+ }
158
+ async function cmsFetchPaginated(endpoint, options = {}) {
151
159
  const url = `${baseUrl}${endpoint}`;
152
- const { revalidate = CACHE.NO_CACHE, tags, ...rest } = {
160
+ const { revalidate = CACHE.NO_CACHE, tags, retries = 1, ...rest } = {
153
161
  ...defaultOptions,
154
162
  ...options
155
163
  };
@@ -163,7 +171,6 @@ function createCmsClient(config) {
163
171
  for (let attempt = 0; attempt <= retries; attempt++) try {
164
172
  const response = await fetch(url, fetchConfig);
165
173
  if (!response.ok) {
166
- console.error(`API request failed: ${response.status} ${url}`);
167
174
  if (attempt < retries && [
168
175
  502,
169
176
  503,
@@ -172,23 +179,31 @@ function createCmsClient(config) {
172
179
  await sleep(Math.pow(2, attempt) * 1e3);
173
180
  continue;
174
181
  }
175
- 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);
176
188
  }
177
189
  return await response.json();
178
190
  } catch (error) {
179
- console.error(`API request error (attempt ${attempt + 1}/${retries + 1}): ${url}`, error);
191
+ if (error instanceof CmsError) throw error;
180
192
  if (attempt < retries) {
181
193
  await sleep(Math.pow(2, attempt) * 1e3);
182
194
  continue;
183
195
  }
196
+ throw new CmsError(`Request failed: ${error instanceof Error ? error.message : String(error)}`, void 0, url);
184
197
  }
185
- return emptyResponse;
198
+ throw new CmsError(`Request failed after ${retries + 1} attempts`, void 0, url);
186
199
  }
187
200
  function buildQueryString(params) {
188
201
  const query = new URLSearchParams();
189
202
  Object.entries(params).forEach(([key, value]) => {
190
- if (value !== void 0 && value !== null) if (Array.isArray(value)) value.forEach((v) => query.append(key, String(v)));
191
- else query.append(key, String(value));
203
+ if (value !== void 0 && value !== null) {
204
+ if (Array.isArray(value)) value.forEach((v) => query.append(key, String(v)));
205
+ else query.append(key, String(value));
206
+ }
192
207
  });
193
208
  return query.toString();
194
209
  }
@@ -222,17 +237,12 @@ function createCmsClient(config) {
222
237
  });
223
238
  }
224
239
  async function fetchPageByUrl(siteId, urlPath, options) {
225
- try {
226
- const targetUrl = urlPath === "/" || urlPath === "" ? "/" : `/${urlPath.replace(/^\/|\/$/g, "")}`;
227
- return await cmsFetch(`/api/public/cms/${siteId}/page/by-url/?url=${encodeURIComponent(targetUrl)}`, {
228
- revalidate: CACHE.SHORT,
229
- tags: ["pages", `page-${targetUrl || "root"}`],
230
- ...options
231
- });
232
- } catch (error) {
233
- console.error(`Error in fetchPageByUrl for ${urlPath}:`, error);
234
- return null;
235
- }
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
+ });
236
246
  }
237
247
  function fetchAboutUs(siteId, options) {
238
248
  return cmsFetch(`/api/public/cms/${siteId}/about-us/`, {
@@ -358,14 +368,73 @@ function createCmsClient(config) {
358
368
  ...options
359
369
  });
360
370
  }
361
- async function submitContactForm(siteId, payload, options) {
362
- return cmsFetch(`/api/public/cms/${siteId}/contact/`, {
363
- method: "POST",
364
- headers: { "Content-Type": "application/json" },
365
- body: JSON.stringify(payload),
366
- revalidate: CACHE.NO_CACHE,
367
- ...options
368
- });
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;
369
438
  }
370
439
  function fetchFaqs(siteId, params, options) {
371
440
  return cmsFetchPaginated(`/api/public/cms/${siteId}/faqs/?${buildQueryString({
@@ -406,8 +475,9 @@ function createCmsClient(config) {
406
475
  referrer
407
476
  }),
408
477
  revalidate: CACHE.NO_CACHE,
409
- tags: ["redirects", "redirect-404-logs"]
410
- }, 0);
478
+ tags: ["redirects", "redirect-404-logs"],
479
+ retries: 0
480
+ });
411
481
  }
412
482
  function fetchStoreSettings(siteId, options) {
413
483
  return cmsFetch(`/api/public/store/${siteId}/settings/`, {
@@ -487,6 +557,7 @@ function createCmsClient(config) {
487
557
  headers: { "Content-Type": "application/json" },
488
558
  body: JSON.stringify(payload),
489
559
  revalidate: CACHE.NO_CACHE,
560
+ retries: 0,
490
561
  ...options
491
562
  });
492
563
  }
@@ -522,6 +593,70 @@ function createCmsClient(config) {
522
593
  ...options
523
594
  });
524
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
+ }
525
660
  return {
526
661
  LIST_DEFAULT_LIMIT,
527
662
  fetchHeader,
@@ -551,6 +686,7 @@ function createCmsClient(config) {
551
686
  resolveRedirect,
552
687
  reportRedirect404,
553
688
  submitContactForm,
689
+ fetchContactConfig,
554
690
  fetchStoreSettings,
555
691
  fetchProductCategories,
556
692
  fetchProductBrands,
@@ -561,6 +697,7 @@ function createCmsClient(config) {
561
697
  fetchCollectionDetail,
562
698
  fetchCollectionDetailById,
563
699
  placeOrder,
700
+ fetchSitemap,
564
701
  fetchSitemapBlogs,
565
702
  fetchSitemapPages,
566
703
  fetchSitemapProducts,
@@ -571,127 +708,116 @@ function createCmsClient(config) {
571
708
  };
572
709
  }
573
710
  //#endregion
574
- //#region src/components/Icon.tsx
575
- /**
576
- * A exhaustive map of Lucide icons commonly used in the CMS.
577
- */
578
- const ICON_MAP = {
579
- Home,
580
- User,
581
- Settings,
582
- Mail,
583
- Bell,
584
- Calendar,
585
- Star,
586
- Heart,
587
- Bookmark,
588
- Search,
589
- Filter,
590
- Download,
591
- Upload,
592
- Trash,
593
- Edit,
594
- Plus,
595
- Minus,
596
- Check,
597
- X,
598
- ChevronRight,
599
- ChevronLeft,
600
- ChevronUp,
601
- ChevronDown,
602
- ChevronsUpDown,
603
- ArrowRight,
604
- ArrowLeft,
605
- ArrowUp,
606
- ArrowDown,
607
- File,
608
- Folder,
609
- Image,
610
- Video,
611
- Music,
612
- Lock,
613
- Unlock,
614
- Eye,
615
- EyeOff,
616
- Sun,
617
- Moon,
618
- Cloud,
619
- Zap,
620
- Activity,
621
- TrendingUp,
622
- TrendingDown,
623
- BarChart,
624
- PieChart,
625
- Database,
626
- Server,
627
- Wifi,
628
- Battery,
629
- Camera,
630
- Mic,
631
- Phone,
632
- MessageSquare,
633
- Send,
634
- Share,
635
- Link,
636
- ExternalLink,
637
- Globe,
638
- MapPin,
639
- Users,
640
- ShoppingCart,
641
- CreditCard,
642
- Package,
643
- Briefcase,
644
- Target,
645
- Flag,
646
- Clipboard,
647
- List,
648
- Grid,
649
- Layout,
650
- Layers,
651
- Copy,
652
- Save,
653
- RefreshCw,
654
- Maximize,
655
- Code,
656
- AlertCircle,
657
- Info,
658
- HelpCircle,
659
- CheckCircle,
660
- XCircle,
661
- Menu,
662
- MoreVertical,
663
- MoreHorizontal,
664
- PlayCircle,
665
- PauseCircle,
666
- LogIn,
667
- LogOut,
668
- UserPlus,
669
- UserMinus,
670
- Tag,
671
- Award,
672
- Gift,
673
- Truck,
674
- Clock,
675
- Handshake,
676
- Stethoscope,
677
- Microscope,
678
- GraduationCap,
679
- BookOpen,
680
- Building2,
681
- Trees,
682
- HeartHandshake,
683
- Brain,
684
- Lightbulb
685
- };
686
- /**
687
- * Dynamic Icon component that renders Lucide icons by name.
688
- * Useful for CMS-driven icons where names are stored as strings.
689
- */
690
- function Icon({ name, fallback = "HelpCircle", ...props }) {
691
- const IconComponent = ICON_MAP[name] ?? ICON_MAP[fallback] ?? HelpCircle;
692
- return React.createElement(IconComponent, props);
693
- }
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
+ ];
694
820
  //#endregion
695
- 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 };
696
822
 
697
823
  //# sourceMappingURL=index.mjs.map