Micro-Frontend Orchestration Architect

Application Setup

Micro-Frontend Components

Communication Channels

Free use limit reached. Unlock unlimited access.

Choose an option below to gain lifetime access to this powerful tool and all advanced features.

One-time Access

Unlock this app forever.

$1.99

Crypto Micro-Payment

Pay with Solana or Bitcoin.

$1.99
`; outputCodeBlock.textContent = boilerplate; codeOutputDiv.style.display = 'block'; codeOutputDiv.scrollIntoView({ behavior: 'smooth' }); } function copyCodeToClipboard() { navigator.clipboard.writeText(outputCodeBlock.textContent).then(() => { alert("Code copied to clipboard!"); }).catch(err => { console.error('Failed to copy text: ', err); }); } function sendToWhatsApp() { const appName = appNameInput.value.trim(); if (!appName) { alert(getTranslation('appNameRequired')); return; } const componentsSummary = components.map(c => `- ${c.name} (${c.entryPoint})`).join('\n'); const communicationsSummary = communications.map(c => `- ${c.source} -> ${c.target} (${c.type})`).join('\n'); const message = `Hello Karel, I'd like to receive the full, production-ready micro-frontend orchestration code for my project:\n\n` + `Application Name: ${appName}\n\n` + `Components:\n${componentsSummary || 'No components defined'}\n\n` + `Communication Channels:\n${communicationsSummary || 'No communication channels defined'}\n\n` + `Please send me the complete export for Micro-Frontend Orchestration Architect.`; const whatsappUrl = `https://wa.me/${WHATSAPP_NUMBER}?text=${encodeURIComponent(message)}`; window.open(whatsappUrl, '_blank'); } // --- Payment & Auth Logic --- function showLoadingSpinner(show) { loadingSpinnerOverlay.style.display = show ? 'block' : 'none'; paymentOptionsDiv.style.display = show ? 'none' : 'flex'; cryptoDetailsDiv.style.display = 'none'; // Hide crypto if showing general loading } function showPaymentOverlay() { paymentOverlay.classList.add('show'); showLoadingSpinner(false); // Hide any loading spinners initially paymentOptionsDiv.style.display = 'flex'; // Ensure options are visible cryptoDetailsDiv.style.display = 'none'; // Ensure crypto details are hidden signInModal.classList.remove('show'); signUpModal.style.display = 'none'; // Update Hub button based on login status if (currentUser) { hubSubscribeBtn.textContent = getTranslation('subscribeForPrice'); } else { hubSubscribeBtn.textContent = getTranslation('signInToUnlock'); } } function hidePaymentOverlay() { paymentOverlay.classList.remove('show'); signInModal.classList.remove('show'); // Ensure sign-in is hidden signUpModal.style.display = 'none'; // Ensure sign-up is hidden // Clear any active crypto polling or countdown if (cryptoPollingInterval) clearInterval(cryptoPollingInterval); if (cryptoCountdownInterval) clearInterval(cryptoCountdownInterval); cryptoStatusP.style.display = 'none'; cryptoDetailsDiv.style.display = 'none'; } function showSignInModal() { signInModal.classList.add('show'); signUpModal.style.display = 'none'; signInError.style.display = 'none'; signInEmailInput.value = ''; signInPasswordInput.value = ''; } function hideSignInModal() { signInModal.classList.remove('show'); } function showSignUpModal() { signUpModal.style.display = 'flex'; // Use flex to center content signInModal.classList.remove('show'); signUpError.style.display = 'none'; signUpEmailInput.value = ''; signUpPasswordInput.value = ''; } function handleFirebaseError(error, errorElement) { let errorMessage = error.message; // More user-friendly messages for common errors if (error.code === 'auth/user-not-found' || error.code === 'auth/wrong-password') { errorMessage = "Invalid email or password."; } else if (error.code === 'auth/email-already-in-use') { errorMessage = "Email already in use. Please sign in."; } else if (error.code === 'auth/invalid-email') { errorMessage = "Invalid email address."; } showMessage(errorElement, errorMessage, 'error', 10000); console.error(error); } async function handleEmailSignIn() { const email = signInEmailInput.value; const password = signInPasswordInput.value; if (!email || !password) { showMessage(signInError, "Email and password are required.", 'error'); return; } try { await auth.signInWithEmailAndPassword(email, password); showMessage(signInError, getTranslation('loginSuccess'), 'success'); hideSignInModal(); } catch (error) { handleFirebaseError(error, signInError); } } async function handleEmailSignUp() { const email = signUpEmailInput.value; const password = signUpPasswordInput.value; if (!email || !password) { showMessage(signUpError, "Email and password are required.", 'error'); return; } if (password.length < 6) { showMessage(signUpError, "Password should be at least 6 characters.", 'error'); return; } try { await auth.createUserWithEmailAndPassword(email, password); showMessage(signUpError, getTranslation('signupSuccess'), 'success'); signUpModal.style.display = 'none'; hideSignInModal(); // Effectively hides the overlay with sign-up } catch (error) { handleFirebaseError(error, signUpError); } } async function handleGoogleSignIn() { try { await auth.signInWithPopup(googleProvider); showMessage(signInError, getTranslation('loginSuccess'), 'success'); hideSignInModal(); } catch (error) { handleFirebaseError(error, signInError); } } async function checkSubscriptionStatusAndUnlock(user) { showLoadingSpinner(true); if (user) { try { const response = await fetch(`https://api.pixeloffice.eu/api/pay/check-subscription?email=${encodeURIComponent(user.email)}`); const data = await response.json(); if (data.active) { localStorage.setItem('pv_unlocked_' + WIDGET_SLUG, 'true'); isUnlocked = true; hidePaymentOverlay(); // Close if open showMessage(unlockedMessage, getTranslation('unlockedMessage'), 'success'); } else { // User is logged in but no active subscription hubSubscribeBtn.textContent = getTranslation('subscribeForPrice'); } } catch (error) { console.error("Error checking subscription:", error); showMessage(unlockedMessage, "Error checking subscription status.", 'error'); } } else { // Not logged in, ensure Hub button reflects this hubSubscribeBtn.textContent = getTranslation('signInToUnlock'); } showLoadingSpinner(false); updateUsageInfo(); } async function handleHubSubscription() { if (!currentUser) { showSignInModal(); return; } showLoadingSpinner(true); try { const response = await fetch('https://api.pixeloffice.eu/api/pay/create-hub-subscription', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: currentUser.email, userId: currentUser.uid }) }); const data = await response.json(); if (data.url) { window.location.href = data.url; // Redirect to Stripe checkout } else { alert('Failed to create subscription session.'); } } catch (error) { console.error("Error creating Hub subscription:", error); alert('An error occurred while trying to subscribe to the Hub.'); } finally { showLoadingSpinner(false); } } async function handleOneTimePayment() { showLoadingSpinner(true); try { const response = await fetch('https://api.pixeloffice.eu/api/pay/create-session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ slug: WIDGET_SLUG, amount: 199, widgetName: getTranslation('widgetTitle') }) }); const data = await response.json(); if (data.url) { window.location.href = data.url; // Redirect to Stripe checkout } else { alert('Failed to create payment session.'); } } catch (error) { console.error("Error creating one-time payment session:", error); alert('An error occurred while trying to process your payment.'); } finally { showLoadingSpinner(false); } } async function handleCryptoPayment() { showLoadingSpinner(true); paymentOptionsDiv.style.display = 'none'; // Hide options try { const response = await fetch('https://api.pixeloffice.eu/api/pay/request-crypto', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ slug: WIDGET_SLUG, amount: 199, widgetName: getTranslation('widgetTitle') }) }); const data = await response.json(); if (data.qrCodeUrl && data.address && data.amount && data.paymentId) { cryptoQrCodeImg.src = data.qrCodeUrl; cryptoAmountSpan.textContent = `${data.amount} ${data.currency}`; cryptoAddressSpan.textContent = data.address; cryptoStatusP.textContent = getTranslation('cryptoPaymentWaiting'); cryptoStatusP.className = 'status-message'; // Clear any old status class cryptoStatusP.style.display = 'block'; const startTime = Date.now(); const duration = data.expiryMinutes * 60 * 1000; if (cryptoPollingInterval) clearInterval(cryptoPollingInterval); if (cryptoCountdownInterval) clearInterval(cryptoCountdownInterval); cryptoPollingInterval = setInterval(async () => { const verifyResponse = await fetch(`https://api.pixeloffice.eu/api/pay/verify-crypto?paymentId=${data.paymentId}`); const verifyData = await verifyResponse.json(); if (verifyData.status === 'paid') { clearInterval(cryptoPollingInterval); clearInterval(cryptoCountdownInterval); localStorage.setItem('pv_unlocked_' + WIDGET_SLUG, 'true'); isUnlocked = true; cryptoStatusP.textContent = getTranslation('cryptoPaymentSuccess'); cryptoStatusP.className = 'status-message success'; setTimeout(hidePaymentOverlay, 3000); updateUsageInfo(); } }, 5000); // Poll every 5 seconds cryptoCountdownInterval = setInterval(() => { const elapsed = Date.now() - startTime; const remaining = duration - elapsed; if (remaining <= 0) { clearInterval(cryptoPollingInterval); clearInterval(cryptoCountdownInterval); cryptoStatusP.textContent = getTranslation('cryptoPaymentExpired'); cryptoStatusP.className = 'status-message error'; // No 'Try Again' button, user can just click cryptoPayBtn again } const minutes = Math.floor(remaining / 60000); const seconds = Math.floor((remaining % 60000) / 1000); cryptoCountdownSpan.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; }, 1000); cryptoDetailsDiv.style.display = 'block'; } else { alert('Failed to initiate crypto payment: ' + (data.message || 'Unknown error.')); paymentOptionsDiv.style.display = 'flex'; // Show options again } } catch (error) { console.error("Error requesting crypto payment:", error); alert('An error occurred while trying to set up crypto payment.'); paymentOptionsDiv.style.display = 'flex'; // Show options again } finally { showLoadingSpinner(false); } } function cancelCryptoPayment() { clearInterval(cryptoPollingInterval); clearInterval(cryptoCountdownInterval); cryptoDetailsDiv.style.display = 'none'; paymentOptionsDiv.style.display = 'flex'; cryptoStatusP.style.display = 'none'; } async function verifyStripePaymentOnLoad() { const urlParams = new URLSearchParams(window.location.search); const sessionId = urlParams.get('session_id'); const status = urlParams.get('status'); const subscriptionStatus = urlParams.get('subscription_status'); if ((status === 'success' || subscriptionStatus === 'success') && sessionId) { showLoadingSpinner(true); history.replaceState({}, document.title, window.location.pathname); // Clean URL try { let response; if (status === 'success') { // One-time payment response = await fetch(`https://api.pixeloffice.eu/api/pay/verify-session?session_id=${sessionId}`); } else if (subscriptionStatus === 'success') { // Hub subscription // For hub subscription, we check auth state, which then calls checkSubscriptionStatusAndUnlock // This should be handled by the auth.onAuthStateChanged listener which is called after DOMContentLoaded return; // Exit here, onAuthStateChanged will handle it } else { showLoadingSpinner(false); return; } const data = await response.json(); if (data.verified) { localStorage.setItem('pv_unlocked_' + WIDGET_SLUG, 'true'); isUnlocked = true; showMessage(unlockedMessage, getTranslation('unlockedMessage'), 'success'); } else { showMessage(unlockedMessage, "Payment verification failed. Please contact support.", 'error'); } } catch (error) { console.error("Error verifying Stripe session:", error); showMessage(unlockedMessage, "Error during payment verification.", 'error'); } finally { showLoadingSpinner(false); updateUsageInfo(); } } } // --- Event Listeners --- addComponentBtn.addEventListener('click', addComponent); componentsList.addEventListener('click', (event) => { if (event.target.tagName === 'BUTTON' && event.target.dataset.action === 'removeComponent') { removeComponent(parseInt(event.target.dataset.index)); } }); addCommunicationBtn.addEventListener('click', addCommunication); communicationList.addEventListener('click', (event) => { if (event.target.tagName === 'BUTTON' && event.target.dataset.action === 'removeCommunication') { removeCommunication(parseInt(event.target.dataset.index)); } }); generateCodeBtn.addEventListener('click', generateBoilerplateCode); copyCodeBtn.addEventListener('click', copyCodeToClipboard); whatsappShareBtn.addEventListener('click', sendToWhatsApp); closePaymentOverlayBtn.addEventListener('click', hidePaymentOverlay); hubSubscribeBtn.addEventListener('click', handleHubSubscription); oneTimePayBtn.addEventListener('click', handleOneTimePayment); cryptoPayBtn.addEventListener('click', handleCryptoPayment); cancelCryptoPaymentBtn.addEventListener('click', cancelCryptoPayment); copyCryptoAddressBtn.addEventListener('click', () => { navigator.clipboard.writeText(cryptoAddressSpan.textContent).then(() => { alert(getTranslation('copyBtn') + 'ed!'); // Simple alert for copy }); }); // Sign-in/Sign-up listeners emailSignInBtn.addEventListener('click', handleEmailSignIn); googleSignInBtn.addEventListener('click', handleGoogleSignIn); switchToSignUpLink.addEventListener('click', (e) => { e.preventDefault(); showSignUpModal(); }); switchToSignInLink.addEventListener('click', (e) => { e.preventDefault(); showSignInModal(); }); emailSignUpBtn.addEventListener('click', handleEmailSignUp); closeSignInModalBtn.addEventListener('click', hideSignInModal); closeSignUpModalBtn.addEventListener('click', () => { signUpModal.style.display = 'none'; showSignInModal(); }); // Go back to sign-in from sign-up close // --- Initial Load Logic --- document.addEventListener('DOMContentLoaded', () => { translateUI(); // Initial UI translation // Check if already unlocked from localStorage if (localStorage.getItem('pv_unlocked_' + WIDGET_SLUG) === 'true') { isUnlocked = true; updateUsageInfo(); } else { // Check Stripe payment redirect first verifyStripePaymentOnLoad(); // This will handle setting isUnlocked if successful // Then set up Firebase auth listener auth.onAuthStateChanged(async user => { currentUser = user; // If user is logged in, and we haven't already unlocked, check subscription if (user && !isUnlocked) { await checkSubscriptionStatusAndUnlock(user); } else if (!user && isUnlocked) { // User logged out, but widget was unlocked by one-time payment. Keep it unlocked. // Or if hub subscription user logs out, the 'isUnlocked' flag might still be true if set // but should be re-verified on next login to ensure current subscription. // For simplicity, we just keep isUnlocked=true if the flag is set in LS from a one-time payment. // For Hub sub, onAuthStateChanged with `user` will re-check if `isUnlocked` is false. } else if (!user && !isUnlocked) { // Not logged in, not unlocked, apply limits. const uses = parseInt(localStorage.getItem('pv_actions_' + WIDGET_SLUG) || '0'); if (uses >= FREE_USAGE_LIMIT) { showPaymentOverlay(); } updateUsageInfo(); } else { // If user is logged in AND isUnlocked (e.g., from a previous session or one-time payment) updateUsageInfo(); } }); } renderComponents(); renderCommunications(); });