@sprintup-cms/sdk 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -454,52 +454,173 @@ function VideoBlock({ block }) {
454
454
  const embedUrl = url.replace("youtube.com/watch?v=", "youtube.com/embed/").replace("youtu.be/", "youtube.com/embed/").replace("vimeo.com/", "player.vimeo.com/video/");
455
455
  return /* @__PURE__ */ jsx("div", { className: "aspect-video rounded-lg overflow-hidden border bg-muted", children: /* @__PURE__ */ jsx("iframe", { src: embedUrl, allow: "autoplay; fullscreen", allowFullScreen: true, className: "w-full h-full", title: d.title || "Video" }) });
456
456
  }
457
+ function normaliseProduct(raw) {
458
+ if (raw.id !== void 0 && raw.price !== void 0) return raw;
459
+ const firstPlan = Array.isArray(raw.plans) && raw.plans.length > 0 ? raw.plans[0] : null;
460
+ return {
461
+ id: raw._id ?? raw.id ?? String(Math.random()),
462
+ name: raw.name ?? "Unnamed",
463
+ description: raw.description,
464
+ price: firstPlan?.price ?? 0,
465
+ image: raw.image ?? raw.thumbnail ?? void 0,
466
+ rating: raw.rating ?? void 0,
467
+ category: raw.type ? raw.type.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) : void 0,
468
+ badge: raw.duration ? `${raw.duration} months` : void 0
469
+ };
470
+ }
471
+ function ProductListBlock({ block }) {
472
+ const d = getData(block);
473
+ const { title, subtitle, endpoint, columns = 3, showPrice = true, showRating = true, limit = 6, currency = "$" } = d;
474
+ const cols = parseInt(String(columns), 10);
475
+ const [state, setState] = React.useState({
476
+ data: null,
477
+ loading: true,
478
+ error: null
479
+ });
480
+ React.useEffect(() => {
481
+ if (!endpoint) {
482
+ setState({ data: [], loading: false, error: null });
483
+ return;
484
+ }
485
+ let cancelled = false;
486
+ async function fetchData() {
487
+ try {
488
+ const res = await fetch(endpoint);
489
+ if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);
490
+ const json = await res.json();
491
+ if (cancelled) return;
492
+ const items = Array.isArray(json) ? json : json.data ?? json.products ?? [];
493
+ setState({ data: items.map(normaliseProduct), loading: false, error: null });
494
+ } catch (err) {
495
+ if (!cancelled) setState({ data: null, loading: false, error: err.message });
496
+ }
497
+ }
498
+ fetchData();
499
+ return () => {
500
+ cancelled = true;
501
+ };
502
+ }, [endpoint]);
503
+ const gridCols = cols === 2 ? "sm:grid-cols-2" : cols === 4 ? "sm:grid-cols-2 lg:grid-cols-4" : "sm:grid-cols-2 lg:grid-cols-3";
504
+ if (state.loading) {
505
+ return /* @__PURE__ */ jsx("section", { className: "py-12", children: /* @__PURE__ */ jsxs("div", { className: "max-w-6xl mx-auto px-4", children: [
506
+ (title || subtitle) && /* @__PURE__ */ jsxs("div", { className: "text-center mb-8", children: [
507
+ title && /* @__PURE__ */ jsx("div", { className: "h-8 w-64 bg-muted animate-pulse rounded mx-auto" }),
508
+ subtitle && /* @__PURE__ */ jsx("div", { className: "h-4 w-96 max-w-full bg-muted/60 animate-pulse rounded mx-auto mt-2" })
509
+ ] }),
510
+ /* @__PURE__ */ jsx("div", { className: `grid gap-6 grid-cols-1 ${gridCols}`, children: Array.from({ length: Math.min(limit, 6) }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "border rounded-lg overflow-hidden", children: [
511
+ /* @__PURE__ */ jsx("div", { className: "aspect-square bg-muted animate-pulse" }),
512
+ /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-3", children: [
513
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-20 bg-muted/60 animate-pulse rounded" }),
514
+ /* @__PURE__ */ jsx("div", { className: "h-5 w-full bg-muted animate-pulse rounded" }),
515
+ /* @__PURE__ */ jsx("div", { className: "h-6 w-16 bg-muted animate-pulse rounded" })
516
+ ] })
517
+ ] }, i)) })
518
+ ] }) });
519
+ }
520
+ if (state.error) {
521
+ return /* @__PURE__ */ jsx("section", { className: "py-12", children: /* @__PURE__ */ jsx("div", { className: "max-w-6xl mx-auto px-4", children: /* @__PURE__ */ jsxs("div", { className: "text-center py-12 border border-dashed border-border rounded-lg bg-muted/20", children: [
522
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Unable to load products" }),
523
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground/70 mt-1", children: state.error })
524
+ ] }) }) });
525
+ }
526
+ const products = (state.data || []).slice(0, limit);
527
+ if (products.length === 0) {
528
+ return /* @__PURE__ */ jsx("section", { className: "py-12", children: /* @__PURE__ */ jsx("div", { className: "max-w-6xl mx-auto px-4", children: /* @__PURE__ */ jsx("div", { className: "text-center py-12 border border-dashed border-border rounded-lg bg-muted/10", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No products available" }) }) }) });
529
+ }
530
+ return /* @__PURE__ */ jsx("section", { className: "py-12", children: /* @__PURE__ */ jsxs("div", { className: "max-w-6xl mx-auto px-4", children: [
531
+ (title || subtitle) && /* @__PURE__ */ jsxs("div", { className: "text-center mb-8", children: [
532
+ title && /* @__PURE__ */ jsx("h2", { className: "text-2xl md:text-3xl font-semibold tracking-tight", children: title }),
533
+ subtitle && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-2", children: subtitle })
534
+ ] }),
535
+ /* @__PURE__ */ jsx("div", { className: `grid gap-6 grid-cols-1 ${gridCols}`, children: products.map((product) => /* @__PURE__ */ jsxs("div", { className: "border rounded-lg overflow-hidden group hover:shadow-md transition-shadow", children: [
536
+ /* @__PURE__ */ jsxs("div", { className: "aspect-square relative bg-muted overflow-hidden", children: [
537
+ product.image ? /* @__PURE__ */ jsx("img", { src: product.image, alt: product.name, className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" }) : /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center text-muted-foreground/30", children: /* @__PURE__ */ jsx("svg", { className: "w-12 h-12", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" }) }) }),
538
+ product.badge && /* @__PURE__ */ jsx("span", { className: "absolute top-3 left-3 text-xs px-2 py-1 rounded bg-primary text-primary-foreground", children: product.badge })
539
+ ] }),
540
+ /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-2", children: [
541
+ product.category && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground uppercase tracking-wide", children: product.category }),
542
+ /* @__PURE__ */ jsx("h3", { className: "font-medium text-sm line-clamp-2", children: product.name }),
543
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-1", children: [
544
+ showPrice && /* @__PURE__ */ jsxs("span", { className: "font-semibold", children: [
545
+ currency,
546
+ typeof product.price === "number" ? product.price.toFixed(2) : product.price
547
+ ] }),
548
+ showRating && product.rating && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
549
+ /* @__PURE__ */ jsx("svg", { className: "w-3.5 h-3.5 fill-yellow-400 text-yellow-400", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" }) }),
550
+ /* @__PURE__ */ jsx("span", { children: product.rating })
551
+ ] })
552
+ ] })
553
+ ] })
554
+ ] }, product.id)) })
555
+ ] }) });
556
+ }
557
+ function FieldValue({ value, fieldType }) {
558
+ if (value === null || value === void 0 || value === "") return null;
559
+ const isHtml = (v) => typeof v === "string" && /<[a-z][\s\S]*>/i.test(v);
560
+ if (fieldType === "richtext" || isHtml(value)) {
561
+ return /* @__PURE__ */ jsx(
562
+ "div",
563
+ {
564
+ className: "prose prose-neutral dark:prose-invert max-w-none",
565
+ dangerouslySetInnerHTML: { __html: String(value) }
566
+ }
567
+ );
568
+ }
569
+ if (fieldType === "image") {
570
+ return /* @__PURE__ */ jsx("img", { src: String(value), alt: "", className: "rounded-lg border max-h-96 object-cover w-full" });
571
+ }
572
+ if (fieldType === "boolean") {
573
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: value ? "Yes" : "No" });
574
+ }
575
+ if (fieldType === "url") {
576
+ return /* @__PURE__ */ jsx("a", { href: String(value), className: "text-primary underline underline-offset-2 break-all", children: String(value) });
577
+ }
578
+ if (Array.isArray(value)) {
579
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: value.join(", ") });
580
+ }
581
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: String(value) });
582
+ }
583
+ function StructuredBlock({ block, pageType }) {
584
+ const nested = block.content ?? block.data ?? {};
585
+ if (pageType?.sections?.length) {
586
+ return /* @__PURE__ */ jsx("div", { className: "space-y-8", children: pageType.sections.map((section) => {
587
+ const sectionData = nested[section.name] ?? {};
588
+ const filledFields = section.fields.filter((f) => {
589
+ const v = sectionData[f.name];
590
+ return v !== void 0 && v !== null && v !== "";
591
+ });
592
+ if (filledFields.length === 0) return null;
593
+ return /* @__PURE__ */ jsx("section", { className: "space-y-4", children: filledFields.map((field) => {
594
+ const value = sectionData[field.name];
595
+ if (value === void 0 || value === null || value === "") return null;
596
+ return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(FieldValue, { value, fieldType: field.fieldType }) }, field.id);
597
+ }) }, section.id);
598
+ }) });
599
+ }
600
+ const isHtml = (v) => typeof v === "string" && /<[a-z][\s\S]*>/i.test(v);
601
+ return /* @__PURE__ */ jsx("div", { className: "space-y-8", children: Object.entries(nested).map(([sectionName, fields]) => {
602
+ if (!fields || typeof fields !== "object" || Array.isArray(fields)) return null;
603
+ const entries = Object.entries(fields).filter(([, v]) => v !== "" && v !== null && v !== void 0);
604
+ if (entries.length === 0) return null;
605
+ return /* @__PURE__ */ jsx("section", { className: "space-y-4", children: entries.map(([fieldName, value]) => {
606
+ if (typeof value === "object" && !Array.isArray(value)) return null;
607
+ return /* @__PURE__ */ jsx("div", { children: isHtml(value) ? /* @__PURE__ */ jsx("div", { className: "prose prose-neutral dark:prose-invert max-w-none", dangerouslySetInnerHTML: { __html: String(value) } }) : Array.isArray(value) ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: value.join(", ") }) : /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: String(value) }) }, fieldName);
608
+ }) }, sectionName);
609
+ }) });
610
+ }
457
611
  function SectionBlock({ block, pageType }) {
458
612
  const d = getData(block);
459
613
  const section = pageType?.sections.find((s) => s.name === block.type);
460
614
  if (section) {
461
- const filledFields = section.fields.filter((f) => d[f.name] !== void 0 && d[f.name] !== "" && d[f.name] !== null);
615
+ const filledFields = section.fields.filter((f) => {
616
+ const v = d[f.name];
617
+ return v !== void 0 && v !== null && v !== "";
618
+ });
462
619
  if (filledFields.length === 0) return null;
463
- return /* @__PURE__ */ jsx("section", { className: "py-4 space-y-4 border-b border-border last:border-0", children: filledFields.map((field) => {
620
+ return /* @__PURE__ */ jsx("section", { className: "space-y-4", children: filledFields.map((field) => {
464
621
  const value = d[field.name];
465
- if (!value && value !== 0 && value !== false) return null;
466
- if (field.fieldType === "richtext") {
467
- return /* @__PURE__ */ jsxs("div", { children: [
468
- /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-1", children: field.label }),
469
- /* @__PURE__ */ jsx("div", { className: "prose prose-neutral dark:prose-invert max-w-none text-sm", dangerouslySetInnerHTML: { __html: String(value) } })
470
- ] }, field.id);
471
- }
472
- if (field.fieldType === "image") {
473
- return /* @__PURE__ */ jsx("figure", { children: /* @__PURE__ */ jsx("img", { src: String(value), alt: field.label, className: "rounded-lg border max-h-96 object-cover" }) }, field.id);
474
- }
475
- if (field.fieldType === "boolean") {
476
- return /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
477
- /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
478
- field.label,
479
- ":"
480
- ] }),
481
- " ",
482
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: value ? "Yes" : "No" })
483
- ] }, field.id);
484
- }
485
- if (field.fieldType === "url") {
486
- return /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
487
- /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
488
- field.label,
489
- ":"
490
- ] }),
491
- " ",
492
- /* @__PURE__ */ jsx("a", { href: String(value), className: "text-primary underline underline-offset-2 break-all", children: String(value) })
493
- ] }, field.id);
494
- }
495
- return /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
496
- /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
497
- field.label,
498
- ":"
499
- ] }),
500
- " ",
501
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: String(value) })
502
- ] }, field.id);
622
+ if (value === void 0 || value === null || value === "") return null;
623
+ return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(FieldValue, { value, fieldType: field.fieldType }) }, field.id);
503
624
  }) });
