@followgate/js 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -10,7 +10,8 @@ type SocialAction = 'follow' | 'repost' | 'like';
10
10
  * Twitter/X configuration
11
11
  */
12
12
  interface TwitterConfig {
13
- handle: string;
13
+ handle?: string;
14
+ overrideHandle?: string;
14
15
  tweetId?: string;
15
16
  username?: string;
16
17
  }
@@ -112,6 +113,13 @@ declare class FollowGateClient {
112
113
  private injectStyles;
113
114
  private createModal;
114
115
  private getContentElement;
116
+ /**
117
+ * Get target handle with priority:
118
+ * 1. config.twitter.overrideHandle (explicit override)
119
+ * 2. serverConfig.targetHandle (from Dashboard)
120
+ * 3. config.twitter.handle (legacy fallback, deprecated)
121
+ */
122
+ private getTargetHandle;
115
123
  private renderUsernameStep;
116
124
  private handleUsernameSubmit;
117
125
  private renderFollowStep;
package/dist/index.d.ts CHANGED
@@ -10,7 +10,8 @@ type SocialAction = 'follow' | 'repost' | 'like';
10
10
  * Twitter/X configuration
11
11
  */
12
12
  interface TwitterConfig {
13
- handle: string;
13
+ handle?: string;
14
+ overrideHandle?: string;
14
15
  tweetId?: string;
15
16
  username?: string;
16
17
  }
@@ -112,6 +113,13 @@ declare class FollowGateClient {
112
113
  private injectStyles;
113
114
  private createModal;
114
115
  private getContentElement;
116
+ /**
117
+ * Get target handle with priority:
118
+ * 1. config.twitter.overrideHandle (explicit override)
119
+ * 2. serverConfig.targetHandle (from Dashboard)
120
+ * 3. config.twitter.handle (legacy fallback, deprecated)
121
+ */
122
+ private getTargetHandle;
115
123
  private renderUsernameStep;
116
124
  private handleUsernameSubmit;
117
125
  private renderFollowStep;
package/dist/index.js CHANGED
@@ -446,6 +446,35 @@ var MODAL_STYLES = `
446
446
  color: #94a3b8;
447
447
  }
448
448
 
449
+ .fg-close-btn {
450
+ position: absolute;
451
+ top: 16px;
452
+ right: 16px;
453
+ width: 32px;
454
+ height: 32px;
455
+ border-radius: 8px;
456
+ background: transparent;
457
+ border: 1px solid #334155;
458
+ color: #64748b;
459
+ cursor: pointer;
460
+ display: flex;
461
+ align-items: center;
462
+ justify-content: center;
463
+ transition: all 0.2s;
464
+ padding: 0;
465
+ }
466
+
467
+ .fg-close-btn:hover {
468
+ background: #1e293b;
469
+ color: #94a3b8;
470
+ border-color: #475569;
471
+ }
472
+
473
+ .fg-close-btn svg {
474
+ width: 16px;
475
+ height: 16px;
476
+ }
477
+
449
478
  .fg-btn-row {
450
479
  display: flex;
451
480
  gap: 8px;
@@ -462,7 +491,8 @@ var ICONS = {
462
491
  x: '<svg viewBox="0 0 24 24"><path fill="currentColor" d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>',
463
492
  check: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>',
464
493
  repost: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>',
465
- warning: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>'
494
+ warning: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>',
495
+ close: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>'
466
496
  };
467
497
  var FollowGateClient = class {
468
498
  config = null;
@@ -635,6 +665,9 @@ var FollowGateClient = class {
635
665
  backdrop.className = "fg-modal-backdrop";
636
666
  backdrop.innerHTML = `
637
667
  <div class="fg-modal">
668
+ <button class="fg-close-btn" id="fg-close-btn" aria-label="Close">
669
+ ${ICONS.close}
670
+ </button>
638
671
  <div id="fg-content"></div>
639
672
  <div class="fg-footer">
640
673
  <p>Powered by <a href="https://followgate.app" target="_blank" rel="noopener">FollowGate</a></p>
@@ -643,6 +676,9 @@ var FollowGateClient = class {
643
676
  `;
644
677
  document.body.appendChild(backdrop);
645
678
  this.modalElement = backdrop;
679
+ document.getElementById("fg-close-btn")?.addEventListener("click", () => {
680
+ this.hide(true);
681
+ });
646
682
  requestAnimationFrame(() => {
647
683
  backdrop.classList.add("fg-visible");
648
684
  });
@@ -655,10 +691,28 @@ var FollowGateClient = class {
655
691
  getContentElement() {
656
692
  return document.getElementById("fg-content");
657
693
  }
694
+ /**
695
+ * Get target handle with priority:
696
+ * 1. config.twitter.overrideHandle (explicit override)
697
+ * 2. serverConfig.targetHandle (from Dashboard)
698
+ * 3. config.twitter.handle (legacy fallback, deprecated)
699
+ */
700
+ getTargetHandle() {
701
+ if (this.config?.twitter?.overrideHandle) {
702
+ return this.config.twitter.overrideHandle;
703
+ }
704
+ if (this.serverConfig?.targetHandle) {
705
+ return this.serverConfig.targetHandle;
706
+ }
707
+ if (this.config?.twitter?.handle) {
708
+ return this.config.twitter.handle;
709
+ }
710
+ return null;
711
+ }
658
712
  renderUsernameStep() {
659
713
  const content = this.getContentElement();
660
714
  if (!content) return;
661
- const handle = this.serverConfig?.targetHandle || this.config?.twitter?.handle;
715
+ const handle = this.getTargetHandle();
662
716
  const hasRepost = !!this.config?.twitter?.tweetId;
663
717
  const allowSkip = this.serverConfig?.allowSkip ?? false;
664
718
  const welcomeTitle = this.serverConfig?.welcomeTitle || "Unlock Free Access";
@@ -713,9 +767,19 @@ var FollowGateClient = class {
713
767
  }
714
768
  renderFollowStep() {
715
769
  const content = this.getContentElement();
716
- if (!content || !this.config?.twitter) return;
717
- const handle = this.config.twitter.handle;
718
- const hasRepost = !!this.config.twitter.tweetId;
770
+ if (!content) return;
771
+ const handle = this.getTargetHandle();
772
+ if (!handle) {
773
+ console.error(
774
+ "[FollowGate] No target handle configured. Set it in Dashboard or use overrideHandle."
775
+ );
776
+ return;
777
+ }
778
+ const hasRepost = !!this.config?.twitter?.tweetId;
779
+ const defaultTitle = hasRepost ? "Step 1: Follow" : "Follow to Continue";
780
+ const defaultMessage = `Follow @${handle} on X`;
781
+ const title = this.serverConfig?.welcomeTitle || defaultTitle;
782
+ const message = this.serverConfig?.welcomeMessage || defaultMessage;
719
783
  content.innerHTML = `
720
784
  ${hasRepost ? this.renderStepIndicator(1) : ""}
721
785
  <div class="fg-icon-box">
@@ -727,8 +791,8 @@ var FollowGateClient = class {
727
791
  <span class="fg-user-badge-text">@${this.currentUser.username}</span>
728
792
  </div>
729
793
  ` : ""}
730
- <h2 class="fg-title">${hasRepost ? "Step 1: Follow" : "Follow to Continue"}</h2>
731
- <p class="fg-subtitle" id="fg-follow-subtitle">Follow @${handle} on X</p>
794
+ <h2 class="fg-title">${this.escapeHtml(title)}</h2>
795
+ <p class="fg-subtitle" id="fg-follow-subtitle">${this.escapeHtml(message)}</p>
732
796
  <div id="fg-follow-actions">
733
797
  <button class="fg-btn fg-btn-dark" id="fg-follow-btn">
734
798
  ${ICONS.x}
@@ -754,8 +818,8 @@ var FollowGateClient = class {
754
818
  }
755
819
  }
756
820
  handleFollowClick() {
757
- if (!this.config?.twitter) return;
758
- const handle = this.config.twitter.handle;
821
+ const handle = this.getTargetHandle();
822
+ if (!handle) return;
759
823
  this.openIntent({
760
824
  platform: "twitter",
761
825
  action: "follow",
@@ -764,8 +828,8 @@ var FollowGateClient = class {
764
828
  this.showFollowConfirmation();
765
829
  }
766
830
  showFollowConfirmation() {
767
- if (!this.config?.twitter) return;
768
- const handle = this.config.twitter.handle;
831
+ const handle = this.getTargetHandle();
832
+ if (!handle) return;
769
833
  const subtitle = document.getElementById("fg-follow-subtitle");
770
834
  const actions = document.getElementById("fg-follow-actions");
771
835
  if (subtitle) {
@@ -811,14 +875,14 @@ var FollowGateClient = class {
811
875
  }
812
876
  }
813
877
  async handleFollowConfirm() {
814
- if (!this.config?.twitter) return;
815
- const handle = this.config.twitter.handle;
878
+ const handle = this.getTargetHandle();
879
+ if (!handle) return;
816
880
  await this.complete({
817
881
  platform: "twitter",
818
882
  action: "follow",
819
883
  target: handle
820
884
  });
821
- if (this.config.twitter.tweetId) {
885
+ if (this.config?.twitter?.tweetId) {
822
886
  this.renderRepostStep();
823
887
  } else {
824
888
  this.renderConfirmStep();
@@ -969,11 +1033,12 @@ var FollowGateClient = class {
969
1033
  this.handleFinish();
970
1034
  });
971
1035
  document.getElementById("fg-redo-follow")?.addEventListener("click", () => {
972
- if (this.config?.twitter) {
1036
+ const handle = this.getTargetHandle();
1037
+ if (handle) {
973
1038
  this.openIntent({
974
1039
  platform: "twitter",
975
1040
  action: "follow",
976
- target: this.config.twitter.handle
1041
+ target: handle
977
1042
  });
978
1043
  }
979
1044
  });
package/dist/index.mjs CHANGED
@@ -420,6 +420,35 @@ var MODAL_STYLES = `
420
420
  color: #94a3b8;
421
421
  }
422
422
 
423
+ .fg-close-btn {
424
+ position: absolute;
425
+ top: 16px;
426
+ right: 16px;
427
+ width: 32px;
428
+ height: 32px;
429
+ border-radius: 8px;
430
+ background: transparent;
431
+ border: 1px solid #334155;
432
+ color: #64748b;
433
+ cursor: pointer;
434
+ display: flex;
435
+ align-items: center;
436
+ justify-content: center;
437
+ transition: all 0.2s;
438
+ padding: 0;
439
+ }
440
+
441
+ .fg-close-btn:hover {
442
+ background: #1e293b;
443
+ color: #94a3b8;
444
+ border-color: #475569;
445
+ }
446
+
447
+ .fg-close-btn svg {
448
+ width: 16px;
449
+ height: 16px;
450
+ }
451
+
423
452
  .fg-btn-row {
424
453
  display: flex;
425
454
  gap: 8px;
@@ -436,7 +465,8 @@ var ICONS = {
436
465
  x: '<svg viewBox="0 0 24 24"><path fill="currentColor" d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>',
437
466
  check: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>',
438
467
  repost: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>',
439
- warning: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>'
468
+ warning: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>',
469
+ close: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>'
440
470
  };
441
471
  var FollowGateClient = class {
442
472
  config = null;
@@ -609,6 +639,9 @@ var FollowGateClient = class {
609
639
  backdrop.className = "fg-modal-backdrop";
610
640
  backdrop.innerHTML = `
611
641
  <div class="fg-modal">
642
+ <button class="fg-close-btn" id="fg-close-btn" aria-label="Close">
643
+ ${ICONS.close}
644
+ </button>
612
645
  <div id="fg-content"></div>
613
646
  <div class="fg-footer">
614
647
  <p>Powered by <a href="https://followgate.app" target="_blank" rel="noopener">FollowGate</a></p>
@@ -617,6 +650,9 @@ var FollowGateClient = class {
617
650
  `;
618
651
  document.body.appendChild(backdrop);
619
652
  this.modalElement = backdrop;
653
+ document.getElementById("fg-close-btn")?.addEventListener("click", () => {
654
+ this.hide(true);
655
+ });
620
656
  requestAnimationFrame(() => {
621
657
  backdrop.classList.add("fg-visible");
622
658
  });
@@ -629,10 +665,28 @@ var FollowGateClient = class {
629
665
  getContentElement() {
630
666
  return document.getElementById("fg-content");
631
667
  }
668
+ /**
669
+ * Get target handle with priority:
670
+ * 1. config.twitter.overrideHandle (explicit override)
671
+ * 2. serverConfig.targetHandle (from Dashboard)
672
+ * 3. config.twitter.handle (legacy fallback, deprecated)
673
+ */
674
+ getTargetHandle() {
675
+ if (this.config?.twitter?.overrideHandle) {
676
+ return this.config.twitter.overrideHandle;
677
+ }
678
+ if (this.serverConfig?.targetHandle) {
679
+ return this.serverConfig.targetHandle;
680
+ }
681
+ if (this.config?.twitter?.handle) {
682
+ return this.config.twitter.handle;
683
+ }
684
+ return null;
685
+ }
632
686
  renderUsernameStep() {
633
687
  const content = this.getContentElement();
634
688
  if (!content) return;
635
- const handle = this.serverConfig?.targetHandle || this.config?.twitter?.handle;
689
+ const handle = this.getTargetHandle();
636
690
  const hasRepost = !!this.config?.twitter?.tweetId;
637
691
  const allowSkip = this.serverConfig?.allowSkip ?? false;
638
692
  const welcomeTitle = this.serverConfig?.welcomeTitle || "Unlock Free Access";
@@ -687,9 +741,19 @@ var FollowGateClient = class {
687
741
  }
688
742
  renderFollowStep() {
689
743
  const content = this.getContentElement();
690
- if (!content || !this.config?.twitter) return;
691
- const handle = this.config.twitter.handle;
692
- const hasRepost = !!this.config.twitter.tweetId;
744
+ if (!content) return;
745
+ const handle = this.getTargetHandle();
746
+ if (!handle) {
747
+ console.error(
748
+ "[FollowGate] No target handle configured. Set it in Dashboard or use overrideHandle."
749
+ );
750
+ return;
751
+ }
752
+ const hasRepost = !!this.config?.twitter?.tweetId;
753
+ const defaultTitle = hasRepost ? "Step 1: Follow" : "Follow to Continue";
754
+ const defaultMessage = `Follow @${handle} on X`;
755
+ const title = this.serverConfig?.welcomeTitle || defaultTitle;
756
+ const message = this.serverConfig?.welcomeMessage || defaultMessage;
693
757
  content.innerHTML = `
694
758
  ${hasRepost ? this.renderStepIndicator(1) : ""}
695
759
  <div class="fg-icon-box">
@@ -701,8 +765,8 @@ var FollowGateClient = class {
701
765
  <span class="fg-user-badge-text">@${this.currentUser.username}</span>
702
766
  </div>
703
767
  ` : ""}
704
- <h2 class="fg-title">${hasRepost ? "Step 1: Follow" : "Follow to Continue"}</h2>
705
- <p class="fg-subtitle" id="fg-follow-subtitle">Follow @${handle} on X</p>
768
+ <h2 class="fg-title">${this.escapeHtml(title)}</h2>
769
+ <p class="fg-subtitle" id="fg-follow-subtitle">${this.escapeHtml(message)}</p>
706
770
  <div id="fg-follow-actions">
707
771
  <button class="fg-btn fg-btn-dark" id="fg-follow-btn">
708
772
  ${ICONS.x}
@@ -728,8 +792,8 @@ var FollowGateClient = class {
728
792
  }
729
793
  }
730
794
  handleFollowClick() {
731
- if (!this.config?.twitter) return;
732
- const handle = this.config.twitter.handle;
795
+ const handle = this.getTargetHandle();
796
+ if (!handle) return;
733
797
  this.openIntent({
734
798
  platform: "twitter",
735
799
  action: "follow",
@@ -738,8 +802,8 @@ var FollowGateClient = class {
738
802
  this.showFollowConfirmation();
739
803
  }
740
804
  showFollowConfirmation() {
741
- if (!this.config?.twitter) return;
742
- const handle = this.config.twitter.handle;
805
+ const handle = this.getTargetHandle();
806
+ if (!handle) return;
743
807
  const subtitle = document.getElementById("fg-follow-subtitle");
744
808
  const actions = document.getElementById("fg-follow-actions");
745
809
  if (subtitle) {
@@ -785,14 +849,14 @@ var FollowGateClient = class {
785
849
  }
786
850
  }
787
851
  async handleFollowConfirm() {
788
- if (!this.config?.twitter) return;
789
- const handle = this.config.twitter.handle;
852
+ const handle = this.getTargetHandle();
853
+ if (!handle) return;
790
854
  await this.complete({
791
855
  platform: "twitter",
792
856
  action: "follow",
793
857
  target: handle
794
858
  });
795
- if (this.config.twitter.tweetId) {
859
+ if (this.config?.twitter?.tweetId) {
796
860
  this.renderRepostStep();
797
861
  } else {
798
862
  this.renderConfirmStep();
@@ -943,11 +1007,12 @@ var FollowGateClient = class {
943
1007
  this.handleFinish();
944
1008
  });
945
1009
  document.getElementById("fg-redo-follow")?.addEventListener("click", () => {
946
- if (this.config?.twitter) {
1010
+ const handle = this.getTargetHandle();
1011
+ if (handle) {
947
1012
  this.openIntent({
948
1013
  platform: "twitter",
949
1014
  action: "follow",
950
- target: this.config.twitter.handle
1015
+ target: handle
951
1016
  });
952
1017
  }
953
1018
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@followgate/js",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "FollowGate SDK - Grow your audience with every download. Require social actions (follow, repost) before users can access your app.",
5
5
  "author": "FollowGate <hello@followgate.app>",
6
6
  "homepage": "https://followgate.app",