@cimplify/sdk 0.24.2 → 0.26.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.
Files changed (43) hide show
  1. package/dist/advanced.d.mts +2 -2
  2. package/dist/advanced.d.ts +2 -2
  3. package/dist/{chunk-H34GSEGR.mjs → chunk-JMNMSVDQ.mjs} +24 -1
  4. package/dist/{chunk-IYQGGFVQ.js → chunk-WMQXSDRC.js} +26 -0
  5. package/dist/{index-DCoxHrmr.d.mts → index-2JxW7TuQ.d.mts} +2 -2
  6. package/dist/{index-DIHoZUz_.d.mts → index-7mX_cUpQ.d.mts} +1 -1
  7. package/dist/{index-DT0Mzy8v.d.ts → index-DBM89ktY.d.ts} +2 -2
  8. package/dist/{index-CJZMsbDi.d.ts → index-DEx3GwlW.d.ts} +1 -1
  9. package/dist/index.d.mts +5 -5
  10. package/dist/index.d.ts +5 -5
  11. package/dist/index.js +18 -6
  12. package/dist/index.mjs +1 -1
  13. package/dist/{payment-DqiwuLma.d.mts → payment-5a21yzi7.d.mts} +22 -1
  14. package/dist/{payment-DqiwuLma.d.ts → payment-5a21yzi7.d.ts} +22 -1
  15. package/dist/{price-BZlddg1A.d.ts → price-BS0Z5Oi9.d.ts} +1 -1
  16. package/dist/{price-DVQrpE7j.d.mts → price-CGPMlQUk.d.mts} +1 -1
  17. package/dist/react.d.mts +457 -405
  18. package/dist/react.d.ts +457 -405
  19. package/dist/react.js +2193 -1065
  20. package/dist/react.mjs +2161 -1059
  21. package/dist/styles.css +1 -1
  22. package/dist/utils.d.mts +3 -3
  23. package/dist/utils.d.ts +3 -3
  24. package/package.json +1 -1
  25. package/registry/accommodation-card.json +17 -0
  26. package/registry/cart-page.json +1 -1
  27. package/registry/catalogue-page.json +1 -1
  28. package/registry/collection-page.json +1 -1
  29. package/registry/compact-service-card.json +16 -0
  30. package/registry/digital-product-card.json +16 -0
  31. package/registry/food-product-card.json +16 -0
  32. package/registry/food-product-layout.json +1 -1
  33. package/registry/index.json +87 -17
  34. package/registry/lease-service-card.json +17 -0
  35. package/registry/product-card.json +1 -1
  36. package/registry/product-page.json +1 -1
  37. package/registry/rental-service-card.json +16 -0
  38. package/registry/retail-product-card.json +16 -0
  39. package/registry/schedule-service-card.json +16 -0
  40. package/registry/search-page.json +1 -1
  41. package/registry/standard-service-card.json +16 -0
  42. package/registry/subscription-card.json +16 -0
  43. package/registry/wholesale-product-card.json +17 -0
