PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | assets/js/login-shortcode.js +313 -15 3.0.4 → 3.0.16 View file →
@@ -1,13 +1,29 @@
1 1 /**
2 2 * Login Shortcode JavaScript
3 - *
3 + *
4 4 * Production-optimized login form interactions and functionality
5 - *
5 + *
6 6 * @package Yatra
7 7 * @version 1.0.0
8 8 */
9 9
10 +// Translation helpers — idempotent shim matching the other frontend bundles.
11 +(function () {
12 + if (typeof window.__ === 'function') return;
13 + if (window.wp && window.wp.i18n && typeof window.wp.i18n.__ === 'function') {
14 + window.__ = function (text, domain) { return window.wp.i18n.__(text, domain || 'yatra'); };
15 + window._n = function (s, p, n, domain) { return window.wp.i18n._n(s, p, n, domain || 'yatra'); };
16 + window._x = function (text, ctx, domain) { return window.wp.i18n._x(text, ctx, domain || 'yatra'); };
17 + window.sprintf = window.sprintf || (window.wp.i18n.sprintf || function (fmt) { return fmt; });
18 + } else {
19 + window.__ = function (text) { return text; };
20 + window._n = function (s, p, n) { return n === 1 ? s : p; };
21 + window._x = function (text) { return text; };
22 + window.sprintf = window.sprintf || function (fmt) { return fmt; };
23 + }
24 +})();
25 +
10 26 (function($) {
11 27 'use strict';
12 28
13 29 // Performance: Cache DOM elements
@@ -50,13 +66,295 @@
50 66
51 67 // Initialize components
52 68 initPasswordToggles();
53 69 initAjaxLogin();
70 + initPanelSwitching();
71 + initAjaxRegister();
72 + initAjaxForgotPassword();
54 73 initAccessibility();
55 74 initPerformanceOptimizations();
56 75 });
57 76
58 77 /**
78 + * Switch between the login / register / forgot-password panels.
79 + * The links carry a real href (wp-admin) as a no-JS fallback; here we
80 + * intercept and reveal the matching inline panel instead.
81 + */
82 + function initPanelSwitching() {
83 + // Reveal `target` panel, hide the rest, and sync tab state + roving
84 + // tabindex. Forgot-password is a sub-view of Login, so the Login tab
85 + // stays active while it is shown.
86 + function activatePanel($container, target, focusPanelInput) {
87 + const $panels = $container.find('.yatra-auth-panel');
88 + if (!$panels.length) {
89 + return;
90 + }
91 + $panels.hide().attr('aria-hidden', 'true');
92 + const $active = $container.find('.yatra-auth-panel[data-panel="' + target + '"]');
93 + $active.show().attr('aria-hidden', 'false');
94 +
95 + const activeTab = target === 'forgot' ? 'login' : target;
96 + $container.find('.yatra-login-tab').each(function() {
97 + const isActive = $(this).data('target') === activeTab;
98 + $(this)
99 + .toggleClass('is-active', isActive)
100 + .attr('aria-selected', isActive ? 'true' : 'false')
101 + .attr('tabindex', isActive ? '0' : '-1');
102 + });
103 +
104 + // Keep the shared header + footer copy in sync with the active panel.
105 + // Each panel carries its own data-header-*/data-footer-text strings
106 + // (server-translated), so this is a single source of truth.
107 + const $root = $container.closest('.yatra-login-container');
108 + const headerTitle = $active.attr('data-header-title');
109 + const headerSubtitle = $active.attr('data-header-subtitle');
110 + const footerText = $active.attr('data-footer-text');
111 + if (typeof headerTitle === 'string') {
112 + $root.find('.yatra-login-header .yatra-login-title').text(headerTitle);
113 + }
114 + if (typeof headerSubtitle === 'string') {
115 + const $subtitle = $root.find('.yatra-login-header .yatra-login-subtitle');
116 + $subtitle.text(headerSubtitle);
117 + // Hide the element entirely when a panel has no subtitle so it
118 + // doesn't leave an empty gap.
119 + $subtitle.css('display', headerSubtitle === '' ? 'none' : '');
120 + }
121 + if (typeof footerText === 'string') {
122 + $root.find('.yatra-login-footer .yatra-security-text').text(footerText);
123 + }
124 +
125 + // Clear any stale messages when switching panels.
126 + $container.find('.yatra-form-error, .yatra-form-success').remove();
127 +
128 + if (focusPanelInput) {
129 + $active.find('input:visible').first().trigger('focus');
130 + }
131 + }
132 +
133 + // Both the top tabs (.yatra-login-tab) and the in-panel text links
134 + // (.yatra-auth-switch, e.g. "Forgot your password?", "Back to login")
135 + // switch panels via a data-target.
136 + $document.on('click', '.yatra-login-tab, .yatra-auth-switch', function(e) {
137 + e.preventDefault();
138 + const target = $(this).data('target');
139 + if (!target) {
140 + return;
141 + }
142 + activatePanel($(this).closest('.yatra-login-form-container'), target, true);
143 + });
144 +
145 + // WAI-ARIA tabs keyboard support: Left/Right/Home/End move between tabs
146 + // (automatic activation — switching panels is cheap here).
147 + $document.on('keydown', '.yatra-login-tab', function(e) {
148 + if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].indexOf(e.key) === -1) {
149 + return;
150 + }
151 + e.preventDefault();
152 + const $tabs = $(this).closest('.yatra-login-tabs').find('.yatra-login-tab');
153 + const count = $tabs.length;
154 + if (!count) {
155 + return;
156 + }
157 + let idx = $tabs.index(this);
158 + if (e.key === 'ArrowLeft') { idx = (idx - 1 + count) % count; }
159 + else if (e.key === 'ArrowRight') { idx = (idx + 1) % count; }
160 + else if (e.key === 'Home') { idx = 0; }
161 + else if (e.key === 'End') { idx = count - 1; }
162 + const $next = $tabs.eq(idx);
163 + activatePanel($next.closest('.yatra-login-form-container'), $next.data('target'), false);
164 + $next.trigger('focus');
165 + });
166 + }
167 +
168 + /**
169 + * Validate the registration form client-side before submitting.
170 + */
171 + function validateRegisterForm($form) {
172 + const email = $form.find('[name="email"]').val().trim();
173 + const password = $form.find('[name="password"]').val();
174 + const confirm = $form.find('[name="confirm_password"]').val();
175 + const firstName = $form.find('[name="first_name"]').val().trim();
176 + const lastName = $form.find('[name="last_name"]').val().trim();
177 +
178 + if (!firstName || !lastName) {
179 + return { valid: false, message: __('Please enter your first and last name.', 'yatra') };
180 + }
181 + if (!email) {
182 + return { valid: false, message: __('Please enter your email address.', 'yatra') };
183 + }
184 + if (!password || password.length < 8) {
185 + return { valid: false, message: __('Password must be at least 8 characters long.', 'yatra') };
186 + }
187 + if (password !== confirm) {
188 + return { valid: false, message: __('Passwords do not match.', 'yatra') };
189 + }
190 + return { valid: true };
191 + }
192 +
193 + /**
194 + * Initialize AJAX registration against the public /auth/register REST route.
195 + * Reuses the same backend the booking checkout uses (email verification
196 + * included), so no new server logic is introduced here.
197 + */
198 + function initAjaxRegister() {
199 + $document.on('submit', '.yatra-register-form[data-ajax="true"]', function(e) {
200 + e.preventDefault();
201 + e.stopPropagation();
202 +
203 + const $form = $(this);
204 + const $submitBtn = $form.find('button[type="submit"]');
205 + if ($submitBtn.prop('disabled')) {
206 + return false;
207 + }
208 +
209 + const validation = validateRegisterForm($form);
210 + if (!validation.valid) {
211 + showFormError($form, validation.message);
212 + return false;
213 + }
214 +
215 + $form.find('.yatra-form-error, .yatra-form-success').remove();
216 + setButtonLoading($submitBtn, true);
217 +
218 + const restBase = (yatra_ajax && yatra_ajax.rest_url ? yatra_ajax.rest_url : '/wp-json/yatra/v1').replace(/\/$/, '');
219 + const payload = {
220 + first_name: $form.find('[name="first_name"]').val().trim(),
221 + last_name: $form.find('[name="last_name"]').val().trim(),
222 + email: $form.find('[name="email"]').val().trim(),
223 + phone: $form.find('[name="phone"]').val().trim(),
224 + password: $form.find('[name="password"]').val(),
225 + confirm_password: $form.find('[name="confirm_password"]').val()
226 + };
227 +
228 + // reCAPTCHA v3: attach a fresh token when registration is protected.
229 + const yatraRc = window.yatraRecaptcha;
230 + const regTokenPromise = (yatraRc && yatraRc.protects && yatraRc.protects('registration'))
231 + ? yatraRc.execute('registration')
232 + : Promise.resolve('');
233 +
234 + regTokenPromise.then(function (recaptchaToken) {
235 + if (recaptchaToken) {
236 + payload.recaptcha_token = recaptchaToken;
237 + }
238 +
239 + const request = $.ajax({
240 + url: restBase + '/auth/register',
241 + method: 'POST',
242 + contentType: 'application/json',
243 + data: JSON.stringify(payload),
244 + headers: yatra_ajax && yatra_ajax.rest_nonce ? { 'X-WP-Nonce': yatra_ajax.rest_nonce } : {},
245 + timeout: 20000,
246 + dataType: 'json'
247 + });
248 +
249 + request.done(function(response) {
250 + if (response && response.success) {
251 + showFormSuccess($form, response.message || __('Registration successful! Please check your email to verify your account.', 'yatra'));
252 + $form[0].reset();
253 + } else {
254 + showFormError($form, (response && response.message) || __('Registration failed. Please try again.', 'yatra'));
255 + }
256 + });
257 +
258 + request.fail(function(xhr, status) {
259 + let message = __('Network error. Please check your connection and try again.', 'yatra');
260 + if (status === 'timeout') {
261 + message = __('Request timed out. Please try again.', 'yatra');
262 + } else if (xhr.responseJSON && xhr.responseJSON.message) {
263 + message = xhr.responseJSON.message;
264 + }
265 + showFormError($form, message);
266 + });
267 +
268 + request.always(function() {
269 + setButtonLoading($submitBtn, false);
270 + });
271 + });
272 +
273 + return false;
274 + });
275 + }
276 +
277 + /**
278 + * Initialize AJAX forgot-password. Delegates to WordPress core's secure
279 + * password-reset flow server-side (retrieve_password) — we never reimplement
280 + * reset-token handling. The entry point stays on the branded page.
281 + */
282 + function initAjaxForgotPassword() {
283 + $document.on('submit', '.yatra-forgot-form[data-ajax="true"]', function(e) {
284 + e.preventDefault();
285 + e.stopPropagation();
286 +
287 + const $form = $(this);
288 + const $submitBtn = $form.find('button[type="submit"]');
289 + if ($submitBtn.prop('disabled')) {
290 + return false;
291 + }
292 +
293 + const userLogin = $form.find('[name="user_login"]').val().trim();
294 + if (!userLogin) {
295 + showFormError($form, __('Please enter your email address or username.', 'yatra'));
296 + return false;
297 + }
298 +
299 + $form.find('.yatra-form-error, .yatra-form-success').remove();
300 + setButtonLoading($submitBtn, true);
301 +
302 + const request = $.ajax({
303 + url: yatra_ajax && yatra_ajax.ajax_url ? yatra_ajax.ajax_url : '/wp-admin/admin-ajax.php',
304 + method: 'POST',
305 + data: {
306 + action: 'yatra_ajax_lost_password',
307 + user_login: userLogin,
308 + yatra_login_nonce: $form.find('[name="yatra_login_nonce"]').val()
309 + },
310 + timeout: 20000,
311 + dataType: 'json'
312 + });
313 +
314 + request.done(function(response) {
315 + if (response && response.success) {
316 + showFormSuccess($form, (response.data && response.data.message) || __('If an account exists for that email, a password reset link has been sent.', 'yatra'));
317 + $form[0].reset();
318 + } else {
319 + showFormError($form, (response && response.data && response.data.message) || __('Unable to process the request. Please try again.', 'yatra'));
320 + }
321 + });
322 +
323 + request.fail(function(xhr, status) {
324 + let message = __('Network error. Please check your connection and try again.', 'yatra');
325 + if (status === 'timeout') {
326 + message = __('Request timed out. Please try again.', 'yatra');
327 + } else if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
328 + message = xhr.responseJSON.data.message;
329 + }
330 + showFormError($form, message);
331 + });
332 +
333 + request.always(function() {
334 + setButtonLoading($submitBtn, false);
335 + });
336 +
337 + return false;
338 + });
339 + }
340 +
341 + /**
342 + * Toggle a submit button's loading state, mirroring the login button pattern.
343 + */
344 + function setButtonLoading($submitBtn, loading) {
345 + if (loading) {
346 + $submitBtn.prop('disabled', true).addClass('yatra-is-loading');
347 + $submitBtn.find('.yatra-btn-text').hide();
348 + $submitBtn.find('.yatra-btn-loading').show();
349 + } else {
350 + $submitBtn.prop('disabled', false).removeClass('yatra-is-loading');
351 + $submitBtn.find('.yatra-btn-loading').hide();
352 + $submitBtn.find('.yatra-btn-text').show();
353 + }
354 + }
355 +
356 + /**
59 357 * Initialize password toggle functionality with performance optimizations
60 358 */
61 359 function initPasswordToggles() {
62 360 const passwordToggles = $('.yatra-password-toggle');
@@ -83,14 +381,14 @@
83 381 if (isPassword) {
84 382 $input.attr('type', 'text');
85 383 $eyeIcon.hide();
86 384 $eyeOffIcon.show();
87 - $toggle.attr('aria-label', 'Hide password');
385 + $toggle.attr('aria-label', __('Hide password', 'yatra'));
88 386 } else {
89 387 $input.attr('type', 'password');
90 388 $eyeIcon.show();
91 389 $eyeOffIcon.hide();
92 - $toggle.attr('aria-label', 'Show password');
390 + $toggle.attr('aria-label', __('Show password', 'yatra'));
93 391 }
94 392 } catch (error) {
95 393 console.error('Error toggling password visibility:', error);
96 394 }
@@ -106,9 +404,9 @@
106 404
107 405 if (!username) {
108 406 return {
109 407 valid: false,
110 - message: yatra_ajax?.strings?.validation_error || 'Please enter your email or username.'
408 + message: yatra_ajax?.strings?.validation_error || __('Please enter your email or username.', 'yatra')
111 409 };
112 410 }
113 411
114 412 if (!password) {
@@ -113,9 +411,9 @@
113 411
114 412 if (!password) {
115 413 return {
116 414 valid: false,
117 - message: yatra_ajax?.strings?.validation_error || 'Please enter your password.'
415 + message: yatra_ajax?.strings?.validation_error || __('Please enter your password.', 'yatra')
118 416 };
119 417 }
120 418
121 419 if (username.length > 60) {
@@ -120,9 +418,9 @@
120 418
121 419 if (username.length > 60) {
122 420 return {
123 421 valid: false,
124 - message: 'Username is too long.'
422 + message: __('Username is too long.', 'yatra')
125 423 };
126 424 }
127 425
128 426 if (password.length > 72) {
@@ -127,9 +425,9 @@
127 425
128 426 if (password.length > 72) {
129 427 return {
130 428 valid: false,
131 - message: 'Password is too long.'
429 + message: __('Password is too long.', 'yatra')
132 430 };
133 431 }
134 432
135 433 return { valid: true };
@@ -174,9 +472,9 @@
174 472 if (yatra_ajax?.debug) {
175 473 setTimeout(() => {
176 474 const entries = performance.getEntriesByType('navigation');
177 475 if (entries.length > 0) {
178 - console.log('Login form load time:', entries[0].loadEventEnd - entries[0].loadEventStart, 'ms');
476 + window.YATRA_DEBUG && console.log('Login form load time:', entries[0].loadEventEnd - entries[0].loadEventStart, 'ms');
179 477 }
180 478 }, 1000);
181 479 }
182 480 }
@@ -252,9 +550,9 @@
252 550 // Success: Redirect or show success message
253 551 if (response.data && (response.data.redirect_url || response.data.redirect)) {
254 552 window.location.href = response.data.redirect_url || response.data.redirect;
255 553 } else {
256 - showFormSuccess($form, response.data.message || 'Login successful!');
554 + showFormSuccess($form, response.data.message || __('Login successful!', 'yatra'));
257 555 setTimeout(() => {
258 556 window.location.reload();
259 557 }, 1500);
260 558 }
@@ -259,18 +557,18 @@
259 557 }, 1500);
260 558 }
261 559 } else {
262 560 // Error: Show error message
263 - showFormError($form, response.data.message || 'Login failed. Please try again.');
561 + showFormError($form, response.data.message || __('Login failed. Please try again.', 'yatra'));
264 562 }
265 563 });
266 -
564 +
267 565 // Handle error
268 566 ajaxRequest.fail(function(xhr, status, error) {
269 - let errorMessage = 'Network error. Please check your connection and try again.';
270 -
567 + let errorMessage = __('Network error. Please check your connection and try again.', 'yatra');
568 +
271 569 if (status === 'timeout') {
272 - errorMessage = 'Request timed out. Please try again.';
570 + errorMessage = __('Request timed out. Please try again.', 'yatra');
273 571 } else if (xhr.responseJSON && xhr.responseJSON.data) {
274 572 errorMessage = xhr.responseJSON.data.message || errorMessage;
275 573 }
276 574