PageSourceSearch

https://www.theteacherscorner.net/includes/js/registration-modal.js?v=1790345380

js theteacherscorner.net collected 2026-09-25 14:10:02 UTC 47,793 bytes, 1,160 lines download raw bytes

1/**
2 * Registration Modal System - Main Controller
3 * This file orchestrates the modal flow, relying on specialized modules for
4 * payment processing (Stripe, PayPal) and validation.
5 */
6
7// Global variables for Stripe Elements (PCI DSS compliant)
8let stripe = null;
9let cardElement = null;
10let subscriptionStripe = null;
11let subscriptionCardElement = null;
12let retryStripe = null;
13let retryCardElement = null;
14
15// Global variables for PayPal
16let paypalSDKLoaded = false;
17let payPalButtonsRendered = false; // Add flag to prevent duplicate rendering
18
19// Global variable to store selected plan
20let selectedPlan = null;
21
22// Store form data globally to preserve on errors
23let savedFormData = {};
24
25
26function showRegisterModal() {
27	showPlanSelectionModal();
28}
29
30function showSubscriptionModal() {
31	showPlanSelectionModal();
32}
33
34function showSubscriptionModalFromModule() {
35	showPlanSelectionModal();
36}
37
38function showPlanSelectionModal() {
39	Swal.fire({
40		title: 'Loading Subscription Plans...',
41		text: 'Please wait...',
42		allowOutsideClick: false,
43		allowEscapeKey: false,
44		showConfirmButton: false,
45		didOpen: () => Swal.showLoading()
46	});
47
48	fetch('https://www.theteacherscorner.net/member/registration_api.php', {
49		method: 'POST',
50		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
51		credentials: 'include', // Fix for cross-subdomain session cookies
52		body: 'action=get_plans'
53	})
54	.then(response => response.json())
55	.then(data => {
56		if (data.success) {
57			displayPlanSelectionModal(data.plans);
58		} else {
59			Swal.fire('Error', data.message || 'Failed to load subscription plans', 'error');
60		}
61	})
62	.catch(error => {
63		Swal.fire('Error', 'Failed to load subscription plans. Please try again.', 'error');
64	});
65}
66
67function displayPlanSelectionModal(plans) {
68	let plansHtml = '<div class="plans-grid">';
69	
70	plans.forEach(plan => {
71		const isRecurring = Boolean(plan.recurring && plan.recurring !== "0" && plan.recurring !== 0);
72		const recurringText = isRecurring ? '<span class="recurring-yes">Recurring: YES</span>' : 'Recurring: <b>NO</b>';
73		const processedDescription = plan.description.replace(
74			/click here for included features/gi,
75			'<a href="#" onclick="event.stopPropagation(); showFeaturesOverlay(); return false;" class="features-link">click here for included features</a>'
76		);
77		
78		plansHtml += `
79			<div class="plan-card" data-plan-id="${plan.id}" onclick="selectPlan(${plan.id}, '${plan.title}', '${plan.price}', '${plan.days} ${plan.period}', ${isRecurring}, '${plan.pp_plan_id || ''}')">
80				<div class="plan-title" style="background-color: #${plan.color};">${plan.title}</div>
81				<div class="plan-price" style="background-color: #${plan.color};">
82					${plan.price}
83					<span>${plan.days} ${plan.period}</span>
84				</div>
85				<div class="plan-recurring">${recurringText}</div>
86				<div class="plan-description">${processedDescription}</div>
87				<button type="button" class="plan-select-btn">Select This Plan</button>
88			</div>
89		`;
90	});
91	
92	plansHtml += '</div>';
93	
94	const featuresOverlayHtml = `
95		<div id="features-overlay" class="features-overlay" style="display: none;">
96			<div class="features-overlay-content">
97				<div class="features-overlay-header">
98					<h3>Premium Subscription Benefits</h3>
99					<button type="button" class="features-close-btn" onclick="hideFeaturesOverlay()">×</button>
100				</div>
101				<div class="features-overlay-body">
102					${getSubscriptionFeaturesHTML()}
103				</div>
104			</div>
105		</div>
106	`;
107
108	Swal.fire({
109		title: 'Choose Your Subscription Plan',
110		html: plansHtml + featuresOverlayHtml,
111		footer: '<div class="back-to-login"><a href="#" onclick="event.preventDefault(); showLoginModal();">Go back to Login</a></div>',
112		showCancelButton: true,
113		showConfirmButton: false,
114		cancelButtonText: 'Cancel',
115		cancelButtonColor: '#999',
116		customClass: { popup: 'swal2-plans-modal' },
117		width: '90%',
118		maxWidth: '1000px'
119	});
120}
121
122function showFeaturesOverlay() {
123	const overlay = document.getElementById('features-overlay');
124	if (overlay) {
125		overlay.style.display = 'flex';
126		overlay.addEventListener('click', function(e) {
127			if (e.target === overlay) {
128				hideFeaturesOverlay();
129			}
130		});
131		document.addEventListener('keydown', handleEscapeKey);
132	}
133}
134
135function hideFeaturesOverlay() {
136	const overlay = document.getElementById('features-overlay');
137	if (overlay) {
138		overlay.style.display = 'none';
139		document.removeEventListener('keydown', handleEscapeKey);
140	}
141}
142
143function handleEscapeKey(e) {
144	if (e.key === 'Escape') {
145		hideFeaturesOverlay();
146	}
147}
148
149function getSubscriptionFeaturesHTML() {
150	return `
151		<div class="subscription-features" style="font-size: 12px; color: #009; text-align: left; padding: 15px; background: #f9f9f9; border-radius: 8px; margin-top: 10px;">
152			<p style="font-weight: 700; font-size: 1.1em; color: red; margin-bottom: 10px;">
153				<a href="#" onclick="event.preventDefault(); hideFeaturesOverlay();" style="color: #00f;">Click here to view all of our Premium Subscription plans</a>
154			</p>
155			<p style="margin-bottom: 10px;">
156				<img src="https://www.theteacherscorner.net/images/world-famous-seal.png" width="74" height="55" alt="World Famous Puzzle and Worksheet Makers" style="float: right; margin-left: 10px;">
157				Subscription Features include:
158			</p>
159			<ul style="margin-left: 0em; font-size: 11px;">
160				<li><b>All Banner Ads removed from the entire website</b><br>
161					<span style="font-size: 9px; font-style: italic;"><b style="color:red;font-weight:bold">Note:</b> Subscriptions do NOT allow citation removal from puzzles and worksheets created on our site.</span>
162				</li>
163				<li><strong>More options available for customizing puzzles!</strong>
164					<ul style="margin-left: 0em;">
165						<li>
165More AI generated word lists and clues!</li>
166						<li>More stock images to choose from!</li>
167						<li>More images allowed per puzzle!</li>
168						<li>More definition lookups (crossword and match-ups)</li>
169						<li>Many more available fonts! (over 125 fonts!)</li>
170						<li>Use ANY font installed on your computer</li>
171						<li>20 word list save slots - and access your saved lists from anywhere!</li>
172						<li>More word/clues for crosswords (up to 50)</li>
173						<li>More word/clues for match-ups (up to 100)</li>
174						<li>More word/clues for fill-in-the-blank (up to 100)</li>
175						<li>More word/clues for word scrambles (up to 100)</li>
176						<li>More options for Sudoku puzzles</li>
177						<li>No wait delays imposed for saving puzzles to an image or PDF</li>
178						<li>'Fake Bank' on our Fill-in-the-Blank worksheets</li>
179						<li>More features coming soon</li>
180					</ul>
181				</li>
182				<li><strong>More Daily Writing Prompts!</strong><br>
183					Over 30 extra prompts for our premium members for every month! (excludes July)<br>
184					<i>Note: Each month of Premium Daily Writing Prompts are made available 21 days before the start of the month.</i>
185				</li>
186				<li><b>All subscriptions support The Teacher's Corner!</b></li>
187				<li><b>FAQ:</b> Can I remove the "<em>Created on The Teacher's Corner</em>" (or similar) citation on the puzzles I create? <b>No</b>. That is not a benefit of our subscriptions. All citations must remain on worksheets and puzzles you create.</li>
188			</ul>
189			<p style="margin-top: 10px; font-size: 11px;">
190				<a href="#" onclick="event.preventDefault(); hideFeaturesOverlay();" style="color: #00f; font-weight: bold;">Go here to view our Premium Subscription plans</a>
191			</p>
192			<p style="font-size: 10px; margin-top: 5px; font-weight:bold">NOTE: ALL Packages contain the same features, longer subscriptions get better price breaks</p>
193			<p style="font-size: 10px;">SAFE: All subscriptions can be paid via Paypal or Stripe, so your sensitive banking information is never seen nor stored by our website.</p>
194			<p style="font-size: 10px;">Click on <a href="https://www.facebook.com/theteacherscorner" rel="noopener" target="_blank">Facebook</a> and LIKE US to get up-to-date information and tips!</p>
195		</div>
196	`;
197}
198
199function selectPlan(planId, title, price, duration, recurring, pp_plan_id) {
200	selectedPlan = {
201		id: planId,
202		title: title,
203		price: price,
204		duration: duration,
205		recurring: recurring,
206        pp_plan_id: pp_plan_id
207	};
208	
209	checkUserLoginStatus().then(status => {
210		if (status.isLoggedIn) {
211			showSubscriptionPaymentModal(status.userInfo);
212		} else {
213			showRegistrationModal();
214		}
215	});
216}
217
218function checkUserLoginStatus() {
219	return fetch('https://www.theteacherscorner.net/member/registration_api.php', {
220		method: 'POST',
221		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
222		credentials: 'include', // Fix for cross-subdomain session cookies
223		body: 'action=check_login_status'
224	})
225	.then(response => response.json())
226	.then(data => {
227        console.log("Login Status Check:", data); // Added for debugging
228		return { isLoggedIn: data.success && data.logged_in, userInfo: data.user_info };
229	})
230	.catch(error => {
231		console.error('Error checking login status:', error);
232		return { isLoggedIn: false, userInfo: null };
233	});
234}
235
236async function showSubscriptionPaymentModal(userInfo) {
237    if (!selectedPlan) {
238        Swal.fire('Error', 'Please select a subscription plan first.', 'error');
239        return;
240    }
241
242    // Create/update the cart on the backend before showing the payment modal
243    try {
244        const cartResponse = await fetch('https://www.theteacherscorner.net/member/registration_api.php', {
245            method: 'POST',
246            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
247            credentials: 'include',
248            body: `action=create_or_update_cart&user_id=${userInfo.user_id}&membership_id=${selectedPlan.id}`
249        });
250        const cartData = await cartResponse.json();
251        if (!cartData.success) {
252            throw new Error(cartData.message || 'Failed to update cart.');
253        }
254    } catch (error) {
255        console.error("Cart update error:", error);
256        Swal.fire('Error', 'Could not prepare your transaction. Please try again.', 'error');
257        return;
258    }
259
260	const recurringText = selectedPlan.recurring ? 'Yes (Cancel Anytime)' : 'No';
261
262	Swal.fire({
263		title: 'Complete Your Subscription',
264		html: `
265			<div class="subscription-payment-form">
266				<div class="form-section">
267					<div class="selected-plan-summary">
268						<h4>Selected Plan: ${selectedPlan.title}</h4>
269						<div class="plan-summary-details">
270							<span>${selectedPlan.duration} • Recurring: ${recurringText}</span>
271							<span class="plan-summary-price">${selectedPlan.price}</span>
272						</div>
273					</div>
274				</div>
275				<div class="form-section">
276					<h3>Payment Method</h3>
277					<div id="ngrok-reminder-message" class="ngrok-reminder" style="display: none; margin-bottom: 15px; padding: 10px; background-color: #fff3cd; border: 1px solid #ffeeba; color: #856404; border-radius: 5px; font-family: monospace; font-size: 12px;"></div>
278					<div class="payment-methods" id="subscription-payment-methods-container"></div>
279					<div id="subscription-stripe-fields" class="stripe-fields">
280						<div class="form-group">
281							<label for="subscription-card-element">Card Information:</label>
282							<div id="subscription-card-element" class="stripe-card-element"></div>
283							<div id="subscription-card-errors" class="stripe-card-errors" role="alert"></div>
284						</div>
285					</div>
286                    <div id="subscription-paypal-button-container" style="display: none; margin-top: 20px;"></div>
287				</div>
288			</div>
289		`,
290		footer: '<div class="back-to-login"><a href="#" onclick="event.preventDefault(); showPlanSelectionModal();">← Back to Plan Selection</a></div>',
291		showCancelButton: true,
292		confirmButtonText: 'Complete Payment',
293		cancelButtonText: 'Cancel',
294		confirmButtonColor: '#666633',
295		cancelButtonColor: '#999',
296		customClass: { popup: 'swal2-registration-modal' },
297		width: '90%',
298		maxWidth: '600px',
299		didOpen: async () => {
300		          payPalButtonsRendered = false; // Reset flag when modal opens
301			const submitBtn = Swal.getConfirmButton();
302			if (submitBtn) {
303				submitBtn.disabled = true;
304				submitBtn.style.opacity = '0.5';
305				submitBtn.style.cursor = 'not-allowed';
306			}
307			populateSubscriptionPaymentMethods();
308			initializeSubscriptionStripeElements();
309		},
310		preConfirm: () => {
311			return processSubscriptionPayment();
312		}
313	});
314}
315
316// Define event handler in a broader scope to prevent re-declaration
317function handlePaymentMethodClick(event, selectFunction) {
318    const target = event.target.closest('.payment-method');
319    if (target) {
320        // Reset rendered flag whenever a new payment method is chosen
321        payPalButtonsRendered = false;
322        console.log("PayPal rendered flag reset to false.");
323        window[selectFunction](target.dataset.gatewayId, target.dataset.gatewayDir, false);
324    }
325}
326
327function populatePaymentMethodsGeneric(containerId, radioName, idPrefix, stripeFieldsId, validityCheckFunction, selectFunction) {
328    const container = document.getElementById(containerId);
329    if (!container || !selectedPlan) return;
330
331    // Use event delegation. Bind a specific instance of the handler.
332    const specificHandler = (event) => handlePaymentMethodClick(event, selectFunction);
333    
334    // Clean up old listeners before adding new ones.
335    const oldHandler = container.currentHandler;
336    if (oldHandler) {
337        container.removeEventListener('click', oldHandler);
338    }
339    container.addEventListener('click', specificHandler);
340    container.currentHandler = specificHandler; // Store reference for cleanup
341
342    fetch('https://www.theteacherscorner.net/member/registration_api.php', {
343        method: 'POST',
344        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
345        credentials: 'include',
346        body: `action=get_gateways&membership_id=${selectedPlan.id}&recurring=${selectedPlan.recurring}`
347    })
348    .then(response => response.json())
349    .then(data => {
350        const ngrokReminderDiv = document.getElementById('ngrok-reminder-message');
351        if (data.ngrok_reminder && ngrokReminderDiv) {
352            ngrokReminderDiv.innerHTML = data.ngrok_reminder;
353            ngrokReminderDiv.style.display = 'block';
354        } else if (ngrokReminderDiv) {
355            ngrokReminderDiv.style.display = 'none';
356            ngrokReminderDiv.innerHTML = '';
357        }
358
359        if (data.success && data.gateways) {
360            let paymentMethodsHtml = '';
361            data.gateways.forEach((gateway, index) => {
362                const isFirst = index === 0;
363                const checkedAttr = (data.gateways.length === 1 || isFirst) ? 'checked' : '';
364                paymentMethodsHtml += `
365                    <div class="payment-method ${checkedAttr ? 'selected' : ''}" data-gateway-id="${gateway.id}" data-gateway-dir="${gateway.dir}">
366                        <input type="radio" name="${radioName}" value="${gateway.id}" id="${idPrefix}${gateway.id}" ${checkedAttr}>
367                        <label for="${idPrefix}${gateway.id}">${gateway.displayname}</label>
368                        <div style="font-size: 0.8em; color: #666; margin-top: 5px;">${gateway.description || ''}</div>
369                    </div>
370                `;
371            });
372            container.innerHTML = paymentMethodsHtml;
373
374            if (data.gateways.length > 0) {
375                setTimeout(() => {
376                    const firstGateway = data.gateways[0];
377                    window[selectFunction](firstGateway.id, firstGateway.dir, true);
378                    validityCheckFunction();
379                }, 100);
380            }
381        } else {
382            container.innerHTML = '<div style="color: red;">No payment methods available for this plan.</div>';
383        }
384    })
385    .catch(error => {
386        console.error('Error loading payment methods:', error);
387        container.innerHTML = '<div style="color: red;">Error loading payment methods. Please try again.</div>';
388    });
389}
390
391function populateSubscriptionPaymentMethods() {
392	populatePaymentMethodsGeneric('subscription-payment-methods-container', 'subscription_payment_method', 'sub_payment_', 'subscription-stripe-fields', checkSubscriptionFormValidity, 'selectSubscriptionPaymentMethod');
393}
394
395function populatePaymentMethods() {
396	populatePaymentMethodsGeneric('payment-methods-container', 'payment_method', 'payment_', 'stripe-fields', checkFormValidity, 'selectPaymentMethodWithData');
397}
398
399function selectPaymentMethodGeneric(gatewayId, gatewayDir, radioName, stripeFieldsId, validityCheckFunction, isAutoSelect = false, paymentData = {}) {
400    // Reset Stripe completeness state whenever a new method is selected
401    window.isStripeElementComplete = false;
402
403    const containerId = (radioName === 'payment_method') ? 'payment-methods-container' : 'subscription-payment-methods-container';
404    document.querySelectorAll(`#${containerId} .payment-method`).forEach(el => el.classList.remove('selected'));
405    
406    const selectedDiv = document.querySelector(`#${containerId} .payment-method[data-gateway-id='${gatewayId}']`);
407    if (selectedDiv) {
408        selectedDiv.classList.add('selected');
409    }
410
411    const radioButton = document.querySelector(`input[name="${radioName}"][value="${gatewayId}"]`);
412    if (radioButton) radioButton.checked = true;
413
414    const stripeFields = document.getElementById(stripeFieldsId);
415    const payPalContainerId = (stripeFieldsId === 'stripe-fields') ? 'paypal-button-container' : 'subscription-paypal-button-container';
416    const payPalContainer = document.getElementById(payPalContainerId);
417    const confirmBtn = Swal.getConfirmButton();
418
419    if (stripeFields && payPalContainer) {
420        if (gatewayDir === 'stripe') {
421            stripeFields.style.display = 'block';
422            stripeFields.classList.add('active');
423            payPalContainer.style.display = 'none';
424            if(confirmBtn) confirmBtn.style.display = 'block';
425        } else if (gatewayDir === 'paypal_modal') {
426            stripeFields.style.display = 'none';
427            stripeFields.classList.remove('active');
428            payPalContainer.style.display = 'block';
429            if(confirmBtn) confirmBtn.style.display = 'none';
430            const isSubscription = (stripeFieldsId === 'subscription-stripe-fields');
431            
432            // For new registrations, paymentData is passed down from processRegistrationAndPayment
433            // For existing users, we need to fetch their ID.
434            let finalPaymentData = paymentData;
435            if (isSubscription && !finalPaymentData.user_id) {
436                 const status = checkUserLoginStatus();
437                 status.then(s => {
438                    if(s.isLoggedIn) {
439                        finalPaymentData.user_id = s.userInfo.user_id;
440                        handlePayPalPayment(finalPaymentData, isSubscription);
441                    }
442                 });
443            } else {
444                 handlePayPalPayment(finalPaymentData, isSubscription);
445            }
446
447        } else { // for legacy paypal, etc.
448            stripeFields.style.display = 'none';
449            stripeFields.classList.remove('active');
450            payPalContainer.style.display = 'none';
451            if(confirmBtn) confirmBtn.style.display = 'block';
452        }
453    }
454    validityCheckFunction();
455}
456
457function selectSubscriptionPaymentMethod(gatewayId, gatewayDir, isAutoSelect = false) {
458	selectPaymentMethodGeneric(gatewayId, gatewayDir, 'subscription_payment_method', 'subscription-stripe-fields', checkSubscriptionFormValidity, isAutoSelect);
459}
460
461function selectPaymentMethodWithData(gatewayId, gatewayDir, isAutoSelect = false) {
462	selectPaymentMethodGeneric(gatewayId, gatewayDir, 'payment_method', 'stripe-fields', checkFormValidity, isAutoSelect, { /* empty payment data for now */ });
463}
464
465function checkSubscriptionFormValidity() {
466	const submitBtn = Swal.getConfirmButton();
467	if (!submitBtn) return;
468	
469	const paymentMethod = document.querySelector('input[name="subscription_payment_method"]:checked');
470	if (!paymentMethod) {
471		submitBtn.disabled = true;
472		return;
473	}
474	
475	const stripeFields = document.getElementById('subscription-stripe-fields');
476	if (stripeFields && stripeFields.classList.contains('active')) {
477		const cardErrors = document.getElementById('subscription-card-errors');
478		const hasErrors = cardErrors && cardErrors.textContent.trim() !== '';
479		const cardElementContainer = document.getElementById('subscription-card-element');
480		if (hasErrors || !cardElementContainer || !cardElementContainer.hasChildNodes()) {
481			submitBtn.disabled = true;
482			return;
483		}
484	}
485	submitBtn.disabled = false;
486}
487
488async function processSubscriptionPayment() {
489	const paymentMethod = document.querySelector('input[name="subscription_payment_method"]:checked');
490	if (!paymentMethod) {
491		Swal.showValidationMessage('Please select a payment method');
492		return false;
493	}
494	
495	const stripeFields = document.getElementById('subscription-stripe-fields');
496	const isStripeWithFields = stripeFields && stripeFields.classList.contains('active');
497	
498	if (isStripeWithFields) {
499		try {
500			if (!subscriptionStripe || !subscriptionCardElement) {
501				Swal.showValidationMessage('Stripe payment system not initialized');
502				return false;
503			}
504			const {token, error} = await subscriptionStripe.createToken(subscriptionCardElement);
505			if (error) {
506				Swal.showValidationMessage(error.message);
507				return false;
508			}
509			if (!token) {
510				Swal.showValidationMessage('Failed to create payment token');
511				return false;
512			}
513			processLoggedInStripePaymentWithToken(paymentMethod.value, token.id);
514		} catch (error) {
515			console.error('Stripe tokenization error:', error);
516			Swal.showValidationMessage('Payment processing error. Please try again.');
517			return false;
518		}
519	}
520	return false;
521}
522
523// Helper function to save form data to sessionStorage
524function saveRegistrationDataToSession() {
525    try {
526        const formData = {
527            fname: document.getElementById('reg_fname')?.value || '',
528            lname: document.getElementById('reg_lname')?.value || '',
529            username: document.getElementById('reg_username')?.value || '',
530            email: document.getElementById('reg_email')?.value || '',
531            confirm_email: document.getElementById('reg_confirm_email')?.value || '',
532            // Do NOT store password fields
533        };
534        sessionStorage.setItem('ttcRegistrationData', JSON.stringify(formData));
535    } catch (error) {
536        console.error("Error saving registration data to session storage:", error);
537    }
538}
539
540// Helper function to restore form data from sessionStorage
541function restoreRegistrationDataFromSession() {
542    try {
543        const savedDataJSON = sessionStorage.getItem('ttcRegistrationData');
544        if (!savedDataJSON) return;
545
546        const savedData = JSON.parse(savedDataJSON);
547
548        if (document.getElementById('reg_fname')) document.getElementById('reg_fname').value = savedData.fname || '';
549        if (document.getElementById('reg_lname')) document.getElementById('reg_lname').value = savedData.lname || '';
550        if (document.getElementById('reg_username')) document.getElementById('reg_username').value = savedData.username || '';
551        if (document.getElementById('reg_email')) document.getElementById('reg_email').value = savedData.email || '';
552        if (document.getElementById('reg_confirm_email')) document.getElementById('reg_confirm_email').value = savedData.confirm_email || '';
553
554        // Re-trigger validation for all fields that were restored
555        setTimeout(() => {
556            Object.keys(savedData).forEach(key => {
557                if (savedData[key] && typeof validateField === 'function') {
558                    const fieldName = key.replace('reg_', '');
559                    if (['fname', 'lname', 'username', 'email', 'confirm_email'].includes(fieldName)) {
560                        validateField(fieldName, savedData[key]);
561                    }
562                }
563            });
564        }, 250);
565    } catch (error) {
566        console.error("Error restoring registration data from session storage:", error);
567        // If data is corrupt, remove it
568        sessionStorage.removeItem('ttcRegistrationData');
569    }
570}
571
572function showRegistrationModal() {
573	if (!selectedPlan) {
574		Swal.fire('Error', 'Please select a subscription plan first.', 'error');
575		return;
576	}
577
578	const recurringText = selectedPlan.recurring ? 'Yes (Cancel Anytime)' : 'No';
579
580	Swal.fire({
581		title: 'Complete Your Registration',
582		html: `
583			<div class="registration-payment-form">
584				<div class="form-section">
585					<div class="selected-plan-summary">
586						<h4>Selected Plan: ${selectedPlan.title}</h4>
587						<div class="plan-summary-details">
588							<span>${selectedPlan.duration} • Recurring: ${recurringText}</span>
589							<span class="plan-summary-price">${selectedPlan.price}</span>
590						</div>
591					</div>
592				</div>
593				<div class="form-section">
594					<h3>Account Information</h3>
595					<div class="form-group"><label for="reg_email">Email Address:</label><input type="email" id="reg_email" name="email" autocomplete="off" required><div id="email_validation" class="validation-message"></div></div>
596					<div class="form-group"><label for="reg_confirm_email">Confirm Email:</label><input type="email" id="reg_confirm_email" name="confirm_email" autocomplete="off" required><div id="confirm_email_validation" class="validation-message"></div></div>
597					<div class="form-row">
598						<div class="form-group">
598<label for="reg_fname">First Name:</label><input type="text" id="reg_fname" name="fname" required></div>
599						<div class="form-group"><label for="reg_lname">Last Name:</label><input type="text" id="reg_lname" name="lname" required></div>
600					</div>
601					<div class="form-group"><label for="reg_username">Username:</label><input type="text" id="reg_username" name="username" autocomplete="off" required><div id="username_validation" class="validation-message"></div></div>
602					<div class="form-row">
603						<div class="form-group"><label for="reg_password">Password:</label><input type="password" id="reg_password" name="password" required><div id="password_validation" class="validation-message"></div></div>
604						<div class="form-group"><label for="reg_password2">Confirm Password:</label><input type="password" id="reg_password2" name="password2" autocomplete="off" required><div id="password2_validation" class="validation-message"></div></div>
605					</div>
606				</div>
607				<div class="form-section">
608					<h3>Payment Method</h3>
609					<div id="ngrok-reminder-message" class="ngrok-reminder" style="display: none; margin-bottom: 15px; padding: 10px; background-color: #fff3cd; border: 1px solid #ffeeba; color: #856404; border-radius: 5px; font-family: monospace; font-size: 12px;"></div>
610					<div class="payment-methods" id="payment-methods-container"></div>
611					<div id="stripe-fields" class="stripe-fields">
612						<div class="form-group"><label for="registration-card-element">Card Information:</label><div id="registration-card-element" class="stripe-card-element"></div><div id="registration-card-errors" class="stripe-card-errors" role="alert"></div></div>
613					</div>
614                    <div id="paypal-button-container" style="display: none; margin-top: 20px;"></div>
615				</div>
616			</div>
617		`,
618		footer: '<div class="back-to-login"><a href="#" onclick="event.preventDefault(); showPlanSelectionModal();">← Back to Plan Selection</a></div>',
619		showCancelButton: true,
620		confirmButtonText: 'Complete Registration & Payment',
621		cancelButtonText: 'Cancel',
622		confirmButtonColor: '#666633',
623		cancelButtonColor: '#999',
624		customClass: { popup: 'swal2-registration-modal' },
625		width: '90%',
626		maxWidth: '600px',
627		didOpen: async () => {
628		    payPalButtonsRendered = false; // Reset flag when modal opens
629			const submitBtn = Swal.getConfirmButton();
630			if (submitBtn) {
631				submitBtn.disabled = true;
632				submitBtn.style.opacity = '0.5';
633				submitBtn.style.cursor = 'not-allowed';
634			}
635			populatePaymentMethods();
636			initializeStripeElements();
637			setTimeout(() => {
638		              addFormValidationListeners();
639		              restoreRegistrationDataFromSession(); // Restore data on open
640
641		              // Add listeners to save subsequent changes to session storage
642		              const form = document.querySelector('.swal2-html-container .registration-payment-form');
643		              if (form) {
644		                  form.addEventListener('input', (e) => {
645		                      // Only save for text and email inputs, ignore passwords
646		                      if (e.target.matches('input[type="text"], input[type="email"]')) {
647		                          saveRegistrationDataToSession();
648		                      }
649		                  });
650		              }
651		          }, 200);
652		},
653		preConfirm: () => {
654			return processRegistrationAndPayment();
655		}
656	});
657	setTimeout(() => setupRealTimeValidation(), 100);
658}
659
660async function processRegistrationAndPayment() {
661	saveFormData();
662	
663	const formData = new FormData();
664	formData.append('fname', document.getElementById('reg_fname').value);
665	formData.append('lname', document.getElementById('reg_lname').value);
666	formData.append('username', document.getElementById('reg_username').value);
667	formData.append('email', document.getElementById('reg_email').value);
668	formData.append('confirm_email', document.getElementById('reg_confirm_email').value);
669	formData.append('password', document.getElementById('reg_password').value);
670	formData.append('password2', document.getElementById('reg_password2').value);
671	formData.append('membership_id', selectedPlan.id);
672	
673	const paymentMethod = document.querySelector('input[name="payment_method"]:checked');
674	if (!paymentMethod) {
675		Swal.showValidationMessage('Please select a payment method');
676		return false;
677	}
678	formData.append('payment_method', paymentMethod.value);
679	
680	const stripeFields = document.getElementById('stripe-fields');
681	const isStripeWithFields = stripeFields && stripeFields.classList.contains('active');
682	
683	if (isStripeWithFields) {
684		try {
685			if (!stripe || !cardElement) {
686				Swal.showValidationMessage('Stripe payment system not initialized');
687				return false;
688			}
689			const {token, error} = await stripe.createToken(cardElement);
690			if (error) {
691				Swal.showValidationMessage(error.message);
692				return false;
693			}
694			if (!token) {
695				Swal.showValidationMessage('Failed to create payment token');
696				return false;
697			}
698			formData.append('stripe_token', token.id);
699			formData.append('process_stripe_direct', '1');
700		} catch (error) {
701			console.error('Stripe tokenization error:', error);
702			Swal.showValidationMessage('Payment processing error. Please try again.');
703			return false;
704		}
705	}
706	
707	Swal.fire({
708		title: 'Processing...',
709		text: 'Creating your account and processing payment...',
710		allowOutsideClick: false,
711		allowEscapeKey: false,
712		showConfirmButton: false,
713		didOpen: () => Swal.showLoading()
714	});
715	
716	formData.append('action', 'register_user');
717	
718	fetch('https://www.theteacherscorner.net/member/registration_api.php', {
719		method: 'POST',
720		credentials: 'include', // Fix for cross-subdomain session cookies
721		body: formData
722	})
723	.then(response => response.json())
724	.then(data => {
725		if (data.success) {
726		          sessionStorage.removeItem('ttcRegistrationData'); // Clear saved form data on success
727			updatePageForLoggedInUser();
728			if (data.gateway_dir === 'paypal_modal') {
729			             // The user is registered, now show the PayPal buttons to complete payment.
730			             // We hide the main submit button and show the PayPal container.
731			             const confirmBtn = Swal.getConfirmButton();
732			             if(confirmBtn) confirmBtn.style.display = 'none';
733			             
734			             const paypalContainer = document.getElementById('paypal-button-container');
735			             if(paypalContainer) paypalContainer.style.display = 'block';
736			             
737			             // We call handlePayPalPayment with the data from the registration response,
738			             // which now includes user_id and total_price.
739			    handlePayPalPayment(data, false);
740			} else if (data.requires_redirect) {
741				handleLegacyPayPalPayment(data);
742			} else if (data.requires_gateway_form || data.gateway_dir === 'stripe') {
743				processStripePaymentDirectWithToken(data);
744			} else {
745				handleGatewayForm(data);
746			}
747		} else {
748			Swal.fire('Error', data.message, 'error').then(() => showRegistrationModalWithData());
749		}
750	})
751	.catch(error => {
752		Swal.fire('Error', 'An error occurred. Please try again.', 'error').then(() => showRegistrationModalWithData());
753	});
754	
755	return false;
756}
757
758function saveFormData() {
759	savedFormData = {
760		fname: document.getElementById('reg_fname')?.value || '',
761		lname: document.getElementById('reg_lname')?.value || '',
762		username: document.getElementById('reg_username')?.value || '',
763		email: document.getElementById('reg_email')?.value || '',
764		confirm_email: document.getElementById('reg_confirm_email')?.value || '',
765		payment_method: document.querySelector('input[name="payment_method"]:checked')?.value || ''
766	};
767}
768
769function showRegistrationModalWithData() {
770	showRegistrationModal();
771	setTimeout(() => restoreFormData(), 100);
772}
773
774function restoreFormData() {
775	if (Object.keys(savedFormData).length === 0) return;
776	
777	Object.keys(savedFormData).forEach(key => {
778		const field = document.getElementById('reg_' + key) || document.getElementById(key);
779		if (field && savedFormData[key]) {
780			field.value = savedFormData[key];
781		}
782	});
783	
784	if (savedFormData.payment_method) {
785		const paymentRadio = document.getElementById('payment_' + savedFormData.payment_method);
786		if (paymentRadio) {
787			paymentRadio.checked = true;
788		}
789	}
790	
791	setTimeout(() => {
792		setupRealTimeValidation();
793		Object.keys(savedFormData).forEach(key => {
794			if (savedFormData[key] && key !== 'payment_method') {
795				const fieldName = key.replace('reg_', '');
796				if (['fname', 'lname', 'username', 'email', 'confirm_email', 'password', 'password2'].includes(fieldName)) {
797					validateField(fieldName, savedFormData[key]);
798				}
799			}
800		});
801	}, 200);
802}
803
804function updatePageForLoggedInUser() {
805	const loginBtn = document.querySelector('.login-btn, .register-btn, [onclick*="showLoginModal"], [onclick*="showRegisterModal"]');
806	if (loginBtn) loginBtn.style.display = 'none';
807	
808	const userMenu = document.querySelector('.user-menu, .account-menu, .logged-in-menu');
809	if (userMenu) userMenu.style.display = 'block';
810	
811	const loginStatus = document.querySelector('.login-status');
812	if (loginStatus) loginStatus.textContent = 'Logged In';
813}
814
815function showPaymentRetryModal(userId, membershipId, errorMessage) {
816	fetch('https://www.theteacherscorner.net/member/registration_api.php', {
817		method: 'POST',
818		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
819		credentials: 'include', // Fix for cross-subdomain session cookies
820		body: `action=get_plans`
821	})
822	.then(response => response.json())
823	.then(data => {
824		if (data.success) {
825			const membership = data.plans.find(plan => plan.id == membershipId);
826			if (!membership) {
827				Swal.fire('Error', 'Membership plan not found', 'error');
828				return;
829			}
830			displayPaymentRetryModal(userId, membership, errorMessage);
831		} else {
832			Swal.fire('Error', 'Failed to load membership details', 'error');
833		}
834	})
835	.catch(error => {
836		Swal.fire('Error', 'Failed to load membership details. Please try again.', 'error');
837	});
838}
839
840function displayPaymentRetryModal(userId, membership, errorMessage) {
841	const isRecurring = Boolean(membership.recurring && membership.recurring !== "0" && membership.recurring !== 0);
842	const recurringText = isRecurring ? 'Yes (Cancel Anytime)' : 'No';
843	
844	Swal.fire({
845		title: 'Payment Failed - Try Again',
846		html: `
847			<div class="payment-retry-form">
848				<div class="error-message" style="background-color: #ffebee; color: #c62828; padding: 15px; border-radius: 5px; margin-bottom: 20px;">
849					<strong>Payment Error:</strong> ${errorMessage}
850				</div>
851				<div class="form-section">
852					<div class="selected-plan-summary">
853						<h4>Selected Plan: ${membership.title}</h4>
854						<div class="plan-summary-details">
855							<span>${membership.days} ${membership.period} • Recurring: ${recurringText}</span>
856							<span class="plan-summary-price">${membership.price}</span>
857						</div>
858					</div>
859				</div>
860				<div class="form-section">
861					<h3>Choose Payment Method</h3>
862					<div class="payment-methods" id="retry-payment-methods-container"></div>
863					<div id="retry-stripe-fields" class="stripe-fields">
864						<div class="form-group"><label for="retry-card-element">Card Information:</label><div id="retry-card-element" class="stripe-card-element"></div><div id="retry-card-errors" class="stripe-card-errors" role="alert"></div></div>
865					</div>
866				</div>
867			</div>
868		`,
869		showCancelButton: true,
870		confirmButtonText: 'Retry Payment',
871		cancelButtonText: 'Cancel',
872		confirmButtonColor: '#666633',
873		cancelButtonColor: '#999',
874		customClass: { popup: 'swal2-payment-retry-modal' },
875		width: '90%',
876		maxWidth: '500px',
877		didOpen: async () => {
878			const submitBtn = Swal.getConfirmButton();
879			if (submitBtn) {
880				submitBtn.disabled = true;
881				submitBtn.style.opacity = '0.5';
882				submitBtn.style.cursor = 'not-allowed';
883			}
884			populateRetryPaymentMethods(membership.id, membership.recurring);
885			initializeRetryStripeElements();
886		},
887		preConfirm: () => {
888			return processPaymentRetry(userId, membership.id);
889		}
890	});
891}
892
893function populateRetryPaymentMethods(membershipId, isRecurring) {
894	const container = document.getElementById('retry-payment-methods-container');
895	if (!container) return;
896	
897	fetch('https://www.theteacherscorner.net/member/registration_api.php', {
898		method: 'POST',
899		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
900		credentials: 'include', // Fix for cross-subdomain session cookies
901		body: `action=get_gateways&membership_id=${membershipId}&recurring=${isRecurring}`
902	})
903	.then(response => response.json())
904	.then(data => {
905		if (data.success && data.gateways) {
906			let paymentMethodsHtml = '';
907			data.gateways.forEach((gateway, index) => {
908				const isFirst = index === 0;
909				const checkedAttr = (data.gateways.length === 1 || isFirst) ? 'checked' : '';
910				paymentMethodsHtml += `
911					<div class="payment-method ${checkedAttr ? 'selected' : ''}" data-gateway-id="${gateway.id}" data-gateway-dir="${gateway.dir}">
912						<input type="radio" name="retry_payment_method" value="${gateway.id}" id="retry_payment_${gateway.id}" ${checkedAttr}>
913						<label for="retry_payment_${gateway.id}">${gateway.displayname}</label>
914						<div style="font-size: 0.8em; color: #666; margin-top: 5px;">${gateway.description || ''}</div>
915					</div>
916				`;
917			});
918			container.innerHTML = paymentMethodsHtml;
919
920			         // Use event delegation for the retry modal as well
921			         container.removeEventListener('click', handleRetryPaymentClick);
922			         container.addEventListener('click', handleRetryPaymentClick);
923
924			         function handleRetryPaymentClick(event) {
925			             const target = event.target.closest('.payment-method');
926			             if (target) {
927			                 selectRetryPaymentMethod(target.dataset.gatewayId, target.dataset.gatewayDir);
928			             }
929			         }
930			
931			if (data.gateways.length > 0) {
932				setTimeout(() => {
933					const firstGateway = data.gateways[0];
934			                 selectRetryPaymentMethod(firstGateway.id, firstGateway.dir, true);
935					checkRetryFormValidity();
936				}, 100);
937			}
938		} else {
939			container.innerHTML = '<div style="color: red;">No payment methods available for this plan.</div>';
940		}
941	})
942	.catch(error => {
943		console.error('Error loading payment methods:', error);
944		container.innerHTML = '<div style="color: red;">Error loading payment methods. Please try again.</div>';
945	});
946}
947
948function selectRetryPaymentMethod(methodId, gatewayDir, isAutoSelect = false) {
949    document.querySelectorAll('#retry-payment-methods-container .payment-method').forEach(el => el.classList.remove('selected'));
950    
951    const selectedDiv = document.querySelector(`#retry-payment-methods-container .payment-method[data-gateway-id="${methodId}"]`);
952    if (selectedDiv) {
953        selectedDiv.classList.add('selected');
954    }
955
956    const radioButton = document.getElementById('retry_payment_' + methodId);
957    if (radioButton) radioButton.checked = true;
958
959    const stripeFields = document.getElementById('retry-stripe-fields');
960    if (stripeFields) {
961        if (gatewayDir === 'stripe') {
962            stripeFields.classList.add('active');
963        } else {
964            stripeFields.classList.remove('active');
965        }
966    }
967    checkRetryFormValidity();
968}
969
970function checkRetryFormValidity() {
971	const submitBtn = Swal.getConfirmButton();
972	if (!submitBtn) return;
973	
974	const paymentMethod = document.querySelector('input[name="retry_payment_method"]:checked');
975	if (!paymentMethod) {
976		submitBtn.disabled = true;
977		return;
978	}
979	
980	const stripeFields = document.getElementById('retry-stripe-fields');
981	if (stripeFields && stripeFields.classList.contains('active')) {
982		const cardErrors = document.getElementById('retry-card-errors');
983		const hasErrors = cardErrors && cardErrors.textContent.trim() !== '';
984		const cardElementContainer = document.getElementById('retry-card-element');
985		if (hasErrors || !cardElementContainer || !cardElementContainer.hasChildNodes()) {
986			submitBtn.disabled = true;
987			return;
988		}
989	}
990	submitBtn.disabled = false;
991}
992
993async function processPaymentRetry(userId, membershipId) {
994	const paymentMethod = document.querySelector('input[name="retry_payment_method"]:checked');
995	if (!paymentMethod) {
996		Swal.showValidationMessage('Please select a payment method');
997		return false;
998	}
999	
1000	const stripeFields = document.getElementById('retry-stripe-fields');
1001	const isStripeWithFields = stripeFields && stripeFields.classList.contains('active');
1002	
1003	if (isStripeWithFields) {
1004		try {
1005			if (!retryStripe || !retryCardElement) {
1006				Swal.showValidationMessage('Stripe payment system not initialized');
1007				return false;
1008			}
1009			const {token, error} = await retryStripe.createToken(retryCardElement);
1010			if (error) {
1011				Swal.showValidationMessage(error.message);
1012				return false;
1013			}
1014			if (!token) {
1015				Swal.showValidationMessage('Failed to create payment token');
1016				return false;
1017			}
1018			processRetryStripePaymentWithToken(userId, membershipId, paymentMethod.value, token.id);
1019		} catch (error) {
1020			console.error('Stripe tokenization error:', error);
1021			Swal.showValidationMessage('Payment processing error. Please try again.');
1022			return false;
1023		}
1024	} else {
1025		handleRetryPayPalPayment(userId, membershipId, paymentMethod.value);
1026	}
1027	return false;
1028}
1029
1030function handleRetryPayPalPayment(userId, membershipId, gatewayId) {
1031	Swal.fire({
1032		title: 'Redirecting to PayPal...',
1033		text: 'Please wait while we prepare your payment.',
1034		allowOutsideClick: false,
1035		didOpen: () => Swal.showLoading()
1036	});
1037	
1038	fetch('https://www.theteacherscorner.net/member/registration_api.php', {
1039		method: 'POST',
1040		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1041		credentials: 'include', // Fix for cross-subdomain session cookies
1042		body: `action=get_paypal_url&user_id=${userId}&membership_id=${membershipId}&gateway_id=${gatewayId}`
1043	})
1044	.then(response => response.json())
1045	.then(formData => {
1046		if (formData.success) {
1047			if (formData.paypal_form_html) {
1048				Swal.fire({
1049					title: 'Redirecting to PayPal...',
1050					text: 'You are logged in and will be redirected to PayPal to complete payment...',
1051					icon: 'info',
1052					timer: 2000,
1053					showConfirmButton: false
1054				}).then(() => {
1055					const formContainer = document.createElement('div');
1056					formContainer.style.display = 'none';
1057					formContainer.innerHTML = formData.paypal_form_html;
1058					document.body.appendChild(formContainer);
1059					setTimeout(() => {
1060						const paypalForm = document.getElementById('pp_form');
1061						if (paypalForm) paypalForm.submit();
1062					}, 500);
1063				});
1064			} else if (formData.paypal_url) {
1065				Swal.fire({
1066					title: 'Redirecting to PayPal...',
1067					text: 'You are logged in and will be redirected to PayPal to complete payment...',
1068					icon: 'info',
1069					timer: 3000,
1070					showConfirmButton: false
1071				}).then(() => {
1072					window.location.href = formData.paypal_url;
1073				});
1074			} else {
1075				Swal.fire('Error', 'Invalid PayPal response format', 'error');
1076			}
1077		} else {
1078			Swal.fire('Error', formData.message || 'Failed to generate PayPal form', 'error');
1079		}
1080	})
1081	.catch(error => {
1082		console.error('PayPal form request failed:', error);
1083		Swal.fire('Error', 'Failed to setup PayPal payment. Please try again.', 'error');
1084	});
1085}
1086
1087
1088function pollForSessionAndReload(userId) {
1089	const pollInterval = 2000; // 2 seconds
1090	const pollTimeout = 40000; // 40 seconds
1091	let pollTimer;
1092	let timeoutTimer;
1093
1094	const cleanup = () => {
1095		clearInterval(pollTimer);
1096		clearTimeout(timeoutTimer);
1097	};
1098
1099	const finalizeAndReload = () => {
1100		cleanup();
1101		updatePageForLoggedInUser();
1102		Swal.fire({
1103			title: 'Account Activated!',
1104			text: 'Your subscription is active. You will now be logged in.',
1105			icon: 'success',
1106			timer: 3000,
1107			showConfirmButton: false
1108		}).then(() => {
1109			window.location.reload();
1110		});
1111	};
1112
1113	// Show waiting modal
1114	Swal.fire({
1115		title: 'Finalizing Your Account...',
1116		html: 'Waiting to receive credentials, please wait.',
1117		allowOutsideClick: false,
1118		allowEscapeKey: false,
1119		showConfirmButton: false,
1120		didOpen: () => {
1121			Swal.showLoading();
1122		}
1123	});
1124
1125	pollTimer = setInterval(() => {
1126		const payload = new URLSearchParams();
1127		payload.append('action', 'check_payment_status');
1128		payload.append('user_id', userId);
1129
1130		fetch('https://www.theteacherscorner.net/member/registration_api.php', {
1131			method: 'POST',
1132			headers: {
1133				'Content-Type': 'application/x-www-form-urlencoded',
1134			},
1135			credentials: 'include',
1136			body: payload.toString()
1137		})
1138		.then(res => res.json())
1139		.then(data => {
1140			if (data && data.success && (data.paid || data.is_paid)) {
1141				finalizeAndReload();
1142			}
1143		})
1144		.catch(err => {
1145			console.error('Polling error:', err);
1146			// Continue polling and rely on timeout to notify the user
1147		});
1148	}, pollInterval);
1149
1150	timeoutTimer = setTimeout(() => {
1151		cleanup();
1152		Swal.fire({
1153			title: 'It is taking longer than usual',
1154			html: 'It is taking longer than usual to receive your payment.<br><br>Please try to log in in 1 minute, then EMAIL so we can rectify: <a href="mailto:[email protected]">[email protected]</a>, THEN leave a message on our voicemail: +1 (720) 663-8322<br><br>We sincerely apologize for the inconvenience and appreciate your patience.',
1155			icon: 'info',
1156			confirmButtonText: 'OK',
1157			confirmButtonColor: '#666633'
1158		});
1159	}, pollTimeout);
1160}

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.