package/dist/styles.css CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.z-50{z-index:50}.container{width:100%}.-mt-px{margin-top:-1px}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.aspect-\[5\/2\]{aspect-ratio:5/2}.aspect-square{aspect-ratio:1}.h-fit{height:fit-content}.h-full{height:100%}.max-h-\[85vh\]{max-height:85vh}.w-2\/5{width:40%}.w-3\/5{width:60%}.w-4\/5{width:80%}.w-full{width:100%}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.list-none{list-style-type:none}.\[appearance\:textfield\]{appearance:textfield}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-border>:not(:last-child)){border-color:var(--color-border,oklch(90% 0 0))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.rounded{border-radius:var(--radius,.5rem)}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-none{--tw-border-style:none;border-style:none}.border-border{border-color:var(--color-border,oklch(90% 0 0))}.border-input{border-color:var(--color-input,oklch(90% 0 0))}.border-muted-foreground\/30{border-color:#6363634d}@supports (color:color-mix(in lab, red, red)){.border-muted-foreground\/30{border-color:color-mix(in oklab, var(--color-muted-foreground,oklch(50% 0 0)) 30%, transparent)}}.border-primary{border-color:var(--color-primary,oklch(50% .1 35))}.bg-background{background-color:var(--color-background,oklch(99% 0 0))}.bg-destructive\/10{background-color:#bb061e1a}@supports (color:color-mix(in lab, red, red)){.bg-destructive\/10{background-color:color-mix(in oklab, var(--color-destructive,oklch(50% .2 25)) 10%, transparent)}}.bg-muted{background-color:var(--color-muted,oklch(95% 0 0))}.bg-primary{background-color:var(--color-primary,oklch(50% .1 35))}.bg-primary\/5{background-color:#934c3a0d}@supports (color:color-mix(in lab, red, red)){.bg-primary\/5{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 5%, transparent)}}.bg-primary\/10{background-color:#934c3a1a}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 10%, transparent)}}.bg-transparent{background-color:#0000}.object-cover{object-fit:cover}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-\[inherit\]{font-family:inherit}.text-\[10px\]{font-size:10px}.text-\[inherit\]{color:inherit}.text-destructive{color:var(--color-destructive,oklch(50% .2 25))}.text-foreground{color:var(--color-foreground,oklch(15% 0 0))}.text-muted-foreground{color:var(--color-muted-foreground,oklch(50% 0 0))}.text-muted-foreground\/60{color:#63636399}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/60{color:color-mix(in oklab, var(--color-muted-foreground,oklch(50% 0 0)) 60%, transparent)}}.text-primary{color:var(--color-primary,oklch(50% .1 35))}.text-primary-foreground{color:var(--color-primary-foreground,oklch(99% 0 0))}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.accent-primary{accent-color:var(--color-primary,oklch(50% .1 35))}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.\[cimplify\:checkout\]{cimplify:checkout}@media (hover:hover){.group-hover\:scale-105:is(:where(.group):hover *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}}.placeholder\:text-muted-foreground::placeholder{color:var(--color-muted-foreground,oklch(50% 0 0))}@media (hover:hover){.hover\:border-primary\/50:hover{border-color:#934c3a80}@supports (color:color-mix(in lab, red, red)){.hover\:border-primary\/50:hover{border-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 50%, transparent)}}.hover\:bg-destructive\/10:hover{background-color:#bb061e1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-destructive\/10:hover{background-color:color-mix(in oklab, var(--color-destructive,oklch(50% .2 25)) 10%, transparent)}}.hover\:bg-muted:hover{background-color:var(--color-muted,oklch(95% 0 0))}.hover\:bg-primary\/90:hover{background-color:#934c3ae6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 90%, transparent)}}.hover\:text-primary:hover{color:var(--color-primary,oklch(50% .1 35))}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-ring:focus{--tw-ring-color:var(--color-ring,oklch(50% .1 35))}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.data-\[checked\]\:border-primary[data-checked]{border-color:var(--color-primary,oklch(50% .1 35))}.data-\[checked\]\:bg-primary\/5[data-checked]{background-color:#934c3a0d}@supports (color:color-mix(in lab, red, red)){.data-\[checked\]\:bg-primary\/5[data-checked]{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 5%, transparent)}}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}.\[\&\:\:-webkit-inner-spin-button\]\:appearance-none::-webkit-inner-spin-button{appearance:none}.\[\&\:\:-webkit-outer-spin-button\]\:appearance-none::-webkit-outer-spin-button{appearance:none}[open]>.\[\[open\]\>\&\]\:rotate-180{rotate:180deg}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-1\/2{top:50%}.z-10{z-index:10}.z-50{z-index:50}.container{width:100%}.mx-auto{margin-inline:auto}.-mt-px{margin-top:-1px}.ml-auto{margin-left:auto}.line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.aspect-\[3\/4\]{aspect-ratio:3/4}.aspect-\[4\/3\]{aspect-ratio:4/3}.aspect-\[5\/2\]{aspect-ratio:5/2}.aspect-\[16\/9\]{aspect-ratio:16/9}.aspect-square{aspect-ratio:1}.h-\[6px\]{height:6px}.h-\[7px\]{height:7px}.h-\[72px\]{height:72px}.h-fit{height:fit-content}.h-full{height:100%}.max-h-\[85vh\]{max-height:85vh}.w-1\/2{width:50%}.w-2\/5{width:40%}.w-3\/4{width:75%}.w-3\/5{width:60%}.w-4\/5{width:80%}.w-\[6px\]{width:6px}.w-\[7px\]{width:7px}.w-\[72px\]{width:72px}.w-full{width:100%}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.list-none{list-style-type:none}.\[appearance\:textfield\]{appearance:textfield}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-border>:not(:last-child)){border-color:var(--color-border,oklch(90% 0 0))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:var(--radius,.5rem)}.rounded-\[14px\]{border-radius:14px}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-none{--tw-border-style:none;border-style:none}.border-border{border-color:var(--color-border,oklch(90% 0 0))}.border-input{border-color:var(--color-input,oklch(90% 0 0))}.border-muted-foreground\/30{border-color:#6363634d}@supports (color:color-mix(in lab, red, red)){.border-muted-foreground\/30{border-color:color-mix(in oklab, var(--color-muted-foreground,oklch(50% 0 0)) 30%, transparent)}}.border-primary{border-color:var(--color-primary,oklch(50% .1 35))}.border-transparent{border-color:#0000}.bg-background{background-color:var(--color-background,oklch(99% 0 0))}.bg-background\/50{background-color:#fcfcfc80}@supports (color:color-mix(in lab, red, red)){.bg-background\/50{background-color:color-mix(in oklab, var(--color-background,oklch(99% 0 0)) 50%, transparent)}}.bg-background\/90{background-color:#fcfcfce6}@supports (color:color-mix(in lab, red, red)){.bg-background\/90{background-color:color-mix(in oklab, var(--color-background,oklch(99% 0 0)) 90%, transparent)}}.bg-background\/92{background-color:#fcfcfceb}@supports (color:color-mix(in lab, red, red)){.bg-background\/92{background-color:color-mix(in oklab, var(--color-background,oklch(99% 0 0)) 92%, transparent)}}.bg-border{background-color:var(--color-border,oklch(90% 0 0))}.bg-destructive{background-color:var(--color-destructive,oklch(50% .2 25))}.bg-destructive\/10{background-color:#bb061e1a}@supports (color:color-mix(in lab, red, red)){.bg-destructive\/10{background-color:color-mix(in oklab, var(--color-destructive,oklch(50% .2 25)) 10%, transparent)}}.bg-foreground{background-color:var(--color-foreground,oklch(15% 0 0))}.bg-muted{background-color:var(--color-muted,oklch(95% 0 0))}.bg-primary{background-color:var(--color-primary,oklch(50% .1 35))}.bg-primary\/5{background-color:#934c3a0d}@supports (color:color-mix(in lab, red, red)){.bg-primary\/5{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 5%, transparent)}}.bg-primary\/10{background-color:#934c3a1a}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 10%, transparent)}}.bg-transparent{background-color:#0000}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.object-cover{object-fit:cover}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-\[inherit\]{font-family:inherit}.text-\[9px\]{font-size:9px}.text-\[10\.5px\]{font-size:10.5px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12\.5px\]{font-size:12.5px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14\.5px\]{font-size:14.5px}.text-\[14px\]{font-size:14px}.text-\[inherit\]{color:inherit}.text-background{color:var(--color-background,oklch(99% 0 0))}.text-border{color:var(--color-border,oklch(90% 0 0))}.text-destructive{color:var(--color-destructive,oklch(50% .2 25))}.text-destructive-foreground{color:var(--color-destructive-foreground,oklch(99% 0 0))}.text-foreground{color:var(--color-foreground,oklch(15% 0 0))}.text-muted-foreground{color:var(--color-muted-foreground,oklch(50% 0 0))}.text-muted-foreground\/30{color:#6363634d}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/30{color:color-mix(in oklab, var(--color-muted-foreground,oklch(50% 0 0)) 30%, transparent)}}.text-muted-foreground\/60{color:#63636399}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/60{color:color-mix(in oklab, var(--color-muted-foreground,oklch(50% 0 0)) 60%, transparent)}}.text-primary{color:var(--color-primary,oklch(50% .1 35))}.text-primary-foreground{color:var(--color-primary-foreground,oklch(99% 0 0))}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.accent-foreground{accent-color:var(--color-foreground,oklch(15% 0 0))}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow-\[0_1px_3px_rgba\(0\,0\,0\,0\.04\)\,0_6px_24px_rgba\(0\,0\,0\,0\.06\)\]{--tw-shadow:0 1px 3px var(--tw-shadow-color,#0000000a), 0 6px 24px var(--tw-shadow-color,#0000000f);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-\[0_1px_4px_rgba\(0\,0\,0\,0\.10\)\,0_4px_12px_rgba\(0\,0\,0\,0\.08\)\]{--tw-shadow:0 1px 4px var(--tw-shadow-color,#0000001a), 0 4px 12px var(--tw-shadow-color,#00000014);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-\[1px\]{--tw-backdrop-blur:blur(1px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-700{--tw-duration:.7s;transition-duration:.7s}.ease-\[cubic-bezier\(0\.19\,1\,0\.22\,1\)\]{--tw-ease:cubic-bezier(.19,1,.22,1);transition-timing-function:cubic-bezier(.19,1,.22,1)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.\[cimplify\:checkout\]{cimplify:checkout}@media (hover:hover){.group-hover\:scale-105:is(:where(.group):hover *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.group-hover\:scale-\[1\.04\]:is(:where(.group):hover *){scale:1.04}.group-hover\:text-foreground:is(:where(.group):hover *){color:var(--color-foreground,oklch(15% 0 0))}.group-hover\:text-primary:is(:where(.group):hover *){color:var(--color-primary,oklch(50% .1 35))}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.placeholder\:text-muted-foreground::placeholder{color:var(--color-muted-foreground,oklch(50% 0 0))}@media (hover:hover){.hover\:-translate-y-\[1px\]:hover{--tw-translate-y:calc(1px * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:\!scale-110:hover{--tw-scale-x:110%!important;--tw-scale-y:110%!important;--tw-scale-z:110%!important;scale:var(--tw-scale-x) var(--tw-scale-y)!important}.hover\:scale-110:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-\[1\.04\]:hover{scale:1.04}.hover\:border-primary\/20:hover{border-color:#934c3a33}@supports (color:color-mix(in lab, red, red)){.hover\:border-primary\/20:hover{border-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 20%, transparent)}}.hover\:border-primary\/50:hover{border-color:#934c3a80}@supports (color:color-mix(in lab, red, red)){.hover\:border-primary\/50:hover{border-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 50%, transparent)}}.hover\:\!bg-foreground:hover{background-color:var(--color-foreground,oklch(15% 0 0))!important}.hover\:bg-background:hover{background-color:var(--color-background,oklch(99% 0 0))}.hover\:bg-destructive\/10:hover{background-color:#bb061e1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-destructive\/10:hover{background-color:color-mix(in oklab, var(--color-destructive,oklch(50% .2 25)) 10%, transparent)}}.hover\:bg-foreground\/90:hover{background-color:#0b0b0be6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-foreground\/90:hover{background-color:color-mix(in oklab, var(--color-foreground,oklch(15% 0 0)) 90%, transparent)}}.hover\:bg-muted:hover{background-color:var(--color-muted,oklch(95% 0 0))}.hover\:bg-muted\/80:hover{background-color:#eeec}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/80:hover{background-color:color-mix(in oklab, var(--color-muted,oklch(95% 0 0)) 80%, transparent)}}.hover\:bg-primary\/90:hover{background-color:#934c3ae6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 90%, transparent)}}.hover\:\!text-background:hover{color:var(--color-background,oklch(99% 0 0))!important}.hover\:text-destructive:hover{color:var(--color-destructive,oklch(50% .2 25))}.hover\:text-foreground:hover{color:var(--color-foreground,oklch(15% 0 0))}.hover\:text-primary:hover{color:var(--color-primary,oklch(50% .1 35))}.hover\:text-primary\/80:hover{color:#934c3acc}@supports (color:color-mix(in lab, red, red)){.hover\:text-primary\/80:hover{color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 80%, transparent)}}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-\[0_2px_6px_rgba\(0\,0\,0\,0\.04\)\,0_12px_40px_rgba\(0\,0\,0\,0\.10\)\]:hover{--tw-shadow:0 2px 6px var(--tw-shadow-color,#0000000a), 0 12px 40px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}}.focus\:border-primary:focus{border-color:var(--color-primary,oklch(50% .1 35))}.focus\:border-primary\/30:focus{border-color:#934c3a4d}@supports (color:color-mix(in lab, red, red)){.focus\:border-primary\/30:focus{border-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 30%, transparent)}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-primary\/10:focus{--tw-ring-color:#934c3a1a}@supports (color:color-mix(in lab, red, red)){.focus\:ring-primary\/10:focus{--tw-ring-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 10%, transparent)}}.focus\:ring-primary\/20:focus{--tw-ring-color:#934c3a33}@supports (color:color-mix(in lab, red, red)){.focus\:ring-primary\/20:focus{--tw-ring-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 20%, transparent)}}.focus\:ring-ring:focus{--tw-ring-color:var(--color-ring,oklch(50% .1 35))}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.data-\[checked\]\:border-primary[data-checked]{border-color:var(--color-primary,oklch(50% .1 35))}.data-\[checked\]\:bg-primary\/5[data-checked]{background-color:#934c3a0d}@supports (color:color-mix(in lab, red, red)){.data-\[checked\]\:bg-primary\/5[data-checked]{background-color:color-mix(in oklab, var(--color-primary,oklch(50% .1 35)) 5%, transparent)}}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}.\[\&\:\:-webkit-inner-spin-button\]\:appearance-none::-webkit-inner-spin-button{appearance:none}.\[\&\:\:-webkit-outer-spin-button\]\:appearance-none::-webkit-outer-spin-button{appearance:none}[open]>.\[\[open\]\>\&\]\:rotate-180{rotate:180deg}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}
package/dist/utils.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- export { C as CURRENCY_SYMBOLS, M as MOBILE_MONEY_PROVIDERS, v as categorizePaymentError, B as detectMobileMoneyProvider, c as formatMoney, d as formatNumberCompact, f as formatPrice, a as formatPriceAdjustment, b as formatPriceCompact, j as formatPriceWithTax, e as formatProductPrice, m as getBasePrice, k as getCurrencySymbol, o as getDiscountPercentage, l as getDisplayPrice, q as getMarkupPercentage, r as getProductCurrency, g as getTaxAmount, h as hasTaxInfo, n as isOnSale, y as isPaymentStatusFailure, z as isPaymentStatusRequiresAction, A as isPaymentStatusSuccess, i as isTaxInclusive, F as isValidPhoneForContext, w as normalizePaymentResponse, D as normalizePhoneToE164, x as normalizeStatusResponse, p as parsePrice, E as resolvePhoneCountryCode } from './index-DCoxHrmr.mjs';
2
- import './payment-DqiwuLma.mjs';
3
- import './price-DVQrpE7j.mjs';
1
+ export { C as CURRENCY_SYMBOLS, M as MOBILE_MONEY_PROVIDERS, v as categorizePaymentError, B as detectMobileMoneyProvider, c as formatMoney, d as formatNumberCompact, f as formatPrice, a as formatPriceAdjustment, b as formatPriceCompact, j as formatPriceWithTax, e as formatProductPrice, m as getBasePrice, k as getCurrencySymbol, o as getDiscountPercentage, l as getDisplayPrice, q as getMarkupPercentage, r as getProductCurrency, g as getTaxAmount, h as hasTaxInfo, n as isOnSale, y as isPaymentStatusFailure, z as isPaymentStatusRequiresAction, A as isPaymentStatusSuccess, i as isTaxInclusive, F as isValidPhoneForContext, w as normalizePaymentResponse, D as normalizePhoneToE164, x as normalizeStatusResponse, p as parsePrice, E as resolvePhoneCountryCode } from './index-2JxW7TuQ.mjs';
2
+ import './payment-5a21yzi7.mjs';
3
+ import './price-CGPMlQUk.mjs';
4
4
  import 'libphonenumber-js';
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { C as CURRENCY_SYMBOLS, M as MOBILE_MONEY_PROVIDERS, v as categorizePaymentError, B as detectMobileMoneyProvider, c as formatMoney, d as formatNumberCompact, f as formatPrice, a as formatPriceAdjustment, b as formatPriceCompact, j as formatPriceWithTax, e as formatProductPrice, m as getBasePrice, k as getCurrencySymbol, o as getDiscountPercentage, l as getDisplayPrice, q as getMarkupPercentage, r as getProductCurrency, g as getTaxAmount, h as hasTaxInfo, n as isOnSale, y as isPaymentStatusFailure, z as isPaymentStatusRequiresAction, A as isPaymentStatusSuccess, i as isTaxInclusive, F as isValidPhoneForContext, w as normalizePaymentResponse, D as normalizePhoneToE164, x as normalizeStatusResponse, p as parsePrice, E as resolvePhoneCountryCode } from './index-DT0Mzy8v.js';
2
- import './payment-DqiwuLma.js';
3
- import './price-BZlddg1A.js';
1
+ export { C as CURRENCY_SYMBOLS, M as MOBILE_MONEY_PROVIDERS, v as categorizePaymentError, B as detectMobileMoneyProvider, c as formatMoney, d as formatNumberCompact, f as formatPrice, a as formatPriceAdjustment, b as formatPriceCompact, j as formatPriceWithTax, e as formatProductPrice, m as getBasePrice, k as getCurrencySymbol, o as getDiscountPercentage, l as getDisplayPrice, q as getMarkupPercentage, r as getProductCurrency, g as getTaxAmount, h as hasTaxInfo, n as isOnSale, y as isPaymentStatusFailure, z as isPaymentStatusRequiresAction, A as isPaymentStatusSuccess, i as isTaxInclusive, F as isValidPhoneForContext, w as normalizePaymentResponse, D as normalizePhoneToE164, x as normalizeStatusResponse, p as parsePrice, E as resolvePhoneCountryCode } from './index-DBM89ktY.js';
2
+ import './payment-5a21yzi7.js';
3
+ import './price-BS0Z5Oi9.js';
4
4
  import 'libphonenumber-js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cimplify/sdk",
3
- "version": "0.24.2",
3
+ "version": "0.26.0",
4
4
  "description": "Cimplify Commerce SDK for storefronts",
5
5
  "keywords": [
6
6
  "cimplify",
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "accommodation-card",
3
+ "title": "AccommodationCard",
4
+ "description": "Hotel/accommodation card with per-night pricing, amenities, capacity, and cancellation.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "price",
8
+ "volume-pricing",
9
+ "cn"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "cards/accommodation-card.tsx",
14
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport type { ServiceCardLayoutProps } from \"./standard-service-card\";\nimport { CardShell, CardImage } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { VolumePricing } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport function AccommodationCard({\n product,\n onBook,\n renderImage,\n renderLink,\n className,\n}: ServiceCardLayoutProps): React.ReactElement {\n const image = product.image_url || product.images?.[0] || \"\";\n const hasTiers = product.quantity_pricing && product.quantity_pricing.length > 1;\n const cancellationHours = product.cancellation_window_minutes\n ? Math.floor(product.cancellation_window_minutes / 60)\n : null;\n const tags = product.tags || [];\n\n return (\n <CardShell product={product} renderLink={renderLink} className={className}>\n <CardImage src={image} alt={product.name} aspectRatio=\"16/9\" renderImage={renderImage}>\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/50 to-transparent pointer-events-none\" />\n\n <span className=\"absolute bottom-3 left-3 inline-flex items-center text-[12px] font-semibold px-2.5 py-1 rounded-lg bg-background/92 backdrop-blur-xl text-foreground shadow-sm\">\n Per night\n </span>\n\n <span className=\"absolute bottom-3 right-3 text-white font-bold text-lg drop-shadow-sm\">\n <Price amount={product.default_price} />/night\n </span>\n </CardImage>\n\n <div className=\"p-4\">\n <h3 className=\"text-[14.5px] font-bold text-foreground leading-snug tracking-tight\">\n {product.name}\n </h3>\n {product.description && (\n <p className=\"text-[12.5px] text-muted-foreground mt-1 leading-relaxed line-clamp-2\">\n {product.description}\n </p>\n )}\n\n {/* Amenity / tag pills */}\n {tags.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mt-2.5\">\n {tags.slice(0, 4).map((tag) => (\n <span key={tag} className=\"text-[10px] font-medium text-muted-foreground px-1.5 py-0.5 bg-muted rounded\">\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Guest capacity */}\n {product.general_service_capacity != null && (\n <div className=\"flex items-center gap-1.5 mt-2 text-[11px] text-muted-foreground\">\n <svg className=\"w-3.5 h-3.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0\" />\n </svg>\n {product.general_service_capacity} guest{product.general_service_capacity > 1 ? \"s\" : \"\"}\n </div>\n )}\n\n {/* Extended stay pricing */}\n {hasTiers && (\n <div className=\"mt-3\">\n <VolumePricing tiers={product.quantity_pricing!} />\n </div>\n )}\n\n <div className=\"flex items-center justify-between mt-3 pt-3 border-t border-border\">\n <div className=\"flex items-center gap-2 text-[11px] text-muted-foreground\">\n {cancellationHours != null && (\n <span className=\"text-emerald-600 font-medium\">\n Free cancellation {cancellationHours}h\n </span>\n )}\n {product.min_order_quantity != null && product.min_order_quantity > 1 && (\n <span>Min. {product.min_order_quantity} nights</span>\n )}\n </div>\n <button\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onBook?.(product); }}\n className=\"text-[13px] font-semibold text-primary hover:text-primary/80 transition-colors\"\n >\n Book room &rarr;\n </button>\n </div>\n </div>\n </CardShell>\n );\n}\n"
15
+ }
16
+ ]
17
+ }
@@ -11,7 +11,7 @@
11
11
  "files": [
12
12
  {
13
13
  "path": "cart-page.tsx",
14
- "content": "\"use client\";\n\nimport React from \"react\";\nimport { useCart } from \"@cimplify/sdk/react\";\nimport { CartSummary } from \"./cart-summary\";\nimport { DiscountInput } from \"@cimplify/sdk/react\";\nimport type { DiscountValidation } from \"@cimplify/sdk\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface CartPageClassNames {\n root?: string;\n header?: string;\n title?: string;\n content?: string;\n discount?: string;\n footer?: string;\n checkoutButton?: string;\n continueButton?: string;\n empty?: string;\n}\n\nexport interface CartPageProps {\n /** Page title. */\n title?: string;\n /** Called when checkout is initiated. */\n onCheckout?: () => void;\n /** Called when \"continue shopping\" is clicked. */\n onContinueShopping?: () => void;\n /** Called when a valid discount is applied. */\n onDiscountApply?: (validation: DiscountValidation) => void;\n /** Called when discount is removed. */\n onDiscountClear?: () => void;\n /** Show discount code input. Default: true. */\n showDiscount?: boolean;\n /** Show \"continue shopping\" link. Default: true. */\n showContinueShopping?: boolean;\n /** Checkout button text. */\n checkoutLabel?: string;\n className?: string;\n classNames?: CartPageClassNames;\n}\n\n/**\n * CartPage — full-page cart view with summary, discount input, and checkout.\n *\n * Composes CartSummary and DiscountInput into a complete cart experience.\n */\nexport function CartPage({\n title = \"Your Cart\",\n onCheckout,\n onContinueShopping,\n onDiscountApply,\n onDiscountClear,\n showDiscount = true,\n showContinueShopping = true,\n checkoutLabel = \"Proceed to Checkout\",\n className,\n classNames,\n}: CartPageProps): React.ReactElement {\n const { isEmpty, subtotal } = useCart();\n\n return (\n <div data-cimplify-cart-page className={cn(className, classNames?.root)}>\n {/* Header */}\n <div data-cimplify-cart-page-header className={classNames?.header}>\n <h1 data-cimplify-cart-page-title className={classNames?.title}>\n {title}\n </h1>\n {showContinueShopping && (\n <button\n type=\"button\"\n onClick={onContinueShopping}\n data-cimplify-cart-continue\n className={classNames?.continueButton}\n >\n Continue Shopping\n </button>\n )}\n </div>\n\n {/* Cart content */}\n <div data-cimplify-cart-page-content className={classNames?.content}>\n <CartSummary />\n\n {/* Discount input */}\n {showDiscount && !isEmpty && (\n <div data-cimplify-cart-page-discount className={classNames?.discount}>\n <DiscountInput\n orderSubtotal={subtotal.toString()}\n onApply={onDiscountApply}\n onClear={onDiscountClear}\n />\n </div>\n )}\n </div>\n\n {/* Footer with checkout */}\n {!isEmpty && (\n <div data-cimplify-cart-page-footer className={classNames?.footer}>\n <button\n type=\"button\"\n onClick={onCheckout}\n data-cimplify-cart-checkout-button\n className={classNames?.checkoutButton}\n >\n {checkoutLabel}\n </button>\n </div>\n )}\n </div>\n );\n}\n"
14
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport { useCart } from \"@cimplify/sdk/react\";\nimport type { DiscountValidation } from \"@cimplify/sdk\";\nimport type { CartLayoutProps } from \"./cart-layouts/shared\";\nimport { DefaultCartLayout } from \"./cart-layouts/default-cart-layout\";\nimport { CompactCartLayout } from \"./cart-layouts/compact-cart-layout\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport type { CartLayoutProps };\n\nexport enum CartTemplate {\n Default = \"default\",\n Compact = \"compact\",\n}\n\nexport interface CartPageClassNames {\n root?: string;\n}\n\nexport interface CartPageProps {\n /** Explicit template. */\n template?: CartTemplate;\n /** Layout map for overrides. */\n layouts?: Partial<Record<CartTemplate | string, React.ComponentType<CartLayoutProps>>>;\n /** Page title. */\n title?: string;\n /** Called when checkout is initiated. */\n onCheckout?: () => void;\n /** Called when \"continue shopping\" is clicked. */\n onContinueShopping?: () => void;\n /** Called when a valid discount is applied. */\n onDiscountApply?: (validation: DiscountValidation) => void;\n /** Called when discount is removed. */\n onDiscountClear?: () => void;\n /** Show discount code input. */\n showDiscount?: boolean;\n /** Checkout button text. */\n checkoutLabel?: string;\n className?: string;\n classNames?: CartPageClassNames;\n}\n\nconst BUILT_IN_LAYOUTS: Record<string, React.ComponentType<CartLayoutProps>> = {\n [CartTemplate.Default]: DefaultCartLayout,\n [CartTemplate.Compact]: CompactCartLayout,\n};\n\nexport function CartPage({\n template,\n layouts,\n onCheckout,\n onContinueShopping,\n onDiscountApply,\n onDiscountClear,\n showDiscount = true,\n checkoutLabel,\n className,\n classNames,\n}: CartPageProps): React.ReactElement {\n const cart = useCart();\n\n const key = template ?? CartTemplate.Default;\n const Layout = layouts?.[key] ?? BUILT_IN_LAYOUTS[key] ?? DefaultCartLayout;\n\n return (\n <div data-cimplify-cart-page className={cn(className, classNames?.root)}>\n <Layout\n cart={cart}\n onCheckout={onCheckout}\n onContinueShopping={onContinueShopping}\n onDiscountApply={onDiscountApply}\n onDiscountClear={onDiscountClear}\n showDiscount={showDiscount}\n checkoutLabel={checkoutLabel}\n />\n </div>\n );\n}\n"
15
15
  }
16
16
  ]
