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 +288 -6 3.0.6 → 3.0.16 View file →
@@ -66,13 +66,295 @@
66 66
67 67 // Initialize components
68 68 initPasswordToggles();
69 69 initAjaxLogin();
70 + initPanelSwitching();
71 + initAjaxRegister();
72 + initAjaxForgotPassword();
70 73 initAccessibility();
71 74 initPerformanceOptimizations();
72 75 });
73 76
74 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 + /**
75 357 * Initialize password toggle functionality with performance optimizations
76 358 */
77 359 function initPasswordToggles() {
78 360 const passwordToggles = $('.yatra-password-toggle');
@@ -190,9 +472,9 @@
190 472 if (yatra_ajax?.debug) {
191 473 setTimeout(() => {
192 474 const entries = performance.getEntriesByType('navigation');
193 475 if (entries.length > 0) {
194 - 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');
195 477 }
196 478 }, 1000);
197 479 }
198 480 }
@@ -275,18 +557,18 @@
275 557 }, 1500);
276 558 }
277 559 } else {
278 560 // Error: Show error message
279 - showFormError($form, response.data.message || 'Login failed. Please try again.');
561 + showFormError($form, response.data.message || __('Login failed. Please try again.', 'yatra'));
280 562 }
281 563 });
282 -
564 +
283 565 // Handle error
284 566 ajaxRequest.fail(function(xhr, status, error) {
285 - let errorMessage = 'Network error. Please check your connection and try again.';
286 -
567 + let errorMessage = __('Network error. Please check your connection and try again.', 'yatra');
568 +
287 569 if (status === 'timeout') {
288 - errorMessage = 'Request timed out. Please try again.';
570 + errorMessage = __('Request timed out. Please try again.', 'yatra');
289 571 } else if (xhr.responseJSON && xhr.responseJSON.data) {
290 572 errorMessage = xhr.responseJSON.data.message || errorMessage;
291 573 }
292 574