Universal Component Code Scaffolder

Component Definition

Framework-specific generation is a premium feature.

Framework-specific generation is a premium feature.

\n\n`; generatedCode += ``; break; case 'web-components': generatedCode = `// Web Component: ${componentName}.js\n\n`; generatedCode += `class ${componentName} extends HTMLElement {\n`; generatedCode += ` constructor() {\n`; generatedCode += ` super();\n`; generatedCode += ` this.attachShadow({ mode: 'open' });\n`; generatedCode += ` }\n\n`; generatedCode += ` static get observedAttributes() {\n`; generatedCode += ` return [${props.map(p => `'${p.name.toLowerCase()}'`).join(', ')}];\n`; generatedCode += ` }\n\n`; generatedCode += ` connectedCallback() {\n`; generatedCode += ` this.render();\n`; if (basicLogic) { generatedCode += ` // Basic Logic / Internal State setup\n`; generatedCode += basicLogic.split('\n').map(line => ` ${line}`).join('\n') + '\n'; } generatedCode += ` }\n\n`; generatedCode += ` attributeChangedCallback(name, oldValue, newValue) {\n`; generatedCode += ` if (oldValue !== newValue) {\n`; generatedCode += ` this.render();\n`; generatedCode += ` }\n`; generatedCode += ` }\n\n`; generatedCode += ` render() {\n`; generatedCode += ` this.shadowRoot.innerHTML = \`\n`; generatedCode += ` \n`; generatedCode += `
\n`; generatedCode += ` \n`; props.forEach(p => { generatedCode += `

${p.name}: \${this.getAttribute('${p.name.toLowerCase()}') || '${p.defaultValue || 'none'}'}

\n`; }); generatedCode += `\n`; generatedCode += ` \n`; if (slots.length > 0) { slots.forEach(s => { generatedCode += ` \n`; }); } else { generatedCode += ` \n`; } generatedCode += `
\n`; generatedCode += ` \`;\n`; generatedCode += ` }\n`; generatedCode += `}\n\n`; generatedCode += `customElements.define('my-${componentName.toLowerCase()}', ${componentName});`; break; default: // Fallback to HTML/CSS for premium if somehow a non-premium framework is selected generatedCode = `\n\n`; generatedCode += `
\n`; generatedCode += ` \n`; props.forEach(p => { generatedCode += `

${p.name}: (Type: ${p.type || 'any'}, Default: ${p.defaultValue || 'none'})

\n`; }); generatedCode += `\n`; generatedCode += ` \n`; slots.forEach(s => { generatedCode += `
\n`; }); generatedCode += `\n`; if (basicLogic) { generatedCode += ` \n`; generatedCode += ` \n`; } generatedCode += `
\n\n`; generatedCode += ``; break; } } else { // FREE: Basic HTML/CSS generation generatedCode = `\n\n`; generatedCode += `
\n`; if (props.length > 0) { generatedCode += ` \n`; props.forEach(p => { generatedCode += `

${p.name}: (Type: ${p.type || 'any'}, Default: ${p.defaultValue || 'none'})

