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 +335 -38 3.0.2.7 → 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 }
@@ -213,29 +511,19 @@
213 511 }
214 512
215 513 // Reset previous errors
216 514 $('.yatra-form-error').remove();
515 + // Field-level validation is already handled by validateLoginForm()
217 516
218 - // Validate username
219 - if (!$username.val().trim()) {
220 - showFieldError($username, 'Please enter your email or username.');
221 - isValid = false;
222 - }
223 -
224 - // Validate password
225 - if (!$password.val().trim()) {
226 - showFieldError($password, 'Please enter your password.');
227 - isValid = false;
228 - }
229 -
230 - if (!isValid) {
231 - return false;
232 - }
233 -
234 517 // Show loading state immediately to prevent flicker
235 518 $submitBtn.prop('disabled', true);
236 - const originalText = $submitBtn.text();
237 - $submitBtn.html('<span class="yatra-spinner"></span> ' + (yatra_ajax?.strings?.loading || 'Loading...'));
519 + // Preserve the original markup once, then toggle the built-in loading spans.
520 + if (!$submitBtn.data('yatraOriginalHtml')) {
521 + $submitBtn.data('yatraOriginalHtml', $submitBtn.html());
522 + }
523 + $submitBtn.addClass('yatra-is-loading');
524 + $submitBtn.find('.yatra-btn-text').hide();
525 + $submitBtn.find('.yatra-btn-loading').show();
238 526
239 527 // Prepare AJAX request data
240 528 const formData = {
241 529 action: 'yatra_ajax_login',
@@ -240,10 +528,11 @@
240 528 const formData = {
241 529 action: 'yatra_ajax_login',
242 530 username: $username.val().trim(),
243 531 password: $password.val().trim(),
244 - remember: $form.find('[name="rememberme"]').is(':checked') ? 'forever' : '',
245 - security: $form.find('[name="yatra-login-nonce"]').val(),
532 + rememberme: $form.find('[name="rememberme"]').is(':checked') ? 'forever' : '',
533 + yatra_login_nonce: $form.find('[name="yatra_login_nonce"]').val(),
534 + redirect_to: $form.find('[name="redirect_to"]').val() || '',
246 535 ajax: true
247 536 };
248 537
249 538 // Perform AJAX request with timeout
@@ -258,12 +547,12 @@
258 547 // Handle success
259 548 ajaxRequest.done(function(response) {
260 549 if (response.success) {
261 550 // Success: Redirect or show success message
262 - if (response.data.redirect) {
263 - window.location.href = response.data.redirect;
551 + if (response.data && (response.data.redirect_url || response.data.redirect)) {
552 + window.location.href = response.data.redirect_url || response.data.redirect;
264 553 } else {
265 - showFormSuccess($form, response.data.message || 'Login successful!');
554 + showFormSuccess($form, response.data.message || __('Login successful!', 'yatra'));
266 555 setTimeout(() => {
267 556 window.location.reload();
268 557 }, 1500);
269 558 }
@@ -268,18 +557,18 @@
268 557 }, 1500);
269 558 }
270 559 } else {
271 560 // Error: Show error message
272 - showFormError($form, response.data.message || 'Login failed. Please try again.');
561 + showFormError($form, response.data.message || __('Login failed. Please try again.', 'yatra'));
273 562 }
274 563 });
275 -
564 +
276 565 // Handle error
277 566 ajaxRequest.fail(function(xhr, status, error) {
278 - let errorMessage = 'Network error. Please check your connection and try again.';
279 -
567 + let errorMessage = __('Network error. Please check your connection and try again.', 'yatra');
568 +
280 569 if (status === 'timeout') {
281 - errorMessage = 'Request timed out. Please try again.';
570 + errorMessage = __('Request timed out. Please try again.', 'yatra');
282 571 } else if (xhr.responseJSON && xhr.responseJSON.data) {
283 572 errorMessage = xhr.responseJSON.data.message || errorMessage;
284 573 }
285 574
@@ -288,9 +577,17 @@
288 577
289 578 // Always restore button state
290 579 ajaxRequest.always(function() {
291 580 $submitBtn.prop('disabled', false);
292 - $submitBtn.text(originalText);
581 + $submitBtn.removeClass('yatra-is-loading');
582 + $submitBtn.find('.yatra-btn-loading').hide();
583 + $submitBtn.find('.yatra-btn-text').show();
584 +
585 + // If some other code replaced the markup, restore it.
586 + const originalHtml = $submitBtn.data('yatraOriginalHtml');
587 + if (originalHtml && $submitBtn.find('.yatra-btn-text').length === 0) {
588 + $submitBtn.html(originalHtml);
589 + }
293 590 });
294 591
295 592 return false;
296 593 });