17
17
  }
@@ -12,7 +12,7 @@
12
12
  "files": [
13
13
  {
14
14
  "path": "catalogue-page.tsx",
15
- "content": "\"use client\";\n\nimport React, { useState, useMemo, useCallback } from \"react\";\nimport type { Product, Category } from \"@cimplify/sdk\";\nimport { useProducts, useCategories } from \"@cimplify/sdk/react\";\nimport { ProductGrid } from \"./product-grid\";\nimport { CategoryFilter } from \"@cimplify/sdk/react\";\nimport { SearchInput } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface CataloguePageClassNames {\n root?: string;\n header?: string;\n title?: string;\n searchContainer?: string;\n filterContainer?: string;\n toolbar?: string;\n gridContainer?: string;\n empty?: string;\n loading?: string;\n pagination?: string;\n}\n\nexport interface CataloguePageProps {\n /** Page title. */\n title?: string;\n /** Pre-fetched products for SSR. When provided, skips client-side fetch. */\n products?: Product[];\n /** Pre-fetched categories for SSR. */\n categories?: Category[];\n /** Show category filter. Default: true. */\n showFilter?: boolean;\n /** Show search input. Default: true. */\n showSearch?: boolean;\n /** Show sort controls. Default: false. */\n showSort?: boolean;\n /** Show in-stock toggle. Default: false. */\n showInStockFilter?: boolean;\n /** Show price range filter. Default: false. */\n showPriceRange?: boolean;\n /** Show tags filter. Default: false. */\n showTagsFilter?: boolean;\n /** Available tags for filtering. When provided, renders tag chips. */\n availableTags?: string[];\n /** Default sort. */\n defaultSort?: { by: \"name\" | \"price\" | \"created_at\" | \"updated_at\"; order: \"asc\" | \"desc\" };\n /** Products per page. */\n pageSize?: number;\n /** Called when a product is selected (e.g. navigate to product page). */\n onProductClick?: (product: Product) => void;\n /** Called when a category is selected. */\n onCategoryClick?: (category: Category) => void;\n /** Custom card renderer passed to ProductGrid. */\n renderCard?: (product: Product) => React.ReactNode;\n /** Custom image renderer passed to ProductGrid. */\n renderImage?: (props: { src: string; alt: string; className?: string }) => React.ReactNode;\n /** Grid column config. */\n columns?: { sm?: number; md?: number; lg?: number; xl?: number };\n className?: string;\n classNames?: CataloguePageClassNames;\n}\n\n/**\n * CataloguePage — browse all products with server-side filtering, sorting, and pagination.\n *\n * SSR-friendly: pass `products` and `categories` props for server rendering.\n * Omit them for fully client-side data fetching.\n */\nexport function CataloguePage({\n title = \"Products\",\n products: productsProp,\n categories: categoriesProp,\n showFilter = true,\n showSearch = true,\n showSort = false,\n showInStockFilter = false,\n showPriceRange = false,\n showTagsFilter = false,\n availableTags,\n defaultSort,\n pageSize,\n onProductClick,\n onCategoryClick,\n renderCard,\n renderImage,\n columns,\n className,\n classNames,\n}: CataloguePageProps): React.ReactElement {\n const [selectedCategoryId, setSelectedCategoryId] = useState<string | null>(null);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [sortBy, setSortBy] = useState<\"name\" | \"price\" | \"created_at\" | \"updated_at\">(defaultSort?.by ?? \"name\");\n const [sortOrder, setSortOrder] = useState<\"asc\" | \"desc\">(defaultSort?.order ?? \"asc\");\n const [inStockOnly, setInStockOnly] = useState(false);\n const [selectedTags, setSelectedTags] = useState<string[]>([]);\n const [minPrice, setMinPrice] = useState<string>(\"\");\n const [maxPrice, setMaxPrice] = useState<string>(\"\");\n const [page, setPage] = useState(1);\n\n const debouncedSearch = useDebounce(searchQuery, 300);\n\n const { products: fetchedProducts, isLoading: productsLoading, pagination } = useProducts({\n enabled: productsProp === undefined,\n category: selectedCategoryId ?? undefined,\n search: debouncedSearch.length >= 2 ? debouncedSearch : undefined,\n sort_by: sortBy,\n sort_order: sortOrder,\n in_stock: inStockOnly || undefined,\n tags: selectedTags.length > 0 ? selectedTags : undefined,\n min_price: minPrice ? parseFloat(minPrice) : undefined,\n max_price: maxPrice ? parseFloat(maxPrice) : undefined,\n page,\n limit: pageSize,\n });\n const { categories: fetchedCategories } = useCategories({\n enabled: showFilter && categoriesProp === undefined,\n });\n\n const allProducts = productsProp ?? fetchedProducts;\n const categories = categoriesProp ?? fetchedCategories;\n\n const handleCategorySelect = useCallback(\n (id: string | null) => {\n setSelectedCategoryId(id);\n setPage(1);\n if (id) {\n const cat = categories.find((c: Category) => c.id === id);\n if (cat) onCategoryClick?.(cat);\n }\n },\n [categories, onCategoryClick],\n );\n\n const handleTagToggle = useCallback((tag: string) => {\n setSelectedTags((prev) =>\n prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag],\n );\n setPage(1);\n }, []);\n\n const totalPages = pagination?.total_pages ?? 1;\n\n return (\n <div data-cimplify-catalogue-page className={cn(className, classNames?.root)}>\n {/* Header */}\n <div data-cimplify-catalogue-header className={classNames?.header}>\n <h1 data-cimplify-catalogue-title className={classNames?.title}>\n {title}\n </h1>\n\n {showSearch && (\n <div data-cimplify-catalogue-search className={classNames?.searchContainer}>\n <SearchInput\n onResultClick={onProductClick}\n searchOptions={{ category: selectedCategoryId ?? undefined }}\n />\n </div>\n )}\n </div>\n\n {/* Category filter */}\n {showFilter && categories.length > 0 && (\n <div data-cimplify-catalogue-filter className={classNames?.filterContainer}>\n <CategoryFilter\n selectedId={selectedCategoryId}\n onSelect={handleCategorySelect}\n />\n </div>\n )}\n\n {/* Toolbar: sort, in-stock, price range, tags */}\n {(showSort || showInStockFilter || showPriceRange || showTagsFilter) && (\n <div data-cimplify-catalogue-toolbar className={cn(\"flex flex-wrap items-center gap-3\", classNames?.toolbar)}>\n {showSort && (\n <select\n data-cimplify-sort\n value={`${sortBy}:${sortOrder}`}\n onChange={(e) => {\n const [by, order] = e.target.value.split(\":\") as [typeof sortBy, typeof sortOrder];\n setSortBy(by);\n setSortOrder(order);\n setPage(1);\n }}\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm\"\n >\n <option value=\"name:asc\">Name A–Z</option>\n <option value=\"name:desc\">Name Z–A</option>\n <option value=\"price:asc\">Price: Low to High</option>\n <option value=\"price:desc\">Price: High to Low</option>\n <option value=\"created_at:desc\">Newest</option>\n <option value=\"created_at:asc\">Oldest</option>\n <option value=\"updated_at:desc\">Recently Updated</option>\n </select>\n )}\n\n {showInStockFilter && (\n <label data-cimplify-stock-filter className=\"flex items-center gap-1.5 text-sm cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={inStockOnly}\n onChange={(e) => { setInStockOnly(e.target.checked); setPage(1); }}\n className=\"accent-primary\"\n />\n In stock only\n </label>\n )}\n\n {showPriceRange && (\n <div data-cimplify-price-range className=\"flex items-center gap-1.5\">\n <input\n type=\"number\"\n placeholder=\"Min\"\n value={minPrice}\n onChange={(e) => { setMinPrice(e.target.value); setPage(1); }}\n className=\"w-20 rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n min={0}\n step=\"0.01\"\n />\n <span className=\"text-muted-foreground text-sm\">–</span>\n <input\n type=\"number\"\n placeholder=\"Max\"\n value={maxPrice}\n onChange={(e) => { setMaxPrice(e.target.value); setPage(1); }}\n className=\"w-20 rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n min={0}\n step=\"0.01\"\n />\n </div>\n )}\n\n {showTagsFilter && availableTags && availableTags.length > 0 && (\n <div data-cimplify-tags-filter className=\"flex flex-wrap gap-1.5\">\n {availableTags.map((tag) => (\n <button\n key={tag}\n type=\"button\"\n onClick={() => handleTagToggle(tag)}\n className={cn(\n \"rounded-full px-3 py-1 text-xs font-medium border transition-colors\",\n selectedTags.includes(tag)\n ? \"bg-primary text-primary-foreground border-primary\"\n : \"bg-background text-foreground border-input hover:bg-muted\",\n )}\n >\n {tag}\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n\n {/* Product grid */}\n <div data-cimplify-catalogue-grid className={classNames?.gridContainer}>\n {productsLoading && !productsProp ? (\n <div\n data-cimplify-catalogue-loading\n aria-busy=\"true\"\n className={classNames?.loading}\n />\n ) : (\n <ProductGrid\n products={allProducts}\n renderCard={renderCard}\n renderImage={renderImage}\n columns={columns}\n emptyMessage=\"No products found\"\n />\n )}\n </div>\n\n {/* Pagination */}\n {totalPages > 1 && (\n <div data-cimplify-catalogue-pagination className={cn(\"flex items-center justify-center gap-2 pt-6\", classNames?.pagination)}>\n <button\n type=\"button\"\n disabled={page <= 1}\n onClick={() => setPage((p) => Math.max(1, p - 1))}\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm disabled:opacity-40\"\n >\n Previous\n </button>\n <span className=\"text-sm text-muted-foreground tabular-nums\">\n {page} / {totalPages}\n </span>\n <button\n type=\"button\"\n disabled={page >= totalPages}\n onClick={() => setPage((p) => p + 1)}\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm disabled:opacity-40\"\n >\n Next\n </button>\n </div>\n )}\n </div>\n );\n}\n\nfunction useDebounce(value: string, delay: number): string {\n const [debounced, setDebounced] = React.useState(value);\n React.useEffect(() => {\n const timer = setTimeout(() => setDebounced(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n return debounced;\n}\n"
15
+ "content": "\"use client\";\n\nimport React, { useState, useCallback } from \"react\";\nimport type { Product, Category, PropertyFacet } from \"@cimplify/sdk\";\nimport { useProducts, useCategories } from \"@cimplify/sdk/react\";\nimport type { CatalogueLayoutProps } from \"./catalogue-layouts/shared\";\nimport { DefaultCatalogueLayout } from \"./catalogue-layouts/default-catalogue-layout\";\nimport { CompactCatalogueLayout } from \"./catalogue-layouts/compact-catalogue-layout\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport type { CatalogueLayoutProps };\n\nexport enum CatalogueTemplate {\n Default = \"default\",\n Compact = \"compact\",\n}\n\nexport interface CataloguePageClassNames {\n root?: string;\n}\n\nexport interface CataloguePageProps {\n /** Explicit template. */\n template?: CatalogueTemplate;\n /** Layout map for overrides. */\n layouts?: Partial<Record<CatalogueTemplate | string, React.ComponentType<CatalogueLayoutProps>>>;\n /** Page title. */\n title?: string;\n /** Pre-fetched products for SSR. */\n products?: Product[];\n /** Pre-fetched categories for SSR. */\n categories?: Category[];\n /** Pre-fetched property facets for sidebar. */\n facets?: PropertyFacet[];\n /** Available tags for filtering. */\n availableTags?: string[];\n /** Products per page. */\n pageSize?: number;\n /** Default sort. */\n defaultSort?: { by: string; order: string };\n /** Custom card renderer. */\n renderCard?: (product: Product) => React.ReactNode;\n /** Custom image renderer. */\n renderImage?: (props: { src: string; alt: string; className?: string }) => React.ReactNode;\n /** Custom link renderer. */\n renderLink?: (props: { href: string; className?: string; children: React.ReactNode }) => React.ReactElement;\n /** Quick-add handler. */\n onQuickAdd?: (product: Product) => void;\n /** Product click handler. */\n onProductClick?: (product: Product) => void;\n className?: string;\n classNames?: CataloguePageClassNames;\n}\n\nconst BUILT_IN_LAYOUTS: Record<string, React.ComponentType<CatalogueLayoutProps>> = {\n [CatalogueTemplate.Default]: DefaultCatalogueLayout,\n [CatalogueTemplate.Compact]: CompactCatalogueLayout,\n};\n\nfunction useDebounce(value: string, delay: number): string {\n const [debounced, setDebounced] = React.useState(value);\n React.useEffect(() => {\n const timer = setTimeout(() => setDebounced(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n return debounced;\n}\n\nexport function CataloguePage({\n template,\n layouts,\n title,\n products: productsProp,\n categories: categoriesProp,\n facets,\n availableTags,\n pageSize,\n defaultSort,\n renderCard,\n renderImage,\n renderLink,\n onQuickAdd,\n onProductClick,\n className,\n classNames,\n}: CataloguePageProps): React.ReactElement {\n const [selectedCategory, setSelectedCategory] = useState<string | null>(null);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [sortBy, setSortBy] = useState(defaultSort?.by ?? \"created_at\");\n const [sortOrder, setSortOrder] = useState(defaultSort?.order ?? \"desc\");\n const [inStockOnly, setInStockOnly] = useState(false);\n const [selectedTags, setSelectedTags] = useState<string[]>([]);\n const [minPrice, setMinPrice] = useState(\"\");\n const [maxPrice, setMaxPrice] = useState(\"\");\n const [page, setPage] = useState(1);\n\n const debouncedSearch = useDebounce(searchQuery, 300);\n\n const { products: fetchedProducts, isLoading, pagination } = useProducts({\n enabled: productsProp === undefined,\n category: selectedCategory ?? undefined,\n search: debouncedSearch.length >= 2 ? debouncedSearch : undefined,\n sort_by: sortBy as \"name\" | \"price\" | \"created_at\" | \"updated_at\",\n sort_order: sortOrder as \"asc\" | \"desc\",\n in_stock: inStockOnly || undefined,\n tags: selectedTags.length > 0 ? selectedTags : undefined,\n min_price: minPrice ? parseFloat(minPrice) : undefined,\n max_price: maxPrice ? parseFloat(maxPrice) : undefined,\n page,\n limit: pageSize,\n });\n\n const { categories: fetchedCategories } = useCategories({\n enabled: categoriesProp === undefined,\n });\n\n const products = productsProp ?? fetchedProducts;\n const categories = categoriesProp ?? fetchedCategories;\n const totalPages = pagination?.total_pages ?? 1;\n\n const handleCategoryChange = useCallback((id: string | null) => {\n setSelectedCategory(id);\n setPage(1);\n }, []);\n\n const handleSortChange = useCallback((by: string, order: string) => {\n setSortBy(by);\n setSortOrder(order);\n setPage(1);\n }, []);\n\n const handleTagToggle = useCallback((tag: string) => {\n setSelectedTags((prev) =>\n prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag],\n );\n setPage(1);\n }, []);\n\n const handlePriceRangeChange = useCallback((min: string, max: string) => {\n setMinPrice(min);\n setMaxPrice(max);\n setPage(1);\n }, []);\n\n const key = template ?? CatalogueTemplate.Default;\n const Layout = layouts?.[key] ?? BUILT_IN_LAYOUTS[key] ?? DefaultCatalogueLayout;\n\n return (\n <div data-cimplify-catalogue-page className={cn(className, classNames?.root)}>\n <Layout\n products={products}\n categories={categories}\n facets={facets}\n isLoading={isLoading}\n selectedCategory={selectedCategory}\n onCategoryChange={handleCategoryChange}\n searchQuery={searchQuery}\n onSearchChange={setSearchQuery}\n sortBy={sortBy}\n sortOrder={sortOrder}\n onSortChange={handleSortChange}\n inStockOnly={inStockOnly}\n onInStockChange={setInStockOnly}\n selectedTags={selectedTags}\n availableTags={availableTags}\n onTagToggle={handleTagToggle}\n minPrice={minPrice}\n maxPrice={maxPrice}\n onPriceRangeChange={handlePriceRangeChange}\n page={page}\n totalPages={totalPages}\n onPageChange={setPage}\n renderCard={renderCard}\n renderImage={renderImage}\n renderLink={renderLink}\n onQuickAdd={onQuickAdd}\n onProductClick={onProductClick}\n title={title}\n />\n </div>\n );\n}\n"
16
16
  }
17
17
  ]
