PageSourceSearch

https://www.rabbiit.com/assets/pricing/pricing-widget.js

js rabbiit.com collected 2026-09-27 09:49:57 UTC 17,923 bytes, 570 lines download raw bytes

1(function (root, factory) {
2    if (typeof module === 'object' && module.exports) {
3        module.exports = factory();
4    } else {
5        root.PricingCalculator = factory();
6    }
7}(typeof globalThis !== 'undefined' ? globalThis : this, function () {
8
9    function getAnnualDiscountPercent(plan) {
10        if (plan && plan.annual_discount_percent != null) {
11            return plan.annual_discount_percent;
12        }
13        return 20;
14    }
15
16    function calculatePlanPrice(plan, quantity, interval, currency) {
17        currency = currency || 'brl';
18        var total = 0;
19        var perUnit = 0;
20        var perMonth = 0;
21
22        if (plan.is_legacy && interval === 'year') {
23            return {
24                total: null,
25                perUnit: null
26            };
27        }
28
29        if (plan.is_free) {
30            return {
31                total: total,
32                perUnit: perUnit
33            };
34        }
35
36        if (plan.users_limit && quantity > plan.users_limit) {
37            return {
38                total: null
39            };
40        }
41
42        if (plan.price.charge_type === 'fixed') {
43            total = plan.price[interval]['unit_amount'][currency];
44            perMonth = total;
45
46            if (interval === 'year') {
47                perMonth /= 12;
48            }
49
50            return {
51                total: total,
52                perMonth: perMonth
53            };
54        }
55
56        if (!quantity) {
57            quantity = 1;
58        }
59
60        if (plan.minimun_users && quantity < plan.minimun_users) {
61            quantity = plan.minimun_users;
62        }
63
64        if (quantity > 10000) {
65            return {
66                total: null,
67                perUnit: null
68            };
69        }
70
71        var tiers = plan.price[interval];
72        var previousLimit = 0;
73
74        tiers.forEach(function (tier) {
75            if (quantity <= previousLimit) {
76                return;
77            }
78
79            var limit = tier.up_to;
80            var unitAmount = tier.unit_amount[currency];
81            var quantityInThisTier = Math.min(quantity, limit) - previousLimit;
82
83            total += quantityInThisTier * unitAmount;
84            previousLimit = limit;
85        });
86
87        perUnit = total / quantity;
88        perMonth = total;
89
90        if (interval === 'year') {
91            perUnit /= 12;
92            perMonth /= 12;
93        }
94
95        return {
96            total: total,
97            perUnit: perUnit,
98            perMonth: perMonth
99        };
100    }
101
102    function calculateAnnualSavings(plan, quantity, currency) {
103        if (!plan || plan.is_free || !plan.price) {
104            return null;
105        }
106
107        var monthPrice = calculatePlanPrice(plan, quantity, 'month', currency);
108        var yearPrice = calculatePlanPrice(plan, quantity, 'year', currency);
109        var monthTotal = monthPrice.total;
110        var yearTotal = yearPrice.total;
111
112        if (monthTotal === null || monthTotal === undefined) {
113            return null;
114        }
115
116        if (yearTotal === null || yearTotal === undefined) {
117            return null;
118        }
119
120        var monthlyBilledForTwelveMonths = monthTotal * 12;
121        var annualSavings = monthlyBilledForTwelveMonths - yearTotal;
122
123        if (annualSavings <= 0) {
124            return null;
125        }
126
127        return annualSavings;
128    }
129
130    function buildPlanPriceTiers(plan, interval, currency) {
131        if (!plan || plan.is_free || !plan.price || plan.price.charge_type !== 'per_user') {
132            return [];
133        }
134
135        if (plan.is_legacy && interval === 'year') {
136            return [];
137        }
138
139        var tiers = plan.price[interval];
140
141        if (!tiers || tiers.length <= 1) {
142            return [];
143        }
144
145        currency = currency || 'brl';
146
147        var previousLimit = 0;
148        var priceTiers = [];
149
150        tiers.forEach(function (tier) {
151            var upTo = tier.up_to;
152            var unitAmount = tier.unit_amount[currency];
153
154            if (unitAmount === undefined || unitAmount === null) {
155                return;
156            }
157
158            var displayUnitAmount = unitAmount;
159
160            if (interval === 'year') {
161                displayUnitAmount = unitAmount / 12;
162            }
163
164            priceTiers.push({
165                from: previousLimit + 1,
166                to: upTo,
167                unitAmount: displayUnitAmount
168            });
169
170            previousLimit = upTo;
171        });
172
173        return priceTiers;
174    }
175
176    return {
177        getAnnualDiscountPercent: getAnnualDiscountPercent,
178        calculatePlanPrice: calculatePlanPrice,
179        calculateAnnualSavings: calculateAnnualSavings,
180        buildPlanPriceTiers: buildPlanPriceTiers
181    };
182}));
183
184(function (root, factory) {
185    if (typeof module === 'object' && module.exports) {
186        module.exports = factory();
187    } else {
188        root.PricingFormat = factory();
189    }
190}(typeof globalThis !== 'undefined' ? globalThis : this, function () {
191
192    var CURRENCY_SYMBOLS = {
193        brl: 'R$',
194        usd: '$',
195        eur: '€',
196        gbp: '£',
197        chf: 'CHF'
198    };
199
200    function formatAmount(amount, alwaysShowDecimal) {
201        if (amount === null || amount === undefined || isNaN(amount)) {
202            return '0';
203        }
204
205        var decimalPlaces = alwaysShowDecimal ? 2 : (amount % 1 === 0 ? 0 : 2);
206
207        return new Intl.NumberFormat('pt-BR', {
208            minimumFractionDigits: decimalPlaces,
209            maximumFractionDigits: decimalPlaces
210        }).format(amount);
211    }
212
213    function currencySymbol(currency) {
214        if (!currency) {
215            return '';
216        }
217        var key = currency.toLowerCase();
218        return CURRENCY_SYMBOLS[key] || key.toUpperCase();
219    }
220
221    function detectCurrency() {
222        var language = (navigator.language || '').toLowerCase();
223        var tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
224
225        if (language.startsWith('pt-br') || tz.startsWith('America/Sao_Paulo')) {
226            return 'brl';
227        }
228
229        if (language.startsWith('de-ch') || language.startsWith('fr-ch') || language.startsWith('it-ch') || tz === 'Europe/Zurich') {
230            return 'chf';
231        }
232
233        var euroLocales = [
234            'pt-pt', 'es-es', 'fr-fr', 'de-de', 'it-it', 'nl-nl',
235            'fi-fi', 'ie-ie', 'el-gr', 'sk-sk', 'si-si', 'lt-lt',
236            'lv-lv', 'ee-ee', 'mt-mt', 'lu-lu', 'cy-cy', 'at-at'
237        ];
238
239        if (euroLocales.indexOf(language) !== -1 || tz.indexOf('Europe/') === 0) {
240            return 'eur';
241        }
242
243        return 'usd';
244    }
245
246    return {
247        formatAmount: formatAmount,
248        currencySymbol: currencySymbol,
249        detectCurrency: detectCurrency,
250        CURRENCY_SYMBOLS: CURRENCY_SYMBOLS
251    };
252}));
253
254(function () {
255    'use strict';
256
257    var Calculator = window.PricingCalculator;
258    var Format = window.PricingFormat;
259
260    if (!Calculator || !Format) {
261        return;
262    }
263
264    function readPricingData(root) {
265        var dataNode = root.querySelector('#rabbiit-pricing-data');
266
267        if (!dataNode) {
268            return null;
269        }
270
271        try {
272            return JSON.parse(dataNode.textContent);
273        } catch (error) {
274            console.error('Rabbiit pricing widget: invalid pricing data', error);
275            return null;
276        }
277    }
278
279    function findPlanById(plans, planId) {
280        for (var i = 0; i < plans.length; i++) {
281            if (plans[i].id === planId) {
282                return plans[i];
283            }
284        }
285        return null;
286    }
287
288    function interpolate(template, params) {
289        if (!template || !params) {
290            return template;
291        }
292
293        return template.replace(/\{\{\s*(\w+)\s*\}\}/g, function (_match, key) {
294            if (params[key] === undefined || params[key] === null) {
295                return '';
296            }
297            return String(params[key]);
298        });
299    }
300
301    function buildQuantityDescription(plan, quantity, strings) {
302        var html = strings.forLabel || 'para ';
303
304        if (plan.price.charge_type === 'fixed') {
305            html += plan.minimun_users + (strings.usersLabelSuffix || ' usuários');
306
307            if (quantity > plan.minimun_users) {
308                html += strings.additionalLabel || '';
309            }
310        } else if (plan.minimun_users && quantity < plan.minimun_users) {
311            html += plan.minimun_users + (strings.usersLabelSuffix || ' usuários');
312        } else {
313            html += quantity + (quantity > 1
314                ? (strings.usersLabelSuffix || ' usuários')
315                : (strings.userLabelSuffix || ' usuário'));
316        }
317
318        return html;
319    }
320
321    function setNodeHidden(node, hidden) {
322        if (!node) {
323            return;
324        }
325
326        node.hidden = hidden;
327    }
328
329    function updatePlanCard(card, plan, state, strings) {
330        var priceCalculated = Calculator.calculatePlanPrice(
331            plan,
332            state.quantity,
333            state.interval,
334            state.currency
335        );
336        var symbol = Format.currencySymbol(state.currency);
337        var priceContainer = card.querySelector('.directive-upgrade-plan-price--container');
338        var unavailableNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price-disabled');
339        var priceNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price');
340        var afterPriceNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__after-price');
341        var unitValueNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price-unit-value strong');
342        var currencySymbolNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price-unit .currency');
343        var totalNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price-total');
344        var intervalNode = priceContainer && priceContainer.querySelector('.directive-upgrade-plan-price--container__price-interval');
345        var isUnavailable = priceCalculated.total === null;
346
347        setNodeHidden(unavailableNode, !isUnavailable);
348        setNodeHidden(priceNode, isUnavailable);
349        setNodeHidden(afterPriceNode, isUnavailable);
350
351        if (isUnavailable) {
352            if (unavailableNode) {
353                if (plan.users_limit) {
354                    unavailableNode.textContent = interpolate(strings.maxUsers || '', { limit: plan.users_limit });
355                } else {
356                    unavailableNode.textContent = strings.unavailable || '';
357                }
358            }
359            return;
360        }
361
362        var unitValue = '0';
363
364        if (!plan.is_free) {
365            if (plan.price.charge_type === 'fixed') {
366                unitValue = Format.formatAmount(priceCalculated.perMonth, false);
367            } else {
368                unitValue = Format.formatAmount(priceCalculated.perUnit, false);
369            }
370        }
371
372        if (unitValueNode) {
373            unitValueNode.textContent = unitValue;
374        }
375
376        if (currencySymbolNode) {
377            currencySymbolNode.textContent = symbol;
378        }
379
380        setNodeHidden(totalNode, plan.is_free);
381        setNodeHidden(intervalNode, plan.is_free);
382
383        if (!plan.is_free) {
384            if (totalNode) {
385                var totalTemplate = strings.totalPerMonth || 'Total {{symbol}}{{amount}} /mês';
386                var totalText = interpolate(totalTemplate, {
387                    symbol: symbol,
388                    amount: Format.formatAmount(priceCalculated.perMonth, false)
389                });
390                var quantityText = buildQuantityDescription(plan, state.quantity, strings);
391                totalNode.innerHTML = totalText + ' ' + quantityText;
392            }
393
394            if (intervalNode) {
395                intervalNode.textContent = state.interval === 'month'
396                    ? (strings.billedMonthly || 'Cobrados mensalmente')
397                    : (strings.billedYearly || 'Cobrados anualmente');
398            }
399        }
400    }
401
402    function bindIntervalButtons(root, state, data, strings) {
403        var buttons = root.querySelectorAll('[data-interval]');
404
405        buttons.forEach(function (button) {
406            button.addEventListener('click', function (event) {
407                event.preventDefault();
408
409                var interval = button.getAttribute('data-interval');
410
411                if (!interval || interval === state.interval) {
412                    return;
413                }
414
415                state.interval = interval;
416
417                buttons.forEach(function (item) {
418                    item.classList.toggle('active', item.getAttribute('data-interval') === interval);
419                });
420
421                refreshPlans(root, state, data, strings);
422            });
423        });
424    }
425
426    function bindQuantityStepper(root, state, data, strings) {
427        var input = root.querySelector('.directive-input-number-stepper__input');
428        var stepButtons = root.querySelectorAll('.directive-input-number-stepper__btn');
429
430        if (!input) {
431            return;
432        }
433
434        var applyQuantity = function (normalizeInput) {
435            refreshPlans(root, state, data, strings, normalizeInput);
436        };
437
438        input.addEventListener('input', function () {
439            applyQuantity(false);
440        });
441        input.addEventListener('change', function () {
442            applyQuantity(true);
443        });
444        input.addEventListener('blur', function () {
445            applyQuantity(true);
446        });
447        input.addEventListener('keydown', function (event) {
448            if (event.key === 'Enter') {
449                applyQuantity(true);
450            }
451        });
452
453        stepButtons.forEach(function (button) {
454            button.addEventListener('click', function () {
455                var step = parseInt(button.getAttribute('data-step'), 10);
456                var quantity = parseInt(input.value, 10) || 1;
457                quantity += step;
458
459                if (quantity < 1) {
460                    quantity = 1;
461                }
462
463                input.value = String(quantity);
464                applyQuantity(true);
465            });
466        });
467    }
468
469    function syncQuantityFromInput(root, state, normalizeInput) {
470        var shouldNormalizeInput = normalizeInput !== false;
471        var input = root.querySelector('.directive-input-number-stepper__input');
472
473        if (!input) {
474            return;
475        }
476
477        var rawValue = input.value.trim();
478
479        if (rawValue === '') {
480            if (shouldNormalizeInput) {
481                state.quantity = 1;
482                input.value = '1';
483            }
484            return;
485        }
486
487        var quantity = parseInt(rawValue, 10);
488
489        if (isNaN(quantity) || quantity < 1) {
490            if (shouldNormalizeInput) {
491                state.quantity = 1;
492                input.value = '1';
493            }
494            return;
495        }
496
497        if (quantity > 10000) {
498            quantity = 10000;
499        }
500
501        state.quantity = quantity;
502
503        if (shouldNormalizeInput) {
504            input.value = String(quantity);
505        }
506    }
507
508    function refreshPlans(root, state, data, strings, normalizeInput) {
509        syncQuantityFromInput(root, state, normalizeInput);
510        var cards = root.querySelectorAll('[data-plan-id]');
511
512        cards.forEach(function (card) {
513            var planId = card.getAttribute('data-plan-id');
514            var plan = findPlanById(data.plans, planId);
515
516            if (plan) {
517                updatePlanCard(card, plan, state, strings);
518            }
519        });
520    }
521
522    function buildStrings(data) {
523        var strings = data.strings || {};
524
525        return {
526            forLabel: strings.forLabel || 'para ',
527            usersLabelSuffix: strings.usersLabelSuffix || ' usuários',
528            userLabelSuffix: strings.userLabelSuffix || ' usuário',
529            additionalLabel: strings.additionalLabel || '',
530            totalPerMonth: strings.totalPerMonth || 'Total {{symbol}}{{amount}} /mês',
531            billedMonthly: strings.billedMonthly || 'Cobrados mensalmente',
532            billedYearly: strings.billedYearly || 'Cobrados anualmente',
533            unavailable: strings.unavailable || 'Indisponível',
534            maxUsers: strings.maxUsers || 'Máximo de {{limit}} usuários'
535        };
536    }
537
538    function initRoot(root) {
539        var data = readPricingData(root);
540
541        if (!data || !data.plans) {
542            return;
543        }
544
545        var state = {
546            interval: (data.defaults && data.defaults.interval) || 'year',
547            quantity: (data.defaults && data.defaults.quantity) || 2,
548            currency: Format.detectCurrency()
549        };
550        var strings = buildStrings(data);
551
552        bindIntervalButtons(root, state, data, strings);
553        bindQuantityStepper(root, state, data, strings);
554        refreshPlans(root, state, data, strings);
555    }
556
557    function init() {
558        var roots = document.querySelectorAll('[data-rabbiit-pricing], #rabbiit-pricing');
559
560        roots.forEach(function (root) {
561            initRoot(root);
562        });
563    }
564
565    if (document.readyState === 'loading') {
566        document.addEventListener('DOMContentLoaded', init);
567    } else {
568        init();
569    }
570}());

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.