504
625
  }
505
626
  const isHtml = (v) => typeof v === "string" && /<[a-z][\s\S]*>/i.test(v);
@@ -507,21 +628,13 @@ function SectionBlock({ block, pageType }) {
507
628
  ([, v]) => v !== "" && v !== null && v !== void 0 && typeof v !== "object" && !Array.isArray(v)
508
629
  );
509
630
  if (entries.length === 0) return null;
510
- return /* @__PURE__ */ jsxs("section", { className: "py-4 space-y-2 border-b border-border last:border-0", children: [
511
- block.label && /* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm text-muted-foreground uppercase tracking-wide", children: block.label }),
512
- entries.map(
513
- ([key, value]) => isHtml(value) ? /* @__PURE__ */ jsx("div", { className: "prose prose-neutral dark:prose-invert max-w-none text-sm", dangerouslySetInnerHTML: { __html: String(value) } }, key) : /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
514
- /* @__PURE__ */ jsxs("span", { className: "font-medium capitalize", children: [
515
- key.replace(/_/g, " "),
516
- ":"
517
- ] }),
518
- " ",
519
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: String(value) })
520
- ] }, key)
521
- )
522
- ] });
631
+ return /* @__PURE__ */ jsx("section", { className: "space-y-2", children: entries.map(
632
+ ([key, value]) => isHtml(value) ? /* @__PURE__ */ jsx("div", { className: "prose prose-neutral dark:prose-invert max-w-none", dangerouslySetInnerHTML: { __html: String(value) } }, key) : /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: String(value) }, key)
633
+ ) });
523
634
  }