18
18
  }
@@ -10,7 +10,7 @@
10
10
  "files": [
11
11
  {
12
12
  "path": "collection-page.tsx",
13
- "content": "\"use client\";\n\nimport React from \"react\";\nimport type { Product, Collection } from \"@cimplify/sdk\";\nimport { useCollection } from \"@cimplify/sdk/react\";\nimport { ProductGrid } from \"./product-grid\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface CollectionPageClassNames {\n root?: string;\n header?: string;\n title?: string;\n description?: string;\n image?: string;\n grid?: string;\n loading?: string;\n}\n\nexport interface CollectionPageProps {\n /** Collection slug or ID. */\n collectionId: string;\n /** Pre-fetched collection for SSR. */\n collection?: Collection;\n /** Pre-fetched products for SSR. */\n products?: Product[];\n /** Custom card renderer. */\n renderCard?: (product: Product) => React.ReactNode;\n /** Custom image renderer. */\n renderImage?: (props: { src: string; alt: string; className?: string }) => React.ReactNode;\n /** Grid column config. */\n columns?: { sm?: number; md?: number; lg?: number; xl?: number };\n className?: string;\n classNames?: CollectionPageClassNames;\n}\n\n/**\n * CollectionPage — curated product collection with header and grid.\n *\n * SSR-friendly: pass `collection` and `products` props for server rendering.\n */\nexport function CollectionPage({\n collectionId,\n collection: collectionProp,\n products: productsProp,\n renderCard,\n renderImage,\n columns,\n className,\n classNames,\n}: CollectionPageProps): React.ReactElement {\n const {\n collection: fetchedCollection,\n products: fetchedProducts,\n isLoading,\n } = useCollection(collectionProp ? null : collectionId, {\n enabled: !collectionProp,\n });\n\n const collection = collectionProp ?? fetchedCollection;\n const products = productsProp ?? fetchedProducts;\n\n if (isLoading && !collection) {\n return (\n <div\n data-cimplify-collection-page\n aria-busy=\"true\"\n className={cn(className, classNames?.root, classNames?.loading)}\n />\n );\n }\n\n if (!collection) {\n return (\n <div data-cimplify-collection-page className={cn(className, classNames?.root)}>\n <p>Collection not found.</p>\n </div>\n );\n }\n\n return (\n <div data-cimplify-collection-page className={cn(className, classNames?.root)}>\n {/* Header */}\n <div data-cimplify-collection-header className={classNames?.header}>\n {collection.image_url && (\n <img\n src={collection.image_url}\n alt={collection.name}\n data-cimplify-collection-image\n className={classNames?.image}\n />\n )}\n <h1 data-cimplify-collection-title className={classNames?.title}>\n {collection.name}\n </h1>\n {collection.description && (\n <p data-cimplify-collection-description className={classNames?.description}>\n {collection.description}\n </p>\n )}\n </div>\n\n {/* Products */}\n <div data-cimplify-collection-grid className={classNames?.grid}>\n <ProductGrid\n products={products}\n renderCard={renderCard}\n renderImage={renderImage}\n columns={columns}\n />\n </div>\n </div>\n );\n}\n"
13
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport type { Product, Collection } from \"@cimplify/sdk\";\nimport { useCollection } from \"@cimplify/sdk/react\";\nimport type { CollectionLayoutProps } from \"./collection-layouts/shared\";\nimport { DefaultCollectionLayout } from \"./collection-layouts/default-collection-layout\";\nimport { FeaturedCollectionLayout } from \"./collection-layouts/featured-collection-layout\";\nimport { CatalogueCollectionLayout } from \"./collection-layouts/catalogue-collection-layout\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport type { CollectionLayoutProps };\n\nexport enum CollectionTemplate {\n Default = \"default\",\n Featured = \"featured\",\n Catalogue = \"catalogue\",\n}\n\nexport interface CollectionPageClassNames {\n root?: string;\n loading?: string;\n}\n\nexport interface CollectionPageProps {\n /** Collection slug or ID. */\n collectionId?: string;\n /** Pre-fetched collection for SSR. */\n collection?: Collection;\n /** Pre-fetched products for SSR. */\n products?: Product[];\n /** Per-slug page map. Highest priority — maps a collection slug to a custom layout. */\n pages?: Record<string, React.ComponentType<CollectionLayoutProps>>;\n /** Per-type template map. Overrides built-in layouts. */\n templates?: Partial<Record<CollectionTemplate | string, React.ComponentType<CollectionLayoutProps>>>;\n /** Custom card renderer. */\n renderCard?: (product: Product) => React.ReactNode;\n /** Custom image renderer. */\n renderImage?: (props: { src: string; alt: string; className?: string }) => React.ReactNode;\n /** Custom link renderer. */\n renderLink?: (props: { href: string; className?: string; children: React.ReactNode }) => React.ReactElement;\n /** Quick add handler for cards. */\n onQuickAdd?: (product: Product) => void;\n className?: string;\n classNames?: CollectionPageClassNames;\n}\n\nconst BUILT_IN_LAYOUTS: Record<string, React.ComponentType<CollectionLayoutProps>> = {\n [CollectionTemplate.Default]: DefaultCollectionLayout,\n [CollectionTemplate.Featured]: FeaturedCollectionLayout,\n [CollectionTemplate.Catalogue]: CatalogueCollectionLayout,\n};\n\nconst LARGE_COLLECTION_THRESHOLD = 30;\n\nfunction resolveTemplateKey(collection: Collection, productCount: number): CollectionTemplate | string {\n const metaTemplate = collection.metadata?.page_template;\n if (typeof metaTemplate === \"string\" && metaTemplate.trim()) {\n return metaTemplate.trim();\n }\n\n if (collection.tags?.includes(\"featured\") || collection.metadata?.is_featured === true) {\n return CollectionTemplate.Featured;\n }\n\n if (productCount > LARGE_COLLECTION_THRESHOLD) {\n return CollectionTemplate.Catalogue;\n }\n\n return CollectionTemplate.Default;\n}\n\nfunction resolveLayout(\n collection: Collection,\n productCount: number,\n pages?: Record<string, React.ComponentType<CollectionLayoutProps>>,\n templates?: Partial<Record<string, React.ComponentType<CollectionLayoutProps>>>,\n): React.ComponentType<CollectionLayoutProps> {\n if (pages?.[collection.slug]) {\n return pages[collection.slug];\n }\n\n const key = resolveTemplateKey(collection, productCount);\n\n if (templates?.[key]) {\n return templates[key]!;\n }\n\n if (BUILT_IN_LAYOUTS[key]) {\n return BUILT_IN_LAYOUTS[key];\n }\n\n return DefaultCollectionLayout;\n}\n\nexport function CollectionPage({\n collectionId,\n collection: collectionProp,\n products: productsProp,\n pages,\n templates,\n renderCard,\n renderImage,\n renderLink,\n onQuickAdd,\n className,\n classNames,\n}: CollectionPageProps): React.ReactElement {\n const resolvedId = collectionId || collectionProp?.slug || collectionProp?.id || \"\";\n const {\n collection: fetchedCollection,\n products: fetchedProducts,\n isLoading,\n } = useCollection(collectionProp ? null : resolvedId, {\n enabled: !collectionProp && resolvedId.length > 0,\n });\n\n const collection = collectionProp ?? fetchedCollection;\n const products = productsProp ?? fetchedProducts;\n\n if (isLoading && !collection) {\n return (\n <div\n data-cimplify-collection-page\n aria-busy=\"true\"\n className={cn(className, classNames?.root, classNames?.loading)}\n >\n <div className=\"animate-pulse space-y-6\">\n <div className=\"h-64 lg:h-80 bg-muted rounded-2xl\" />\n <div className=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4\">\n {Array.from({ length: 8 }).map((_, i) => (\n <div key={i}>\n <div className=\"aspect-square bg-muted rounded-[14px]\" />\n <div className=\"mt-3 space-y-2\">\n <div className=\"h-4 w-3/4 bg-muted rounded\" />\n <div className=\"h-3 w-1/2 bg-muted rounded\" />\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n }\n\n if (!collection) {\n return (\n <div data-cimplify-collection-page className={cn(className, classNames?.root)}>\n <p className=\"text-muted-foreground\">Collection not found.</p>\n </div>\n );\n }\n\n const Layout = resolveLayout(collection, products.length, pages, templates);\n\n return (\n <div data-cimplify-collection-page className={cn(className, classNames?.root)}>\n <Layout\n collection={collection}\n products={products}\n renderCard={renderCard}\n renderImage={renderImage}\n renderLink={renderLink}\n onQuickAdd={onQuickAdd}\n />\n </div>\n );\n}\n"
14
14
  }
15
15
  ]
