@gait-financial/react 0.1.12 → 0.1.15

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/wc/button.js CHANGED
@@ -1,128 +1,25 @@
1
- (function(exports) {
2
- "use strict";
3
- function darkenColor(hex, percent) {
4
- const color = hex.replace("#", "");
5
- const num = parseInt(color, 16);
6
- const r = num >> 16 & 255;
7
- const g = num >> 8 & 255;
8
- const b = num & 255;
9
- const newR = Math.max(0, Math.floor(r * (1 - percent)));
10
- const newG = Math.max(0, Math.floor(g * (1 - percent)));
11
- const newB = Math.max(0, Math.floor(b * (1 - percent)));
12
- return `#${(newR << 16 | newG << 8 | newB).toString(16).padStart(6, "0")}`;
13
- }
14
- function isColorDark(hex) {
15
- const color = hex.replace("#", "");
16
- const num = parseInt(color, 16);
17
- const r = num >> 16 & 255;
18
- const g = num >> 8 & 255;
19
- const b = num & 255;
20
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
21
- return luminance < 0.5;
22
- }
23
- const colors = {
24
- // Primary colors (CTA button - dark)
25
- primary: {
26
- main: "rgb(47, 44, 37)",
27
- dark: "rgb(35, 33, 28)",
28
- contrastText: "#ffffff"
29
- }
30
- };
31
- const spacing = {
32
- sm: "8px",
33
- md: "16px",
34
- lg: "24px"
35
- };
36
- const borderRadius = {
37
- md: "8px"
38
- };
39
- const transitions = {
40
- normal: "250ms ease-in-out"
41
- };
42
- const zIndex = {
43
- modalBackdrop: 1040
44
- };
45
- const typography = {
46
- fontFamily: {
47
- primary: '"Inter", "Helvetica", "Arial", sans-serif'
48
- },
49
- fontSize: {
50
- // 12px
51
- sm: "0.875rem",
52
- // 14px
53
- md: "1rem",
54
- // 16px
55
- lg: "1.125rem"
56
- },
57
- fontWeight: {
58
- medium: 500
59
- },
60
- lineHeight: {
61
- normal: 1.5
62
- }
63
- };
64
- const GAIT_BRAND_COLOR = "#4147BF";
65
- const GAIT_SECONDARY_COLOR = "#F3F2FF";
66
- const splitPaymentConfig = {
67
- /**
68
- * Split payment fee amount
69
- * This fee is added to the total cost when using split payment
70
- */
71
- fee: 10
72
- };
73
- function getThemeStyles(darkenColor2, isColorDark2) {
74
- return {
75
- bg: GAIT_BRAND_COLOR,
76
- bgHover: darkenColor2(GAIT_BRAND_COLOR, 0.15),
77
- bgActive: darkenColor2(GAIT_BRAND_COLOR, 0.25),
78
- color: isColorDark2(GAIT_BRAND_COLOR) ? "#ffffff" : "#000000"
79
- };
80
- }
81
- function getSizeStyles(size) {
82
- const sizeStyles = {
83
- small: {
84
- padding: "8px 20px",
85
- fontSize: typography.fontSize.sm,
86
- minHeight: "36px"
87
- },
88
- medium: {
89
- padding: "12px 28px",
90
- fontSize: typography.fontSize.md,
91
- minHeight: "44px"
92
- },
93
- large: {
94
- padding: "16px 36px",
95
- fontSize: typography.fontSize.lg,
96
- minHeight: "52px"
97
- }
98
- };
99
- return sizeStyles[size];
100
- }
101
- function generateButtonStyles(size, darkenColorFn, isColorDarkFn) {
102
- const themeStyles = getThemeStyles(darkenColorFn, isColorDarkFn);
103
- const currentSize = getSizeStyles(size);
104
- return `
1
+ (function(I){"use strict";function F(r,t){const e=r.replace("#",""),i=parseInt(e,16),a=i>>16&255,n=i>>8&255,o=i&255,d=Math.max(0,Math.floor(a*(1-t))),l=Math.max(0,Math.floor(n*(1-t))),s=Math.max(0,Math.floor(o*(1-t)));return`#${(d<<16|l<<8|s).toString(16).padStart(6,"0")}`}function q(r){const t=r.replace("#",""),e=parseInt(t,16),i=e>>16&255,a=e>>8&255,n=e&255;return(.299*i+.587*a+.114*n)/255<.5}const h={primary:{main:"rgb(47, 44, 37)",dark:"rgb(35, 33, 28)",contrastText:"#ffffff"}},E={sm:"8px",md:"16px",lg:"24px"},P={md:"8px"},f={normal:"250ms ease-in-out"},O={modalBackdrop:1040},y={fontFamily:{primary:'"Inter", "Helvetica", "Arial", sans-serif'},fontSize:{sm:"0.875rem",md:"1rem",lg:"1.125rem"},fontWeight:{medium:500},lineHeight:{normal:1.5}},g="#4147BF",C="#F3F2FF",B={fee:10};function N(r,t){return{bg:g,bgHover:r(g,.15),bgActive:r(g,.25),color:t(g)?"#ffffff":"#000000"}}function G(r){return{small:{padding:"8px 20px",fontSize:y.fontSize.sm,minHeight:"36px"},medium:{padding:"12px 28px",fontSize:y.fontSize.md,minHeight:"44px"},large:{padding:"16px 36px",fontSize:y.fontSize.lg,minHeight:"52px"}}[r]}function D(r,t,e){const i=N(t,e),a=G(r);return`
105
2
  <style>
106
3
  :host {
107
4
  display: inline-block;
108
- --gait-button-bg: ${themeStyles.bg};
109
- --gait-button-bg-hover: ${themeStyles.bgHover};
110
- --gait-button-bg-active: ${themeStyles.bgActive};
111
- --gait-button-color: ${themeStyles.color};
112
- --gait-button-padding: ${currentSize.padding};
113
- --gait-button-font-size: ${currentSize.fontSize};
114
- --gait-button-min-height: ${currentSize.minHeight};
5
+ --gait-button-bg: ${i.bg};
6
+ --gait-button-bg-hover: ${i.bgHover};
7
+ --gait-button-bg-active: ${i.bgActive};
8
+ --gait-button-color: ${i.color};
9
+ --gait-button-padding: ${a.padding};
10
+ --gait-button-font-size: ${a.fontSize};
11
+ --gait-button-min-height: ${a.minHeight};
115
12
  }
116
13
 
117
14
  button {
118
15
  display: inline-flex;
119
16
  align-items: center;
120
17
  justify-content: center;
121
- gap: ${spacing.sm};
18
+ gap: ${E.sm};
122
19
 
123
- font-family: ${typography.fontFamily.primary};
124
- font-weight: ${typography.fontWeight.medium};
125
- line-height: ${typography.lineHeight.normal};
20
+ font-family: ${y.fontFamily.primary};
21
+ font-weight: ${y.fontWeight.medium};
22
+ line-height: ${y.lineHeight.normal};
126
23
  font-size: var(--gait-button-font-size);
127
24
 
128
25
  padding: var(--gait-button-padding);
@@ -132,10 +29,10 @@
132
29
  color: var(--gait-button-color);
133
30
 
134
31
  border: none;
135
- border-radius: ${borderRadius.md};
32
+ border-radius: ${P.md};
136
33
  cursor: pointer;
137
34
 
138
- transition: all ${transitions.normal};
35
+ transition: all ${f.normal};
139
36
 
140
37
  /* Remove default button styles */
141
38
  outline: none;
@@ -184,15 +81,7 @@
184
81
  width: 100%;
185
82
  }
186
83
  </style>
187
- `;
188
- }
189
- function injectModalStyles() {
190
- if (document.getElementById("gait-modal-styles")) {
191
- return;
192
- }
193
- const style = document.createElement("style");
194
- style.id = "gait-modal-styles";
195
- style.textContent = `
84
+ `}function V(){if(document.getElementById("gait-modal-styles"))return;const r=document.createElement("style");r.id="gait-modal-styles",r.textContent=`
196
85
  /* Modal Backdrop - The overlay behind the modal */
197
86
  .gait-modal-backdrop {
198
87
  position: fixed;
@@ -204,9 +93,9 @@
204
93
  display: flex;
205
94
  align-items: center;
206
95
  justify-content: center;
207
- z-index: ${zIndex.modalBackdrop};
96
+ z-index: ${O.modalBackdrop};
208
97
  opacity: 0;
209
- transition: opacity ${transitions.normal};
98
+ transition: opacity ${f.normal};
210
99
  overflow: hidden;
211
100
  }
212
101
 
@@ -507,7 +396,7 @@
507
396
  border: 1px solid #e5e7eb;
508
397
  border-radius: 8px;
509
398
  cursor: pointer;
510
- transition: all ${transitions.normal};
399
+ transition: all ${f.normal};
511
400
  color: #6b7280;
512
401
  }
513
402
 
@@ -518,9 +407,9 @@
518
407
  }
519
408
 
520
409
  .gait-modal-split-option.selected {
521
- background: ${GAIT_SECONDARY_COLOR};
522
- border: 1px solid ${GAIT_BRAND_COLOR};
523
- color: ${GAIT_BRAND_COLOR};
410
+ background: ${C};
411
+ border: 1px solid ${g};
412
+ color: ${g};
524
413
  }
525
414
 
526
415
  .gait-modal-email-inputs {
@@ -539,11 +428,11 @@
539
428
  background: #fff;
540
429
  color: #111827;
541
430
  outline: none;
542
- transition: all ${transitions.normal};
431
+ transition: all ${f.normal};
543
432
  }
544
433
 
545
434
  .gait-modal-email-input:focus {
546
- border-color: ${colors.primary.main};
435
+ border-color: ${h.primary.main};
547
436
  box-shadow: 0 0 0 2px rgba(47, 44, 37, 0.1);
548
437
  }
549
438
 
@@ -606,7 +495,7 @@
606
495
 
607
496
  .gait-modal-email-item-amount {
608
497
  font-weight: 500;
609
- color: ${colors.primary.main};
498
+ color: ${h.primary.main};
610
499
  font-size: 14px;
611
500
  }
612
501
 
@@ -625,12 +514,12 @@
625
514
  border: none;
626
515
  cursor: pointer;
627
516
  padding: 2px 4px;
628
- transition: color ${transitions.normal};
517
+ transition: color ${f.normal};
629
518
  }
630
519
 
631
520
  .gait-modal-email-item-edit:hover,
632
521
  .gait-modal-email-item-remove:hover {
633
- color: ${colors.primary.main};
522
+ color: ${h.primary.main};
634
523
  }
635
524
 
636
525
  /* For "By amount" mode - two input fields side by side */
@@ -653,18 +542,18 @@
653
542
  padding: 8px 16px;
654
543
  font-size: 13px;
655
544
  font-weight: 500;
656
- border: 1px solid ${colors.primary.main};
545
+ border: 1px solid ${h.primary.main};
657
546
  border-radius: 6px;
658
- background: ${colors.primary.main};
659
- color: ${colors.primary.contrastText};
547
+ background: ${h.primary.main};
548
+ color: ${h.primary.contrastText};
660
549
  cursor: pointer;
661
- transition: all ${transitions.normal};
550
+ transition: all ${f.normal};
662
551
  white-space: nowrap;
663
552
  }
664
553
 
665
554
  .gait-modal-done-button:hover {
666
- background: ${colors.primary.dark};
667
- border-color: ${colors.primary.dark};
555
+ background: ${h.primary.dark};
556
+ border-color: ${h.primary.dark};
668
557
  }
669
558
 
670
559
  .gait-modal-email-amount-input {
@@ -680,7 +569,7 @@
680
569
  }
681
570
 
682
571
  .gait-modal-email-amount-input:focus {
683
- border-color: ${colors.primary.main};
572
+ border-color: ${h.primary.main};
684
573
  box-shadow: 0 0 0 2px rgba(47, 44, 37, 0.1);
685
574
  }
686
575
 
@@ -692,11 +581,11 @@
692
581
  border: none;
693
582
  cursor: pointer;
694
583
  text-align: left;
695
- transition: color ${transitions.normal};
584
+ transition: color ${f.normal};
696
585
  }
697
586
 
698
587
  .gait-modal-add-email-button:hover {
699
- color: ${colors.primary.main};
588
+ color: ${h.primary.main};
700
589
  text-decoration: underline;
701
590
  }
702
591
 
@@ -716,7 +605,7 @@
716
605
  .gait-modal-split-amount {
717
606
  font-size: 18px;
718
607
  font-weight: 600;
719
- color: ${GAIT_BRAND_COLOR};
608
+ color: ${g};
720
609
  }
721
610
 
722
611
  .gait-modal-send-split-button {
@@ -725,16 +614,16 @@
725
614
  font-size: 14px;
726
615
  font-weight: 500;
727
616
  border-radius: 8px;
728
- border: 1px solid ${GAIT_BRAND_COLOR};
729
- background: ${GAIT_SECONDARY_COLOR};
730
- color: ${GAIT_BRAND_COLOR};
617
+ border: 1px solid ${g};
618
+ background: ${C};
619
+ color: ${g};
731
620
  cursor: pointer;
732
- transition: all ${transitions.normal};
621
+ transition: all ${f.normal};
733
622
  margin-top: 16px;
734
623
  }
735
624
 
736
625
  .gait-modal-send-split-button:hover {
737
- background: ${GAIT_SECONDARY_COLOR};
626
+ background: ${C};
738
627
  filter: brightness(0.98);
739
628
  }
740
629
 
@@ -754,10 +643,10 @@
754
643
  font-weight: 600;
755
644
  border-radius: 10px;
756
645
  border: none;
757
- background: var(--gait-modal-button-bg, ${colors.primary.main});
758
- color: var(--gait-modal-button-color, ${colors.primary.contrastText});
646
+ background: var(--gait-modal-button-bg, ${h.primary.main});
647
+ color: var(--gait-modal-button-color, ${h.primary.contrastText});
759
648
  cursor: pointer;
760
- transition: all ${transitions.normal};
649
+ transition: all ${f.normal};
761
650
  margin-top: 12px;
762
651
  display: flex;
763
652
  flex-direction: row;
@@ -768,7 +657,7 @@
768
657
  }
769
658
 
770
659
  .gait-modal-confirm-button-with-amount:hover {
771
- background-color: var(--gait-modal-button-bg-hover, ${colors.primary.dark});
660
+ background-color: var(--gait-modal-button-bg-hover, ${h.primary.dark});
772
661
  }
773
662
 
