PageSourceSearch

https://backoffice.motorica.io/assets/js/signup-promo.js?v=1790776897

js motorica.io collected 2026-10-02 01:58:37 UTC 8,693 bytes, 263 lines download raw bytes

1(function () {
2	'use strict';
3
4	function initSignupPromo() {
5		var cfg = window.MOTORICA_SIGNUP || {};
6		var i18n = cfg.i18n || {};
7		var urls = cfg.urls || {};
8		var csrf = cfg.csrf || {};
9		var defaultPricing = cfg.defaultPricing || {};
10
11		var codeInput = document.getElementById('signup-promo-code');
12		var applyBtn = document.getElementById('signup-promo-apply');
13		var errorEl = document.getElementById('signup-promo-error');
14		var successEl = document.getElementById('signup-promo-success');
15		var successTitleEl = document.getElementById('signup-promo-success-title');
16		var successSavingsEl = document.getElementById('signup-promo-success-savings');
17		var savingsYearEl = document.getElementById('signup-promo-savings-year');
18		var savingsMonthEl = document.getElementById('signup-promo-savings-month');
19		var monthlyNetEl = document.getElementById('signup-price-monthly-net');
20		var monthlyGrossEl = document.getElementById('signup-price-monthly-gross');
21		var annualPaymentEl = document.getElementById('signup-price-annual-payment');
22		var detailEl = document.getElementById('signup-price-detail');
23		var applyBtnDefaultHtml = applyBtn ? applyBtn.innerHTML : '';
24		var isBusy = false;
25
26		function formatEuro(cents) {
27			var value = (Number(cents) || 0) / 100;
28			return value.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
29		}
30
31		function replaceAmount(template, amount) {
32			return String(template || '').replace('{amount}', amount || '');
33		}
34
35		function monthlyGrossFromNet(monthlyNetCents, vatPercent) {
36			var net = Number(monthlyNetCents) || 0;
37			if (net <= 0) return 0;
38			var vat = Number(vatPercent);
39			if (!isFinite(vat)) vat = Number(defaultPricing.vat_percent) || 19;
40			return net + Math.round(net * (vat / 100));
41		}
42
43		function resolvePricing(pricing) {
44			var vatPercent = pricing.vat_percent != null
45				? pricing.vat_percent
46				: (defaultPricing.vat_percent || 19);
47			var monthlyNet = pricing.monthly_net_cents != null
48				? Number(pricing.monthly_net_cents)
49				: Math.round((Number(pricing.net_cents) || 0) / 12);
50			var monthlyGross = pricing.monthly_gross_cents != null
51				? Number(pricing.monthly_gross_cents)
52				: monthlyGrossFromNet(monthlyNet, vatPercent);
53
54			return {
55				net_cents: Number(pricing.net_cents) || 0,
56				gross_cents: Number(pricing.gross_cents) || 0,
57				vat_percent: vatPercent,
58				monthly_net_cents: monthlyNet,
59				monthly_gross_cents: monthlyGross,
60				net_formatted: pricing.net_formatted || formatEuro(pricing.net_cents),
61				gross_formatted: pricing.gross_formatted || formatEuro(pricing.gross_cents),
62				monthly_formatted: pricing.monthly_formatted || formatEuro(monthlyNet),
63				monthly_gross_formatted: pricing.monthly_gross_formatted || formatEuro(monthlyGross),
64			};
65		}
66
67		function setPricing(pricing) {
68			if (!pricing) return;
69			var resolved = resolvePricing(pricing);
70
71			if (monthlyNetEl) {
72				monthlyNetEl.textContent = resolved.monthly_formatted;
73			}
74			if (monthlyGrossEl) {
75				monthlyGrossEl.textContent = resolved.monthly_gross_formatted;
76			}
77			if (annualPaymentEl) {
78				if (i18n.price_annual_payment) {
79					annualPaymentEl.textContent = String(i18n.price_annual_payment)
80						.replace('{gross}', resolved.gross_formatted);
81				} else {
82					annualPaymentEl.textContent = resolved.gross_formatted;
83				}
84			}
85			if (detailEl) {
86				var vatPercent = String(resolved.vat_percent || 19).replace('.0', '');
87				if (i18n.price_breakdown) {
88					detailEl.textContent = String(i18n.price_breakdown)
89						.replace('{net}', resolved.net_formatted)
90						.replace('{vat}', vatPercent);
91				} else {
92					detailEl.textContent = resolved.net_formatted + ' · ' + vatPercent + ' %';
93				}
94			}
95		}
96
97		function defaultPricingPayload() {
98			return {
99				gross_cents: defaultPricing.gross_cents,
100				net_cents: defaultPricing.net_cents,
101				vat_percent: defaultPricing.vat_percent,
102				monthly_net_cents: defaultPricing.monthly_net_cents,
103				monthly_gross_cents: defaultPricing.monthly_gross_cents,
104				gross_formatted: formatEuro(defaultPricing.gross_cents),
105				net_formatted: formatEuro(defaultPricing.net_cents),
106				monthly_formatted: formatEuro(defaultPricing.monthly_net_cents),
107				monthly_gross_formatted: formatEuro(defaultPricing.monthly_gross_cents),
108			};
109		}
110
111		function showError(message) {
112			if (errorEl) {
113				errorEl.textContent = message || '';
114				errorEl.hidden = !message;
115			}
116			if (successEl) successEl.hidden = true;
117		}
118
119		function showSuccess(message, savings) {
120			if (!successEl) return;
121			if (!message) {
122				successEl.hidden = true;
123				if (successTitleEl) successTitleEl.textContent = '';
124				if (successSavingsEl) successSavingsEl.hidden = true;
125				return;
126			}
127
128			if (successTitleEl) {
129				successTitleEl.textContent = message;
130			}
131			if (errorEl) errorEl.hidden = true;
132			successEl.hidden = false;
133
134			var yearAmount = savings && savings.year_net_formatted ? savings.year_net_formatted : '';
135			var monthAmount = savings && savings.month_net_formatted ? savings.month_net_formatted : '';
136			var hasSavings = savings && (Number(savings.year_net_cents) > 0 || Number(savings.month_net_cents) > 0);
137
138			if (successSavingsEl && savingsYearEl && savingsMonthEl && hasSavings) {
139				savingsYearEl.textContent = replaceAmount(i18n.promo_savings_year, yearAmount);
140				savingsMonthEl.textContent = replaceAmount(i18n.promo_savings_month, monthAmount);
141				successSavingsEl.hidden = false;
142			} else if (successSavingsEl) {
143				successSavingsEl.hidden = true;
144			}
145		}
146
147		function setApplyBusy(busy) {
148			isBusy = busy;
149			if (!applyBtn) return;
150			applyBtn.disabled = busy;
151			applyBtn.classList.toggle('is-loading', busy);
152			applyBtn.setAttribute('aria-busy', busy ? 'true' : 'false');
153			if (busy) {
154				applyBtn.innerHTML = '<i class="fas fa-spinner fa-spin" aria-hidden="true"></i> ' +
155					(i18n.promo_applying || '');
156			} else {
157				applyBtn.innerHTML = applyBtnDefaultHtml;
158			}
159			if (window.MotoricaBusy) {
160				if (busy) {
161					window.MotoricaBusy.show({ busyMessage: i18n.promo_busy_message || i18n.promo_applying || '' });
162				} else {
163					window.MotoricaBusy.hide();
164				}
165			}
166		}
167
168		function validatePromo(options) {
169			options = options || {};
170			var showBusyUi = options.showBusyUi !== false;
171
172			if (!codeInput || !urls.validatePromo || !applyBtn) return;
173			if (isBusy) return;
174
175			var code = (codeInput.value || '').trim();
176			if (code === '') {
177				setPricing(defaultPricingPayload());
178				showError('');
179				showSuccess('');
180				return;
181			}
182
183			if (showBusyUi) {
184				setApplyBusy(true);
185			}
186
187			var body = new URLSearchParams();
188			body.append('promo_code', code);
189			if (cfg.lang) body.append('lang', cfg.lang);
190			if (csrf.name && csrf.hash) body.append(csrf.name, csrf.hash);
191
192			fetch(urls.validatePromo, {
193				method: 'POST',
194				headers: {
195					'Content-Type': 'application/x-www-form-urlencoded',
196					'Accept': 'application/json',
197					'X-Requested-With': 'XMLHttpRequest',
198				},
199				body: body.toString(),
200				credentials: 'same-origin',
201			})
202				.then(function (r) { return r.json(); })
203				.then(function (data) {
204					if (!data || typeof data !== 'object') return;
205					if (!data.valid) {
206						setPricing(defaultPricingPayload());
207						showError(data.message || '');
208						showSuccess('');
209						return;
210					}
211					setPricing(data.pricing || null);
212					showSuccess(i18n.promo_applied || '', data.savings || null);
213				})
214				.catch(function () {
215					showError('');
216				})
217				.finally(function () {
218					if (showBusyUi) {
219						setApplyBusy(false);
220					}
221				});
222		}
223
224		if (applyBtn) {
225			applyBtn.addEventListener('click', function () {
226				validatePromo({ showBusyUi: true });
227			});
228		}
229		if (codeInput && (codeInput.value || '').trim() !== '') {
230			validatePromo({ showBusyUi: false });
231		}
232
233		var form = document.getElementById('signup-form');
234		var submitBtn = document.getElementById('signup-submit-btn');
235		var submitStarted = false;
236
237		if (form && submitBtn) {
238			form.addEventListener('submit', function () {
239				if (submitStarted) {
240					return;
241				}
242				submitStarted = true;
243				submitBtn.disabled = true;
244				submitBtn.classList.add('is-loading');
245				submitBtn.setAttribute('aria-busy', 'true');
246				submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin" aria-hidden="true"></i> ' +
247					(i18n.submit_processing || '…');
248				if (window.MotoricaBusy) {
249					window.MotoricaBusy.show({
250						busyMessage: i18n.submit_processing || '',
251					});
252				}
253				// Keep default submit; overlay stays until navigation.
254			});
255		}
256	}
257
258	if (document.readyState === 'loading') {
259		document.addEventListener('DOMContentLoaded', initSignupPromo);
260	} else {
261		initSignupPromo();
262	}
263})();

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.