524
635
  var BUILT_IN = {
636
+ // Structured page type — content is nested { sectionName: { fieldName: value } }
637
+ "__structured__": (b, pt) => /* @__PURE__ */ jsx(StructuredBlock, { block: b, pageType: pt }),
525
638
  heading: (b) => /* @__PURE__ */ jsx(HeadingBlock, { block: b }),
526
639
  text: (b) => /* @__PURE__ */ jsx(TextBlock, { block: b }),
527
640
  richtext: (b) => /* @__PURE__ */ jsx(RichTextBlock, { block: b }),
@@ -536,7 +649,8 @@ var BUILT_IN = {
536
649
  alert: (b) => /* @__PURE__ */ jsx(AlertBlock, { block: b }),
537
650
  divider: () => /* @__PURE__ */ jsx(DividerBlock, {}),
538
651
  spacer: (b) => /* @__PURE__ */ jsx(SpacerBlock, { block: b }),
539
- video: (b) => /* @__PURE__ */ jsx(VideoBlock, { block: b })
652
+ video: (b) => /* @__PURE__ */ jsx(VideoBlock, { block: b }),
653
+ "product-list": (b) => /* @__PURE__ */ jsx(ProductListBlock, { block: b })
540
654
  };
541
655
  function CMSBlocks({ blocks, pageType, className = "", custom = {} }) {
542
656
  if (!blocks?.length) return null;