freezone/portal/index.html
2025-06-27 04:13:31 +02:00

334 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Zanzibar Digital Freezone Portal</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<!-- Custom CSS -->
<style>
/* Stripe Elements styling */
#payment-element {
min-height: 40px;
padding: 10px;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
background-color: #ffffff;
}
.payment-ready {
border-color: #198754 !important;
border-width: 2px !important;
box-shadow: 0 0 0 0.2rem rgba(25, 135, 84, 0.25) !important;
}
/* Loading state for payment form */
.payment-loading {
opacity: 0.7;
pointer-events: none;
}
/* Error display styling */
#payment-errors {
margin-top: 1rem;
margin-bottom: 1rem;
display: none;
}
/* Fade in animation for registration form */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Transition animations */
.transition-all {
transition: all 0.5s ease-in-out;
}
</style>
</head>
<body>
<div id="app"></div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Stripe JavaScript SDK -->
<script src="https://js.stripe.com/v3/"></script>
<!-- Stripe Integration for Portal -->
<script>
let stripe;
let elements;
let paymentElement;
// Stripe publishable key - replace with your actual key from Stripe Dashboard
const STRIPE_PUBLISHABLE_KEY = 'pk_test_51MCkZTC7LG8OeRdIcqmmoDkRwDObXSwYdChprMHJYoD2VRO8OCDBV5KtegLI0tLFXJo9yyvEXi7jzk1NAB5owj8i00DkYSaV9y';
// Initialize Stripe when the script loads
document.addEventListener('DOMContentLoaded', function() {
console.log('🔧 Zanzibar Portal Stripe integration loaded');
// Initialize Stripe
if (window.Stripe) {
stripe = Stripe(STRIPE_PUBLISHABLE_KEY);
console.log('✅ Stripe initialized for portal');
} else {
console.error('❌ Stripe.js not loaded');
}
});
// Create payment intent on server (supports both company and resident registration)
window.createPaymentIntent = async function(formDataJson) {
console.log('💳 Creating payment intent...');
try {
// Parse the JSON string from Rust
let formData;
if (typeof formDataJson === 'string') {
formData = JSON.parse(formDataJson);
} else {
formData = formDataJson;
}
// Determine endpoint based on registration type
const isResidentRegistration = formData.type === 'resident_registration';
const endpoint = isResidentRegistration
? 'http://127.0.0.1:3001/resident/create-payment-intent'
: 'http://127.0.0.1:3001/company/create-payment-intent';
console.log('📋 Registration type:', isResidentRegistration ? 'Resident' : 'Company');
console.log('🔧 Server endpoint:', endpoint);
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData)
});
console.log('📡 Server response status:', response.status);
if (!response.ok) {
const errorText = await response.text();
console.error('❌ Payment intent creation failed:', errorText);
let errorData;
try {
errorData = JSON.parse(errorText);
} catch (e) {
errorData = { error: errorText };
}
const errorMsg = errorData.error || 'Failed to create payment intent';
console.error('💥 Error details:', errorData);
throw new Error(errorMsg);
}
const responseData = await response.json();
console.log('✅ Payment intent created successfully');
console.log('🔑 Client secret received:', responseData.client_secret ? 'Yes' : 'No');
const { client_secret } = responseData;
if (!client_secret) {
throw new Error('No client secret received from server');
}
return client_secret;
} catch (error) {
console.error('❌ Payment intent creation error:', error.message);
console.error('🔧 Troubleshooting:');
console.error(' 1. Check if server is running on port 3001');
console.error(' 2. Verify Stripe API keys in .env file');
console.error(' 3. Check server logs for detailed error info');
throw error;
}
};
// Initialize Stripe Elements with client secret
window.initializeStripeElements = async function(clientSecret) {
console.log('🔧 Initializing Stripe Elements...');
console.log('🔑 Client secret format check:', clientSecret ? 'Valid' : 'Missing');
try {
if (!stripe) {
throw new Error('Stripe not initialized - check your publishable key');
}
// Create Elements instance with client secret
elements = stripe.elements({
clientSecret: clientSecret,
appearance: {
theme: 'stripe',
variables: {
colorPrimary: '#0099FF',
colorBackground: '#ffffff',
colorText: '#30313d',
colorDanger: '#df1b41',
fontFamily: 'system-ui, sans-serif',
spacingUnit: '4px',
borderRadius: '6px',
}
}
});
// Clear the payment element container first
const paymentElementDiv = document.getElementById('payment-element');
if (!paymentElementDiv) {
throw new Error('Payment element container not found');
}
paymentElementDiv.innerHTML = '';
// Create and mount the Payment Element
paymentElement = elements.create('payment');
paymentElement.mount('#payment-element');
// Handle real-time validation errors from the Payment Element
paymentElement.on('change', (event) => {
const displayError = document.getElementById('payment-errors');
if (event.error) {
displayError.textContent = event.error.message;
displayError.style.display = 'block';
displayError.classList.remove('alert-success');
displayError.classList.add('alert-danger');
} else {
displayError.style.display = 'none';
}
});
// Handle when the Payment Element is ready
paymentElement.on('ready', () => {
console.log('✅ Stripe Elements ready for payment');
// Add a subtle success indicator
const paymentCard = paymentElementDiv.closest('.card');
if (paymentCard) {
paymentCard.style.borderColor = '#0099FF';
paymentCard.style.borderWidth = '2px';
}
// Update button text to show payment is ready
const submitButton = document.getElementById('submit-payment');
const submitText = document.getElementById('submit-text');
if (submitButton && submitText) {
submitButton.disabled = false;
submitText.textContent = 'Complete Payment';
submitButton.classList.remove('btn-secondary');
submitButton.classList.add('btn-success');
}
});
console.log('✅ Stripe Elements initialized successfully');
return true;
} catch (error) {
console.error('❌ Error initializing Stripe Elements:', error);
// Show helpful error message
const errorElement = document.getElementById('payment-errors');
if (errorElement) {
errorElement.innerHTML = `
<div class="alert alert-warning alert-dismissible" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>
<strong>Stripe Setup Required:</strong> ${error.message || 'Failed to load payment form'}<br><br>
<strong>Next Steps:</strong><br>
1. Get your Stripe API keys from <a href="https://dashboard.stripe.com/apikeys" target="_blank">Stripe Dashboard</a><br>
2. Replace the placeholder publishable key in the code<br>
3. Set up a server to create payment intents<br><br>
<small>The integration is complete - you just need real Stripe credentials!</small>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
`;
errorElement.style.display = 'block';
}
throw error;
}
};
// Confirm payment with Stripe
window.confirmStripePayment = async function(clientSecret) {
console.log('🔄 Confirming payment...');
try {
// Ensure elements are ready before submitting
if (!elements) {
console.error('❌ Payment elements not initialized');
throw new Error('Payment form not ready. Please wait a moment and try again.');
}
console.log('🔄 Step 1: Submitting payment elements...');
// Step 1: Submit the payment elements first (required by new Stripe API)
const { error: submitError } = await elements.submit();
if (submitError) {
console.error('❌ Elements submit failed:', submitError);
throw new Error(submitError.message || 'Payment form validation failed.');
}
console.log('✅ Step 1 complete: Elements submitted successfully');
console.log('🔄 Step 2: Confirming payment with Stripe...');
// Step 2: Confirm payment with Stripe
const { error, paymentIntent } = await stripe.confirmPayment({
elements,
clientSecret: clientSecret,
confirmParams: {
return_url: `${window.location.origin}/success`,
},
redirect: 'if_required'
});
if (error) {
console.error('❌ Payment confirmation failed:', error);
throw new Error(error.message);
}
if (paymentIntent && paymentIntent.status === 'succeeded') {
console.log('✅ Payment completed successfully!');
console.log('🆔 Payment Intent ID:', paymentIntent.id);
return true;
} else {
console.error('❌ Unexpected payment status:', paymentIntent?.status);
throw new Error('Payment processing failed. Please try again.');
}
} catch (error) {
console.error('❌ Payment confirmation error:', error.message);
throw error;
}
};
console.log('✅ Zanzibar Portal Stripe integration ready');
console.log('🏠 Portal supports resident registration with Stripe payments');
</script>
<!-- WASM Application -->
<script type="module">
async function run() {
try {
// Load the WASM module for the Yew application
const init = await import('./pkg/zanzibar_freezone_portal.js');
await init.default();
console.log('✅ Zanzibar Digital Freezone Portal initialized');
console.log('🏠 Portal ready for resident registration');
} catch (error) {
console.error('❌ Failed to initialize WASM application:', error);
console.error('🔧 Make sure to build the WASM module with: trunk build');
}
}
run();
</script>
</body>
</html>