\n`; }); } else { generatedCode += ` \n`; } generatedCode += `\n`; if (slots.length > 0) { generatedCode += ` \n`; slots.forEach(s => { generatedCode += `
\n`; }); } else { generatedCode += ` \n`; } generatedCode += `\n`; if (basicLogic) { generatedCode += ` \n`; generatedCode += ` \n`; } else { generatedCode += ` \n`; } generatedCode += `
\n\n`; generatedCode += ``; } document.getElementById('generatedCode').textContent = generatedCode; document.getElementById('outputPanel').style.display = 'block'; if (!isUnlocked) { usageCount++; localStorage.setItem(`pv_actions_${WIDGET_SLUG}`, usageCount.toString()); if (usageCount >= FREE_USES_LIMIT) { showPaymentOverlay(); } } } function copyCodeToClipboard() { const code = document.getElementById('generatedCode').textContent; navigator.clipboard.writeText(code).then(() => { alert(translations[currentLang]['copySuccess']); }).catch(err => { console.error('Failed to copy text: ', err); alert(translations[currentLang]['copyError']); }); } function sendCodeViaWhatsApp() { const componentName = document.getElementById('componentName').value.trim() || 'N/A'; const props = getFieldValues('propsContainer', 'prop-name', 'prop-type', 'prop-default'); const slots = getFieldValues('slotsContainer', 'slot-name', 'slot-description'); const basicLogic = document.getElementById('basicLogic').value.trim(); const targetFramework = document.getElementById('targetFramework').selectedOptions[0].text; let propDetails = props.length > 0 ? props.map(p => `- ${p.name}: ${p.type || 'N/A'} (Default: ${p.defaultValue || 'none'})`).join('\n') : translations[currentLang]['whatsappMessageNoProps']; let slotDetails = slots.length > 0 ? slots.map(s => `- ${s.name}: ${s.description || 'N/A'}`).join('\n') : translations[currentLang]['whatsappMessageNoSlots']; let logicDetails = basicLogic || translations[currentLang]['whatsappMessageNoLogic']; const message = `${translations[currentLang]['whatsappMessageGreeting']} ${translations[currentLang]['whatsappMessageComponentName']} ${componentName} ${translations[currentLang]['whatsappMessageFramework']} ${targetFramework} ${translations[currentLang]['whatsappMessageProps']} ${propDetails} ${translations[currentLang]['whatsappMessageSlots']} ${slotDetails} ${translations[currentLang]['whatsappMessageLogic']} ${logicDetails} ${translations[currentLang]['whatsappMessageClosing']}`; const whatsappUrl = `https://wa.me/${WA_PHONE_NUMBER}?text=${encodeURIComponent(message)}`; window.open(whatsappUrl, '_blank'); } // --- Monetization & Auth Logic --- function showLoadingSpinner() { document.getElementById('loadingSpinner').style.display = 'flex'; } function hideLoadingSpinner() { document.getElementById('loadingSpinner').style.display = 'none'; } function updateHubButtonText() { const hubButton = document.getElementById('hubPaymentButton'); if (hubButton) { if (currentUser) { hubButton.innerHTML = translations[currentLang]['hubSubscriptionBtnSubscribe']; } else { hubButton.innerHTML = translations[currentLang]['hubSubscriptionBtnSignIn']; } } } function unlockWidget() { isUnlocked = true; localStorage.setItem(`pv_unlocked_${WIDGET_SLUG}`, "true"); document.getElementById('paymentOverlay').classList.remove('active'); document.getElementById('frameworksLockedOverlay').classList.remove('active'); document.getElementById('frameworkWarning').style.display = 'none'; // Enable premium framework options document.querySelectorAll('#targetFramework option[disabled]').forEach(option => { option.removeAttribute('disabled'); }); } function lockWidget() { isUnlocked = false; localStorage.removeItem(`pv_unlocked_${WIDGET_SLUG}`); // Ensure local storage reflects locked state // Disable premium framework options document.querySelectorAll('#targetFramework option:not([value="html-css"])').forEach(option => { option.setAttribute('disabled', 'disabled'); }); document.getElementById('targetFramework').value = 'html-css'; // Reset selection document.getElementById('frameworksLockedOverlay').classList.add('active'); document.getElementById('frameworkWarning').style.display = 'block'; } function showPaymentOverlay() { if (isUnlocked) return; // Don't show if already unlocked document.getElementById('paymentOverlay').classList.add('active'); updateHubButtonText(); } function hidePaymentOverlay() { document.getElementById('paymentOverlay').classList.remove('active'); document.getElementById('paymentError').style.display = 'none'; } function showLoginModal(isRegisterMode = false) { document.getElementById('loginModal').classList.add('active'); const title = document.getElementById('loginModalTitle'); const authButton = document.getElementById('emailAuthButton'); const toggleButton = document.getElementById('toggleAuthMode'); const loginError = document.getElementById('loginError'); loginError.style.display = 'none'; document.getElementById('loginEmail').value = ''; document.getElementById('loginPassword').value = ''; if (isRegisterMode) { title.innerHTML = translations[currentLang]['registerTitle']; authButton.innerHTML = translations[currentLang]['registerButton']; authButton.onclick = handleRegister; toggleButton.innerHTML = translations[currentLang]['switchToSignIn']; toggleButton.onclick = () => showLoginModal(false); } else { title.innerHTML = translations[currentLang]['signInTitle']; authButton.innerHTML = translations[currentLang]['signInButton']; authButton.onclick = handleLogin; toggleButton.innerHTML = translations[currentLang]['switchToRegister']; toggleButton.onclick = () => showLoginModal(true); } } function hideLoginModal() { document.getElementById('loginModal').classList.remove('active'); document.getElementById('loginError').style.display = 'none'; } async function handleLogin(event) { event.preventDefault(); const email = document.getElementById('loginEmail').value; const password = document.getElementById('loginPassword').value; const loginError = document.getElementById('loginError'); loginError.style.display = 'none'; showLoadingSpinner(); try { const userCredential = await auth.signInWithEmailAndPassword(email, password); currentUser = userCredential.user; await checkSubscriptionAndUnlock(currentUser.email); hideLoginModal(); hidePaymentOverlay(); // Close payment overlay if open alert(translations[currentLang]['loginSuccess']); } catch (error) { console.error("Login Error:", error); loginError.textContent = translations[currentLang]['loginError']; loginError.style.display = 'block'; } finally { hideLoadingSpinner(); } } async function handleRegister(event) { event.preventDefault(); const email = document.getElementById('loginEmail').value; const password = document.getElementById('loginPassword').value; const loginError = document.getElementById('loginError'); loginError.style.display = 'none'; showLoadingSpinner(); try { await auth.createUserWithEmailAndPassword(email, password); alert(translations[currentLang]['registerSuccess']); showLoginModal(false); // Switch to login after successful registration } catch (error) { console.error("Register Error:", error); loginError.textContent = translations[currentLang]['registerError'] + " " + error.message; loginError.style.display = 'block'; } finally { hideLoadingSpinner(); } } async function handleGoogleSignIn() { const loginError = document.getElementById('loginError'); loginError.style.display = 'none'; showLoadingSpinner(); try { const result = await auth.signInWithPopup(googleProvider); currentUser = result.user; await checkSubscriptionAndUnlock(currentUser.email); hideLoginModal(); hidePaymentOverlay(); alert(translations[currentLang]['loginSuccess']); } catch (error) { console.error("Google Sign-In Error:", error); loginError.textContent = translations[currentLang]['loginError'] + " " + error.message; loginError.style.display = 'block'; } finally { hideLoadingSpinner(); } } async function checkSubscriptionAndUnlock(email) { if (!email) { console.warn("No email provided for subscription check."); lockWidget(); return false; } showLoadingSpinner(); try { const response = await fetch(`${BASE_API_URL}/check-subscription?email=${encodeURIComponent(email)}`); const data = await response.json(); if (data.active) { unlockWidget(); hidePaymentOverlay(); return true; } else { lockWidget(); return false; } } catch (error) { console.error("Error checking subscription:", error); lockWidget(); // Default to locked on error return false; } finally { hideLoadingSpinner(); } } async function createStripeSession(slug, amount, widgetName, isSubscription = false) { showLoadingSpinner(); const paymentError = document.getElementById('paymentError'); paymentError.style.display = 'none'; try { let body = { slug, amount, widgetName }; let apiUrl = `${BASE_API_URL}/create-session`; if (isSubscription) { if (!currentUser || !currentUser.email || !currentUser.uid) { throw new Error("User must be logged in for subscription."); } body = { email: currentUser.email, userId: currentUser.uid }; apiUrl = `${BASE_API_URL}/create-hub-subscription`; } const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const data = await response.json(); if (data.url) { window.location.href = data.url; } else { throw new Error(data.message || "Failed to create Stripe session."); } } catch (error) { console.error("Stripe payment error:", error); paymentError.textContent = error.message; paymentError.style.display = 'block'; } finally { hideLoadingSpinner(); } } let cryptoPollInterval = null; async function showCryptoModal() { hidePaymentOverlay(); document.getElementById('cryptoModal').classList.add('active'); const cryptoStatus = document.getElementById('cryptoStatus'); cryptoStatus.style.display = 'none'; cryptoStatus.textContent = translations[currentLang]['cryptoStatusPending']; document.getElementById('cryptoQrCode').style.display = 'none'; showLoadingSpinner(); try { const response = await fetch(`${BASE_API_URL}/request-crypto`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ slug: WIDGET_SLUG, amount: STRIPE_AMOUNT, widgetName: translations[currentLang]['widgetTitle'] }) }); const data = await response.json(); if (data.address && data.amount && data.network && data.invoiceId) { document.getElementById('cryptoAddress').textContent = data.address; document.getElementById('cryptoAmount').textContent = `${(data.amount / 100).toFixed(2)} USD (or equivalent in ${data.network})`; document.getElementById('cryptoNetwork').textContent = data.network; const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(data.address)}`; document.getElementById('cryptoQrCode').src = qrCodeUrl; document.getElementById('cryptoQrCode').style.display = 'block'; cryptoStatus.style.display = 'block'; cryptoPollInterval = setInterval(() => verifyCryptoPayment(data.invoiceId), 5000); // Poll every 5 seconds } else { throw new Error(data.message || "Failed to get crypto payment details."); } } catch (error) { console.error("Crypto payment request error:", error); cryptoStatus.textContent = translations[currentLang]['cryptoStatusError'] + ": " + error.message; cryptoStatus.style.display = 'block'; } finally { hideLoadingSpinner(); } } function hideCryptoModal() { document.getElementById('cryptoModal').classList.remove('active'); if (cryptoPollInterval) { clearInterval(cryptoPollInterval); cryptoPollInterval = null; } } async function verifyCryptoPayment(invoiceId) { const cryptoStatus = document.getElementById('cryptoStatus'); try { const response = await fetch(`${BASE_API_URL}/verify-crypto?invoiceId=${invoiceId}`); const data = await response.json(); if (data.status === 'paid') { cryptoStatus.textContent = translations[currentLang]['cryptoStatusSuccess']; cryptoStatus.style.color = var('--accent-color'); unlockWidget(); hideCryptoModal(); if (cryptoPollInterval) clearInterval(cryptoPollInterval); alert(translations[currentLang]['cryptoStatusSuccess']); } else if (data.status === 'pending') { cryptoStatus.textContent = translations[currentLang]['cryptoStatusPending']; } else { // Payment failed or expired cryptoStatus.textContent = translations[currentLang]['cryptoStatusError']; cryptoStatus.style.color = '#ff007f'; if (cryptoPollInterval) clearInterval(cryptoPollInterval); } } catch (error) { console.error("Crypto verification error:", error); cryptoStatus.textContent = translations[currentLang]['cryptoStatusError'] + ": " + error.message; cryptoStatus.style.color = '#ff007f'; if (cryptoPollInterval) clearInterval(cryptoPollInterval); } } function clearUrlParams() { const url = new URL(window.location.href); const params = ['status', 'session_id', 'subscription_status']; params.forEach(param => url.searchParams.delete(param)); window.history.replaceState({}, document.title, url.toString()); } // --- Event Listeners and Initial Load --- document.addEventListener('DOMContentLoaded', () => { // Set initial language and update UI document.getElementById('language-switcher').value = currentLang; updateUI(currentLang); // Add event listeners document.getElementById('language-switcher').addEventListener('change', switchLanguage); document.getElementById('generateCodeButton').addEventListener('click', generateCode); document.getElementById('copyCodeButton').addEventListener('click', copyCodeToClipboard); document.getElementById('whatsappButton').addEventListener('click', sendCodeViaWhatsApp); document.getElementById('hubPaymentButton').addEventListener('click', async () => { if (currentUser) { await createStripeSession(WIDGET_SLUG, HUB_SUB_AMOUNT, translations[currentLang]['widgetTitle'], true); } else { showLoginModal(false); // Show login modal } }); document.getElementById('oneTimePaymentButton').addEventListener('click', () => createStripeSession(WIDGET_SLUG, STRIPE_AMOUNT, translations[currentLang]['widgetTitle']) ); document.getElementById('cryptoPaymentButton').addEventListener('click', showCryptoModal); document.getElementById('loginForm').addEventListener('submit', (e) => e.preventDefault()); // Prevent default form submission document.getElementById('googleSignInButton').addEventListener('click', handleGoogleSignIn); // --- Firebase Auth State Listener & Initial Checks --- auth.onAuthStateChanged(async user => { currentUser = user; updateHubButtonText(); // Update button text based on login state if (user) { console.log("User logged in:", user.email); // Check if already unlocked from localStorage if (isUnlocked) { console.log("Widget already unlocked via localStorage."); unlockWidget(); return; } // If not unlocked, check subscription status from API await checkSubscriptionAndUnlock(user.email); } else { console.log("User not logged in."); // If not logged in, ensure widget is locked (unless localStorage says otherwise) if (!isUnlocked) { lockWidget(); } else { console.log("Widget is unlocked via localStorage even without login."); unlockWidget(); } } }); // --- Stripe Payment Verification --- const urlParams = new URLSearchParams(window.location.search); const stripeStatus = urlParams.get('status'); // For one-time payment const subscriptionStatus = urlParams.get('subscription_status'); // For Hub subscription const sessionId = urlParams.get('session_id'); if ((stripeStatus === 'success' || subscriptionStatus === 'success') && sessionId) { showLoadingSpinner(); console.log("Stripe success detected, verifying session..."); try { let verifyUrl = `${BASE_API_URL}/verify-session?session_id=${sessionId}`; if (subscriptionStatus === 'success' && currentUser) { // For subscription success, we rely on the auth listener to check subscription status, // but a direct verification might be faster if user is already logged in. // However, the `check-subscription` API handles this correctly via email. // Let's ensure the user is logged in for hub subscription verification. // If not logged in, auth.onAuthStateChanged will eventually trigger checkSubscriptionAndUnlock // after a successful login flow. For now, assume user is logged in or will log in. verifyUrl = `${BASE_API_URL}/verify-hub-subscription?session_id=${sessionId}`; // Added for direct hub subscription verification } const response = await fetch(verifyUrl); const data = await response.json(); if (data.active || data.status === 'complete' || data.subscriptionActive) { unlockWidget(); alert("Payment successful! Widget unlocked."); hidePaymentOverlay(); } else { alert("Payment verification failed. Please try again or contact support."); lockWidget(); } } catch (error) { console.error("Payment verification error:", error); alert("An error occurred during payment verification. Please contact support."); lockWidget(); } finally { hideLoadingSpinner(); clearUrlParams(); } } else if (urlParams.get('status') === 'cancel') { alert("Payment cancelled."); clearUrlParams(); } // Initial check for usage limit or unlock status on load (if no Stripe redirect happened) if (!isUnlocked && usageCount >= FREE_USES_LIMIT && !urlParams.get('status') && !urlParams.get('subscription_status')) { showPaymentOverlay(); } else if (!isUnlocked) { lockWidget(); // Ensure framework options are locked unless explicitly unlocked } });