diy-template-components 5.12.0 → 5.14.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.
package/build/index.js CHANGED
@@ -363,7 +363,7 @@ const useSectionStyles$b = createUseStyles(theme => ({
363
363
  bottom: 0,
364
364
  left: 0,
365
365
  width: '100%',
366
- zIndex: '100'
366
+ zIndex: '10000'
367
367
  }
368
368
  },
369
369
  mobileAppNameClass: {
@@ -623,7 +623,11 @@ const Button = /*#__PURE__*/React.forwardRef(function Button({
623
623
  rel: data?.isExternal ? 'nofollow noopener' : null,
624
624
  className: (active ? classes.active : '') + ' ' + classes[type] + ' ' + classes[size] + ' ' + classes.anchorClass,
625
625
  style: styling
626
- }, elementProps), data?.value) : /*#__PURE__*/React__default["default"].createElement("button", _extends({
626
+ }, elementProps), /*#__PURE__*/React__default["default"].createElement("span", {
627
+ dangerouslySetInnerHTML: {
628
+ __html: data?.value || ''
629
+ }
630
+ })) : /*#__PURE__*/React__default["default"].createElement("button", _extends({
627
631
  ref: ref,
628
632
  className: (active ? classes.active : '') + ' ' + classes[type] + ' ' + classes[size],
629
633
  onClick: isEdit ? null : onClick,
@@ -10924,7 +10928,7 @@ const SingleVideoSlide$1 = props => {
10924
10928
  currencySymbol
10925
10929
  } = props;
10926
10930
  const showCourseInstallmentData = data?.courseOverviewData;
10927
- const InstalmentData = isEdit ? data?.courseOverviewData?.installments?.formData?.installments[0].installmentAmount : data?.courseOverviewData?.installments?.formData?.installmentInfo?.installments[0].installmentPrice;
10931
+ const InstalmentData = isEdit ? data?.courseOverviewData?.installments?.formData?.installments[0].installmentAmount : data?.courseOverviewData?.installments?.formData?.installmentInfo?.installments?.[0]?.installmentPrice;
10928
10932
  const checkIfOfferIsValid = () => moment__default["default"]().diff(moment__default["default"](props?.data?.endDate || 0)) < 0;
10929
10933
  const checkForShowDiscount = () => {
10930
10934
  if (props.data.endDate === null || checkIfOfferIsValid()) {
@@ -12623,29 +12627,125 @@ const useCounterSectionStyles = createUseStyles(theme => {
12623
12627
  const DURATION_MS = 2000;
12624
12628
  const EASING = t => 1 - Math.pow(1 - t, 3); // easeOutCubic
12625
12629
 
12626
- function parseCounterValue(value) {
12630
+ function getPlainTextFromHtml(html) {
12631
+ if (typeof document === 'undefined') {
12632
+ return String(html).replace(/<[^>]*>/g, '');
12633
+ }
12634
+ const div = document.createElement('div');
12635
+ div.innerHTML = html;
12636
+ return div.textContent || div.innerText || '';
12637
+ }
12638
+ function parseStyleString(styleStr) {
12639
+ if (!styleStr || typeof styleStr !== 'string') return {};
12640
+ const obj = {};
12641
+ styleStr.split(';').forEach(part => {
12642
+ const colonIdx = part.indexOf(':');
12643
+ if (colonIdx > 0) {
12644
+ const key = part.slice(0, colonIdx).trim();
12645
+ const val = part.slice(colonIdx + 1).trim();
12646
+ if (key && val) obj[key] = val;
12647
+ }
12648
+ });
12649
+ return obj;
12650
+ }
12651
+ const SEMANTIC_TAG_STYLES = {
12652
+ strong: {
12653
+ fontWeight: 'bold'
12654
+ },
12655
+ b: {
12656
+ fontWeight: 'bold'
12657
+ },
12658
+ em: {
12659
+ fontStyle: 'italic'
12660
+ },
12661
+ i: {
12662
+ fontStyle: 'italic'
12663
+ },
12664
+ u: {
12665
+ textDecoration: 'underline'
12666
+ }
12667
+ };
12668
+
12669
+ /**
12670
+ * Parses value (plain text or HTML) to extract number for animation and wrapper attributes.
12671
+ * Returns { end, suffix, wrapperProps } or null if not parseable.
12672
+ * wrapperProps: { className, style } for the rich text wrapper, or null if plain text.
12673
+ */
12674
+ function parseCounterValueFromHtml(value) {
12627
12675
  if (value == null || value === '') return null;
12628
- const match = String(value).trim().match(/^([\d.]+)(.*)$/);
12676
+ const str = String(value).trim();
12677
+ const plainText = /<[^>]+>/.test(str) ? getPlainTextFromHtml(str) : str;
12678
+ const match = plainText.match(/^([\d.]+)(.*)$/);
12629
12679
  if (!match) return null;
12630
12680
  const num = parseFloat(match[1]);
12631
12681
  if (Number.isNaN(num)) return null;
12632
- return {
12633
- end: num,
12634
- suffix: match[2] || ''
12635
- };
12682
+ const suffix = match[2] || '';
12683
+ if (!/<[^>]+>/.test(str)) {
12684
+ return {
12685
+ end: num,
12686
+ suffix,
12687
+ wrapperProps: null
12688
+ };
12689
+ }
12690
+ if (typeof document === 'undefined') {
12691
+ return {
12692
+ end: num,
12693
+ suffix,
12694
+ wrapperProps: null
12695
+ };
12696
+ }
12697
+ try {
12698
+ const parser = new DOMParser();
12699
+ const doc = parser.parseFromString(str, 'text/html');
12700
+ const root = doc.body?.firstElementChild;
12701
+ if (!root) return {
12702
+ end: num,
12703
+ suffix,
12704
+ wrapperProps: null
12705
+ };
12706
+ const classes = [];
12707
+ const styleObj = {};
12708
+ const collectFromElement = el => {
12709
+ const c = el.getAttribute('class');
12710
+ if (c) classes.push(...c.split(/\s+/).filter(Boolean));
12711
+ const s = el.getAttribute('style');
12712
+ if (s) Object.assign(styleObj, parseStyleString(s));
12713
+ const tagStyles = SEMANTIC_TAG_STYLES[el.tagName?.toLowerCase()];
12714
+ if (tagStyles) Object.assign(styleObj, tagStyles);
12715
+ for (const child of el.children) {
12716
+ collectFromElement(child);
12717
+ }
12718
+ };
12719
+ collectFromElement(root);
12720
+ const className = [...new Set(classes)].join(' ');
12721
+ return {
12722
+ end: num,
12723
+ suffix,
12724
+ wrapperProps: className || Object.keys(styleObj).length ? {
12725
+ className,
12726
+ style: styleObj
12727
+ } : null
12728
+ };
12729
+ } catch {
12730
+ return {
12731
+ end: num,
12732
+ suffix,
12733
+ wrapperProps: null
12734
+ };
12735
+ }
12636
12736
  }
12637
12737
  function AnimatedCounter({
12638
12738
  value,
12639
12739
  className,
12640
12740
  refSetter
12641
12741
  }) {
12642
- const elRef = React.useRef(null);
12742
+ const animSpanRef = React.useRef(null);
12643
12743
  const rafIdRef = React.useRef(null);
12644
12744
  const hasAnimatedRef = React.useRef(false);
12645
12745
  React.useLayoutEffect(() => {
12646
12746
  hasAnimatedRef.current = false; // Reset so we can re-animate when value changes (e.g. live edit)
12647
- const parsed = parseCounterValue(value);
12648
- if (!parsed || !elRef.current) return;
12747
+ const parsed = parseCounterValueFromHtml(value);
12748
+ if (!parsed || !animSpanRef.current) return;
12649
12749
  const observer = new IntersectionObserver(entries => {
12650
12750
  const [entry] = entries;
12651
12751
  if (!entry.isIntersecting || hasAnimatedRef.current) return;
@@ -12660,9 +12760,9 @@ function AnimatedCounter({
12660
12760
  const progress = Math.min(elapsed / DURATION_MS, 1);
12661
12761
  const eased = EASING(progress);
12662
12762
  const current = eased * end;
12663
- if (elRef.current) {
12763
+ if (animSpanRef.current) {
12664
12764
  const displayValue = Number.isInteger(end) ? Math.round(current) : parseFloat(current.toFixed(2));
12665
- elRef.current.textContent = displayValue + suffix;
12765
+ animSpanRef.current.textContent = displayValue + suffix;
12666
12766
  }
12667
12767
  if (progress < 1) {
12668
12768
  rafIdRef.current = requestAnimationFrame(tick);
@@ -12672,22 +12772,34 @@ function AnimatedCounter({
12672
12772
  }, {
12673
12773
  threshold: 0.2
12674
12774
  });
12675
- observer.observe(elRef.current);
12775
+ observer.observe(animSpanRef.current);
12676
12776
  return () => {
12677
12777
  observer.disconnect();
12678
12778
  if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current);
12679
12779
  };
12680
12780
  }, [value]);
12681
- const parsed = parseCounterValue(value);
12781
+ const parsed = parseCounterValueFromHtml(value);
12682
12782
  const setRef = el => {
12683
- elRef.current = el;
12684
12783
  refSetter?.(el);
12685
12784
  };
12686
12785
  if (parsed) {
12786
+ const {
12787
+ end,
12788
+ suffix,
12789
+ wrapperProps
12790
+ } = parsed;
12791
+ const initialDisplay = Number.isInteger(end) ? '0' : '0.00';
12792
+ const animatedContent = /*#__PURE__*/React__default["default"].createElement("span", {
12793
+ ref: animSpanRef
12794
+ }, initialDisplay, suffix);
12795
+ const content = wrapperProps ? /*#__PURE__*/React__default["default"].createElement("span", {
12796
+ className: wrapperProps.className || undefined,
12797
+ style: Object.keys(wrapperProps.style || {}).length ? wrapperProps.style : undefined
12798
+ }, animatedContent) : animatedContent;
12687
12799
  return /*#__PURE__*/React__default["default"].createElement("h2", {
12688
12800
  ref: setRef,
12689
12801
  className: className
12690
- }, "0", parsed.suffix);
12802
+ }, content);
12691
12803
  }
12692
12804
  return /*#__PURE__*/React__default["default"].createElement("h2", {
12693
12805
  ref: refSetter,
@@ -12739,6 +12851,7 @@ function CounterSection({
12739
12851
  key: item._id || index,
12740
12852
  className: classes.counterItem
12741
12853
  }, value != null && value !== '' && /*#__PURE__*/React__default["default"].createElement(AnimatedCounter, {
12854
+ key: `${item._id || index}-${/<[^>]+>/.test(value || '') ? 'html' : 'plain'}`,
12742
12855
  value: value,
12743
12856
  className: classes.value,
12744
12857
  refSetter: getValueRefSetter(item)
@@ -12866,8 +12979,8 @@ const getFloatingButtonBase = theme => ({
12866
12979
  zIndex: 9999,
12867
12980
  display: 'inline-flex',
12868
12981
  alignItems: 'center',
12869
- gap: '10px',
12870
- padding: '12px 20px',
12982
+ gap: '8px',
12983
+ padding: '14px',
12871
12984
  backgroundColor: theme?.colors?.ctaColor || '#FFFFFF',
12872
12985
  border: `1px solid ${theme?.palette?.border?.secondary ?? '#E5E7EB'}`,
12873
12986
  borderRadius: '9999px',
@@ -12877,7 +12990,6 @@ const getFloatingButtonBase = theme => ({
12877
12990
  fontFamily: theme?.typography?.fontFamily ?? 'inherit',
12878
12991
  fontWeight: theme?.typography?.fontWeight?.bold ?? 700,
12879
12992
  fontSize: '14px',
12880
- textTransform: 'uppercase',
12881
12993
  letterSpacing: '0.02em',
12882
12994
  color: theme?.colors?.CtaTextColor || 'white',
12883
12995
  transition: 'box-shadow 0.2s ease, transform 0.2s ease'
@@ -12889,7 +13001,7 @@ const getFloatingButtonResponsive = theme => {
12889
13001
  ...base,
12890
13002
  bottom: 142,
12891
13003
  left: 16,
12892
- padding: '10px 16px',
13004
+ padding: '10px',
12893
13005
  gap: '8px',
12894
13006
  fontSize: '12px',
12895
13007
  background: theme?.colors?.ctaColor || '#FFFFFF'
@@ -12922,6 +13034,17 @@ const whatsAppIconSvgSize = {
12922
13034
  };
12923
13035
  const getWhatsAppIconSize = breakpoint => whatsAppIconSvgSize[breakpoint] ?? whatsAppIconSvgSize.mobile;
12924
13036
 
13037
+ /**
13038
+ * Height of the sticky bar (full-width CTA) by breakpoint for page padding.
13039
+ * Used to prevent content overlap when the sticky bar is fixed at the bottom.
13040
+ * Values match the sticky bar's padding + content height per breakpoint.
13041
+ */
13042
+ const STICKY_BAR_HEIGHT_BY_BREAKPOINT = {
13043
+ mobile: 0,
13044
+ tablet: 78,
13045
+ desktop: 82
13046
+ };
13047
+
12925
13048
  const DEFAULT_WHATSAPP_ICON_COLOR = '#25D366';
12926
13049
 
12927
13050
  /** Build WhatsApp wa.me URL from phone number (digits only, optional + prefix). */
@@ -13013,6 +13136,7 @@ function StickyCta({
13013
13136
  const contentList = node?.contentList?.components || [];
13014
13137
  const showEditHint = sectionData?.isDefaultEditor ?? false;
13015
13138
  sectionData?.bgSection?.components?.[0]?.sectionBgData?.metadata?.value;
13139
+ const redirectUrl = node?.subheading?.metadata?.value || '';
13016
13140
  const propsFromSection = node ? {
13017
13141
  type: node?.title?.metadata?.value ?? type,
13018
13142
  stickyMessage: contentList[1]?.contentPara?.metadata?.value ?? stickyMessage,
@@ -13035,7 +13159,7 @@ function StickyCta({
13035
13159
  whatsAppButtonBackgroundColor,
13036
13160
  iconImageUrl
13037
13161
  };
13038
- const whatsAppUrl = buildWhatsAppUrl(p?.whatsAppPhoneNumber) ?? p?.whatsAppHref ?? '#';
13162
+ buildWhatsAppUrl(p?.whatsAppPhoneNumber) ?? p?.whatsAppHref ?? '#';
13039
13163
  const theme = useTheme() || {};
13040
13164
  const [breakpoint, setBreakpoint] = React.useState(() => typeof window !== 'undefined' ? getBreakpoint(window.innerWidth, isMobile) : 'mobile');
13041
13165
  React.useEffect(() => {
@@ -13074,7 +13198,7 @@ function StickyCta({
13074
13198
  const iconSize = getWhatsAppIconSize(breakpoint);
13075
13199
  const iconColor = p.whatsAppIconColor ?? DEFAULT_WHATSAPP_ICON_COLOR;
13076
13200
  return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, isMobile ? null : editHintBlock, /*#__PURE__*/React__default["default"].createElement("a", {
13077
- href: whatsAppUrl,
13201
+ href: redirectUrl,
13078
13202
  target: "_blank",
13079
13203
  rel: "noopener noreferrer",
13080
13204
  "access-cta": "DIY",
@@ -13092,23 +13216,53 @@ function StickyCta({
13092
13216
  size: iconSize,
13093
13217
  color: iconColor,
13094
13218
  iconImageUrl: p.iconImageUrl
13095
- }), /*#__PURE__*/React__default["default"].createElement("span", null, p.whatsAppLabel)));
13219
+ }), p.whatsAppLabel ? /*#__PURE__*/React__default["default"].createElement("span", {
13220
+ dangerouslySetInnerHTML: {
13221
+ __html: p.whatsAppLabel
13222
+ }
13223
+ }) : null));
13096
13224
  }
13097
13225
 
13098
13226
  // type === 'sticky' – use primary Button (same as other sections) linking to WhatsApp
13099
13227
  const barStyle = getStickyBarStyle(breakpoint, theme);
13100
13228
  const textStyle = getStickyTextStyle(breakpoint, theme);
13101
- return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, isMobile ? null : editHintBlock, /*#__PURE__*/React__default["default"].createElement("div", {
13229
+ React.useEffect(() => {
13230
+ document.body.classList.add('has-sticky-cta');
13231
+ return () => document.body.classList.remove('has-sticky-cta');
13232
+ }, []);
13233
+ const stickyBarPaddingStyles = `
13234
+ body.has-sticky-cta {
13235
+ padding-bottom: ${STICKY_BAR_HEIGHT_BY_BREAKPOINT.mobile}px;
13236
+ }
13237
+ @media (min-width: ${BREAKPOINTS.tablet}px) {
13238
+ body.has-sticky-cta {
13239
+ padding-bottom: ${STICKY_BAR_HEIGHT_BY_BREAKPOINT.tablet}px;
13240
+ }
13241
+ }
13242
+ @media (min-width: ${BREAKPOINTS.desktop}px) {
13243
+ body.has-sticky-cta {
13244
+ padding-bottom: ${STICKY_BAR_HEIGHT_BY_BREAKPOINT.desktop}px;
13245
+ }
13246
+ }
13247
+ `;
13248
+ return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("style", {
13249
+ dangerouslySetInnerHTML: {
13250
+ __html: stickyBarPaddingStyles
13251
+ }
13252
+ }), isMobile ? null : editHintBlock, /*#__PURE__*/React__default["default"].createElement("div", {
13102
13253
  style: barStyle,
13103
13254
  role: "banner",
13104
13255
  "access-cta-sticky": "DIY"
13105
13256
  }, /*#__PURE__*/React__default["default"].createElement("p", {
13106
- style: textStyle
13107
- }, p.stickyMessage), /*#__PURE__*/React__default["default"].createElement(Button, {
13257
+ style: textStyle,
13258
+ dangerouslySetInnerHTML: {
13259
+ __html: p.stickyMessage || ''
13260
+ }
13261
+ }), /*#__PURE__*/React__default["default"].createElement(Button, {
13108
13262
  data: {
13109
13263
  isLink: 1,
13110
13264
  isExternal: 1,
13111
- link: whatsAppUrl,
13265
+ link: redirectUrl,
13112
13266
  value: p.stickyButtonText
13113
13267
  },
13114
13268
  type: "primary",
@@ -13672,7 +13826,13 @@ function VideoWorkshopPromotion({
13672
13826
  ref: videoData?.videoTextContent?.metadata?.refSetter,
13673
13827
  data: videoData?.videoTextContent?.metadata,
13674
13828
  type: 'primary',
13675
- size: isMobile ? 'small' : 'medium'
13829
+ size: isMobile ? 'small' : 'medium',
13830
+ onClick: () => {
13831
+ const url = videoData?.videoTextHeading?.metadata?.value;
13832
+ if (url) {
13833
+ window.open(url, '_blank', 'noopener,noreferrer');
13834
+ }
13835
+ }
13676
13836
  }))), /*#__PURE__*/React__default["default"].createElement("div", {
13677
13837
  className: classes.videoBlock
13678
13838
  }, /*#__PURE__*/React__default["default"].createElement("div", {