PageSourceSearch

https://bitbank.nz/static/js/live-data-upsell.js

js bitbank.nz collected 2026-10-03 20:48:03 UTC 23,155 bytes, 688 lines download raw bytes

1/**
2 * Live Data Upsell System
3 * Handles live data promotion banners and pricing modals
4 */
5
6class LiveDataUpsell {
7    constructor() {
8        this.checkUserSubscription().then(() => {
9            if (!this.isUserSubscribed) {
10                this.init();
11                this.setupEventListeners();
12                this.startDelayedPopup();
13            }
14        });
15    }
16
17    init() {
18        // Banner removed per user request
19        this.createPricingModal();
20    }
21
22    createUpsellBanner() {
23        // Check if banner already exists
24        if (document.getElementById('live-data-banner')) return;
25
26        const banner = document.createElement('div');
27        banner.id = 'live-data-banner';
28        banner.className = 'live-data-banner';
29        banner.innerHTML = `
30            <div class="live-data-banner-content">
31                <div class="live-data-banner-icon">⚡</div>
32                <div class="live-data-banner-text">
33                    <strong>Get Hourly Forecasts</strong>
34                    <span>Hourly predictions, live streaming & instant alerts</span>
35                </div>
36                <div class="live-data-banner-actions">
37                    <button class="live-data-banner-btn primary" onclick="liveDataUpsell.showPricingModal()">
38                        Subscribe Now
39                    </button>
40                    <button class="live-data-banner-btn secondary" onclick="liveDataUpsell.dismissBanner()">
41                        &times;
42                    </button>
43                </div>
44            </div>
45        `;
46
47        // Insert banner at top of page
48        document.body.insertBefore(banner, document.body.firstChild);
49        
50        // Add CSS styles
51        this.addBannerStyles();
52        
53        // Animate in
54        setTimeout(() => {
55            banner.classList.add('show');
56        }, 500);
57    }
58
59    addBannerStyles() {
60        if (document.getElementById('live-data-banner-styles')) return;
61
62        const styles = document.createElement('style');
63        styles.id = 'live-data-banner-styles';
64        styles.textContent = `
65            .live-data-banner {
66                position: fixed;
67                top: -80px;
68                left: 0;
69                right: 0;
70                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
71                color: white;
72                z-index: 1000;
73                transition: transform 0.3s ease-in-out;
74                box-shadow: 0 2px 10px rgba(0,0,0,0.1);
75            }
76            
77            .live-data-banner.show {
78                transform: translateY(80px);
79            }
80            
81            .live-data-banner-content {
82                display: flex;
83                align-items: center;
84                justify-content: space-between;
85                max-width: 1200px;
86                margin: 0 auto;
87                padding: 12px 20px;
88                gap: 16px;
89            }
90            
91            .live-data-banner-icon {
92                font-size: 24px;
93                animation: pulse 2s infinite;
94            }
95            
96            @keyframes pulse {
97                0% { transform: scale(1); }
98                50% { transform: scale(1.1); }
99                100% { transform: scale(1); }
100            }
101            
102            .live-data-banner-text {
103                flex: 1;
104                display: flex;
105                flex-direction: column;
106                gap: 2px;
107            }
108            
109            .live-data-banner-text strong {
110                font-size: 16px;
111                font-weight: 600;
112            }
113            
114            .live-data-banner-text span {
115                font-size: 13px;
116                opacity: 0.9;
117            }
118            
119            .live-data-banner-actions {
120                display: flex;
121                gap: 8px;
122                align-items: center;
123            }
124            
125            .live-data-banner-btn {
126                padding: 8px 16px;
127                border: none;
128                border-radius: 6px;
129                font-weight: 500;
130                cursor: pointer;
131                transition: all 0.2s;
132                font-size: 14px;
133            }
134            
135            .live-data-banner-btn.primary {
136                background: rgba(255,255,255,0.2);
137                color: white;
138                backdrop-filter: blur(10px);
139            }
140            
141            .live-data-banner-btn.primary:hover {
142                background: rgba(255,255,255,0.3);
143                transform: translateY(-1px);
144            }
145            
146            .live-data-banner-btn.secondary {
147                background: transparent;
148                color: white;
149                font-size: 18px;
150                padding: 4px 8px;
151                opacity: 0.7;
152            }
153            
154            .live-data-banner-btn.secondary:hover {
155                opacity: 1;
156                background: rgba(255,255,255,0.1);
157            }
158            
159            /* Adjust page content for banner */
160            body.banner-active {
161                padding-top: 80px;
162            }
163            
164            @media (max-width: 768px) {
165                .live-data-banner-content {
166                    flex-direction: column;
167                    text-align: center;
168                    gap: 12px;
169                    padding: 16px 20px;
170                }
171                
172                .live-data-banner-text {
173                    order: 1;
174                }
175                
176                .live-data-banner-icon {
177                    order: 2;
178                    font-size: 20px;
179                }
180                
181                .live-data-banner-actions {
182                    order: 3;
183                    justify-content: center;
184                }
185                
186                body.banner-active {
187                    padding-top: 120px;
188                }
189            }
190        `;
191        
192        document.head.appendChild(styles);
193        document.body.classList.add('banner-active');
194    }
195
196    createPricingModal() {
197        // Check if modal already exists
198        if (document.getElementById('pricing-modal')) return;
199
200        const modal = document.createElement('div');
201        modal.id = 'pricing-modal';
202        modal.className = 'pricing-modal';
203        modal.style.display = 'none';
204        modal.innerHTML = `
205            <div class="pricing-modal-content">
206                <div class="pricing-modal-header">
207                    <h3>Choose Your Plan</h3>
208                    <button class="pricing-modal-close" onclick="liveDataUpsell.hidePricingModal()">&times;</button>
209                </div>
210                
211                <div class="pricing-plans">
212                    <div class="pricing-plan recommended">
213                        <div class="plan-badge">Most Popular</div>
214                        <div class="plan-header">
215                            <h4>Pro Plan</h4>
216                            <div class="plan-price">
217                                <span class="price">$19</span>
218                                <span class="period">/month</span>
219                            </div>
220                        </div>
221                        <div class="plan-features">
222                            <div class="feature">Hourly predictions for 70+ coins</div>
223                            <div class="feature">Live streaming price data</div>
224                            <div class="feature">Advanced interactive charts</div>
225                            <div class="feature">Instant price alerts</div>
226                            <div class="feature">Technical indicators</div>
227                            <div class="feature">Market sentiment analysis</div>
228                        </div>
229                        <button class="plan-button primary" onclick="liveDataUpsell.selectPlan('pro')">
230                            Subscribe
231                        </button>
232                    </div>
233                    
234                    <div class="pricing-plan">
235                        <div class="plan-header">
236                            <h4>Premium Plan</h4>
237                            <div class="plan-price">
238                                <span class="price">$49</span>
239                                <span class="period">/month</span>
240                            </div>
241                        </div>
242                        <div class="plan-features">
243                            <div class="feature">Everything in Pro</div>
244                            <div class="feature">Portfolio tracking</div>
245                            <div class="feature">Advanced AI insights</div>
246                            <div class="feature">Custom alert strategies</div>
247                            <div class="feature">API access</div>
248                            <div class="feature">Priority support</div>
249                        </div>
250                        <button class="plan-button secondary" onclick="liveDataUpsell.selectPlan('premium')">
251                            Subscribe
252                        </button>
253                    </div>
254
255                    <div class="pricing-plan">
256                        <div class="plan-header">
257                            <h4>Basic Plan</h4>
258                            <div class="plan-price">
259                                <span class="price">$9</span>
260                                <span class="period">/month</span>
261                            </div>
262                        </div>
263                        <div class="plan-features">
264                            <div class="feature">Hourly data for 10 coins</div>
265                            <div class="feature">Basic charts</div>
266                            <div class="feature">Email alerts</div>
267                            <div class="feature">Standard predictions</div>
268                        </div>
269                        <button class="plan-button secondary" onclick="liveDataUpsell.selectPlan('basic')">
270                            Subscribe
271                        </button>
272                    </div>
273                </div>
274                
275                <div class="pricing-footer">
276                    <div class="trust-indicators">
277                        <div class="trust-item">
278                            <span class="trust-icon">🔒</span>
279                            <span>Secure payments</span>
280                        </div>
281                        <div class="trust-item">
282                            <span class="trust-icon">↩️</span>
283                            <span>Cancel anytime</span>
284                        </div>
285                        <div class="trust-item">
286                            <span class="trust-icon">📞</span>
287                            <span>24/7 support</span>
288                        </div>
289                    </div>
290                </div>
291            </div>
292        `;
293        
294        document.body.appendChild(modal);
295        this.addModalStyles();
296    }
297
298    addModalStyles() {
299        if (document.getElementById('pricing-modal-styles')) return;
300
301        const styles = document.createElement('style');
302        styles.id = 'pricing-modal-styles';
303        styles.textContent = `
304            .pricing-modal {
305                position: fixed;
306                top: 0;
307                left: 0;
308                right: 0;
309                bottom: 0;
310                background: rgba(0,0,0,0.8);
311                z-index: 2000;
312                display: flex;
313                align-items: center;
314                justify-content: center;
315                padding: 20px;
316                backdrop-filter: blur(5px);
317            }
318            
319            .pricing-modal-content {
320                background: white;
321                border-radius: 16px;
322                max-width: 1000px;
323                width: 100%;
324                max-height: 90vh;
325                overflow-y: auto;
326                position: relative;
327                animation: modalSlideIn 0.3s ease-out;
328            }
329            
330            @keyframes modalSlideIn {
331                from {
332                    opacity: 0;
333                    transform: translateY(-50px) scale(0.95);
334                }
335                to {
336                    opacity: 1;
337                    transform: translateY(0) scale(1);
338                }
339            }
340            
341            .pricing-modal-header {
342                display: flex;
343                justify-content: space-between;
344                align-items: center;
345                padding: 24px 24px 16px;
346                border-bottom: 1px solid #eee;
347            }
348            
349            .pricing-modal-header h3 {
350                margin: 0;
351                font-size: 24px;
352                color: #333;
353            }
354            
355            .pricing-modal-close {
356                background: none;
357                border: none;
358                font-size: 24px;
359                cursor: pointer;
360                color: #666;
361                padding: 4px;
362                border-radius: 4px;
363                transition: all 0.2s;
364            }
365            
366            .pricing-modal-close:hover {
367                background: #f5f5f5;
368                color: #333;
369            }
370            
371            .pricing-plans {
372                display: grid;
373                grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
374                gap: 20px;
375                padding: 24px;
376            }
377            
378            .pricing-plan {
379                border: 2px solid #eee;
380                border-radius: 12px;
381                padding: 24px;
382                position: relative;
383                background: white;
384                transition: all 0.3s ease;
385            }
386            
387            .pricing-plan:hover {
388                border-color: #667eea;
389                transform: translateY(-4px);
390                box-shadow: 0 8px 25px rgba(102, 126, 234, 0.15);
391            }
392            
393            .pricing-plan.recommended {
394                border-color: #667eea;
395                background: linear-gradient(135deg, #f8f9ff 0%, #fff 100%);
396            }
397            
398            .plan-badge {
399                position: absolute;
400                top: -12px;
401                left: 50%;
402                transform: translateX(-50%);
403                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
404                color: white;
405                padding: 6px 16px;
406                border-radius: 20px;
407                font-size: 12px;
408                font-weight: 600;
409            }
410            
411            .plan-header {
412                text-align: center;
413                margin-bottom: 24px;
414            }
415            
416            .plan-header h4 {
417                margin: 0 0 12px 0;
418                font-size: 20px;
419                color: #333;
420            }
421            
422            .plan-price {
423                display: flex;
424                align-items: baseline;
425                justify-content: center;
426                gap: 4px;
427                margin-bottom: 8px;
428            }
429            
430            .plan-price .price {
431                font-size: 36px;
432                font-weight: 700;
433                color: #333;
434            }
435            
436            .plan-price .period {
437                font-size: 16px;
438                color: #666;
439            }
440            
441            .plan-trial {
442                font-size: 14px;
443                color: #4caf50;
444                font-weight: 500;
445            }
446            
447            .plan-features {
448                margin-bottom: 24px;
449            }
450            
451            .feature {
452                display: flex;
453                align-items: center;
454                gap: 8px;
455                padding: 8px 0;
456                font-size: 14px;
457                color: #555;
458            }
459            
460            .plan-button {
461                width: 100%;
462                padding: 12px 24px;
463                border: none;
464                border-radius: 8px;
465                font-weight: 600;
466                font-size: 16px;
467                cursor: pointer;
468                transition: all 0.2s;
469            }
470            
471            .plan-button.primary {
472                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
473                color: white;
474            }
475            
476            .plan-button.primary:hover {
477                transform: translateY(-2px);
478                box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
479            }
480            
481            .plan-button.secondary {
482                background: white;
483                color: #667eea;
484                border: 2px solid #667eea;
485            }
486            
487            .plan-button.secondary:hover {
488                background: #667eea;
489                color: white;
490                transform: translateY(-2px);
491            }
492            
493            .pricing-footer {
494                padding: 24px;
495                border-top: 1px solid #eee;
496                background: #f8f9fa;
497                border-radius: 0 0 16px 16px;
498            }
499            
500            .trust-indicators {
501                display: flex;
502                justify-content: center;
503                gap: 32px;
504                flex-wrap: wrap;
505            }
506            
507            .trust-item {
508                display: flex;
509                align-items: center;
510                gap: 8px;
511                font-size: 14px;
512                color: #666;
513            }
514            
515            .trust-icon {
516                font-size: 16px;
517            }
518            
519            @media (max-width: 768px) {
520                .pricing-plans {
521                    grid-template-columns: 1fr;
522                    padding: 16px;
523                }
524                
525                .pricing-plan {
526                    padding: 20px;
527                }
528                
529                .trust-indicators {
530                    gap: 16px;
531                    flex-direction: column;
532                    align-items: center;
533                }
534            }
535        `;
536        
537        document.head.appendChild(styles);
538    }
539
540    showPricingModal() {
541        const modal = document.getElementById('pricing-modal');
542        if (modal) {
543            modal.style.display = 'flex';
544            document.body.style.overflow = 'hidden';
545            
546            // Track event
547            this.trackEvent('pricing_modal_opened');
548        }
549    }
550
551    hidePricingModal() {
552        const modal = document.getElementById('pricing-modal');
553        if (modal) {
554            modal.style.display = 'none';
555            document.body.style.overflow = 'auto';
556        }
557    }
558
559    selectPlan(planType) {
560        // Check if user is logged in
561        const currentUser = window.getCurrentUser ? window.getCurrentUser() : null;
562        
563        if (currentUser) {
564            // User is logged in, redirect to checkout
565            window.location.href = `/pricing?plan=${planType}`;
566        } else {
567            // Show signup modal first
568            this.hidePricingModal();
569            if (window.showSignupModal) {
570                window.showSignupModal();
571            } else {
572                window.location.href = `/signup?redirect=/pricing&plan=${planType}`;
573            }
574        }
575        
576        // Track event
577        this.trackEvent('plan_selected', { plan: planType });
578    }
579
580    dismissBanner() {
581        const banner = document.getElementById('live-data-banner');
582        if (banner) {
583            banner.classList.remove('show');
584            document.body.classList.remove('banner-active');
585            setTimeout(() => {
586                banner.remove();
587            }, 300);
588        }
589        
590        // Store dismissal in localStorage
591        localStorage.setItem('live_data_banner_dismissed', Date.now().toString());
592        
593        // Track event
594        this.trackEvent('banner_dismissed');
595    }
596
597    startDelayedPopup() {
598        // Don't show popup if banner was recently dismissed
599        const dismissed = localStorage.getItem('live_data_banner_dismissed');
600        if (dismissed && (Date.now() - parseInt(dismissed)) < 24 * 60 * 60 * 1000) {
601            return;
602        }
603
604        // Show popup after 30 seconds on coin pages
605        if (window.location.pathname.includes('/coin/')) {
606            setTimeout(() => {
607                // Only show if user hasn't already interacted with upsells
608                if (!localStorage.getItem('upsell_interaction')) {
609                    this.showPricingModal();
610                    localStorage.setItem('upsell_interaction', Date.now().toString());
611                    
612                    // Track event
613                    this.trackEvent('delayed_popup_shown');
614                }
615            }, 30000); // 30 seconds
616        }
617    }
618
619    setupEventListeners() {
620        // Close modal on outside click
621        document.addEventListener('click', (e) => {
622            const modal = document.getElementById('pricing-modal');
623            if (modal && e.target === modal) {
624                this.hidePricingModal();
625            }
626        });
627
628        // ESC key to close modal
629        document.addEventListener('keydown', (e) => {
630            if (e.key === 'Escape') {
631                this.hidePricingModal();
632            }
633        });
634    }
635
636    async checkUserSubscription() {
637        try {
638            // Get current user from auth system
639            const currentUser = window.getCurrentUser ? window.getCurrentUser() : null;
640            
641            if (!currentUser) {
642                this.isUserSubscribed = false;
643                return;
644            }
645
646            // Check if user has active subscription
647            // Similar logic to netwrck payments.py - any active plan means subscribed
648            this.isUserSubscribed = currentUser.is_subscribed === true;
649            
650            console.log('User subscription status:', {
651                user_id: currentUser.id,
652                is_subscribed: currentUser.is_subscribed,
653                subscription_status: currentUser.subscription_status,
654                should_show_upsell: !this.isUserSubscribed
655            });
656            
657        } catch (error) {
658            console.error('Error checking subscription status:', error);
659            // Default to showing upsell if we can't determine status
660            this.isUserSubscribed = false;
661        }
662    }
663
664    trackEvent(eventName, properties = {}) {
665        // Integrate with analytics if available
666        if (typeof gtag !== 'undefined') {
667            gtag('event', eventName, {
668                ...properties,
669                event_category: 'upsell',
670                page_location: window.location.href
671            });
672        }
673        
674        // Console log for debugging
675        console.log('Upsell Event:', eventName, properties);
676    }
677}
678
679// Initialize the upsell system
680let liveDataUpsell;
681
682document.addEventListener('DOMContentLoaded', function() {
683    // Only initialize on coin pages or main coins page
684    if (window.location.pathname.includes('/coin') || window.location.pathname === '/coins') {
685        liveDataUpsell = new LiveDataUpsell();
686        window.liveDataUpsell = liveDataUpsell;
687    }
688});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.