16
16
  }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "compact-service-card",
3
+ "title": "CompactServiceCard",
4
+ "description": "Horizontal service card with thumbnail for list views.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "price",
8
+ "cn"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "cards/compact-service-card.tsx",
13
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport type { ServiceCardLayoutProps } from \"./standard-service-card\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { parsePrice } from \"@cimplify/sdk\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nfunction formatDuration(minutes: number, unit?: string): string {\n if (unit && unit !== \"minutes\") return `${minutes} ${unit}`;\n if (minutes >= 60) {\n const h = Math.floor(minutes / 60);\n const m = minutes % 60;\n return m > 0 ? `${h}h ${m}m` : `${h} hr`;\n }\n return `${minutes} min`;\n}\n\nexport function CompactServiceCard({\n product,\n renderImage,\n renderLink,\n className,\n}: ServiceCardLayoutProps): React.ReactElement {\n const image = product.image_url || product.images?.[0] || \"\";\n const hasDeposit = product.deposit_type && product.deposit_type !== \"none\" && product.deposit_amount;\n const hasBillingPlans = product.billing_plans && product.billing_plans.length > 0;\n const href = `/products/${product.slug}`;\n\n const content = (\n <div className=\"flex items-center gap-4 p-3\">\n {/* Thumbnail */}\n <div className=\"w-[72px] h-[72px] rounded-xl overflow-hidden bg-muted shrink-0\">\n {renderImage ? (\n renderImage({ src: image, alt: product.name, className: \"w-full h-full object-cover\" })\n ) : (\n <img src={image} alt={product.name} className=\"w-full h-full object-cover\" />\n )}\n </div>\n\n {/* Info */}\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"text-[14px] font-semibold text-foreground leading-tight truncate\">\n {product.name}\n </h3>\n {product.description && (\n <p className=\"text-[12px] text-muted-foreground mt-0.5 truncate\">\n {product.description}\n </p>\n )}\n <div className=\"flex items-center gap-2 mt-2 flex-wrap\">\n {product.duration_minutes != null && (\n <span className=\"text-[10.5px] font-medium text-muted-foreground px-1.5 py-0.5 bg-muted rounded\">\n {formatDuration(product.duration_minutes, product.duration_unit)}\n </span>\n )}\n <span className=\"flex items-center gap-1 text-[10.5px] font-medium text-emerald-600\">\n <span className=\"w-[6px] h-[6px] rounded-full bg-emerald-500\" />\n Available\n </span>\n {hasDeposit && (\n <span className=\"text-[10.5px] font-medium text-amber-600\">\n <Price amount={product.deposit_amount!} /> deposit\n </span>\n )}\n {hasBillingPlans && (\n <span className=\"inline-flex items-center gap-0.5 text-[10.5px] font-semibold text-primary px-1.5 py-0.5 bg-primary/10 rounded\">\n Subscription\n </span>\n )}\n </div>\n </div>\n\n {/* Price + Chevron */}\n <div className=\"flex items-center gap-2 shrink-0\">\n <Price amount={product.default_price} className=\"text-sm font-bold\" />\n <svg className=\"w-4 h-4 text-muted-foreground group-hover:text-primary transition-colors\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </div>\n </div>\n );\n\n const shellClass = cn(\n \"group block text-left cursor-pointer bg-card rounded-[14px] overflow-hidden\",\n \"shadow-[0_1px_3px_rgba(0,0,0,0.04),0_6px_24px_rgba(0,0,0,0.06)]\",\n \"border border-transparent\",\n \"transition-all duration-300 ease-[cubic-bezier(0.19,1,0.22,1)]\",\n \"hover:-translate-y-[1px] hover:shadow-[0_2px_6px_rgba(0,0,0,0.04),0_12px_40px_rgba(0,0,0,0.10)] hover:border-primary/20\",\n className,\n );\n\n if (renderLink) {\n return renderLink({ href, className: shellClass, children: content });\n }\n\n return <a href={href} className={shellClass} data-cimplify-card>{content}</a>;\n}\n"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "digital-product-card",
3
+ "title": "DigitalProductCard",
4
+ "description": "Digital product card with type badge, file info, and event details.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "price",
8
+ "cn"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "cards/digital-product-card.tsx",
13
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport type { CardLayoutProps } from \"@cimplify/sdk/react\";\nimport { CardShell, CardImage } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\n\nconst TYPE_BADGES: Record<string, { label: string; color: string }> = {\n download: { label: \"Download\", color: \"bg-sky-50 text-sky-700 border-sky-200/60\" },\n license: { label: \"License\", color: \"bg-blue-50 text-blue-700 border-blue-200/60\" },\n event_ticket: { label: \"Event\", color: \"bg-violet-50 text-violet-700 border-violet-200/60\" },\n access_pass: { label: \"Access\", color: \"bg-emerald-50 text-emerald-700 border-emerald-200/60\" },\n gift_card: { label: \"Gift Card\", color: \"bg-amber-50 text-amber-700 border-amber-200/60\" },\n};\n\nexport function DigitalProductCard({\n product,\n renderImage,\n renderLink,\n className,\n}: CardLayoutProps): React.ReactElement {\n const image = product.image_url || product.images?.[0] || \"\";\n const typeBadge = product.digital_type ? TYPE_BADGES[product.digital_type] : null;\n const isTicket = product.digital_type === \"event_ticket\";\n\n return (\n <CardShell product={product} renderLink={renderLink} className={className}>\n <CardImage src={image} alt={product.name} aspectRatio=\"4/3\" renderImage={renderImage}>\n {typeBadge && (\n <span className={`absolute top-3 left-3 text-[11px] font-semibold tracking-wide border px-2 py-0.5 rounded-md ${typeBadge.color}`}>\n {typeBadge.label}\n </span>\n )}\n </CardImage>\n\n <div className=\"p-3.5\">\n <h3 className=\"text-[14.5px] font-semibold text-foreground leading-snug tracking-tight truncate\">\n {product.name}\n </h3>\n\n {/* Event: date + venue */}\n {isTicket && (product.event_date || product.venue) && (\n <p className=\"text-[12px] text-muted-foreground mt-1 truncate\">\n {product.event_date && new Date(product.event_date).toLocaleDateString(undefined, { month: \"short\", day: \"numeric\" })}\n {product.event_date && product.venue && \" · \"}\n {product.venue}\n </p>\n )}\n\n {/* Non-event: description */}\n {!isTicket && product.description && (\n <p className=\"text-[12px] text-muted-foreground mt-1 line-clamp-1\">\n {product.description}\n </p>\n )}\n\n <div className=\"flex items-center justify-between mt-2.5\">\n <Price amount={product.default_price} className=\"text-sm font-bold\" />\n\n {/* File info pill */}\n {product.file_type && (\n <span className=\"text-[10px] text-muted-foreground bg-muted px-1.5 py-0.5 rounded\">\n {product.file_type.toUpperCase()}\n {product.file_size_mb != null && ` · ${product.file_size_mb >= 1024 ? `${(product.file_size_mb / 1024).toFixed(1)}GB` : `${product.file_size_mb}MB`}`}\n </span>\n )}\n </div>\n </div>\n </CardShell>\n );\n}\n"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "food-product-card",
3
+ "title": "FoodProductCard",
4
+ "description": "Product card for food items with tags, badges, and quick-add.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "price",
8
+ "cn"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "cards/food-product-card.tsx",
13
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport { PRODUCT_TYPE } from \"@cimplify/sdk\";\nimport type { CardLayoutProps } from \"@cimplify/sdk/react\";\nimport { CardShell, CardImage, QuickAddButton } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\n\nexport function FoodProductCard({\n product,\n onQuickAdd,\n renderImage,\n renderLink,\n className,\n}: CardLayoutProps): React.ReactElement {\n const image = product.image_url || product.images?.[0] || \"\";\n const tags = product.tags || [];\n const isSignature = product.metadata?.is_signature === true;\n const isNew = product.metadata?.is_new === true;\n const isComposite = product.type === PRODUCT_TYPE.Composite;\n\n return (\n <CardShell product={product} renderLink={renderLink} className={className}>\n <CardImage src={image} alt={product.name} aspectRatio=\"square\" renderImage={renderImage}>\n {/* Badges */}\n {isSignature && (\n <span className=\"absolute top-3 left-3 inline-flex items-center gap-1 text-[11px] font-semibold tracking-wide bg-amber-50 text-amber-700 border border-amber-200/60 px-2 py-0.5 rounded-md\">\n <svg className=\"w-3 h-3 opacity-70\" fill=\"currentColor\" viewBox=\"0 0 16 16\"><path d=\"M8 .75l1.85 4.95L15 6.2l-3.7 3.2 1.1 5.1L8 12.15 3.6 14.5l1.1-5.1L1 6.2l5.15-.5z\" /></svg>\n Signature\n </span>\n )}\n {isNew && !isSignature && (\n <span className=\"absolute top-3 left-3 text-[11px] font-semibold tracking-wide bg-emerald-50 text-emerald-700 border border-emerald-200/60 px-2 py-0.5 rounded-md\">\n New\n </span>\n )}\n {isComposite && (\n <span className=\"absolute top-3 left-3 text-[11px] font-semibold tracking-wide bg-violet-50 text-violet-700 border border-violet-200/60 px-2 py-0.5 rounded-md\">\n Customizable\n </span>\n )}\n\n {/* Quick add */}\n <QuickAddButton\n onClick={() => onQuickAdd?.(product)}\n className=\"absolute bottom-3 right-3\"\n />\n </CardImage>\n\n <div className=\"p-3.5\">\n <h3 className=\"text-[14.5px] font-semibold text-foreground leading-snug tracking-tight truncate\">\n {product.name}\n </h3>\n {product.description && (\n <p className=\"text-[12.5px] text-muted-foreground mt-1 leading-relaxed line-clamp-2\">\n {product.description}\n </p>\n )}\n <div className=\"flex items-center justify-between mt-3\">\n {isComposite ? (\n <span className=\"text-sm font-bold\">From <Price amount={product.default_price} /></span>\n ) : (\n <Price amount={product.default_price} className=\"text-sm font-bold\" />\n )}\n {tags.length > 0 && (\n <div className=\"flex gap-1\">\n {tags.slice(0, 2).map((tag) => (\n <span key={tag} className=\"text-[10px] font-medium text-muted-foreground px-1.5 py-0.5 bg-muted rounded\">\n {tag}\n </span>\n ))}\n </div>\n )}\n </div>\n </div>\n </CardShell>\n );\n}\n"
14
+ }
15
+ ]
16
+ }
@@ -12,7 +12,7 @@
12
12
  "files": [
13
13
  {
14
14
  "path": "layouts/food-product-layout.tsx",
15
- "content": "\"use client\";\n\nimport React from \"react\";\nimport type { ProductLayoutProps } from \"@cimplify/sdk/react\";\nimport {\n TwoColumnGrid,\n RelatedProductsSection,\n MetadataStringList,\n CustomAttributesTable,\n TagPills,\n getMetadataStringList,\n} from \"@cimplify/sdk/react\";\nimport { ProductImageGallery } from \"@cimplify/sdk/react\";\nimport { ProductCustomizer } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { parsePrice } from \"@cimplify/sdk\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nconst DIETARY_TAGS = new Set([\n \"vegetarian\", \"vegan\", \"gluten-free\", \"dairy-free\", \"nut-free\",\n \"spicy\", \"halal\", \"kosher\", \"organic\", \"sugar-free\",\n]);\n\nexport function FoodProductLayout({\n product,\n onAddToCart,\n renderBreadcrumb,\n relatedProducts,\n showRelated = true,\n className,\n}: ProductLayoutProps): React.ReactElement {\n const images = product.images || (product.image_url ? [product.image_url] : []);\n const allTags = product.tags || [];\n const dietaryTags = allTags.filter((t) => DIETARY_TAGS.has(t));\n const otherTags = allTags.filter((t) => !DIETARY_TAGS.has(t));\n const ingredients = getMetadataStringList(product.metadata, \"ingredients\");\n const pairings = getMetadataStringList(product.metadata, \"pairings\");\n\n return (\n <div data-cimplify-product-layout=\"food\" className={cn(\"space-y-8\", className)}>\n {renderBreadcrumb?.(product)}\n\n <TwoColumnGrid\n left={\n <ProductImageGallery\n images={images}\n productName={product.name}\n />\n }\n right={\n <div className=\"space-y-6\">\n {/* Tags */}\n <div className=\"flex flex-wrap gap-1.5\">\n {dietaryTags.map((tag) => (\n <span key={tag} className=\"inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full\">\n {tag}\n </span>\n ))}\n {product.allergies && product.allergies.map((allergy) => (\n <span key={allergy} className=\"inline-flex items-center gap-1 px-2.5 py-1 bg-amber-100 text-amber-800 text-xs font-medium rounded-full\">\n Contains {allergy}\n </span>\n ))}\n </div>\n\n <TagPills tags={otherTags} />\n\n {/* Name + Price */}\n <div>\n <h1 data-cimplify-product-layout-name className=\"text-3xl lg:text-4xl font-bold tracking-tight\">\n {product.name}\n </h1>\n <Price amount={product.default_price} className=\"text-2xl font-semibold mt-2 block\" />\n </div>\n\n {/* Quick stats */}\n {(product.calories != null || product.preparation_time_minutes != null) && (\n <div data-cimplify-product-layout-stats className=\"flex flex-wrap gap-4 py-3 border-y border-border\">\n {product.preparation_time_minutes != null && (\n <span className=\"flex items-center gap-1.5 text-sm text-muted-foreground\">\n <strong className=\"text-foreground\">{product.preparation_time_minutes} min</strong> prep time\n </span>\n )}\n {product.calories != null && (\n <span className=\"flex items-center gap-1.5 text-sm text-muted-foreground\">\n <strong className=\"text-foreground\">{product.calories}</strong> calories\n </span>\n )}\n </div>\n )}\n\n {/* Description */}\n {product.description && (\n <p data-cimplify-product-layout-description className=\"text-muted-foreground leading-relaxed\">\n {product.description}\n </p>\n )}\n\n {/* Ingredients + Pairings */}\n {(ingredients.length > 0 || pairings.length > 0) && (\n <div className=\"grid sm:grid-cols-2 gap-4\">\n <MetadataStringList items={ingredients} label=\"Key Ingredients\" />\n <MetadataStringList items={pairings} label=\"Pairings\" />\n </div>\n )}\n\n {/* Custom Attributes */}\n <CustomAttributesTable attributes={product.custom_attributes || []} />\n\n {/* Customizer (variants, add-ons, composites, bundles, billing, volume) */}\n <ProductCustomizer\n product={product}\n onAddToCart={onAddToCart}\n />\n </div>\n }\n />\n\n {showRelated && relatedProducts && relatedProducts.length > 0 && (\n <RelatedProductsSection\n products={relatedProducts}\n title=\"You may also enjoy\"\n />\n )}\n </div>\n );\n}\n"
15
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport type { ProductLayoutProps } from \"@cimplify/sdk/react\";\nimport {\n TwoColumnGrid,\n RelatedProductsSection,\n MetadataStringList,\n CustomAttributesTable,\n TagPills,\n getMetadataStringList,\n} from \"@cimplify/sdk/react\";\nimport { ProductImageGallery } from \"@cimplify/sdk/react\";\nimport { ProductCustomizer } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { parsePrice } from \"@cimplify/sdk\";\nimport { cn } from \"@cimplify/sdk/react\";\n\n\nexport function FoodProductLayout({\n product,\n onAddToCart,\n renderBreadcrumb,\n relatedProducts,\n showRelated = true,\n className,\n}: ProductLayoutProps): React.ReactElement {\n const images = product.images || (product.image_url ? [product.image_url] : []);\n const tags = product.tags || [];\n const ingredients = getMetadataStringList(product.metadata, \"ingredients\");\n const pairings = getMetadataStringList(product.metadata, \"pairings\");\n\n return (\n <div data-cimplify-product-layout=\"food\" className={cn(\"space-y-8\", className)}>\n {renderBreadcrumb?.(product)}\n\n <TwoColumnGrid\n left={\n <ProductImageGallery\n images={images}\n productName={product.name}\n />\n }\n right={\n <div className=\"space-y-6\">\n {/* Tags + Allergens */}\n <div className=\"flex flex-wrap gap-1.5\">\n {tags.map((tag) => (\n <span key={tag} className=\"inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full\">\n {tag}\n </span>\n ))}\n {product.allergies && product.allergies.map((allergy) => (\n <span key={allergy} className=\"inline-flex items-center gap-1 px-2.5 py-1 bg-amber-100 text-amber-800 text-xs font-medium rounded-full\">\n Contains {allergy}\n </span>\n ))}\n </div>\n\n {/* Name + Price */}\n <div>\n <h1 data-cimplify-product-layout-name className=\"text-3xl lg:text-4xl font-bold tracking-tight\">\n {product.name}\n </h1>\n <Price amount={product.default_price} className=\"text-2xl font-semibold mt-2 block\" />\n </div>\n\n {/* Quick stats */}\n {(product.calories != null || product.preparation_time_minutes != null) && (\n <div data-cimplify-product-layout-stats className=\"flex flex-wrap gap-4 py-3 border-y border-border\">\n {product.preparation_time_minutes != null && (\n <span className=\"flex items-center gap-1.5 text-sm text-muted-foreground\">\n <strong className=\"text-foreground\">{product.preparation_time_minutes} min</strong> prep time\n </span>\n )}\n {product.calories != null && (\n <span className=\"flex items-center gap-1.5 text-sm text-muted-foreground\">\n <strong className=\"text-foreground\">{product.calories}</strong> calories\n </span>\n )}\n </div>\n )}\n\n {/* Description */}\n {product.description && (\n <p data-cimplify-product-layout-description className=\"text-muted-foreground leading-relaxed\">\n {product.description}\n </p>\n )}\n\n {/* Ingredients + Pairings */}\n {(ingredients.length > 0 || pairings.length > 0) && (\n <div className=\"grid sm:grid-cols-2 gap-4\">\n <MetadataStringList items={ingredients} label=\"Key Ingredients\" />\n <MetadataStringList items={pairings} label=\"Pairings\" />\n </div>\n )}\n\n {/* Custom Attributes */}\n <CustomAttributesTable attributes={product.custom_attributes || []} />\n\n {/* Customizer (variants, add-ons, composites, bundles, billing, volume) */}\n <ProductCustomizer\n product={product}\n onAddToCart={onAddToCart}\n />\n </div>\n }\n />\n\n {showRelated && relatedProducts && relatedProducts.length > 0 && (\n <RelatedProductsSection\n products={relatedProducts}\n title=\"You may also enjoy\"\n />\n )}\n </div>\n );\n}\n"
16
16
  }
17
17
  ]