774
663
  .gait-modal-confirm-button-with-amount:active {
@@ -815,7 +704,7 @@
815
704
  background: var(--gait-modal-button-bg, #e91e63);
816
705
  color: var(--gait-modal-button-color, #fff);
817
706
  cursor: pointer;
818
- transition: all ${transitions.normal};
707
+ transition: all ${f.normal};
819
708
  }
820
709
 
821
710
  .gait-modal-confirm-button:hover {
@@ -828,29 +717,29 @@
828
717
 
829
718
  /* Modal Footer - The button section */
830
719
  .gait-modal-footer {
831
- padding: ${spacing.lg};
720
+ padding: ${E.lg};
832
721
  border-top: 1px solid #e5e7eb;
833
722
  display: flex;
834
723
  justify-content: flex-end;
835
- gap: ${spacing.md};
724
+ gap: ${E.md};
836
725
  }
837
726
 
838
727
  /* Modal Button - The close button */
839
728
  .gait-modal-button {
840
- padding: ${spacing.sm} ${spacing.lg};
841
- font-family: ${typography.fontFamily.primary};
842
- font-size: ${typography.fontSize.md};
843
- font-weight: ${typography.fontWeight.medium};
729
+ padding: ${E.sm} ${E.lg};
730
+ font-family: ${y.fontFamily.primary};
731
+ font-size: ${y.fontSize.md};
732
+ font-weight: ${y.fontWeight.medium};
844
733
  border: none;
845
- border-radius: ${borderRadius.md};
734
+ border-radius: ${P.md};
846
735
  cursor: pointer;
847
- transition: all ${transitions.normal};
848
- background-color: ${colors.primary.main};
849
- color: ${colors.primary.contrastText};
736
+ transition: all ${f.normal};
737
+ background-color: ${h.primary.main};
738
+ color: ${h.primary.contrastText};
850
739
  }
851
740
 
852
741
  .gait-modal-button:hover {
853
- background-color: ${colors.primary.dark};
742
+ background-color: ${h.primary.dark};
854
743
  }
855
744
 
856
745
  .gait-modal-button:active {
@@ -945,73 +834,55 @@
945
834
  background-color: #ef4444;
946
835
  color: #fff;
947
836
  }
948
- `;
949
- document.head.appendChild(style);
950
- }
951
- const GAIT_LOGO_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 311.96 313.95" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M168.07,139.33c6.81-15.69,22.45-26.66,40.65-26.66s33.83,10.97,40.66,26.66h62.58C303.19,60.94,236.71,0,155.98,0S8.77,60.95,0,139.33h168.07Z"/><path fill="currentColor" d="M249.37,174.62c-6.81,15.68-22.46,26.65-40.65,26.65s-33.82-10.97-40.64-26.65H0c8.77,78.39,75.25,139.33,155.98,139.33s147.21-60.94,155.98-139.33h-62.59Z"/></svg>`;
952
- function generateButtonTemplate(disabled, customStyle) {
953
- return `
837
+ `,document.head.appendChild(r)}const j='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 311.96 313.95" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M168.07,139.33c6.81-15.69,22.45-26.66,40.65-26.66s33.83,10.97,40.66,26.66h62.58C303.19,60.94,236.71,0,155.98,0S8.77,60.95,0,139.33h168.07Z"/><path fill="currentColor" d="M249.37,174.62c-6.81,15.68-22.46,26.65-40.65,26.65s-33.82-10.97-40.64-26.65H0c8.77,78.39,75.25,139.33,155.98,139.33s147.21-60.94,155.98-139.33h-62.59Z"/></svg>';function H(r,t){return`
954
838
  <button
955
839
  type="button"
956
- ${disabled ? "disabled" : ""}
957
- style="${customStyle}"
840
+ ${r?"disabled":""}
841
+ style="${t}"
958
842
  >
959
- <span class="gait-button-icon">${GAIT_LOGO_ICON}</span>
843
+ <span class="gait-button-icon">${j}</span>
960
844
  <span class="gait-button-label">Gait</span>
961
845
  </button>
962
- `;
963
- }
964
- function formatCurrency(amount) {
965
- return `R ${amount.toFixed(2)}`;
966
- }
967
- const closeIcon = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">\n <path d="M18 6L6 18M6 6l12 12" />\n</svg>\n';
968
- const checkIcon = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">\n <path d="M20 6L9 17l-5-5" />\n</svg>\n';
969
- const crossIcon = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">\n <path d="M18 6L6 18M6 6l12 12" />\n</svg>\n';
970
- const spinnerIcon = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">\n <path d="M21 12a9 9 0 11-6.22-8.56" />\n</svg>\n';
971
- function renderItems(items) {
972
- if (!items || items.length === 0) {
973
- return "";
974
- }
975
- return items.map((item) => `
846
+ `}function A(r){return`R ${r.toFixed(2)}`}const _=`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
847
+ <path d="M18 6L6 18M6 6l12 12" />
848
+ </svg>
849
+ `,$=`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
850
+ <path d="M20 6L9 17l-5-5" />
851
+ </svg>
852
+ `,U=`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
853
+ <path d="M18 6L6 18M6 6l12 12" />
854
+ </svg>
855
+ `,W=`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
856
+ <path d="M21 12a9 9 0 11-6.22-8.56" />
857
+ </svg>
858
+ `;function J(r){return!r||r.length===0?"":r.map(t=>`
976
859
  <div class="gait-modal-item">
977
860
  <img
978
- src="${item.image || ""}"
979
- alt="${item.name || "Product"}"
861
+ src="${t.image||""}"
862
+ alt="${t.name||"Product"}"
980
863
  class="gait-modal-item-image"
981
864
  />
982
865
  <div class="gait-modal-item-meta">
983
866
  <h3 class="gait-modal-item-title">
984
- ${item.name || ""}
867
+ ${t.name||""}
985
868
  </h3>
986
869
  <div class="gait-modal-item-quantity">
987
- Qty: ${item.quantity || 0}
988
- <span class="gait-modal-item-price">${formatCurrency(item.price || 0)}</span>
870
+ Qty: ${t.quantity||0}
871
+ <span class="gait-modal-item-price">${A(t.price||0)}</span>
989
872
  </div>
990
873
  </div>
991
874
  </div>
992
- `).join("");
993
- }
994
- function renderPriceBreakdown(breakdown) {
995
- if (!breakdown || breakdown.length === 0) {
996
- return "";
997
- }
998
- return breakdown.map((item) => {
999
- const price = typeof item.price === "number" ? item.price : parseFloat(String(item.price)) || 0;
1000
- return `
875
+ `).join("")}function Y(r){return!r||r.length===0?"":r.map(t=>{const e=typeof t.price=="number"?t.price:parseFloat(String(t.price))||0;return`
1001
876
  <div class="gait-modal-price-item">
1002
- <span>${item.name || ""}</span>
1003
- <span>${formatCurrency(price)}</span>
877
+ <span>${t.name||""}</span>
878
+ <span>${A(e)}</span>
1004
879
  </div>
1005
- `;
1006
- }).join("");
1007
- }
1008
- function generateModalTemplate(items, priceBreakdown, totalCost) {
1009
- return `
880
+ `}).join("")}function K(r,t,e){return`
1010
881
  <div class="gait-modal-container">
1011
882
  <div class="gait-modal-header">
1012
883
  <h2 class="gait-modal-header-title">Split Payment</h2>
1013
884
  <button class="gait-modal-close-icon" data-gait-modal-close aria-label="Close">
1014
- ${closeIcon}
885
+ ${_}
1015
886
  </button>
1016
887
  </div>
1017
888
 
@@ -1019,20 +890,20 @@
1019
890
  <!-- LEFT -->
1020
891
  <div class="gait-modal-left-panel">
1021
892
  <div class="gait-modal-items-container">
1022
- ${renderItems(items)}
893
+ ${J(r)}
1023
894
  </div>
1024
895
 
1025
896
  <div class="gait-modal-price-section">
1026
897
  <h4 class="gait-modal-price-title">Price details</h4>
1027
898
 
1028
- ${renderPriceBreakdown(priceBreakdown)}
899
+ ${Y(t)}
1029
900
 
1030
- ${totalCost > 0 ? `
901
+ ${e>0?`
1031
902
  <div class="gait-modal-price-total">
1032
903
  <span>Total</span>
1033
- <span>${formatCurrency(totalCost)}</span>
904
+ <span>${A(e)}</span>
1034
905
  </div>
1035
- ` : ""}
906
+ `:""}
1036
907
  </div>
1037
908
  </div>
1038
909
 
@@ -1066,1560 +937,22 @@
1066
937
  </div>
1067
938
  </div>
1068
939
  </div>
1069
- `;
1070
- }
1071
- function getDataAttributes(element) {
1072
- const dataAttrs = {};
1073
- Array.from(element.attributes).forEach((attr) => {
1074
- if (attr.name.startsWith("data-")) {
1075
- dataAttrs[attr.name] = attr.value;
1076
- }
1077
- });
1078
- return dataAttrs;
1079
- }
1080
- function getAllAttributes(element) {
1081
- const attrs = {};
1082
- Array.from(element.attributes).forEach((attr) => {
1083
- attrs[attr.name] = attr.value;
1084
- });
1085
- return attrs;
1086
- }
1087
- function createAttributeGetters(element) {
1088
- return {
1089
- get dataId() {
1090
- return element.getAttribute("data-id") || null;
1091
- },
1092
- get disabled() {
1093
- return element.hasAttribute("disabled") && element.getAttribute("disabled") !== "false";
1094
- },
1095
- get size() {
1096
- return element.getAttribute("size") || "medium";
1097
- },
1098
- get items() {
1099
- const itemsAttr = element.getAttribute("items");
1100
- if (!itemsAttr) {
1101
- return [];
1102
- }
1103
- try {
1104
- return JSON.parse(itemsAttr);
1105
- } catch (e) {
1106
- console.warn("[GaitButton] Failed to parse items attribute:", e);
1107
- return [];
1108
- }
1109
- },
1110
- get totalCost() {
1111
- const totalCostAttr = element.getAttribute("total-cost");
1112
- if (!totalCostAttr) {
1113
- return 0;
1114
- }
1115
- const parsed = parseFloat(totalCostAttr);
1116
- const baseTotal = isNaN(parsed) ? 0 : parsed;
1117
- return baseTotal + splitPaymentConfig.fee;
1118
- },
1119
- get priceBreakdown() {
1120
- const totalCostAttr = element.getAttribute("total-cost");
1121
- const parsed = parseFloat(totalCostAttr || "0");
1122
- const baseTotal = isNaN(parsed) ? 0 : parsed;
1123
- const allPriceBreakdown = [
1124
- {
1125
- name: "Subtotal",
1126
- price: baseTotal
1127
- },
1128
- {
1129
- name: "Split Payment Fee",
1130
- price: splitPaymentConfig.fee
1131
- }
1132
- ];
1133
- return allPriceBreakdown;
1134
- },
1135
- get customer() {
1136
- const customerAttr = element.getAttribute("customer");
1137
- if (!customerAttr) {
1138
- return null;
1139
- }
1140
- try {
1141
- return JSON.parse(customerAttr);
1142
- } catch (e) {
1143
- console.warn("[GaitButton] Failed to parse customer attribute:", e);
1144
- return null;
1145
- }
1146
- },
1147
- get webhook() {
1148
- const webhookAttr = element.getAttribute("webhook");
1149
- if (!webhookAttr) {
1150
- return null;
1151
- }
1152
- try {
1153
- return JSON.parse(webhookAttr);
1154
- } catch (e) {
1155
- console.warn("[GaitButton] Failed to parse webhook attribute:", e);
1156
- return null;
1157
- }
1158
- },
1159
- get merchantStoreUrl() {
1160
- const url = element.getAttribute("merchant-store-url");
1161
- if (url && url.trim() !== "") {
1162
- return url.trim();
1163
- }
1164
- if (typeof window !== "undefined" && window.location) {
1165
- return window.location.origin;
1166
- }
1167
- return "";
1168
- }
1169
- };
1170
- }
1171
- function isValidEmail(email) {
1172
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1173
- return emailRegex.test(email);
1174
- }
1175
- function calculateSplit(totalCost, splitMethod, emailData) {
1176
- if (emailData.length === 0) {
1177
- return totalCost;
1178
- }
1179
- if (splitMethod === "equally") {
1180
- const participantCount = emailData.length + 1;
1181
- const perPerson = totalCost / participantCount;
1182
- return Math.max(0, totalCost - perPerson * emailData.length);
1183
- } else if (splitMethod === "by-amount") {
1184
- let totalSplit = 0;
1185
- emailData.forEach((item) => {
1186
- const amount = item.amount || 0;
1187
- totalSplit += amount;
1188
- });
1189
- return Math.max(0, totalCost - totalSplit);
1190
- }
1191
- return totalCost;
1192
- }
1193
- function calculateEqualSplit(totalCost, participantCount) {
1194
- return totalCost / participantCount;
1195
- }
1196
- function getEmailData(container, splitMethod, totalCost) {
1197
- const data = [];
1198
- const items = container.querySelectorAll("[data-email-item]");
1199
- items.forEach((item) => {
1200
- const email = item.getAttribute("data-email-item") || "";
1201
- if (email) {
1202
- if (splitMethod === "by-amount") {
1203
- const amountSpan = item.querySelector(".gait-modal-email-item-amount");
1204
- const amount = amountSpan ? parseFloat(amountSpan.textContent?.replace(/[^\d.]/g, "") || "0") || 0 : 0;
1205
- data.push({ email, amount });
1206
- } else {
1207
- const participantCount = items.length + 1;
1208
- const amount = calculateEqualSplit(totalCost, participantCount);
1209
- data.push({ email, amount });
1210
- }
1211
- }
1212
- });
1213
- return data;
1214
- }
1215
- function getEmailDataCount(container) {
1216
- const items = container.querySelectorAll("[data-email-item]");
1217
- const inputs = container.querySelectorAll("[data-email-input]");
1218
- return items.length + inputs.length;
1219
- }
1220
- class SplitPaymentManager {
1221
- modalElement;
1222
- totalCost;
1223
- currentSplitMethod = "equally";
1224
- dispatchEventFn;
1225
- isSplitSent = false;
1226
- isSplitLoading = false;
1227
- splitFeedbackElement = null;
1228
- feedbackHandler = null;
1229
- constructor(modalElement, totalCost, dispatchEventFn) {
1230
- this.modalElement = modalElement;
1231
- this.totalCost = totalCost;
1232
- this.dispatchEventFn = dispatchEventFn;
1233
- this.setup();
1234
- this.setupFeedbackListener();
1235
- }
1236
- setup() {
1237
- const splitOptions = this.modalElement.querySelectorAll("[data-split-method]");
1238
- const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
1239
- const addEmailButton = this.modalElement.querySelector("[data-add-email]");
1240
- const sendSplitButton = this.modalElement.querySelector("[data-send-split]");
1241
- if (!emailInputsContainer || !addEmailButton || !sendSplitButton) {
1242
- return;
1243
- }
1244
- splitOptions.forEach((option) => {
1245
- option.addEventListener("click", () => {
1246
- if (this.isSplitSent) {
1247
- return;
1248
- }
1249
- splitOptions.forEach((opt) => opt.classList.remove("selected"));
1250
- option.classList.add("selected");
1251
- const newSplitMethod = option.getAttribute("data-split-method") || "equally";
1252
- const oldSplitMethod = this.currentSplitMethod;
1253
- this.currentSplitMethod = newSplitMethod;
1254
- if (oldSplitMethod !== newSplitMethod) {
1255
- this.updateActiveInputsForSplitMethod(oldSplitMethod, newSplitMethod);
1256
- }
1257
- this.updateEmailItemsForSplitMethod();
1258
- this.updateSplitCalculation();
1259
- });
1260
- });
1261
- addEmailButton.addEventListener("click", () => this.addEmailField());
1262
- emailInputsContainer.addEventListener("input", (e) => {
1263
- const target = e.target;
1264
- if (target.hasAttribute("data-email-input")) {
1265
- this.updateSplitCalculation();
1266
- }
1267
- });
1268
- sendSplitButton.addEventListener("click", () => this.handleSendSplit());
1269
- this.updateSplitCalculation();
1270
- }
1271
- addEmailField() {
1272
- if (this.isSplitSent) {
1273
- return;
1274
- }
1275
- const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
1276
- if (!emailInputsContainer) return;
1277
- const existingEmptyInput = this.findEmptyInput(emailInputsContainer);
1278
- if (existingEmptyInput) {
1279
- if (existingEmptyInput instanceof HTMLInputElement) {
1280
- existingEmptyInput.focus();
1281
- } else {
1282
- const emailInput = existingEmptyInput.querySelector("[data-email-input]");
1283
- if (emailInput) {
1284
- emailInput.focus();
1285
- }
1286
- }
1287
- return;
1288
- }
1289
- if (this.currentSplitMethod === "by-amount") {
1290
- this.addByAmountInput(emailInputsContainer);
1291
- } else {
1292
- this.addEquallyInput(emailInputsContainer);
1293
- }
1294
- }
1295
- findEmptyInput(container) {
1296
- if (this.currentSplitMethod === "by-amount") {
1297
- const rows = container.querySelectorAll(".gait-modal-email-amount-row");
1298
- for (const row of Array.from(rows)) {
1299
- const emailInput = row.querySelector("[data-email-input]");
1300
- if (emailInput && !emailInput.value.trim()) {
1301
- return row;
1302
- }
1303
- }
1304
- } else {
1305
- const inputs = container.querySelectorAll("[data-email-input]");
1306
- for (const input of Array.from(inputs)) {
1307
- if (!input.parentElement?.classList.contains("gait-modal-email-amount-row")) {
1308
- if (!input.value.trim()) {
1309
- return input;
1310
- }
1311
- }
1312
- }
1313
- }
1314
- return null;
1315
- }
1316
- removeEmptyInputs(container) {
1317
- const rows = container.querySelectorAll(".gait-modal-email-amount-row");
1318
- rows.forEach((row) => {
1319
- const emailInput = row.querySelector("[data-email-input]");
1320
- if (emailInput && !emailInput.value.trim()) {
1321
- container.removeChild(row);
1322
- }
1323
- });
1324
- const inputs = container.querySelectorAll("[data-email-input]");
1325
- inputs.forEach((input) => {
1326
- if (!input.parentElement?.classList.contains("gait-modal-email-amount-row")) {
1327
- if (!input.value.trim()) {
1328
- container.removeChild(input);
1329
- }
1330
- }
1331
- });
1332
- }
1333
- /**
1334
- * Calculate the remaining amount available for a new split
1335
- * This is totalCost minus all amounts already assigned to other people
1336
- */
1337
- calculateRemainingAmount(excludeRow) {
1338
- const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
1339
- if (!emailInputsContainer) return this.totalCost;
1340
- let totalAssigned = 0;
1341
- const items = emailInputsContainer.querySelectorAll("[data-email-item]");
1342
- items.forEach((item) => {
1343
- const amountSpan = item.querySelector(".gait-modal-email-item-amount");
1344
- if (amountSpan) {
1345
- const amountText = amountSpan.textContent || "0.00";
1346
- const amount = parseFloat(amountText.replace(/[^\d.]/g, "")) || 0;
1347
- totalAssigned += amount;
1348
- }
1349
- });
1350
- const rows = emailInputsContainer.querySelectorAll(".gait-modal-email-amount-row");
1351
- rows.forEach((row) => {
1352
- if (row === excludeRow) return;
1353
- const amountInput = row.querySelector("[data-email-amount-input]");
1354
- if (amountInput && amountInput.value) {
1355
- const amount = parseFloat(amountInput.value) || 0;
1356
- totalAssigned += amount;
1357
- }
1358
- });
1359
- const remaining = Math.max(0, this.totalCost - totalAssigned);
1360
- return remaining;
1361
- }
1362
- /**
1363
- * Update max attribute on all amount inputs
1364
- */
1365
- updateAmountInputMaxValues(excludeRow) {
1366
- const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
1367
- if (!emailInputsContainer) return;
1368
- const remaining = this.calculateRemainingAmount(excludeRow);
1369
- const maxValue = remaining.toFixed(2);
1370
- const amountInputs = emailInputsContainer.querySelectorAll("[data-email-amount-input]");
1371
- amountInputs.forEach((input) => {
1372
- const row = input.closest(".gait-modal-email-amount-row");
1373
- if (row !== excludeRow) {
1374
- input.max = maxValue;
1375
- const currentValue = parseFloat(input.value) || 0;
1376
- if (currentValue > remaining) {
1377
- input.value = maxValue;
1378
- input.style.borderColor = "#ef4444";
1379
- setTimeout(() => {
1380
- input.style.borderColor = "";
1381
- }, 2e3);
1382
- }
1383
- }
1384
- });
1385
- }
1386
- addByAmountInput(container) {
1387
- const row = document.createElement("div");
1388
- row.className = "gait-modal-email-amount-row";
1389
- const emailInput = document.createElement("input");
1390
- emailInput.type = "email";
1391
- emailInput.className = "gait-modal-email-input";
1392
- emailInput.placeholder = "Email address";
1393
- emailInput.setAttribute("data-email-input", "");
1394
- const amountInput = document.createElement("input");
1395
- amountInput.type = "number";
1396
- amountInput.className = "gait-modal-email-amount-input";
1397
- amountInput.placeholder = "0.00";
1398
- amountInput.step = "0.01";
1399
- amountInput.min = "0";
1400
- amountInput.setAttribute("data-email-amount-input", "");
1401
- const remaining = this.calculateRemainingAmount();
1402
- amountInput.max = remaining.toFixed(2);
1403
- const doneButton = document.createElement("button");
1404
- doneButton.type = "button";
1405
- doneButton.className = "gait-modal-done-button";
1406
- doneButton.textContent = "Done";
1407
- doneButton.setAttribute("data-done-email", "");
1408
- const handleDone = () => {
1409
- const email = emailInput.value.trim();
1410
- let amount = parseFloat(amountInput.value) || 0;
1411
- const remaining2 = this.calculateRemainingAmount(row);
1412
- if (amount > remaining2) {
1413
- amount = remaining2;
1414
- amountInput.value = remaining2.toFixed(2);
1415
- amountInput.style.borderColor = "#ef4444";
1416
- setTimeout(() => {
1417
- amountInput.style.borderColor = "";
1418
- }, 2e3);
1419
- }
1420
- if (email && isValidEmail(email)) {
1421
- const item = this.createEmailItem(email, amount);
1422
- container.replaceChild(item, row);
1423
- this.updateSplitCalculation();
1424
- this.updateAmountInputMaxValues();
1425
- } else if (!email) {
1426
- container.removeChild(row);
1427
- this.updateAmountInputMaxValues();
1428
- }
1429
- };
1430
- amountInput.addEventListener("input", () => {
1431
- const remaining2 = this.calculateRemainingAmount(row);
1432
- const currentValue = parseFloat(amountInput.value) || 0;
1433
- if (currentValue > remaining2) {
1434
- amountInput.value = remaining2.toFixed(2);
1435
- amountInput.style.borderColor = "#ef4444";
1436
- setTimeout(() => {
1437
- amountInput.style.borderColor = "";
1438
- }, 2e3);
1439
- } else {
1440
- amountInput.style.borderColor = "";
1441
- }
1442
- this.updateAmountInputMaxValues(row);
1443
- });
1444
- doneButton.addEventListener("click", handleDone);
1445
- emailInput.addEventListener("blur", () => {
1446
- if (!emailInput.value.trim() && !amountInput.value) {
1447
- setTimeout(() => {
1448
- if (row.parentElement && !emailInput.value.trim()) {
1449
- container.removeChild(row);
1450
- }
1451
- }, 100);
1452
- }
1453
- });
1454
- row.appendChild(emailInput);
1455
- row.appendChild(amountInput);
1456
- row.appendChild(doneButton);
1457
- container.appendChild(row);
1458
- emailInput.focus();
1459
- }
1460
- addEquallyInput(container) {
1461
- const input = document.createElement("input");
1462
- input.type = "email";
1463
- input.className = "gait-modal-email-input";
1464
- input.placeholder = "Email address";
1465
- input.setAttribute("data-email-input", "");
1466
- const handleEmailDone = () => {
1467
- const email = input.value.trim();
1468
- if (email && isValidEmail(email)) {
1469
- this.convertInputToEmailItem(input);
1470
- this.updateSplitCalculation();
1471
- }
1472
- };
1473
- input.addEventListener("blur", () => {
1474
- const email = input.value.trim();
1475
- if (email && isValidEmail(email)) {
1476
- handleEmailDone();
1477
- } else if (!email) {
1478
- setTimeout(() => {
1479
- if (input.parentElement && !input.value.trim()) {
1480
- container.removeChild(input);
1481
- }
1482
- }, 100);
1483
- }
1484
- });
1485
- input.addEventListener("keydown", (e) => {
1486
- if (e.key === "Enter") {
1487
- e.preventDefault();
1488
- handleEmailDone();
1489
- }
1490
- });
1491
- container.appendChild(input);
1492
- input.focus();
1493
- }
1494
- createEmailItem(email, amount = 0, isSent = false) {
1495
- const item = document.createElement("div");
1496
- item.className = `gait-modal-email-item${isSent ? " sent" : ""}`;
1497
- item.setAttribute("data-email-item", email);
1498
- let displayAmount = amount;
1499
- if (this.currentSplitMethod === "equally") {
1500
- const container = this.modalElement.querySelector("[data-email-inputs]");
1501
- const participantCount = container ? getEmailDataCount(container) + 1 : 2;
1502
- displayAmount = calculateEqualSplit(this.totalCost, participantCount);
1503
- }
1504
- const checkmark = isSent ? `<div class="gait-modal-email-item-sent-check">${checkIcon}</div>` : "";
1505
- item.innerHTML = `
1506
- <span class="gait-modal-email-item-email">${email}</span>
940
+ `}function Z(r){const t={};return Array.from(r.attributes).forEach(e=>{e.name.startsWith("data-")&&(t[e.name]=e.value)}),t}function Q(r){const t={};return Array.from(r.attributes).forEach(e=>{t[e.name]=e.value}),t}function X(r){return{get dataId(){return r.getAttribute("data-id")||null},get disabled(){return r.hasAttribute("disabled")&&r.getAttribute("disabled")!=="false"},get size(){return r.getAttribute("size")||"medium"},get items(){const t=r.getAttribute("items");if(!t)return[];try{return JSON.parse(t)}catch(e){return console.warn("[GaitButton] Failed to parse items attribute:",e),[]}},get totalCost(){const t=r.getAttribute("total-cost");if(!t)return 0;const e=parseFloat(t);return(isNaN(e)?0:e)+B.fee},get priceBreakdown(){const t=r.getAttribute("total-cost"),e=parseFloat(t||"0");return[{name:"Subtotal",price:isNaN(e)?0:e},{name:"Split Payment Fee",price:B.fee}]},get customer(){const t=r.getAttribute("customer");if(!t)return null;try{return JSON.parse(t)}catch(e){return console.warn("[GaitButton] Failed to parse customer attribute:",e),null}},get webhook(){const t=r.getAttribute("webhook");if(!t)return null;try{return JSON.parse(t)}catch(e){return console.warn("[GaitButton] Failed to parse webhook attribute:",e),null}},get merchantStoreUrl(){const t=r.getAttribute("merchant-store-url");return t&&t.trim()!==""?t.trim():typeof window<"u"&&window.location?window.location.origin:""}}}function v(r){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(r)}function L(r,t,e){if(e.length===0)return r;if(t==="equally"){const i=e.length+1,a=r/i;return Math.max(0,r-a*e.length)}else if(t==="by-amount"){let i=0;return e.forEach(a=>{const n=a.amount||0;i+=n}),Math.max(0,r-i)}return r}function w(r,t){return r/t}function R(r,t,e){const i=[],a=r.querySelectorAll("[data-email-item]");return a.forEach(n=>{const o=n.getAttribute("data-email-item")||"";if(o)if(t==="by-amount"){const d=n.querySelector(".gait-modal-email-item-amount"),l=d&&parseFloat(d.textContent?.replace(/[^\d.]/g,"")||"0")||0;i.push({email:o,amount:l})}else{const d=a.length+1,l=w(e,d);i.push({email:o,amount:l})}}),i}function z(r){const t=r.querySelectorAll("[data-email-item]"),e=r.querySelectorAll("[data-email-input]");return t.length+e.length}class tt{modalElement;totalCost;currentSplitMethod="equally";dispatchEventFn;isSplitSent=!1;isSplitLoading=!1;splitFeedbackElement=null;feedbackHandler=null;constructor(t,e,i){this.modalElement=t,this.totalCost=e,this.dispatchEventFn=i,this.setup(),this.setupFeedbackListener()}setup(){const t=this.modalElement.querySelectorAll("[data-split-method]"),e=this.modalElement.querySelector("[data-email-inputs]"),i=this.modalElement.querySelector("[data-add-email]"),a=this.modalElement.querySelector("[data-send-split]");!e||!i||!a||(t.forEach(n=>{n.addEventListener("click",()=>{if(this.isSplitSent)return;t.forEach(l=>l.classList.remove("selected")),n.classList.add("selected");const o=n.getAttribute("data-split-method")||"equally",d=this.currentSplitMethod;this.currentSplitMethod=o,d!==o&&this.updateActiveInputsForSplitMethod(d,o),this.updateEmailItemsForSplitMethod(),this.updateSplitCalculation()})}),i.addEventListener("click",()=>this.addEmailField()),e.addEventListener("input",n=>{n.target.hasAttribute("data-email-input")&&this.updateSplitCalculation()}),a.addEventListener("click",()=>this.handleSendSplit()),this.updateSplitCalculation())}addEmailField(){if(this.isSplitSent)return;const t=this.modalElement.querySelector("[data-email-inputs]");if(!t)return;const e=this.findEmptyInput(t);if(e){if(e instanceof HTMLInputElement)e.focus();else{const i=e.querySelector("[data-email-input]");i&&i.focus()}return}this.currentSplitMethod==="by-amount"?this.addByAmountInput(t):this.addEquallyInput(t)}findEmptyInput(t){if(this.currentSplitMethod==="by-amount"){const e=t.querySelectorAll(".gait-modal-email-amount-row");for(const i of Array.from(e)){const a=i.querySelector("[data-email-input]");if(a&&!a.value.trim())return i}}else{const e=t.querySelectorAll("[data-email-input]");for(const i of Array.from(e))if(!i.parentElement?.classList.contains("gait-modal-email-amount-row")&&!i.value.trim())return i}return null}removeEmptyInputs(t){t.querySelectorAll(".gait-modal-email-amount-row").forEach(a=>{const n=a.querySelector("[data-email-input]");n&&!n.value.trim()&&t.removeChild(a)}),t.querySelectorAll("[data-email-input]").forEach(a=>{a.parentElement?.classList.contains("gait-modal-email-amount-row")||a.value.trim()||t.removeChild(a)})}calculateRemainingAmount(t){const e=this.modalElement.querySelector("[data-email-inputs]");if(!e)return this.totalCost;let i=0;return e.querySelectorAll("[data-email-item]").forEach(d=>{const l=d.querySelector(".gait-modal-email-item-amount");if(l){const s=l.textContent||"0.00",m=parseFloat(s.replace(/[^\d.]/g,""))||0;i+=m}}),e.querySelectorAll(".gait-modal-email-amount-row").forEach(d=>{if(d===t)return;const l=d.querySelector("[data-email-amount-input]");if(l&&l.value){const s=parseFloat(l.value)||0;i+=s}}),Math.max(0,this.totalCost-i)}updateAmountInputMaxValues(t){const e=this.modalElement.querySelector("[data-email-inputs]");if(!e)return;const i=this.calculateRemainingAmount(t),a=i.toFixed(2);e.querySelectorAll("[data-email-amount-input]").forEach(o=>{o.closest(".gait-modal-email-amount-row")!==t&&(o.max=a,(parseFloat(o.value)||0)>i&&(o.value=a,o.style.borderColor="#ef4444",setTimeout(()=>{o.style.borderColor=""},2e3)))})}addByAmountInput(t){const e=document.createElement("div");e.className="gait-modal-email-amount-row";const i=document.createElement("input");i.type="email",i.className="gait-modal-email-input",i.placeholder="Email address",i.setAttribute("data-email-input","");const a=document.createElement("input");a.type="number",a.className="gait-modal-email-amount-input",a.placeholder="0.00",a.step="0.01",a.min="0",a.setAttribute("data-email-amount-input","");const n=this.calculateRemainingAmount();a.max=n.toFixed(2);const o=document.createElement("button");o.type="button",o.className="gait-modal-done-button",o.textContent="Done",o.setAttribute("data-done-email","");const d=()=>{const l=i.value.trim();let s=parseFloat(a.value)||0;const m=this.calculateRemainingAmount(e);if(s>m&&(s=m,a.value=m.toFixed(2),a.style.borderColor="#ef4444",setTimeout(()=>{a.style.borderColor=""},2e3)),l&&v(l)){const u=this.createEmailItem(l,s);t.replaceChild(u,e),this.updateSplitCalculation(),this.updateAmountInputMaxValues()}else l||(t.removeChild(e),this.updateAmountInputMaxValues())};a.addEventListener("input",()=>{const l=this.calculateRemainingAmount(e);(parseFloat(a.value)||0)>l?(a.value=l.toFixed(2),a.style.borderColor="#ef4444",setTimeout(()=>{a.style.borderColor=""},2e3)):a.style.borderColor="",this.updateAmountInputMaxValues(e)}),o.addEventListener("click",d),i.addEventListener("blur",()=>{!i.value.trim()&&!a.value&&setTimeout(()=>{e.parentElement&&!i.value.trim()&&t.removeChild(e)},100)}),e.appendChild(i),e.appendChild(a),e.appendChild(o),t.appendChild(e),i.focus()}addEquallyInput(t){const e=document.createElement("input");e.type="email",e.className="gait-modal-email-input",e.placeholder="Email address",e.setAttribute("data-email-input","");const i=()=>{const a=e.value.trim();a&&v(a)&&(this.convertInputToEmailItem(e),this.updateSplitCalculation())};e.addEventListener("blur",()=>{const a=e.value.trim();a&&v(a)?i():a||setTimeout(()=>{e.parentElement&&!e.value.trim()&&t.removeChild(e)},100)}),e.addEventListener("keydown",a=>{a.key==="Enter"&&(a.preventDefault(),i())}),t.appendChild(e),e.focus()}createEmailItem(t,e=0,i=!1){const a=document.createElement("div");a.className=`gait-modal-email-item${i?" sent":""}`,a.setAttribute("data-email-item",t);let n=e;if(this.currentSplitMethod==="equally"){const s=this.modalElement.querySelector("[data-email-inputs]"),m=s?z(s)+1:2;n=w(this.totalCost,m)}const o=i?`<div class="gait-modal-email-item-sent-check">${$}</div>`:"";a.innerHTML=`
941
+ <span class="gait-modal-email-item-email">${t}</span>
1507
942
  <div class="gait-modal-email-item-right">
1508
943
  <div class="gait-modal-email-item-amount-container">
1509
- <span class="gait-modal-email-item-amount">R ${displayAmount.toFixed(2)}</span>
1510
- ${checkmark}
944
+ <span class="gait-modal-email-item-amount">R ${n.toFixed(2)}</span>
945
+ ${o}
1511
946
  </div>
1512
947
  <div class="gait-modal-email-item-actions">
1513
- <button class="gait-modal-email-item-edit" data-edit-email="${email}">Edit</button>
1514
- <button class="gait-modal-email-item-remove" data-remove-email="${email}">Remove</button>
948
+ <button class="gait-modal-email-item-edit" data-edit-email="${t}">Edit</button>
949
+ <button class="gait-modal-email-item-remove" data-remove-email="${t}">Remove</button>
1515
950
  </div>
1516
951
  </div>
1517
- `;
1518
- const editButton = item.querySelector(`[data-edit-email="${email}"]`);
1519
- if (editButton) {
1520
- editButton.addEventListener("click", (e) => {
1521
- e.preventDefault();
1522
- e.stopPropagation();
1523
- this.convertEmailItemToInput(item);
1524
- });
1525
- }
1526
- const removeButton = item.querySelector(`[data-remove-email="${email}"]`);
1527
- if (removeButton) {
1528
- removeButton.addEventListener("click", (e) => {
1529
- e.preventDefault();
1530
- e.stopPropagation();
1531
- this.removeEmailItem(item);
1532
- });
1533
- }
1534
- return item;
1535
- }
1536
- convertEmailItemToInput(item) {
1537
- const container = item.parentElement;
1538
- if (!container) return;
1539
- const email = item.getAttribute("data-email-item") || "";
1540
- if (this.currentSplitMethod === "by-amount") {
1541
- const row = document.createElement("div");
1542
- row.className = "gait-modal-email-amount-row";
1543
- const emailInput = document.createElement("input");
1544
- emailInput.type = "email";
1545
- emailInput.className = "gait-modal-email-input";
1546
- emailInput.placeholder = "Email address";
1547
- emailInput.setAttribute("data-email-input", "");
1548
- emailInput.value = email;
1549
- const amountInput = document.createElement("input");
1550
- amountInput.type = "number";
1551
- amountInput.className = "gait-modal-email-amount-input";
1552
- amountInput.placeholder = "0.00";
1553
- amountInput.step = "0.01";
1554
- amountInput.min = "0";
1555
- amountInput.setAttribute("data-email-amount-input", "");
1556
- const amountSpan = item.querySelector(".gait-modal-email-item-amount");
1557
- let initialAmount = 0;
1558
- if (amountSpan) {
1559
- const amountText = amountSpan.textContent || "0.00";
1560
- initialAmount = parseFloat(amountText.replace(/[^\d.]/g, "")) || 0;
1561
- amountInput.value = initialAmount.toFixed(2);
1562
- }
1563
- const remaining = this.calculateRemainingAmount(row) + initialAmount;
1564
- amountInput.max = remaining.toFixed(2);
1565
- const doneButton = document.createElement("button");
1566
- doneButton.type = "button";
1567
- doneButton.className = "gait-modal-done-button";
1568
- doneButton.textContent = "Done";
1569
- doneButton.setAttribute("data-done-email", "");
1570
- doneButton.addEventListener("click", () => {
1571
- const emailVal = emailInput.value.trim();
1572
- let amountVal = parseFloat(amountInput.value) || 0;
1573
- const remaining2 = this.calculateRemainingAmount(row) + initialAmount;
1574
- if (amountVal > remaining2) {
1575
- amountVal = remaining2;
1576
- amountInput.value = remaining2.toFixed(2);
1577
- amountInput.style.borderColor = "#ef4444";
1578
- setTimeout(() => {
1579
- amountInput.style.borderColor = "";
1580
- }, 2e3);
1581
- }
1582
- if (emailVal && isValidEmail(emailVal)) {
1583
- const newItem = this.createEmailItem(emailVal, amountVal);
1584
- if (row.parentElement) {
1585
- row.parentElement.replaceChild(newItem, row);
1586
- }
1587
- this.updateSplitCalculation();
1588
- this.updateAmountInputMaxValues();
1589
- } else if (!emailVal) {
1590
- if (row.parentElement) {
1591
- row.parentElement.removeChild(row);
1592
- }
1593
- this.updateAmountInputMaxValues();
1594
- }
1595
- });
1596
- amountInput.addEventListener("input", () => {
1597
- const remaining2 = this.calculateRemainingAmount(row) + initialAmount;
1598
- const currentValue = parseFloat(amountInput.value) || 0;
1599
- if (currentValue > remaining2) {
1600
- amountInput.value = remaining2.toFixed(2);
1601
- amountInput.style.borderColor = "#ef4444";
1602
- setTimeout(() => {
1603
- amountInput.style.borderColor = "";
1604
- }, 2e3);
1605
- } else {
1606
- amountInput.style.borderColor = "";
1607
- }
1608
- this.updateAmountInputMaxValues(row);
1609
- });
1610
- row.appendChild(emailInput);
1611
- row.appendChild(amountInput);
1612
- row.appendChild(doneButton);
1613
- if (item.parentElement) {
1614
- item.parentElement.replaceChild(row, item);
1615
- }
1616
- emailInput.focus();
1617
- emailInput.select();
1618
- } else {
1619
- const input = document.createElement("input");
1620
- input.type = "email";
1621
- input.className = "gait-modal-email-input";
1622
- input.placeholder = "Email address";
1623
- input.setAttribute("data-email-input", "");
1624
- input.value = email;
1625
- const handleEmailDone = () => {
1626
- const emailVal = input.value.trim();
1627
- if (emailVal && isValidEmail(emailVal)) {
1628
- this.convertInputToEmailItem(input);
1629
- this.updateSplitCalculation();
1630
- } else if (!emailVal) {
1631
- setTimeout(() => {
1632
- if (input.parentElement && !input.value.trim()) {
1633
- input.parentElement.removeChild(input);
1634
- }
1635
- }, 100);
1636
- }
1637
- };
1638
- input.addEventListener("blur", handleEmailDone);
1639
- input.addEventListener("keydown", (e) => {
1640
- if (e.key === "Enter") {
1641
- e.preventDefault();
1642
- handleEmailDone();
1643
- }
1644
- });
1645
- if (item.parentElement) {
1646
- item.parentElement.replaceChild(input, item);
1647
- }
1648
- input.focus();
1649
- input.select();
1650
- }
1651
- }
1652
- convertInputToEmailItem(input) {
1653
- const email = input.value.trim();
1654
- if (!email || !isValidEmail(email)) {
1655
- return;
1656
- }
1657
- const container = input.parentElement;
1658
- if (!container) return;
1659
- const participantCount = getEmailDataCount(container) + 1;
1660
- const perPersonAmount = this.currentSplitMethod === "equally" ? calculateEqualSplit(this.totalCost, participantCount) : 0;
1661
- const item = this.createEmailItem(email, perPersonAmount);
1662
- container.replaceChild(item, input);
1663
- }
1664
- removeEmailItem(item) {
1665
- const container = item.parentElement;
1666
- if (container) {
1667
- container.removeChild(item);
1668
- this.updateSplitCalculation();
1669
- this.updateAmountInputMaxValues();
1670
- }
1671
- }
1672
- updateActiveInputsForSplitMethod(oldMethod, newMethod) {
1673
- const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
1674
- if (!emailInputsContainer) return;
1675
- this.removeEmptyInputs(emailInputsContainer);
1676
- if (oldMethod === "equally" && newMethod === "by-amount") {
1677
- const emailInputs = Array.from(emailInputsContainer.querySelectorAll("[data-email-input]")).filter((input) => !input.parentElement?.classList.contains("gait-modal-email-amount-row"));
1678
- emailInputs.forEach((input) => {
1679
- const email = input.value.trim();
1680
- const container = input.parentElement;
1681
- if (!container) return;
1682
- const row = document.createElement("div");
1683
- row.className = "gait-modal-email-amount-row";
1684
- const emailInput = document.createElement("input");
1685
- emailInput.type = "email";
1686
- emailInput.className = "gait-modal-email-input";
1687
- emailInput.placeholder = "Email address";
1688
- emailInput.setAttribute("data-email-input", "");
1689
- emailInput.value = email;
1690
- const amountInput = document.createElement("input");
1691
- amountInput.type = "number";
1692
- amountInput.className = "gait-modal-email-amount-input";
1693
- amountInput.placeholder = "0.00";
1694
- amountInput.step = "0.01";
1695
- amountInput.min = "0";
1696
- amountInput.setAttribute("data-email-amount-input", "");
1697
- const remaining = this.calculateRemainingAmount(row);
1698
- amountInput.max = remaining.toFixed(2);
1699
- const doneButton = document.createElement("button");
1700
- doneButton.type = "button";
1701
- doneButton.className = "gait-modal-done-button";
1702
- doneButton.textContent = "Done";
1703
- doneButton.setAttribute("data-done-email", "");
1704
- doneButton.addEventListener("click", () => {
1705
- const emailVal = emailInput.value.trim();
1706
- let amountVal = parseFloat(amountInput.value) || 0;
1707
- const remaining2 = this.calculateRemainingAmount(row);
1708
- if (amountVal > remaining2) {
1709
- amountVal = remaining2;
1710
- amountInput.value = remaining2.toFixed(2);
1711
- amountInput.style.borderColor = "#ef4444";
1712
- setTimeout(() => {
1713
- amountInput.style.borderColor = "";
1714
- }, 2e3);
1715
- }
1716
- if (emailVal && isValidEmail(emailVal)) {
1717
- const item = this.createEmailItem(emailVal, amountVal);
1718
- container.replaceChild(item, row);
1719
- this.updateSplitCalculation();
1720
- this.updateAmountInputMaxValues();
1721
- }
1722
- });
1723
- amountInput.addEventListener("input", () => {
1724
- const remaining2 = this.calculateRemainingAmount(row);
1725
- const currentValue = parseFloat(amountInput.value) || 0;
1726
- if (currentValue > remaining2) {
1727
- amountInput.value = remaining2.toFixed(2);
1728
- amountInput.style.borderColor = "#ef4444";
1729
- setTimeout(() => {
1730
- amountInput.style.borderColor = "";
1731
- }, 2e3);
1732
- } else {
1733
- amountInput.style.borderColor = "";
1734
- }
1735
- this.updateAmountInputMaxValues(row);
1736
- });
1737
- row.appendChild(emailInput);
1738
- row.appendChild(amountInput);
1739
- row.appendChild(doneButton);
1740
- container.replaceChild(row, input);
1741
- emailInput.focus();
1742
- });
1743
- }
1744
- if (oldMethod === "by-amount" && newMethod === "equally") {
1745
- const rows = emailInputsContainer.querySelectorAll(".gait-modal-email-amount-row");
1746
- rows.forEach((row) => {
1747
- const emailInput = row.querySelector("[data-email-input]");
1748
- if (!emailInput) return;
1749
- const email = emailInput.value.trim();
1750
- const container = row.parentElement;
1751
- if (!container) return;
1752
- const input = document.createElement("input");
1753
- input.type = "email";
1754
- input.className = "gait-modal-email-input";
1755
- input.placeholder = "Email address";
1756
- input.setAttribute("data-email-input", "");
1757
- input.value = email;
1758
- const handleEmailDone = () => {
1759
- const emailVal = input.value.trim();
1760
- if (emailVal && isValidEmail(emailVal)) {
1761
- this.convertInputToEmailItem(input);
1762
- this.updateSplitCalculation();
1763
- }
1764
- };
1765
- input.addEventListener("blur", handleEmailDone);
1766
- input.addEventListener("keydown", (e) => {
1767
- if (e.key === "Enter") {
1768
- e.preventDefault();
1769
- handleEmailDone();
1770
- }
1771
- });
1772
- container.replaceChild(input, row);
1773
- input.focus();
1774
- });
1775
- }
1776
- }
1777
- updateEmailItemsForSplitMethod() {
1778
- const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
1779
- if (!emailInputsContainer) return;
1780
- const items = emailInputsContainer.querySelectorAll("[data-email-item]");
1781
- const participantCount = items.length + 1;
1782
- items.forEach((item) => {
1783
- const rightColumn = item.querySelector(".gait-modal-email-item-right");
1784
- if (!rightColumn) return;
1785
- const amountSpan = rightColumn.querySelector(".gait-modal-email-item-amount");
1786
- if (!amountSpan) return;
1787
- if (this.currentSplitMethod === "equally") {
1788
- const perPerson = calculateEqualSplit(this.totalCost, participantCount);
1789
- amountSpan.textContent = `R ${perPerson.toFixed(2)}`;
1790
- } else if (this.currentSplitMethod === "by-amount") {
1791
- const currentAmountText = amountSpan.textContent || "R 0.00";
1792
- const currentAmount = parseFloat(currentAmountText.replace(/[^\d.]/g, "")) || 0;
1793
- amountSpan.textContent = `R ${currentAmount.toFixed(2)}`;
1794
- }
1795
- });
1796
- }
1797
- updateSplitCalculation() {
1798
- const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
1799
- const confirmAmountDisplay = this.modalElement.querySelector("[data-confirm-amount]");
1800
- if (!emailInputsContainer || !confirmAmountDisplay) {
1801
- return;
1802
- }
1803
- const emailData = getEmailData(emailInputsContainer, this.currentSplitMethod, this.totalCost);
1804
- const emailInputs = emailInputsContainer.querySelectorAll("[data-email-input]");
1805
- emailInputs.forEach((input) => {
1806
- const email = input.value.trim();
1807
- if (email && isValidEmail(email)) {
1808
- const participantCount = emailData.length + emailInputs.length + 1;
1809
- const amount = this.currentSplitMethod === "equally" ? calculateEqualSplit(this.totalCost, participantCount) : 0;
1810
- emailData.push({ email, amount });
1811
- }
1812
- });
1813
- const yourAmount = emailData.length > 0 ? calculateSplit(this.totalCost, this.currentSplitMethod, emailData) : this.totalCost;
1814
- confirmAmountDisplay.textContent = `R ${yourAmount.toFixed(2)}`;
1815
- if (this.currentSplitMethod === "by-amount") {
1816
- this.updateAmountInputMaxValues();
1817
- }
1818
- }
1819
- handleSendSplit() {
1820
- const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
1821
- const sendSplitButton = this.modalElement.querySelector("[data-send-split]");
1822
- if (!emailInputsContainer || !sendSplitButton) return;
1823
- const emailData = getEmailData(emailInputsContainer, this.currentSplitMethod, this.totalCost);
1824
- if (emailData.length === 0) {
1825
- this.showFeedback("Please add at least one email address", "error");
1826
- return;
1827
- }
1828
- if (this.isSplitSent || this.isSplitLoading) {
1829
- return;
1830
- }
1831
- this.isSplitLoading = true;
1832
- sendSplitButton.disabled = true;
1833
- sendSplitButton.textContent = "Sending...";
1834
- this.showFeedback("Sending split payment...", "loading");
1835
- const yourAmount = calculateSplit(this.totalCost, this.currentSplitMethod, emailData);
1836
- this.dispatchEventFn(new CustomEvent("gait-split-sent", {
1837
- detail: {
1838
- splitMethod: this.currentSplitMethod,
1839
- emails: emailData.map((d) => d.email),
1840
- emailData,
1841
- totalCost: this.totalCost,
1842
- yourAmount,
1843
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1844
- },
1845
- bubbles: true,
1846
- composed: true
1847
- }));
1848
- }
1849
- setupFeedbackListener() {
1850
- this.feedbackHandler = (event) => {
1851
- const customEvent = event;
1852
- if (customEvent.detail) {
1853
- this.handleSplitFeedback(customEvent.detail);
1854
- }
1855
- };
1856
- document.addEventListener("gait-split-feedback", this.feedbackHandler);
1857
- }
1858
- // Cleanup method (can be called when modal is closed)
1859
- cleanup() {
1860
- if (this.feedbackHandler) {
1861
- document.removeEventListener("gait-split-feedback", this.feedbackHandler);
1862
- this.feedbackHandler = null;
1863
- }
1864
- }
1865
- handleSplitFeedback(feedback) {
1866
- this.isSplitLoading = false;
1867
- const sendSplitButton = this.modalElement.querySelector("[data-send-split]");
1868
- if (feedback.success) {
1869
- this.isSplitSent = true;
1870
- this.markEmailsAsSent();
1871
- this.disableSplitOptions();
1872
- if (sendSplitButton) {
1873
- sendSplitButton.disabled = true;
1874
- sendSplitButton.textContent = "Split sent!";
1875
- }
1876
- this.showFeedback(feedback.message || "Split payment sent successfully", "success");
1877
- } else {
1878
- if (sendSplitButton) {
1879
- sendSplitButton.disabled = false;
1880
- sendSplitButton.textContent = "Send split";
1881
- }
1882
- this.showFeedback(feedback.message || feedback.error || "Failed to send split payment", "error");
1883
- }
1884
- }
1885
- markEmailsAsSent() {
1886
- const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
1887
- if (!emailInputsContainer) return;
1888
- const emailItems = emailInputsContainer.querySelectorAll("[data-email-item]");
1889
- emailItems.forEach((item) => {
1890
- if (!item.classList.contains("sent")) {
1891
- item.classList.add("sent");
1892
- const rightColumn = item.querySelector(".gait-modal-email-item-right");
1893
- if (rightColumn) {
1894
- let amountContainer = rightColumn.querySelector(".gait-modal-email-item-amount-container");
1895
- const amountSpan = rightColumn.querySelector(".gait-modal-email-item-amount");
1896
- if (!amountContainer && amountSpan) {
1897
- amountContainer = document.createElement("div");
1898
- amountContainer.className = "gait-modal-email-item-amount-container";
1899
- amountSpan.parentElement?.replaceChild(amountContainer, amountSpan);
1900
- amountContainer.appendChild(amountSpan);
1901
- }
1902
- if (amountContainer && !amountContainer.querySelector(".gait-modal-email-item-sent-check")) {
1903
- const checkmark = document.createElement("div");
1904
- checkmark.className = "gait-modal-email-item-sent-check";
1905
- checkmark.innerHTML = checkIcon;
1906
- amountContainer.appendChild(checkmark);
1907
- }
1908
- }
1909
- const actions = item.querySelector(".gait-modal-email-item-actions");
1910
- if (actions) {
1911
- actions.style.display = "none";
1912
- }
1913
- }
1914
- });
1915
- }
1916
- showFeedback(message, type) {
1917
- if (this.splitFeedbackElement) {
1918
- this.splitFeedbackElement.remove();
1919
- this.splitFeedbackElement = null;
1920
- }
1921
- const feedback = document.createElement("div");
1922
- feedback.className = `gait-modal-feedback gait-modal-feedback-${type}`;
1923
- feedback.setAttribute("data-split-feedback", "");
1924
- const icon = type === "success" ? checkIcon : type === "error" ? crossIcon : spinnerIcon;
1925
- feedback.innerHTML = `
1926
- <span class="gait-modal-feedback-icon">${icon}</span>
1927
- <span class="gait-modal-feedback-message">${message}</span>
1928
- `;
1929
- const splitSection = this.modalElement.querySelector(".gait-modal-split-section");
1930
- if (splitSection) {
1931
- splitSection.insertBefore(feedback, splitSection.firstChild);
1932
- this.splitFeedbackElement = feedback;
1933
- if (type !== "loading") {
1934
- setTimeout(() => {
1935
- if (this.splitFeedbackElement === feedback) {
1936
- feedback.style.opacity = "0";
1937
- setTimeout(() => {
1938
- if (feedback.parentElement) {
1939
- feedback.remove();
1940
- }
1941
- if (this.splitFeedbackElement === feedback) {
1942
- this.splitFeedbackElement = null;
1943
- }
1944
- }, 300);
1945
- }
1946
- }, 5e3);
1947
- }
1948
- }
1949
- }
1950
- disableSplitOptions() {
1951
- const splitOptions = this.modalElement.querySelectorAll("[data-split-method]");
1952
- splitOptions.forEach((option) => {
1953
- const optionElement = option;
1954
- optionElement.style.pointerEvents = "none";
1955
- optionElement.style.opacity = "0.5";
1956
- optionElement.style.cursor = "not-allowed";
1957
- optionElement.setAttribute("aria-disabled", "true");
1958
- });
1959
- const addEmailButton = this.modalElement.querySelector("[data-add-email]");
1960
- if (addEmailButton) {
1961
- addEmailButton.style.pointerEvents = "none";
1962
- addEmailButton.style.opacity = "0.5";
1963
- addEmailButton.style.cursor = "not-allowed";
1964
- addEmailButton.setAttribute("aria-disabled", "true");
1965
- }
1966
- }
1967
- }
1968
- async function sendSplitPayment(request, timeout = 2e3) {
1969
- console.log("[SplitPaymentAPI] Sending split payment request:", request);
1970
- const apiUrl = "https://api.usegait.cloud/checkout";
1971
- try {
1972
- const response = await fetch(`${apiUrl}/v1/payments`, {
1973
- method: "POST",
1974
- headers: {
1975
- "Content-Type": "application/json"
1976
- },
1977
- body: JSON.stringify(request),
1978
- signal: AbortSignal.timeout(timeout)
1979
- });
1980
- const apiResponse = await response.json().catch(() => ({
1981
- success: false,
1982
- error: "Failed to parse API response"
1983
- }));
1984
- if (apiResponse.success) {
1985
- const successResponse = {
1986
- success: true,
1987
- message: apiResponse.message,
1988
- data: apiResponse.data
1989
- };
1990
- console.log("[SplitPaymentAPI] Split payment sent successfully:", successResponse);
1991
- return successResponse;
1992
- } else {
1993
- const errorResponse = {
1994
- success: false,
1995
- message: apiResponse.message,
1996
- error: apiResponse.error
1997
- };
1998
- console.error("[SplitPaymentAPI] Split payment failed:", errorResponse);
1999
- return errorResponse;
2000
- }
2001
- } catch (error) {
2002
- const errorResponse = {
2003
- success: false,
2004
- message: "Failed to send split payment",
2005
- error: error instanceof Error ? error.message : "Network error or server unavailable"
2006
- };
2007
- console.error("[SplitPaymentAPI] Split payment failed:", errorResponse, error);
2008
- return errorResponse;
2009
- }
2010
- }
2011
- let random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
2012
- let customRandom = (alphabet, defaultSize, getRandom) => {
2013
- let mask = (2 << Math.log2(alphabet.length - 1)) - 1;
2014
- let step = -~(1.6 * mask * defaultSize / alphabet.length);
2015
- return (size = defaultSize) => {
2016
- let id = "";
2017
- while (true) {
2018
- let bytes = getRandom(step);
2019
- let j = step | 0;
2020
- while (j--) {
2021
- id += alphabet[bytes[j] & mask] || "";
2022
- if (id.length >= size) return id;
2023
- }
2024
- }
2025
- };
2026
- };
2027
- let customAlphabet = (alphabet, size = 21) => customRandom(alphabet, size | 0, random);
2028
- const config = {
2029
- apiGateway: {
2030
- MERCHANT_SERVICE_API: "https://api.usegait.cloud/merchant",
2031
- CHECKOUT_SERVICE_API: "https://api.usegait.cloud/checkout"
2032
- },
2033
- paylink: {
2034
- DOMAIN: "https://checkout.usegait.cloud"
2035
- }
2036
- };
2037
- const domain$1 = config.apiGateway.MERCHANT_SERVICE_API;
2038
- class MerchantService {
2039
- static async getMerchant(checkoutId) {
2040
- try {
2041
- const resp = await fetch(`${domain$1}/v1/merchant/identity/${checkoutId}`, {
2042
- "method": "GET",
2043
- headers: {
2044
- "Content-Type": "application/json"
2045
- }
2046
- });
2047
- return await resp.json();
2048
- } catch (error) {
2049
- return error;
2050
- }
2051
- }
2052
- }
2053
- const domain = config.apiGateway.CHECKOUT_SERVICE_API;
2054
- class PaymentsService {
2055
- static async getPayment(paymentId) {
2056
- try {
2057
- const resp = await fetch(`${domain}/v1/payments/${paymentId}`, {
2058
- "method": "GET",
2059
- headers: {
2060
- "Content-Type": "application/json"
2061
- }
2062
- });
2063
- return await resp.json();
2064
- } catch (error) {
2065
- return error;
2066
- }
2067
- }
2068
- static async updatePaymentStatus(merchantId, splitId, status) {
2069
- try {
2070
- const resp = await fetch(`${domain}/v1/payments/merchants/${merchantId}/${splitId}/status`, {
2071
- "method": "PUT",
2072
- headers: {
2073
- "Content-Type": "application/json"
2074
- },
2075
- body: JSON.stringify({
2076
- status
2077
- })
2078
- });
2079
- return await resp.json();
2080
- } catch (error) {
2081
- return error;
2082
- }
2083
- }
2084
- static async createWebhook(merchantId, splitId, webhook) {
2085
- try {
2086
- const resp = await fetch(`${domain}/v1/webhooks/${merchantId}/${splitId}`, {
2087
- "method": "POST",
2088
- headers: {
2089
- "Content-Type": "application/json"
2090
- },
2091
- body: JSON.stringify(webhook)
2092
- });
2093
- return await resp.json();
2094
- } catch (error) {
2095
- return error;
2096
- }
2097
- }
2098
- static async updatePayment(merchantId, paymentId, splitId) {
2099
- try {
2100
- const resp = await fetch(`${domain}/v1/payments/merchants/${merchantId}/${splitId}`, {
2101
- "method": "PUT",
2102
- headers: {
2103
- "Content-Type": "application/json"
2104
- },
2105
- body: JSON.stringify({
2106
- paymentId
2107
- })
2108
- });
2109
- return await resp.json();
2110
- } catch (error) {
2111
- return error;
2112
- }
2113
- }
2114
- }
2115
- function devLog(...args) {
2116
- {
2117
- console.log(...args);
2118
- }
2119
- }
2120
- const _GAIT_BUTTON_VERSION_ENV = typeof process !== "undefined" && process.env ? "0.1.0" : void 0;
2121
- const GAIT_BUTTON_VERSION = _GAIT_BUTTON_VERSION_ENV || "0.1.4";
2122
- class GaitButton extends HTMLElement {
2123
- buttonElement = null;
2124
- hasRendered = false;
2125
- mutationObserver = null;
2126
- modalElement = null;
2127
- modalId;
2128
- splitPaymentManager = null;
2129
- attributeGetters;
2130
- storedSplitPaymentRequest = null;
2131
- merchantId = null;
2132
- paymentProvider = "stripe";
2133
- // Expose version as static and instance properties for easy access
2134
- static version = GAIT_BUTTON_VERSION;
2135
- version = GAIT_BUTTON_VERSION;
2136
- // Observed attributes for reactivity (label, theme, brand-color removed - fixed to Gait / #4147BF)
2137
- static get observedAttributes() {
2138
- return ["data-id", "disabled", "size", "style", "items", "price-breakdown", "total-cost", "webhook", "merchant-store-url"];
2139
- }
2140
- constructor() {
2141
- super();
2142
- this.attachShadow({ mode: "open" });
2143
- this.modalId = `gait-modal-${Math.random().toString(36).substr(2, 9)}`;
2144
- this.attributeGetters = createAttributeGetters(this);
2145
- }
2146
- // Lifecycle: Called when element is connected to DOM
2147
- connectedCallback() {
2148
- if (!this.hasRendered && this.shadowRoot) {
2149
- this.render();
2150
- }
2151
- this.setupEventListeners();
2152
- this.setupMutationObserver();
2153
- this.setupApiListeners();
2154
- setTimeout(() => this.processData(), 0);
2155
- }
2156
- // Lifecycle: Called when element is disconnected from DOM
2157
- disconnectedCallback() {
2158
- if (this.buttonElement) {
2159
- this.buttonElement.removeEventListener("click", this.handleClick);
2160
- }
2161
- if (this.mutationObserver) {
2162
- this.mutationObserver.disconnect();
2163
- this.mutationObserver = null;
2164
- }
2165
- this.closeModal();
2166
- if (this.splitPaymentManager) {
2167
- this.splitPaymentManager = null;
2168
- }
2169
- this.removeEventListener("gait-split-sent", this.handleSplitSent);
2170
- }
2171
- // Lifecycle: Called when observed attributes change
2172
- attributeChangedCallback(name, oldValue, newValue) {
2173
- if (oldValue === newValue) {
2174
- return;
2175
- }
2176
- if (!this.isConnected || !this.shadowRoot) {
2177
- return;
2178
- }
2179
- if (name === "style" && this.buttonElement && this.hasRendered) {
2180
- const sanitizedStyle = (newValue || "").replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
2181
- this.buttonElement.setAttribute("style", sanitizedStyle);
2182
- return;
2183
- }
2184
- if (this.hasRendered) {
2185
- this.render();
2186
- this.setupEventListeners();
2187
- }
2188
- if (name === "data-id" || name.startsWith("data-")) {
2189
- if (name === "data-id") {
2190
- this.merchantId = null;
2191
- }
2192
- setTimeout(() => this.processData(), 0);
2193
- }
2194
- }
2195
- // Getters for attributes with defaults (using attribute getters)
2196
- get dataId() {
2197
- return this.attributeGetters.dataId;
2198
- }
2199
- get disabled() {
2200
- return this.attributeGetters.disabled;
2201
- }
2202
- get size() {
2203
- return this.attributeGetters.size;
2204
- }
2205
- get items() {
2206
- return this.attributeGetters.items;
2207
- }
2208
- get priceBreakdown() {
2209
- return this.attributeGetters.priceBreakdown;
2210
- }
2211
- get totalCost() {
2212
- return this.attributeGetters.totalCost;
2213
- }
2214
- get customer() {
2215
- return this.attributeGetters.customer;
2216
- }
2217
- get webhook() {
2218
- return this.attributeGetters.webhook;
2219
- }
2220
- get merchantStoreUrl() {
2221
- return this.attributeGetters.merchantStoreUrl;
2222
- }
2223
- // Get all data-* attributes
2224
- getDataAttributes() {
2225
- return getDataAttributes(this);
2226
- }
2227
- // Fetch merchantId from API using checkoutId (data-id)
2228
- async fetchMerchantId() {
2229
- const checkoutId = this.dataId;
2230
- if (!checkoutId) {
2231
- console.warn("[GaitButton] No checkoutId (data-id) provided, cannot fetch merchantId");
2232
- this.showMerchantIdError();
2233
- return false;
2234
- }
2235
- try {
2236
- const response = await MerchantService.getMerchant(checkoutId);
2237
- if (response.success && response.data?.merchantId) {
2238
- this.merchantId = response.data.merchantId;
2239
- this.paymentProvider = response.data.paymentProvider;
2240
- return true;
2241
- } else {
2242
- console.error("[GaitButton] Failed to fetch merchantId:", response);
2243
- this.showMerchantIdError();
2244
- return false;
2245
- }
2246
- } catch (error) {
2247
- console.error("[GaitButton] Error fetching merchantId:", error);
2248
- this.showMerchantIdError();
2249
- return false;
2250
- }
2251
- }
2252
- // Show error message when merchantId cannot be identified
2253
- showMerchantIdError() {
2254
- const errorMessage = "Unable to identify your account. Please go to your dashboard and copy your checkout identity.";
2255
- alert(errorMessage);
2256
- this.dispatchEvent(new CustomEvent("gait-merchant-id-error", {
2257
- detail: {
2258
- message: errorMessage,
2259
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2260
- },
2261
- bubbles: true,
2262
- composed: true
2263
- }));
2264
- }
2265
- // Process data from attributes and do something (like console.log)
2266
- async processData() {
2267
- const dataId = this.dataId;
2268
- const allDataAttrs = this.getDataAttributes();
2269
- if (dataId && !this.merchantId) {
2270
- await this.fetchMerchantId();
2271
- }
2272
- if (dataId || Object.keys(allDataAttrs).length > 0) {
2273
- const data = {
2274
- id: dataId,
2275
- ...allDataAttrs,
2276
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2277
- };
2278
- devLog("[GaitButton] Processing data v0.0.16:", data);
2279
- this.dispatchEvent(new CustomEvent("gait-data-processed", {
2280
- detail: data,
2281
- bubbles: true,
2282
- composed: true
2283
- }));
2284
- }
2285
- }
2286
- // Handle click events
2287
- handleClick = async (event) => {
2288
- if (this.disabled) {
2289
- event.preventDefault();
2290
- event.stopPropagation();
2291
- return;
2292
- }
2293
- const clickData = {
2294
- label: "Gait",
2295
- dataId: this.dataId,
2296
- dataAttributes: this.getDataAttributes(),
2297
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2298
- };
2299
- this.dispatchEvent(new CustomEvent("gait-click", {
2300
- detail: clickData,
2301
- bubbles: true,
2302
- composed: true
2303
- }));
2304
- this.dispatchEvent(new CustomEvent("click", {
2305
- detail: clickData,
2306
- bubbles: true,
2307
- composed: true
2308
- }));
2309
- if (!this.merchantId) {
2310
- const fetched = await this.fetchMerchantId();
2311
- if (!fetched || !this.merchantId) {
2312
- return;
2313
- }
2314
- }
2315
- this.openModal();
2316
- };
2317
- // Setup event listeners
2318
- setupEventListeners() {
2319
- if (!this.shadowRoot) {
2320
- return;
2321
- }
2322
- if (this.buttonElement) {
2323
- this.buttonElement.removeEventListener("click", this.handleClick);
2324
- }
2325
- this.buttonElement = this.shadowRoot.querySelector("button");
2326
- if (this.buttonElement) {
2327
- this.buttonElement.addEventListener("click", this.handleClick);
2328
- }
2329
- }
2330
- // Setup MutationObserver to watch for data-* attribute changes
2331
- setupMutationObserver() {
2332
- if (this.mutationObserver) {
2333
- this.mutationObserver.disconnect();
2334
- }
2335
- this.mutationObserver = new MutationObserver((mutations) => {
2336
- let shouldProcessData = false;
2337
- for (const mutation of mutations) {
2338
- if (mutation.type === "attributes") {
2339
- const attrName = mutation.attributeName;
2340
- if (attrName && attrName.startsWith("data-") && !GaitButton.observedAttributes.includes(attrName)) {
2341
- shouldProcessData = true;
2342
- break;
2343
- }
2344
- }
2345
- }
2346
- if (shouldProcessData && this.isConnected) {
2347
- this.processData();
2348
- }
2349
- });
2350
- this.mutationObserver.observe(this, {
2351
- attributes: true,
2352
- attributeFilter: void 0
2353
- // Observe all attributes
2354
- });
2355
- }
2356
- // Setup API event listeners
2357
- setupApiListeners() {
2358
- this.addEventListener("gait-split-sent", this.handleSplitSent);
2359
- }
2360
- // Handle confirm payment (Pay remaining button)
2361
- handleConfirmPayment = async () => {
2362
- console.log("[GaitButton] Pay remaining button clicked");
2363
- if (!this.storedSplitPaymentRequest) {
2364
- console.error("[GaitButton] Split payment must be sent first before paying remaining amount");
2365
- return;
2366
- }
2367
- if (!this.merchantId) {
2368
- const fetched = await this.fetchMerchantId();
2369
- if (!fetched || !this.merchantId) {
2370
- return;
2371
- }
2372
- }
2373
- const initiatorObject = {
2374
- merchantId: this.merchantId,
2375
- splitId: this.storedSplitPaymentRequest.splitId,
2376
- paymentId: this.storedSplitPaymentRequest.paymentSplits.find((payment) => payment.initiator)?.paymentId
2377
- };
2378
- const paylinkDomain = config.paylink.DOMAIN;
2379
- const paylink = `${paylinkDomain}/payment/${initiatorObject.paymentId}`;
2380
- window.open(paylink, "_blank");
2381
- this.dispatchEvent(new CustomEvent("gait-confirm", {
2382
- detail: initiatorObject,
2383
- bubbles: true,
2384
- composed: true
2385
- }));
2386
- this.closeModal();
2387
- };
2388
- // Handle split payment sent event and make API call
2389
- handleSplitSent = async (event) => {
2390
- const customEvent = event;
2391
- const splitData = customEvent.detail;
2392
- if (!this.merchantId) {
2393
- const fetched = await this.fetchMerchantId();
2394
- if (!fetched || !this.merchantId) {
2395
- return;
2396
- }
2397
- }
2398
- const merchantId = this.merchantId;
2399
- const customer = this.customer;
2400
- if (!customer || !customer.email) {
2401
- console.error("[GaitButton] Customer email is required for split payment");
2402
- return;
2403
- }
2404
- const webhook = this.webhook;
2405
- if (!webhook || !webhook.url || webhook.body == null) {
2406
- console.error("[GaitButton] Webhook with url and body is required");
2407
- return;
2408
- }
2409
- const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2410
- const generateId = customAlphabet(alphabet, 6);
2411
- const splitId = generateId();
2412
- const paymentSplits = [];
2413
- paymentSplits.push({
2414
- paymentId: generateId(),
2415
- email: customer.email,
2416
- amount: splitData.yourAmount,
2417
- isPaid: false,
2418
- datePaid: null,
2419
- initiator: true
2420
- });
2421
- splitData.emailData.forEach((emailData) => {
2422
- paymentSplits.push({
2423
- paymentId: generateId(),
2424
- email: emailData.email,
2425
- amount: emailData.amount || 0,
2426
- isPaid: false,
2427
- datePaid: null,
2428
- initiator: false
2429
- });
2430
- });
2431
- const id = {
2432
- merchantId,
2433
- splitId,
2434
- paymentSplits,
2435
- splitMethod: splitData.splitMethod,
2436
- timestamp: splitData.timestamp,
2437
- totalCost: splitData.totalCost,
2438
- items: this.items,
2439
- priceBreakdown: this.priceBreakdown,
2440
- brandColor: GAIT_BRAND_COLOR,
2441
- merchantStoreUrl: this.merchantStoreUrl,
2442
- subTotal: this.priceBreakdown.find((item) => item.name === "Subtotal")?.price || 0,
2443
- paymentProvider: this.paymentProvider
2444
- };
2445
- console.log("[GaitButton] Split payment request body:", JSON.stringify(id, null, 2));
2446
- try {
2447
- const response = await sendSplitPayment(id, 2e3);
2448
- if (response.success) {
2449
- const finalSplitId = response.data?.splitId || splitId;
2450
- this.storedSplitPaymentRequest = {
2451
- ...id,
2452
- splitId: finalSplitId
2453
- };
2454
- try {
2455
- const webhookResponse = await PaymentsService.createWebhook(
2456
- merchantId,
2457
- finalSplitId,
2458
- {
2459
- url: webhook.url,
2460
- body: webhook.body
2461
- }
2462
- );
2463
- console.log("[GaitButton] Webhook created:", webhookResponse);
2464
- } catch (webhookError) {
2465
- console.error("[GaitButton] Failed to create webhook:", webhookError);
2466
- }
2467
- }
2468
- if (this.modalElement) {
2469
- const confirmButton = this.modalElement.querySelector("[data-gait-modal-confirm]");
2470
- if (confirmButton) {
2471
- confirmButton.disabled = false;
2472
- confirmButton.style.opacity = "1";
2473
- confirmButton.style.cursor = "pointer";
2474
- }
2475
- }
2476
- this.dispatchEvent(new CustomEvent("gait-split-feedback", {
2477
- detail: {
2478
- success: response.success,
2479
- message: response.message,
2480
- data: response.success ? this.storedSplitPaymentRequest : response.data,
2481
- error: response.error,
2482
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2483
- },
2484
- bubbles: true,
2485
- composed: true
2486
- }));
2487
- console.log("[GaitButton] Split payment API response:", response);
2488
- } catch (error) {
2489
- console.error("[GaitButton] Split payment API error:", error);
2490
- this.dispatchEvent(new CustomEvent("gait-split-feedback", {
2491
- detail: {
2492
- success: false,
2493
- message: "Failed to send split payment",
2494
- error: error instanceof Error ? error.message : "Unknown error",
2495
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2496
- },
2497
- bubbles: true,
2498
- composed: true
2499
- }));
2500
- }
2501
- };
2502
- // Open modal
2503
- openModal() {
2504
- injectModalStyles();
2505
- this.closeModal();
2506
- this.modalElement = document.createElement("div");
2507
- this.modalElement.className = "gait-modal-backdrop";
2508
- this.modalElement.id = this.modalId;
2509
- if (this.modalElement) {
2510
- this.modalElement.style.setProperty("--gait-modal-brand-color", GAIT_BRAND_COLOR);
2511
- this.modalElement.style.setProperty("--gait-modal-button-bg", GAIT_BRAND_COLOR);
2512
- this.modalElement.style.setProperty("--gait-modal-button-bg-hover", darkenColor(GAIT_BRAND_COLOR, 0.15));
2513
- this.modalElement.style.setProperty("--gait-modal-button-color", isColorDark(GAIT_BRAND_COLOR) ? "#ffffff" : "#000000");
2514
- }
2515
- this.modalElement.innerHTML = generateModalTemplate(
2516
- this.items,
2517
- this.priceBreakdown,
2518
- this.totalCost
2519
- );
2520
- const closeButton = this.modalElement.querySelector("[data-gait-modal-close]");
2521
- if (closeButton) {
2522
- closeButton.addEventListener("click", this.closeModal);
2523
- }
2524
- const confirmButton = this.modalElement.querySelector("[data-gait-modal-confirm]");
2525
- if (confirmButton) {
2526
- confirmButton.disabled = true;
2527
- confirmButton.style.opacity = "0.5";
2528
- confirmButton.style.cursor = "not-allowed";
2529
- confirmButton.addEventListener("click", this.handleConfirmPayment);
2530
- }
2531
- this.splitPaymentManager = new SplitPaymentManager(
2532
- this.modalElement,
2533
- this.totalCost,
2534
- (event) => this.dispatchEvent(event)
2535
- );
2536
- this.modalElement._splitPaymentManager = this.splitPaymentManager;
2537
- this.modalElement.addEventListener("click", (e) => {
2538
- if (e.target === this.modalElement) {
2539
- this.closeModal();
2540
- }
2541
- });
2542
- const handleEscape = (e) => {
2543
- if (e.key === "Escape" && this.modalElement) {
2544
- this.closeModal();
2545
- document.removeEventListener("keydown", handleEscape);
2546
- }
2547
- };
2548
- document.addEventListener("keydown", handleEscape);
2549
- document.body.appendChild(this.modalElement);
2550
- requestAnimationFrame(() => {
2551
- if (this.modalElement) {
2552
- this.modalElement.classList.add("gait-modal-open");
2553
- }
2554
- });
2555
- }
2556
- // Close modal
2557
- closeModal = () => {
2558
- if (this.modalElement) {
2559
- if (this.splitPaymentManager) {
2560
- this.splitPaymentManager.cleanup();
2561
- }
2562
- this.modalElement.classList.remove("gait-modal-open");
2563
- setTimeout(() => {
2564
- if (this.modalElement && this.modalElement.parentNode) {
2565
- this.modalElement.parentNode.removeChild(this.modalElement);
2566
- }
2567
- this.modalElement = null;
2568
- this.splitPaymentManager = null;
2569
- }, 250);
2570
- }
2571
- };
2572
- // Render the component
2573
- render() {
2574
- const shadow = this.shadowRoot;
2575
- if (!shadow) {
2576
- return;
2577
- }
2578
- const wasFirstRender = !this.hasRendered;
2579
- const styles = generateButtonStyles(
2580
- this.size,
2581
- darkenColor,
2582
- isColorDark
2583
- );
2584
- const hostStyle = this.getAttribute("style") || "";
2585
- const sanitizedStyle = hostStyle.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
2586
- shadow.innerHTML = `
2587
- ${styles}
2588
- ${generateButtonTemplate(this.disabled, sanitizedStyle)}
2589
- `;
2590
- this.hasRendered = true;
2591
- this.setupEventListeners();
2592
- if (wasFirstRender) {
2593
- const allAttributes = getAllAttributes(this);
2594
- console.log("[GaitButton] rendered", allAttributes);
2595
- this.dispatchEvent(new CustomEvent("gait-loaded", {
2596
- detail: {
2597
- attributes: allAttributes,
2598
- dataId: this.dataId,
2599
- dataAttributes: this.getDataAttributes(),
2600
- label: "Gait",
2601
- size: this.size,
2602
- disabled: this.disabled,
2603
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2604
- },
2605
- bubbles: true,
2606
- composed: true
2607
- }));
2608
- }
2609
- }
2610
- }
2611
- if (!customElements.get("gait-button")) {
2612
- customElements.define("gait-button", GaitButton);
2613
- }
2614
- if (!customElements.get("gait-button")) {
2615
- customElements.define("gait-button", GaitButton);
2616
- }
2617
- if (typeof window !== "undefined") {
2618
- window.GaitButton = GaitButton;
2619
- window.GaitButtonVersion = GAIT_BUTTON_VERSION;
2620
- console.log(`[GaitButton] Version: ${GAIT_BUTTON_VERSION}`);
2621
- }
2622
- exports.GAIT_BUTTON_VERSION = GAIT_BUTTON_VERSION;
2623
- exports.GaitButton = GaitButton;
2624
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2625
- })(this.GaitButton = this.GaitButton || {});
952
+ `;const d=a.querySelector(`[data-edit-email="${t}"]`);d&&d.addEventListener("click",s=>{s.preventDefault(),s.stopPropagation(),this.convertEmailItemToInput(a)});const l=a.querySelector(`[data-remove-email="${t}"]`);return l&&l.addEventListener("click",s=>{s.preventDefault(),s.stopPropagation(),this.removeEmailItem(a)}),a}convertEmailItemToInput(t){if(!t.parentElement)return;const i=t.getAttribute("data-email-item")||"";if(this.currentSplitMethod==="by-amount"){const a=document.createElement("div");a.className="gait-modal-email-amount-row";const n=document.createElement("input");n.type="email",n.className="gait-modal-email-input",n.placeholder="Email address",n.setAttribute("data-email-input",""),n.value=i;const o=document.createElement("input");o.type="number",o.className="gait-modal-email-amount-input",o.placeholder="0.00",o.step="0.01",o.min="0",o.setAttribute("data-email-amount-input","");const d=t.querySelector(".gait-modal-email-item-amount");let l=0;if(d){const u=d.textContent||"0.00";l=parseFloat(u.replace(/[^\d.]/g,""))||0,o.value=l.toFixed(2)}const s=this.calculateRemainingAmount(a)+l;o.max=s.toFixed(2);const m=document.createElement("button");m.type="button",m.className="gait-modal-done-button",m.textContent="Done",m.setAttribute("data-done-email",""),m.addEventListener("click",()=>{const u=n.value.trim();let c=parseFloat(o.value)||0;const p=this.calculateRemainingAmount(a)+l;if(c>p&&(c=p,o.value=p.toFixed(2),o.style.borderColor="#ef4444",setTimeout(()=>{o.style.borderColor=""},2e3)),u&&v(u)){const b=this.createEmailItem(u,c);a.parentElement&&a.parentElement.replaceChild(b,a),this.updateSplitCalculation(),this.updateAmountInputMaxValues()}else u||(a.parentElement&&a.parentElement.removeChild(a),this.updateAmountInputMaxValues())}),o.addEventListener("input",()=>{const u=this.calculateRemainingAmount(a)+l;(parseFloat(o.value)||0)>u?(o.value=u.toFixed(2),o.style.borderColor="#ef4444",setTimeout(()=>{o.style.borderColor=""},2e3)):o.style.borderColor="",this.updateAmountInputMaxValues(a)}),a.appendChild(n),a.appendChild(o),a.appendChild(m),t.parentElement&&t.parentElement.replaceChild(a,t),n.focus(),n.select()}else{const a=document.createElement("input");a.type="email",a.className="gait-modal-email-input",a.placeholder="Email address",a.setAttribute("data-email-input",""),a.value=i;const n=()=>{const o=a.value.trim();o&&v(o)?(this.convertInputToEmailItem(a),this.updateSplitCalculation()):o||setTimeout(()=>{a.parentElement&&!a.value.trim()&&a.parentElement.removeChild(a)},100)};a.addEventListener("blur",n),a.addEventListener("keydown",o=>{o.key==="Enter"&&(o.preventDefault(),n())}),t.parentElement&&t.parentElement.replaceChild(a,t),a.focus(),a.select()}}convertInputToEmailItem(t){const e=t.value.trim();if(!e||!v(e))return;const i=t.parentElement;if(!i)return;const a=z(i)+1,n=this.currentSplitMethod==="equally"?w(this.totalCost,a):0,o=this.createEmailItem(e,n);i.replaceChild(o,t)}removeEmailItem(t){const e=t.parentElement;e&&(e.removeChild(t),this.updateSplitCalculation(),this.updateAmountInputMaxValues())}updateActiveInputsForSplitMethod(t,e){const i=this.modalElement.querySelector("[data-email-inputs]");i&&(this.removeEmptyInputs(i),t==="equally"&&e==="by-amount"&&Array.from(i.querySelectorAll("[data-email-input]")).filter(n=>!n.parentElement?.classList.contains("gait-modal-email-amount-row")).forEach(n=>{const o=n.value.trim(),d=n.parentElement;if(!d)return;const l=document.createElement("div");l.className="gait-modal-email-amount-row";const s=document.createElement("input");s.type="email",s.className="gait-modal-email-input",s.placeholder="Email address",s.setAttribute("data-email-input",""),s.value=o;const m=document.createElement("input");m.type="number",m.className="gait-modal-email-amount-input",m.placeholder="0.00",m.step="0.01",m.min="0",m.setAttribute("data-email-amount-input","");const u=this.calculateRemainingAmount(l);m.max=u.toFixed(2);const c=document.createElement("button");c.type="button",c.className="gait-modal-done-button",c.textContent="Done",c.setAttribute("data-done-email",""),c.addEventListener("click",()=>{const p=s.value.trim();let b=parseFloat(m.value)||0;const T=this.calculateRemainingAmount(l);if(b>T&&(b=T,m.value=T.toFixed(2),m.style.borderColor="#ef4444",setTimeout(()=>{m.style.borderColor=""},2e3)),p&&v(p)){const st=this.createEmailItem(p,b);d.replaceChild(st,l),this.updateSplitCalculation(),this.updateAmountInputMaxValues()}}),m.addEventListener("input",()=>{const p=this.calculateRemainingAmount(l);(parseFloat(m.value)||0)>p?(m.value=p.toFixed(2),m.style.borderColor="#ef4444",setTimeout(()=>{m.style.borderColor=""},2e3)):m.style.borderColor="",this.updateAmountInputMaxValues(l)}),l.appendChild(s),l.appendChild(m),l.appendChild(c),d.replaceChild(l,n),s.focus()}),t==="by-amount"&&e==="equally"&&i.querySelectorAll(".gait-modal-email-amount-row").forEach(n=>{const o=n.querySelector("[data-email-input]");if(!o)return;const d=o.value.trim(),l=n.parentElement;if(!l)return;const s=document.createElement("input");s.type="email",s.className="gait-modal-email-input",s.placeholder="Email address",s.setAttribute("data-email-input",""),s.value=d;const m=()=>{const u=s.value.trim();u&&v(u)&&(this.convertInputToEmailItem(s),this.updateSplitCalculation())};s.addEventListener("blur",m),s.addEventListener("keydown",u=>{u.key==="Enter"&&(u.preventDefault(),m())}),l.replaceChild(s,n),s.focus()}))}updateEmailItemsForSplitMethod(){const t=this.modalElement.querySelector("[data-email-inputs]");if(!t)return;const e=t.querySelectorAll("[data-email-item]"),i=e.length+1;e.forEach(a=>{const n=a.querySelector(".gait-modal-email-item-right");if(!n)return;const o=n.querySelector(".gait-modal-email-item-amount");if(o){if(this.currentSplitMethod==="equally"){const d=w(this.totalCost,i);o.textContent=`R ${d.toFixed(2)}`}else if(this.currentSplitMethod==="by-amount"){const d=o.textContent||"R 0.00",l=parseFloat(d.replace(/[^\d.]/g,""))||0;o.textContent=`R ${l.toFixed(2)}`}}})}updateSplitCalculation(){const t=this.modalElement.querySelector("[data-email-inputs]"),e=this.modalElement.querySelector("[data-confirm-amount]");if(!t||!e)return;const i=R(t,this.currentSplitMethod,this.totalCost),a=t.querySelectorAll("[data-email-input]");a.forEach(o=>{const d=o.value.trim();if(d&&v(d)){const l=i.length+a.length+1,s=this.currentSplitMethod==="equally"?w(this.totalCost,l):0;i.push({email:d,amount:s})}});const n=i.length>0?L(this.totalCost,this.currentSplitMethod,i):this.totalCost;e.textContent=`R ${n.toFixed(2)}`,this.currentSplitMethod==="by-amount"&&this.updateAmountInputMaxValues()}handleSendSplit(){const t=this.modalElement.querySelector("[data-email-inputs]"),e=this.modalElement.querySelector("[data-send-split]");if(!t||!e)return;const i=R(t,this.currentSplitMethod,this.totalCost);if(i.length===0){this.showFeedback("Please add at least one email address","error");return}if(this.isSplitSent||this.isSplitLoading)return;this.isSplitLoading=!0,e.disabled=!0,e.textContent="Sending...",this.showFeedback("Sending split payment...","loading");const a=L(this.totalCost,this.currentSplitMethod,i);this.dispatchEventFn(new CustomEvent("gait-split-sent",{detail:{splitMethod:this.currentSplitMethod,emails:i.map(n=>n.email),emailData:i,totalCost:this.totalCost,yourAmount:a,timestamp:new Date().toISOString()},bubbles:!0,composed:!0}))}setupFeedbackListener(){this.feedbackHandler=t=>{const e=t;e.detail&&this.handleSplitFeedback(e.detail)},document.addEventListener("gait-split-feedback",this.feedbackHandler)}cleanup(){this.feedbackHandler&&(document.removeEventListener("gait-split-feedback",this.feedbackHandler),this.feedbackHandler=null)}handleSplitFeedback(t){this.isSplitLoading=!1;const e=this.modalElement.querySelector("[data-send-split]");t.success?(this.isSplitSent=!0,this.markEmailsAsSent(),this.disableSplitOptions(),e&&(e.disabled=!0,e.textContent="Split sent!"),this.showFeedback(t.message||"Split payment sent successfully","success")):(e&&(e.disabled=!1,e.textContent="Send split"),this.showFeedback(t.message||t.error||"Failed to send split payment","error"))}markEmailsAsSent(){const t=this.modalElement.querySelector("[data-email-inputs]");if(!t)return;t.querySelectorAll("[data-email-item]").forEach(i=>{if(!i.classList.contains("sent")){i.classList.add("sent");const a=i.querySelector(".gait-modal-email-item-right");if(a){let o=a.querySelector(".gait-modal-email-item-amount-container");const d=a.querySelector(".gait-modal-email-item-amount");if(!o&&d&&(o=document.createElement("div"),o.className="gait-modal-email-item-amount-container",d.parentElement?.replaceChild(o,d),o.appendChild(d)),o&&!o.querySelector(".gait-modal-email-item-sent-check")){const l=document.createElement("div");l.className="gait-modal-email-item-sent-check",l.innerHTML=$,o.appendChild(l)}}const n=i.querySelector(".gait-modal-email-item-actions");n&&(n.style.display="none")}})}showFeedback(t,e){this.splitFeedbackElement&&(this.splitFeedbackElement.remove(),this.splitFeedbackElement=null);const i=document.createElement("div");i.className=`gait-modal-feedback gait-modal-feedback-${e}`,i.setAttribute("data-split-feedback","");const a=e==="success"?$:e==="error"?U:W;i.innerHTML=`
953
+ <span class="gait-modal-feedback-icon">${a}</span>
954
+ <span class="gait-modal-feedback-message">${t}</span>
955
+ `;const n=this.modalElement.querySelector(".gait-modal-split-section");n&&(n.insertBefore(i,n.firstChild),this.splitFeedbackElement=i,e!=="loading"&&setTimeout(()=>{this.splitFeedbackElement===i&&(i.style.opacity="0",setTimeout(()=>{i.parentElement&&i.remove(),this.splitFeedbackElement===i&&(this.splitFeedbackElement=null)},300))},5e3))}disableSplitOptions(){this.modalElement.querySelectorAll("[data-split-method]").forEach(i=>{const a=i;a.style.pointerEvents="none",a.style.opacity="0.5",a.style.cursor="not-allowed",a.setAttribute("aria-disabled","true")});const e=this.modalElement.querySelector("[data-add-email]");e&&(e.style.pointerEvents="none",e.style.opacity="0.5",e.style.cursor="not-allowed",e.setAttribute("aria-disabled","true"))}}async function et(r,t=2e3){console.log("[SplitPaymentAPI] Sending split payment request:",r);const e="https://api.usegait.com/checkout";try{const a=await(await fetch(`${e}/v1/payments`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r),signal:AbortSignal.timeout(t)})).json().catch(()=>({success:!1,error:"Failed to parse API response"}));if(a.success){const n={success:!0,message:a.message,data:a.data};return console.log("[SplitPaymentAPI] Split payment sent successfully:",n),n}else{const n={success:!1,message:a.message,error:a.error};return console.error("[SplitPaymentAPI] Split payment failed:",n),n}}catch(i){const a={success:!1,message:"Failed to send split payment",error:i instanceof Error?i.message:"Network error or server unavailable"};return console.error("[SplitPaymentAPI] Split payment failed:",a,i),a}}let at=r=>crypto.getRandomValues(new Uint8Array(r)),it=(r,t,e)=>{let i=(2<<Math.log2(r.length-1))-1,a=-~(1.6*i*t/r.length);return(n=t)=>{let o="";for(;;){let d=e(a),l=a|0;for(;l--;)if(o+=r[d[l]&i]||"",o.length>=n)return o}}},ot=(r,t=21)=>it(r,t|0,at);const M={apiGateway:{MERCHANT_SERVICE_API:"https://api.usegait.com/merchant",CHECKOUT_SERVICE_API:"https://api.usegait.com/checkout"},paylink:{DOMAIN:"https://checkout.usegait.com"}},nt=M.apiGateway.MERCHANT_SERVICE_API;class rt{static async getMerchant(t){try{return await(await fetch(`${nt}/v1/merchant/identity/${t}`,{method:"GET",headers:{"Content-Type":"application/json"}})).json()}catch(e){return e}}}const k=M.apiGateway.CHECKOUT_SERVICE_API;class lt{static async getPayment(t){try{return await(await fetch(`${k}/v1/payments/${t}`,{method:"GET",headers:{"Content-Type":"application/json"}})).json()}catch(e){return e}}static async updatePaymentStatus(t,e,i){try{return await(await fetch(`${k}/v1/payments/merchants/${t}/${e}/status`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({status:i})})).json()}catch(a){return a}}static async createWebhook(t,e,i){try{return await(await fetch(`${k}/v1/webhooks/${t}/${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})).json()}catch(a){return a}}static async updatePayment(t,e,i){try{return await(await fetch(`${k}/v1/payments/merchants/${t}/${i}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({paymentId:e})})).json()}catch(a){return a}}}const S=(typeof process<"u"&&process.env?"0.1.0":void 0)||"0.1.4";class x extends HTMLElement{buttonElement=null;hasRendered=!1;mutationObserver=null;modalElement=null;modalId;splitPaymentManager=null;attributeGetters;storedSplitPaymentRequest=null;merchantId=null;paymentProvider="stripe";static version=S;version=S;static get observedAttributes(){return["data-id","disabled","size","style","items","price-breakdown","total-cost","webhook","merchant-store-url"]}constructor(){super(),this.attachShadow({mode:"open"}),this.modalId=`gait-modal-${Math.random().toString(36).substr(2,9)}`,this.attributeGetters=X(this)}connectedCallback(){!this.hasRendered&&this.shadowRoot&&this.render(),this.setupEventListeners(),this.setupMutationObserver(),this.setupApiListeners(),setTimeout(()=>this.processData(),0)}disconnectedCallback(){this.buttonElement&&this.buttonElement.removeEventListener("click",this.handleClick),this.mutationObserver&&(this.mutationObserver.disconnect(),this.mutationObserver=null),this.closeModal(),this.splitPaymentManager&&(this.splitPaymentManager=null),this.removeEventListener("gait-split-sent",this.handleSplitSent)}attributeChangedCallback(t,e,i){if(e!==i&&!(!this.isConnected||!this.shadowRoot)){if(t==="style"&&this.buttonElement&&this.hasRendered){const a=(i||"").replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,"");this.buttonElement.setAttribute("style",a);return}this.hasRendered&&(this.render(),this.setupEventListeners()),(t==="data-id"||t.startsWith("data-"))&&(t==="data-id"&&(this.merchantId=null),setTimeout(()=>this.processData(),0))}}get dataId(){return this.attributeGetters.dataId}get disabled(){return this.attributeGetters.disabled}get size(){return this.attributeGetters.size}get items(){return this.attributeGetters.items}get priceBreakdown(){return this.attributeGetters.priceBreakdown}get totalCost(){return this.attributeGetters.totalCost}get customer(){return this.attributeGetters.customer}get webhook(){return this.attributeGetters.webhook}get merchantStoreUrl(){return this.attributeGetters.merchantStoreUrl}getDataAttributes(){return Z(this)}async fetchMerchantId(){const t=this.dataId;if(!t)return console.warn("[GaitButton] No checkoutId (data-id) provided, cannot fetch merchantId"),this.showMerchantIdError(),!1;try{const e=await rt.getMerchant(t);return e.success&&e.data?.merchantId?(this.merchantId=e.data.merchantId,this.paymentProvider=e.data.paymentProvider,!0):(console.error("[GaitButton] Failed to fetch merchantId:",e),this.showMerchantIdError(),!1)}catch(e){return console.error("[GaitButton] Error fetching merchantId:",e),this.showMerchantIdError(),!1}}showMerchantIdError(){const t="Unable to identify your account. Please go to your dashboard and copy your checkout identity.";alert(t),this.dispatchEvent(new CustomEvent("gait-merchant-id-error",{detail:{message:t,timestamp:new Date().toISOString()},bubbles:!0,composed:!0}))}async processData(){const t=this.dataId,e=this.getDataAttributes();if(t&&!this.merchantId&&await this.fetchMerchantId(),t||Object.keys(e).length>0){const i={id:t,...e,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent("gait-data-processed",{detail:i,bubbles:!0,composed:!0}))}}handleClick=async t=>{if(this.disabled){t.preventDefault(),t.stopPropagation();return}const e={label:"Gait",dataId:this.dataId,dataAttributes:this.getDataAttributes(),timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent("gait-click",{detail:e,bubbles:!0,composed:!0})),this.dispatchEvent(new CustomEvent("click",{detail:e,bubbles:!0,composed:!0})),!(!this.merchantId&&(!await this.fetchMerchantId()||!this.merchantId))&&this.openModal()};setupEventListeners(){this.shadowRoot&&(this.buttonElement&&this.buttonElement.removeEventListener("click",this.handleClick),this.buttonElement=this.shadowRoot.querySelector("button"),this.buttonElement&&this.buttonElement.addEventListener("click",this.handleClick))}setupMutationObserver(){this.mutationObserver&&this.mutationObserver.disconnect(),this.mutationObserver=new MutationObserver(t=>{let e=!1;for(const i of t)if(i.type==="attributes"){const a=i.attributeName;if(a&&a.startsWith("data-")&&!x.observedAttributes.includes(a)){e=!0;break}}e&&this.isConnected&&this.processData()}),this.mutationObserver.observe(this,{attributes:!0,attributeFilter:void 0})}setupApiListeners(){this.addEventListener("gait-split-sent",this.handleSplitSent)}handleConfirmPayment=async()=>{if(console.log("[GaitButton] Pay remaining button clicked"),!this.storedSplitPaymentRequest){console.error("[GaitButton] Split payment must be sent first before paying remaining amount");return}if(!this.merchantId&&(!await this.fetchMerchantId()||!this.merchantId))return;const t={merchantId:this.merchantId,splitId:this.storedSplitPaymentRequest.splitId,paymentId:this.storedSplitPaymentRequest.paymentSplits.find(a=>a.initiator)?.paymentId},i=`${M.paylink.DOMAIN}/payment/${t.paymentId}`;window.open(i,"_blank"),this.dispatchEvent(new CustomEvent("gait-confirm",{detail:t,bubbles:!0,composed:!0})),this.closeModal()};handleSplitSent=async t=>{const i=t.detail;if(!this.merchantId&&(!await this.fetchMerchantId()||!this.merchantId))return;const a=this.merchantId,n=this.customer;if(!n||!n.email){console.error("[GaitButton] Customer email is required for split payment");return}const o=this.webhook;if(!o||!o.url||o.body==null){console.error("[GaitButton] Webhook with url and body is required");return}const l=ot("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",6),s=l(),m=[];m.push({paymentId:l(),email:n.email,amount:i.yourAmount,isPaid:!1,datePaid:null,initiator:!0}),i.emailData.forEach(c=>{m.push({paymentId:l(),email:c.email,amount:c.amount||0,isPaid:!1,datePaid:null,initiator:!1})});const u={merchantId:a,splitId:s,paymentSplits:m,splitMethod:i.splitMethod,timestamp:i.timestamp,totalCost:i.totalCost,items:this.items,priceBreakdown:this.priceBreakdown,brandColor:g,merchantStoreUrl:this.merchantStoreUrl,subTotal:this.priceBreakdown.find(c=>c.name==="Subtotal")?.price||0,paymentProvider:this.paymentProvider};console.log("[GaitButton] Split payment request body:",JSON.stringify(u,null,2));try{const c=await et(u,2e3);if(c.success){const p=c.data?.splitId||s;this.storedSplitPaymentRequest={...u,splitId:p};try{const b=await lt.createWebhook(a,p,{url:o.url,body:o.body});console.log("[GaitButton] Webhook created:",b)}catch(b){console.error("[GaitButton] Failed to create webhook:",b)}}if(this.modalElement){const p=this.modalElement.querySelector("[data-gait-modal-confirm]");p&&(p.disabled=!1,p.style.opacity="1",p.style.cursor="pointer")}this.dispatchEvent(new CustomEvent("gait-split-feedback",{detail:{success:c.success,message:c.message,data:c.success?this.storedSplitPaymentRequest:c.data,error:c.error,timestamp:new Date().toISOString()},bubbles:!0,composed:!0})),console.log("[GaitButton] Split payment API response:",c)}catch(c){console.error("[GaitButton] Split payment API error:",c),this.dispatchEvent(new CustomEvent("gait-split-feedback",{detail:{success:!1,message:"Failed to send split payment",error:c instanceof Error?c.message:"Unknown error",timestamp:new Date().toISOString()},bubbles:!0,composed:!0}))}};openModal(){V(),this.closeModal(),this.modalElement=document.createElement("div"),this.modalElement.className="gait-modal-backdrop",this.modalElement.id=this.modalId,this.modalElement&&(this.modalElement.style.setProperty("--gait-modal-brand-color",g),this.modalElement.style.setProperty("--gait-modal-button-bg",g),this.modalElement.style.setProperty("--gait-modal-button-bg-hover",F(g,.15)),this.modalElement.style.setProperty("--gait-modal-button-color",q(g)?"#ffffff":"#000000")),this.modalElement.innerHTML=K(this.items,this.priceBreakdown,this.totalCost);const t=this.modalElement.querySelector("[data-gait-modal-close]");t&&t.addEventListener("click",this.closeModal);const e=this.modalElement.querySelector("[data-gait-modal-confirm]");e&&(e.disabled=!0,e.style.opacity="0.5",e.style.cursor="not-allowed",e.addEventListener("click",this.handleConfirmPayment)),this.splitPaymentManager=new tt(this.modalElement,this.totalCost,a=>this.dispatchEvent(a)),this.modalElement._splitPaymentManager=this.splitPaymentManager,this.modalElement.addEventListener("click",a=>{a.target===this.modalElement&&this.closeModal()});const i=a=>{a.key==="Escape"&&this.modalElement&&(this.closeModal(),document.removeEventListener("keydown",i))};document.addEventListener("keydown",i),document.body.appendChild(this.modalElement),requestAnimationFrame(()=>{this.modalElement&&this.modalElement.classList.add("gait-modal-open")})}closeModal=()=>{this.modalElement&&(this.splitPaymentManager&&this.splitPaymentManager.cleanup(),this.modalElement.classList.remove("gait-modal-open"),setTimeout(()=>{this.modalElement&&this.modalElement.parentNode&&this.modalElement.parentNode.removeChild(this.modalElement),this.modalElement=null,this.splitPaymentManager=null},250))};render(){const t=this.shadowRoot;if(!t)return;const e=!this.hasRendered,i=D(this.size,F,q),n=(this.getAttribute("style")||"").replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,"");if(t.innerHTML=`
956
+ ${i}
957
+ ${H(this.disabled,n)}
958
+ `,this.hasRendered=!0,this.setupEventListeners(),e){const o=Q(this);console.log("[GaitButton] rendered",o),this.dispatchEvent(new CustomEvent("gait-loaded",{detail:{attributes:o,dataId:this.dataId,dataAttributes:this.getDataAttributes(),label:"Gait",size:this.size,disabled:this.disabled,timestamp:new Date().toISOString()},bubbles:!0,composed:!0}))}}}customElements.get("gait-button")||customElements.define("gait-button",x),customElements.get("gait-button")||customElements.define("gait-button",x),typeof window<"u"&&(window.GaitButton=x,window.GaitButtonVersion=S,console.log(`[GaitButton] Version: ${S}`)),I.GAIT_BUTTON_VERSION=S,I.GaitButton=x,Object.defineProperty(I,Symbol.toStringTag,{value:"Module"})})(this.GaitButton=this.GaitButton||{});