18
18
  }
@@ -366,32 +366,60 @@
366
366
  ]
367
367
  },
368
368
  {
369
- "name": "booking-page",
370
- "title": "BookingPage",
371
- "description": "Full booking flow with date/slot picker, staff selection, and cart integration.",
369
+ "name": "food-product-card",
370
+ "title": "FoodProductCard",
371
+ "description": "Product card for food items with tags, badges, and quick-add.",
372
372
  "type": "component",
373
373
  "registryDependencies": [
374
- "date-slot-picker",
375
- "staff-picker",
376
374
  "price",
377
375
  "cn"
378
376
  ]
379
377
  },
380
378
  {
381
- "name": "bookings-page",
382
- "title": "BookingsPage",
383
- "description": "Booking history with filter tabs and inline detail view.",
379
+ "name": "retail-product-card",
380
+ "title": "RetailProductCard",
381
+ "description": "Product card for retail with color swatches, sale badge, wishlist, and sold-out overlay.",
384
382
  "type": "component",
385
383
  "registryDependencies": [
386
- "booking-list",
387
- "booking-card",
384
+ "price",
385
+ "cn"
386
+ ]
387
+ },
388
+ {
389
+ "name": "wholesale-product-card",
390
+ "title": "WholesaleProductCard",
391
+ "description": "B2B product card with price range, MOQ badge, and stock count.",
392
+ "type": "component",
393
+ "registryDependencies": [
394
+ "price",
395
+ "price-range",
396
+ "cn"
397
+ ]
398
+ },
399
+ {
400
+ "name": "digital-product-card",
401
+ "title": "DigitalProductCard",
402
+ "description": "Digital product card with type badge, file info, and event details.",
403
+ "type": "component",
404
+ "registryDependencies": [
405
+ "price",
406
+ "cn"
407
+ ]
408
+ },
409
+ {
410
+ "name": "standard-service-card",
411
+ "title": "StandardServiceCard",
412
+ "description": "Service card with hero image, duration, deposit, and availability.",
413
+ "type": "component",
414
+ "registryDependencies": [
415
+ "price",
388
416
  "cn"
389
417
  ]
390
418
  },
391
419
  {
392
- "name": "service-card",
393
- "title": "ServiceCard",
394
- "description": "Service display card with image, duration, price, and book action.",
420
+ "name": "compact-service-card",
421
+ "title": "CompactServiceCard",
422
+ "description": "Horizontal service card with thumbnail for list views.",
395
423
  "type": "component",
396
424
  "registryDependencies": [
397
425
  "price",
@@ -399,12 +427,54 @@
399
427
  ]
400
428
  },
401
429
  {
402
- "name": "service-grid",
403
- "title": "ServiceGrid",
404
- "description": "Responsive grid of bookable services with self-fetching.",
430
+ "name": "schedule-service-card",
431
+ "title": "ScheduleServiceCard",
432
+ "description": "Service card with next available time slot pills.",
405
433
  "type": "component",
406
434
  "registryDependencies": [
407
- "service-card",
435
+ "price",
436
+ "cn"
437
+ ]
438
+ },
439
+ {
440
+ "name": "rental-service-card",
441
+ "title": "RentalServiceCard",
442
+ "description": "Rental card with per-day/hour pricing, deposit, and availability count.",
443
+ "type": "component",
444
+ "registryDependencies": [
445
+ "price",
446
+ "cn"
447
+ ]
448
+ },
449
+ {
450
+ "name": "accommodation-card",
451
+ "title": "AccommodationCard",
452
+ "description": "Hotel/accommodation card with per-night pricing, amenities, capacity, and cancellation.",
453
+ "type": "component",
454
+ "registryDependencies": [
455
+ "price",
456
+ "volume-pricing",
457
+ "cn"
458
+ ]
459
+ },
460
+ {
461
+ "name": "lease-service-card",
462
+ "title": "LeaseServiceCard",
463
+ "description": "Long-term lease card with per-month/year pricing, volume tiers, and billing.",
464
+ "type": "component",
465
+ "registryDependencies": [
466
+ "price",
467
+ "volume-pricing",
468
+ "cn"
469
+ ]
470
+ },
471
+ {
472
+ "name": "subscription-card",
473
+ "title": "SubscriptionCard",
474
+ "description": "Subscription card with billing plan options, trial badge, and setup fee.",
475
+ "type": "component",
476
+ "registryDependencies": [
477
+ "price",
408
478
  "cn"
409
479
  ]
410
480
  },
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "lease-service-card",
3
+ "title": "LeaseServiceCard",
4
+ "description": "Long-term lease card with per-month/year pricing, volume tiers, and billing.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "price",
8
+ "volume-pricing",
9
+ "cn"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "cards/lease-service-card.tsx",
14
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport type { ServiceCardLayoutProps } from \"./standard-service-card\";\nimport { CardShell, CardImage } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { VolumePricing } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nconst UNIT_LABELS: Record<string, string> = {\n weeks: \"week\",\n months: \"mo\",\n years: \"yr\",\n};\n\nexport function LeaseServiceCard({\n product,\n onBook,\n renderImage,\n renderLink,\n className,\n}: ServiceCardLayoutProps): React.ReactElement {\n const image = product.image_url || product.images?.[0] || \"\";\n const hasDeposit = product.deposit_type && product.deposit_type !== \"none\" && product.deposit_amount;\n const hasTiers = product.quantity_pricing && product.quantity_pricing.length > 1;\n const unit = product.duration_unit ? UNIT_LABELS[product.duration_unit] || product.duration_unit : \"mo\";\n const tags = product.tags || [];\n const cancellationHours = product.cancellation_window_minutes\n ? Math.floor(product.cancellation_window_minutes / 60)\n : null;\n\n return (\n <CardShell product={product} renderLink={renderLink} className={className}>\n <CardImage src={image} alt={product.name} aspectRatio=\"16/9\" renderImage={renderImage}>\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/50 to-transparent pointer-events-none\" />\n\n <span className=\"absolute bottom-3 left-3 inline-flex items-center text-[12px] font-semibold px-2.5 py-1 rounded-lg bg-background/92 backdrop-blur-xl text-foreground shadow-sm\">\n Per {unit}\n </span>\n\n <span className=\"absolute bottom-3 right-3 text-white font-bold text-lg drop-shadow-sm\">\n <Price amount={product.default_price} />/{unit}\n </span>\n </CardImage>\n\n <div className=\"p-4\">\n <h3 className=\"text-[14.5px] font-bold text-foreground leading-snug tracking-tight\">\n {product.name}\n </h3>\n {product.description && (\n <p className=\"text-[12.5px] text-muted-foreground mt-1 leading-relaxed line-clamp-2\">\n {product.description}\n </p>\n )}\n\n {/* Tags as amenity pills */}\n {tags.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mt-2.5\">\n {tags.slice(0, 4).map((tag) => (\n <span key={tag} className=\"text-[10px] font-medium text-muted-foreground px-1.5 py-0.5 bg-muted rounded\">\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Volume tiers (lease terms) */}\n {hasTiers && (\n <div className=\"mt-3\">\n <VolumePricing tiers={product.quantity_pricing!} />\n </div>\n )}\n\n {/* Info row */}\n <div className=\"flex flex-wrap items-center gap-2 mt-3\">\n {product.min_order_quantity != null && product.min_order_quantity > 1 && (\n <span className=\"text-[10.5px] font-medium text-muted-foreground px-1.5 py-0.5 bg-muted rounded\">\n Min. {product.min_order_quantity} {unit}{product.min_order_quantity > 1 ? \"s\" : \"\"}\n </span>\n )}\n {hasDeposit && (\n <span className=\"text-[10.5px] font-medium text-amber-600\">\n <Price amount={product.deposit_amount!} /> deposit\n </span>\n )}\n {cancellationHours != null && (\n <span className=\"text-[10.5px] font-medium text-muted-foreground\">\n {cancellationHours >= 720 ? `${Math.floor(cancellationHours / 720)} month notice` : `${cancellationHours}h notice`}\n </span>\n )}\n </div>\n\n <div className=\"flex items-center justify-between mt-3 pt-3 border-t border-border\">\n {product.billing_plans && product.billing_plans.length > 0 && (\n <span className=\"text-[10.5px] font-semibold text-primary px-1.5 py-0.5 bg-primary/10 rounded\">\n {product.billing_plans[0].frequency} billing\n </span>\n )}\n <button\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onBook?.(product); }}\n className=\"text-[13px] font-semibold text-primary hover:text-primary/80 transition-colors ml-auto\"\n >\n Enquire now &rarr;\n </button>\n </div>\n </div>\n </CardShell>\n );\n}\n"
15
+ }
16
+ ]
17
+ }
@@ -11,7 +11,7 @@
11
11
  "files": [
12
12
  {
13
13
  "path": "product-card.tsx",
14
- "content": "\"use client\";\n\nimport React, { useCallback, useState } from \"react\";\nimport { Dialog } from \"@base-ui/react/dialog\";\nimport type { Product, ProductWithDetails } from \"@cimplify/sdk\";\nimport { useProduct } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { ProductSheet } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nconst ASPECT_STYLES: Record<string, React.CSSProperties> = {\n square: { aspectRatio: \"1/1\" },\n \"4/3\": { aspectRatio: \"4/3\" },\n \"16/10\": { aspectRatio: \"16/10\" },\n \"3/4\": { aspectRatio: \"3/4\" },\n};\n\nexport interface ProductCardClassNames {\n root?: string;\n imageContainer?: string;\n image?: string;\n body?: string;\n name?: string;\n description?: string;\n price?: string;\n badges?: string;\n badge?: string;\n modal?: string;\n modalOverlay?: string;\n}\n\nexport interface ProductCardProps {\n /** The product to display. */\n product: Product;\n /** Display mode: \"card\" opens a modal, \"page\" renders as a link. Auto-detected from product.display_mode. */\n displayMode?: \"card\" | \"page\";\n /** Link href for page mode. Default: `/menu/${product.slug}` */\n href?: string;\n /** Custom modal content renderer. Receives the fully-loaded product and a close callback. */\n renderModal?: (product: ProductWithDetails, onClose: () => void) => React.ReactNode;\n /** Custom image renderer (e.g. Next.js Image). */\n renderImage?: (props: {\n src: string;\n alt: string;\n className?: string;\n }) => React.ReactNode;\n /** Custom link renderer for page mode (e.g. Next.js Link). */\n renderLink?: (props: {\n href: string;\n className?: string;\n children: React.ReactNode;\n }) => React.ReactElement;\n /** Replace the entire default card body. */\n children?: React.ReactNode;\n /** Image aspect ratio. Default: \"4/3\". */\n aspectRatio?: \"square\" | \"4/3\" | \"16/10\" | \"3/4\";\n className?: string;\n classNames?: ProductCardClassNames;\n}\n\n/**\n * ProductCard — a product display card with two modes:\n *\n * - **card** (default): clickable button that opens a Base UI Dialog modal\n * - **page**: a plain `<a>` link for SEO-friendly product pages\n */\nexport function ProductCard({\n product,\n displayMode,\n href,\n renderModal,\n renderImage,\n renderLink,\n children,\n aspectRatio = \"4/3\",\n className,\n classNames,\n}: ProductCardProps): React.ReactElement {\n const mode = displayMode ?? product.display_mode ?? \"card\";\n const [isOpen, setIsOpen] = useState(false);\n const [shouldFetch, setShouldFetch] = useState(false);\n\n // Prefetch on pointer enter, always fetch when open\n const { product: productDetails } = useProduct(\n product.slug ?? product.id,\n { enabled: shouldFetch || isOpen },\n );\n\n const handlePrefetch = useCallback(() => {\n setShouldFetch(true);\n }, []);\n\n const handleOpenChange = useCallback((open: boolean) => {\n setIsOpen(open);\n if (open) {\n setShouldFetch(true);\n }\n }, []);\n\n const handleClose = useCallback(() => {\n setIsOpen(false);\n }, []);\n\n const imageUrl = product.image_url || product.images?.[0];\n\n const cardBody = children ?? (\n <>\n {/* Image */}\n {imageUrl && (\n <div\n data-cimplify-product-card-image-container\n className={cn(\"overflow-hidden rounded-t-xl\", classNames?.imageContainer)}\n style={ASPECT_STYLES[aspectRatio]}\n >\n {renderImage ? (\n renderImage({\n src: imageUrl,\n alt: product.name,\n className: classNames?.image,\n })\n ) : (\n <img\n src={imageUrl}\n alt={product.name}\n className={cn(\"w-full h-full object-cover transition-transform duration-300 group-hover:scale-105\", classNames?.image)}\n data-cimplify-product-card-image\n />\n )}\n </div>\n )}\n\n {/* Body */}\n <div\n data-cimplify-product-card-body\n className={cn(\"p-4 flex flex-col gap-1\", classNames?.body)}\n >\n <span\n data-cimplify-product-card-name\n className={cn(\"font-semibold text-foreground leading-tight\", classNames?.name)}\n >\n {product.name}\n </span>\n {product.description && (\n <span\n data-cimplify-product-card-description\n className={cn(\"text-sm text-muted-foreground line-clamp-2\", classNames?.description)}\n >\n {product.description}\n </span>\n )}\n <Price\n amount={product.default_price}\n className={cn(\"text-sm font-medium text-foreground mt-1\", classNames?.price)}\n />\n </div>\n </>\n );\n\n // Page mode — render as a link\n if (mode === \"page\") {\n const linkHref = href ?? `/menu/${product.slug}`;\n const linkClassName = cn(\"group block no-underline text-[inherit] rounded-xl border border-border bg-background transition-shadow duration-200 hover:shadow-md\", className, classNames?.root);\n\n if (renderLink) {\n return renderLink({ href: linkHref, className: linkClassName, children: cardBody });\n }\n\n return (\n <a\n href={linkHref}\n data-cimplify-product-card\n data-display-mode=\"page\"\n className={linkClassName}\n >\n {cardBody}\n </a>\n );\n }\n\n // Card mode — render as Base UI Dialog\n return (\n <Dialog.Root open={isOpen} onOpenChange={handleOpenChange}>\n <Dialog.Trigger\n onPointerEnter={handlePrefetch}\n data-cimplify-product-card\n data-display-mode=\"card\"\n className={cn(\n \"group block w-full text-left p-0 cursor-pointer font-[inherit] text-[inherit] rounded-xl border border-border bg-background transition-shadow duration-200 hover:shadow-md\",\n className,\n classNames?.root,\n )}\n >\n {cardBody}\n </Dialog.Trigger>\n\n <Dialog.Portal>\n <Dialog.Backdrop\n data-cimplify-product-card-backdrop\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity\",\n classNames?.modalOverlay,\n )}\n />\n <Dialog.Popup\n data-cimplify-product-card-modal\n className={cn(\n \"fixed z-50 rounded-2xl p-0 max-w-lg w-full h-fit max-h-[85vh] overflow-auto bg-background shadow-2xl outline-none\",\n classNames?.modal,\n )}\n >\n {isOpen && (\n productDetails ? (\n renderModal ? (\n renderModal(productDetails, handleClose)\n ) : (\n <ProductSheet\n product={productDetails}\n onClose={handleClose}\n renderImage={renderImage}\n />\n )\n ) : (\n <div\n data-cimplify-product-card-modal-loading\n aria-busy=\"true\"\n className=\"flex flex-col\"\n >\n <div className=\"aspect-[5/2] bg-muted animate-pulse\" />\n <div className=\"p-6 space-y-3\">\n <div className=\"h-5 w-3/5 bg-muted rounded animate-pulse\" />\n <div className=\"h-7 w-2/5 bg-muted rounded animate-pulse\" />\n <div className=\"h-4 w-4/5 bg-muted rounded animate-pulse\" />\n </div>\n <div className=\"mx-6 border-t border-border pt-4 pb-6\">\n <div className=\"h-14 bg-muted rounded-full animate-pulse\" />\n </div>\n </div>\n )\n )}\n </Dialog.Popup>\n </Dialog.Portal>\n </Dialog.Root>\n );\n}\n"
14
+ "content": "\"use client\";\n\nimport React, { useCallback, useState } from \"react\";\nimport { Dialog } from \"@base-ui/react/dialog\";\nimport type { Product, ProductWithDetails } from \"@cimplify/sdk\";\nimport { PRODUCT_TYPE, RENDER_HINT, DURATION_UNIT } from \"@cimplify/sdk\";\nimport { useProduct } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { ProductSheet } from \"@cimplify/sdk/react\";\nimport { CardVariant } from \"@cimplify/sdk/react\";\nimport type { CardLayoutProps } from \"@cimplify/sdk/react\";\nimport { FoodProductCard } from \"@cimplify/sdk/react\";\nimport { RetailProductCard } from \"@cimplify/sdk/react\";\nimport { WholesaleProductCard } from \"@cimplify/sdk/react\";\nimport { DigitalProductCard } from \"@cimplify/sdk/react\";\nimport { StandardServiceCard } from \"@cimplify/sdk/react\";\nimport { RentalServiceCard } from \"@cimplify/sdk/react\";\nimport { AccommodationCard } from \"@cimplify/sdk/react\";\nimport { LeaseServiceCard } from \"@cimplify/sdk/react\";\nimport { SubscriptionCard } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nconst ASPECT_STYLES: Record<string, React.CSSProperties> = {\n square: { aspectRatio: \"1/1\" },\n \"4/3\": { aspectRatio: \"4/3\" },\n \"16/10\": { aspectRatio: \"16/10\" },\n \"3/4\": { aspectRatio: \"3/4\" },\n};\n\nexport interface ProductCardClassNames {\n root?: string;\n imageContainer?: string;\n image?: string;\n body?: string;\n name?: string;\n description?: string;\n price?: string;\n badges?: string;\n badge?: string;\n modal?: string;\n modalOverlay?: string;\n}\n\nexport interface ProductCardProps {\n /** The product to display. */\n product: Product;\n /** Display mode: \"card\" opens a modal, \"page\" renders as a link. Auto-detected from product.display_mode. */\n displayMode?: \"card\" | \"page\";\n /** Explicit card variant. Auto-detected from product data when omitted. */\n variant?: CardVariant;\n /** Per-slug card map. Highest priority — maps a product slug to a custom card component. */\n cards?: Record<string, React.ComponentType<CardLayoutProps>>;\n /** Link href for page mode. Default: `/menu/${product.slug}` */\n href?: string;\n /** Custom modal content renderer. Receives the fully-loaded product and a close callback. */\n renderModal?: (product: ProductWithDetails, onClose: () => void) => React.ReactNode;\n /** Custom image renderer (e.g. Next.js Image). */\n renderImage?: (props: {\n src: string;\n alt: string;\n className?: string;\n }) => React.ReactNode;\n /** Custom link renderer for page mode (e.g. Next.js Link). */\n renderLink?: (props: {\n href: string;\n className?: string;\n children: React.ReactNode;\n }) => React.ReactElement;\n /** Called when quick-add button is clicked (if the resolved card supports it). */\n onQuickAdd?: (product: Product) => void;\n /** Replace the entire default card body. */\n children?: React.ReactNode;\n /** Image aspect ratio. Default: \"4/3\". */\n aspectRatio?: \"square\" | \"4/3\" | \"16/10\" | \"3/4\";\n className?: string;\n classNames?: ProductCardClassNames;\n}\n\nconst BUILT_IN_CARDS: Record<string, React.ComponentType<CardLayoutProps>> = {\n [CardVariant.Food]: FoodProductCard,\n [CardVariant.Retail]: RetailProductCard,\n [CardVariant.Wholesale]: WholesaleProductCard,\n [CardVariant.Digital]: DigitalProductCard,\n [CardVariant.Standard]: StandardServiceCard,\n [CardVariant.Rental]: RentalServiceCard,\n [CardVariant.Accommodation]: AccommodationCard,\n [CardVariant.Lease]: LeaseServiceCard,\n [CardVariant.Subscription]: SubscriptionCard,\n};\n\nconst RENTAL_UNITS = new Set<string>([DURATION_UNIT.Days, DURATION_UNIT.Hours]);\nconst LEASE_UNITS = new Set<string>([DURATION_UNIT.Weeks, DURATION_UNIT.Months, DURATION_UNIT.Years]);\n\nfunction resolveCardVariant(product: Product): CardVariant {\n if (product.quantity_pricing && product.quantity_pricing.length > 1) {\n return CardVariant.Wholesale;\n }\n if (product.type === PRODUCT_TYPE.Digital) {\n return CardVariant.Digital;\n }\n if (product.type === PRODUCT_TYPE.Service) {\n if (product.duration_unit && RENTAL_UNITS.has(product.duration_unit)) {\n return CardVariant.Rental;\n }\n if (product.duration_unit === DURATION_UNIT.Nights) {\n return CardVariant.Accommodation;\n }\n if (product.duration_unit && LEASE_UNITS.has(product.duration_unit)) {\n return CardVariant.Lease;\n }\n if (product.billing_plans && product.billing_plans.length > 0 && !product.duration_minutes) {\n return CardVariant.Subscription;\n }\n return CardVariant.Standard;\n }\n if (product.render_hint === RENDER_HINT.Food) {\n return CardVariant.Food;\n }\n if (product.render_hint === RENDER_HINT.Physical) {\n return CardVariant.Retail;\n }\n return CardVariant.Retail;\n}\n\n/**\n * ProductCard — a product display card with two modes:\n *\n * - **card** (default): clickable button that opens a Base UI Dialog modal\n * - **page**: a plain `<a>` link for SEO-friendly product pages\n */\nexport function ProductCard({\n product,\n displayMode,\n variant,\n cards,\n href,\n renderModal,\n renderImage,\n renderLink,\n onQuickAdd,\n children,\n aspectRatio = \"4/3\",\n className,\n classNames,\n}: ProductCardProps): React.ReactElement {\n const mode = displayMode ?? product.display_mode ?? \"card\";\n const [isOpen, setIsOpen] = useState(false);\n const [shouldFetch, setShouldFetch] = useState(false);\n\n // Prefetch on pointer enter, always fetch when open\n const { product: productDetails } = useProduct(\n product.slug ?? product.id,\n { enabled: shouldFetch || isOpen },\n );\n\n const handlePrefetch = useCallback(() => {\n setShouldFetch(true);\n }, []);\n\n const handleOpenChange = useCallback((open: boolean) => {\n setIsOpen(open);\n if (open) {\n setShouldFetch(true);\n }\n }, []);\n\n const handleClose = useCallback(() => {\n setIsOpen(false);\n }, []);\n\n const imageUrl = product.image_url || product.images?.[0];\n\n const cardBody = (() => {\n if (children) return children;\n\n // Resolve card variant: slug map → explicit variant → auto-detect\n const SlugCard = cards?.[product.slug];\n if (SlugCard) {\n return <SlugCard product={product} onQuickAdd={onQuickAdd} renderImage={renderImage} renderLink={renderLink} />;\n }\n\n const key = variant ?? resolveCardVariant(product);\n const ResolvedCard = BUILT_IN_CARDS[key];\n if (ResolvedCard) {\n return <ResolvedCard product={product} onQuickAdd={onQuickAdd} renderImage={renderImage} renderLink={renderLink} />;\n }\n\n // Fallback: minimal default\n return (\n <>\n {imageUrl && (\n <div\n data-cimplify-product-card-image-container\n className={cn(\"overflow-hidden rounded-t-xl\", classNames?.imageContainer)}\n style={ASPECT_STYLES[aspectRatio]}\n >\n {renderImage ? (\n renderImage({ src: imageUrl, alt: product.name, className: classNames?.image })\n ) : (\n <img\n src={imageUrl}\n alt={product.name}\n className={cn(\"w-full h-full object-cover transition-transform duration-300 group-hover:scale-105\", classNames?.image)}\n data-cimplify-product-card-image\n />\n )}\n </div>\n )}\n <div data-cimplify-product-card-body className={cn(\"p-4 flex flex-col gap-1\", classNames?.body)}>\n <span data-cimplify-product-card-name className={cn(\"font-semibold text-foreground leading-tight\", classNames?.name)}>\n {product.name}\n </span>\n {product.description && (\n <span data-cimplify-product-card-description className={cn(\"text-sm text-muted-foreground line-clamp-2\", classNames?.description)}>\n {product.description}\n </span>\n )}\n <Price amount={product.default_price} className={cn(\"text-sm font-medium text-foreground mt-1\", classNames?.price)} />\n </div>\n </>\n );\n })();\n\n // Page mode — render as a link\n if (mode === \"page\") {\n const linkHref = href ?? `/menu/${product.slug}`;\n const linkClassName = cn(\"group block no-underline text-[inherit] rounded-xl border border-border bg-background transition-shadow duration-200 hover:shadow-md\", className, classNames?.root);\n\n if (renderLink) {\n return renderLink({ href: linkHref, className: linkClassName, children: cardBody });\n }\n\n return (\n <a\n href={linkHref}\n data-cimplify-product-card\n data-display-mode=\"page\"\n className={linkClassName}\n >\n {cardBody}\n </a>\n );\n }\n\n // Card mode — render as Base UI Dialog\n return (\n <Dialog.Root open={isOpen} onOpenChange={handleOpenChange}>\n <Dialog.Trigger\n onPointerEnter={handlePrefetch}\n data-cimplify-product-card\n data-display-mode=\"card\"\n className={cn(\n \"group block w-full text-left p-0 cursor-pointer font-[inherit] text-[inherit] rounded-xl border border-border bg-background transition-shadow duration-200 hover:shadow-md\",\n className,\n classNames?.root,\n )}\n >\n {cardBody}\n </Dialog.Trigger>\n\n <Dialog.Portal>\n <Dialog.Backdrop\n data-cimplify-product-card-backdrop\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity\",\n classNames?.modalOverlay,\n )}\n />\n <Dialog.Popup\n data-cimplify-product-card-modal\n className={cn(\n \"fixed z-50 rounded-2xl p-0 max-w-lg w-full h-fit max-h-[85vh] overflow-auto bg-background shadow-2xl outline-none\",\n classNames?.modal,\n )}\n >\n {isOpen && (\n productDetails ? (\n renderModal ? (\n renderModal(productDetails, handleClose)\n ) : (\n <ProductSheet\n product={productDetails}\n onClose={handleClose}\n renderImage={renderImage}\n />\n )\n ) : (\n <div\n data-cimplify-product-card-modal-loading\n aria-busy=\"true\"\n className=\"flex flex-col\"\n >\n <div className=\"aspect-[5/2] bg-muted animate-pulse\" />\n <div className=\"p-6 space-y-3\">\n <div className=\"h-5 w-3/5 bg-muted rounded animate-pulse\" />\n <div className=\"h-7 w-2/5 bg-muted rounded animate-pulse\" />\n <div className=\"h-4 w-4/5 bg-muted rounded animate-pulse\" />\n </div>\n <div className=\"mx-6 border-t border-border pt-4 pb-6\">\n <div className=\"h-14 bg-muted rounded-full animate-pulse\" />\n </div>\n </div>\n )\n )}\n </Dialog.Popup>\n </Dialog.Portal>\n </Dialog.Root>\n );\n}\n"
15
15
  }
16
16
  ]
17
17
  }