initial commit
This commit is contained in:
599
platform/static/css/main.css
Normal file
599
platform/static/css/main.css
Normal file
@@ -0,0 +1,599 @@
|
||||
/* Zanzibar Digital Freezone - Main CSS */
|
||||
/* Based on the original Actix MVC app styling */
|
||||
|
||||
/* Custom CSS Variables for Very Soft Pastel Colors */
|
||||
:root {
|
||||
/* Very Muted Pastel Colors */
|
||||
--bs-primary: #d4e6f1;
|
||||
--bs-primary-rgb: 212, 230, 241;
|
||||
--bs-secondary: #e8eaed;
|
||||
--bs-secondary-rgb: 232, 234, 237;
|
||||
--bs-success: #d5f4e6;
|
||||
--bs-success-rgb: 213, 244, 230;
|
||||
--bs-info: #d6f0f7;
|
||||
--bs-info-rgb: 214, 240, 247;
|
||||
--bs-warning: #fef9e7;
|
||||
--bs-warning-rgb: 254, 249, 231;
|
||||
--bs-danger: #fdeaea;
|
||||
--bs-danger-rgb: 253, 234, 234;
|
||||
|
||||
/* Light theme colors */
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #343a40;
|
||||
|
||||
/* Text colors - always black or white */
|
||||
--text-primary: #212529;
|
||||
--text-secondary: #495057;
|
||||
--text-muted: #6c757d;
|
||||
}
|
||||
|
||||
/* Dark theme variables */
|
||||
[data-bs-theme="dark"] {
|
||||
/* Very Muted Dark Pastels */
|
||||
--bs-primary: #2c3e50;
|
||||
--bs-primary-rgb: 44, 62, 80;
|
||||
--bs-secondary: #34495e;
|
||||
--bs-secondary-rgb: 52, 73, 94;
|
||||
--bs-success: #27ae60;
|
||||
--bs-success-rgb: 39, 174, 96;
|
||||
--bs-info: #3498db;
|
||||
--bs-info-rgb: 52, 152, 219;
|
||||
--bs-warning: #f39c12;
|
||||
--bs-warning-rgb: 243, 156, 18;
|
||||
--bs-danger: #e74c3c;
|
||||
--bs-danger-rgb: 231, 76, 60;
|
||||
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #adb5bd;
|
||||
--text-muted: #6c757d;
|
||||
}
|
||||
|
||||
/* Global Styles */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 50px; /* Height of the fixed header */
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: var(--bs-light);
|
||||
color: var(--text-primary);
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Dark theme body */
|
||||
[data-bs-theme="dark"] body {
|
||||
background-color: #1a1d20;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Header Styles */
|
||||
.header {
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 1030;
|
||||
background-color: #212529 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header .container-fluid {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.header h5 {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.header .navbar-toggler {
|
||||
border: none;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.header .navbar-toggler:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.header .nav-link {
|
||||
color: white !important;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.header .nav-link:hover {
|
||||
color: #adb5bd !important;
|
||||
}
|
||||
|
||||
.header .nav-link.active {
|
||||
color: white !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header .dropdown-menu {
|
||||
border: 1px solid #dee2e6;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Sidebar Styles */
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
position: fixed;
|
||||
height: calc(100vh - 90px); /* Subtract header and footer height */
|
||||
top: 50px; /* Position below header */
|
||||
background-color: #f8f9fa;
|
||||
border-right: 1px solid #dee2e6;
|
||||
overflow-y: auto;
|
||||
z-index: 1010;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Dark theme sidebar */
|
||||
[data-bs-theme="dark"] .sidebar {
|
||||
background-color: #1a1d20 !important;
|
||||
border-right: 1px solid #495057;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
color: #495057;
|
||||
text-decoration: none;
|
||||
padding: 0.75rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover {
|
||||
background-color: #e9ecef;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
background-color: #e7f3ff;
|
||||
color: #212529;
|
||||
border-left: 4px solid #d4e6f1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Dark theme sidebar nav links */
|
||||
[data-bs-theme="dark"] .sidebar .nav-link {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .sidebar .nav-link:hover {
|
||||
background-color: #2d3339 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .sidebar .nav-link.active {
|
||||
background-color: #34495e !important;
|
||||
color: #ffffff !important;
|
||||
border-left: 4px solid #2c3e50 !important;
|
||||
}
|
||||
|
||||
/* Dark theme sidebar cards */
|
||||
[data-bs-theme="dark"] .sidebar .card {
|
||||
background-color: #2d3339 !important;
|
||||
border-color: #495057 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .sidebar .card.bg-white {
|
||||
background-color: #2d3339 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .sidebar .card.bg-dark {
|
||||
background-color: #34495e !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* Dark theme sidebar card icons */
|
||||
[data-bs-theme="dark"] .sidebar .bg-dark {
|
||||
background-color: #ffffff !important;
|
||||
color: #212529 !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .sidebar .bg-white {
|
||||
background-color: #2d3339 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* Dark theme dividers */
|
||||
[data-bs-theme="dark"] .sidebar hr {
|
||||
border-color: #495057 !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Dark theme text colors */
|
||||
[data-bs-theme="dark"] .sidebar .text-muted {
|
||||
color: #adb5bd !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .sidebar h6 {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .sidebar small {
|
||||
color: #adb5bd !important;
|
||||
}
|
||||
|
||||
.sidebar .nav-link i {
|
||||
margin-right: 0.5rem;
|
||||
width: 1.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Main Content Area */
|
||||
.main-content {
|
||||
margin-left: 240px;
|
||||
min-height: calc(100vh - 90px);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Footer Styles */
|
||||
.footer {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
background-color: #212529 !important;
|
||||
color: white;
|
||||
position: relative;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
/* Feature Cards (Home Page) */
|
||||
.compact-card {
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.compact-card .card-body {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.compact-card .card-text {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.card-header h6 {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Toast Notifications */
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 1055;
|
||||
}
|
||||
|
||||
.toast {
|
||||
min-width: 300px;
|
||||
margin-bottom: 0.5rem;
|
||||
border: none;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.toast-header {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
.toast-success .toast-header {
|
||||
background-color: var(--bs-success);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.toast-error .toast-header {
|
||||
background-color: var(--bs-danger);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.toast-warning .toast-header {
|
||||
background-color: var(--bs-warning);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.toast-info .toast-header {
|
||||
background-color: var(--bs-info);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.toast-body {
|
||||
padding: 0.75rem;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* Login Form Styles */
|
||||
.login-container {
|
||||
min-height: calc(100vh - 50px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #0099FF 0%, #00CC66 100%);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.login-card .card-header {
|
||||
background-color: var(--bs-primary);
|
||||
color: var(--text-primary);
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.login-card .form-control:focus {
|
||||
border-color: var(--bs-primary);
|
||||
box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.25);
|
||||
}
|
||||
|
||||
.login-card .btn-primary {
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.login-card .btn-primary:hover {
|
||||
background-color: rgba(var(--bs-primary-rgb), 0.8);
|
||||
border-color: rgba(var(--bs-primary-rgb), 0.8);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
position: fixed;
|
||||
height: calc(100vh - 90px);
|
||||
top: 50px;
|
||||
}
|
||||
.main-content {
|
||||
margin-left: 240px;
|
||||
min-height: calc(100vh - 90px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
position: fixed;
|
||||
height: calc(100vh - 90px);
|
||||
top: 50px;
|
||||
left: -240px;
|
||||
transition: left 0.3s ease;
|
||||
z-index: 1020;
|
||||
box-shadow: 0.5rem 0 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.sidebar.show {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.header .d-md-flex {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Utility Classes */
|
||||
.text-truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.shadow-sm {
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;
|
||||
}
|
||||
|
||||
.border-start {
|
||||
border-left: 1px solid #dee2e6 !important;
|
||||
}
|
||||
|
||||
.border-4 {
|
||||
border-width: 4px !important;
|
||||
}
|
||||
|
||||
/* Loading States */
|
||||
.loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.spinner-border-sm {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
/* Focus States for Accessibility */
|
||||
.nav-link:focus,
|
||||
.btn:focus,
|
||||
.form-control:focus {
|
||||
outline: 2px solid var(--bs-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Button and component overrides for very muted pastel colors */
|
||||
.btn-primary {
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #c3d9ed;
|
||||
border-color: #c3d9ed;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
color: #212529;
|
||||
border-color: var(--bs-primary);
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover {
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: var(--bs-success);
|
||||
border-color: var(--bs-success);
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #c8f0dd;
|
||||
border-color: #c8f0dd;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
/* Dark theme button overrides */
|
||||
[data-bs-theme="dark"] .btn-primary {
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .btn-primary:hover {
|
||||
background-color: #34495e;
|
||||
border-color: #34495e;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .btn-outline-primary {
|
||||
color: #ffffff;
|
||||
border-color: var(--bs-primary);
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .btn-outline-primary:hover {
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .btn-success {
|
||||
background-color: var(--bs-success);
|
||||
border-color: var(--bs-success);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .btn-success:hover {
|
||||
background-color: #2ecc71;
|
||||
border-color: #2ecc71;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Card styling improvements */
|
||||
.card {
|
||||
border: 1px solid #e9ecef;
|
||||
background-color: #ffffff;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .card {
|
||||
background-color: #2d3339 !important;
|
||||
border-color: #495057 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* Text color overrides - always black or white */
|
||||
.text-primary {
|
||||
color: #212529 !important;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: #495057 !important;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #6c757d !important;
|
||||
}
|
||||
|
||||
/* Dark theme text overrides */
|
||||
[data-bs-theme="dark"] .text-primary {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .text-secondary {
|
||||
color: #adb5bd !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .text-muted {
|
||||
color: #6c757d !important;
|
||||
}
|
||||
|
||||
/* Border color overrides */
|
||||
.border-primary {
|
||||
border-color: var(--bs-primary) !important;
|
||||
}
|
||||
|
||||
.border-success {
|
||||
border-color: var(--bs-success) !important;
|
||||
}
|
||||
|
||||
/* Background color overrides */
|
||||
.bg-primary {
|
||||
background-color: var(--bs-primary) !important;
|
||||
color: #212529 !important;
|
||||
}
|
||||
|
||||
.bg-success {
|
||||
background-color: var(--bs-success) !important;
|
||||
color: #212529 !important;
|
||||
}
|
||||
|
||||
/* Dark theme background overrides */
|
||||
[data-bs-theme="dark"] .bg-primary {
|
||||
background-color: var(--bs-primary) !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .bg-success {
|
||||
background-color: var(--bs-success) !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* Print Styles */
|
||||
@media print {
|
||||
.header,
|
||||
.sidebar,
|
||||
.footer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
399
platform/static/js/stripe-integration.js
Normal file
399
platform/static/js/stripe-integration.js
Normal file
@@ -0,0 +1,399 @@
|
||||
// Stripe Integration for Company Registration
|
||||
// This file handles the Stripe Elements integration for the Yew WASM application
|
||||
|
||||
let stripe;
|
||||
let elements;
|
||||
let paymentElement;
|
||||
|
||||
// Stripe publishable key - this should be set from the server or environment
|
||||
const STRIPE_PUBLISHABLE_KEY = 'pk_test_51234567890abcdef'; // Replace with actual key
|
||||
|
||||
// Initialize Stripe when the script loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('🔧 Stripe integration script loaded');
|
||||
|
||||
// Initialize Stripe
|
||||
if (window.Stripe) {
|
||||
stripe = Stripe(STRIPE_PUBLISHABLE_KEY);
|
||||
console.log('✅ Stripe initialized');
|
||||
} else {
|
||||
console.error('❌ Stripe.js not loaded');
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize Stripe Elements with client secret
|
||||
window.initializeStripeElements = async function(clientSecret) {
|
||||
console.log('🔧 Initializing Stripe Elements with client secret:', clientSecret);
|
||||
|
||||
try {
|
||||
if (!stripe) {
|
||||
throw new Error('Stripe not initialized');
|
||||
}
|
||||
|
||||
// Create Elements instance with client secret
|
||||
elements = stripe.elements({
|
||||
clientSecret: clientSecret,
|
||||
appearance: {
|
||||
theme: 'stripe',
|
||||
variables: {
|
||||
colorPrimary: '#198754',
|
||||
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 = '#198754';
|
||||
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');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle loading state
|
||||
paymentElement.on('loaderstart', () => {
|
||||
console.log('🔄 Stripe Elements loading...');
|
||||
});
|
||||
|
||||
paymentElement.on('loaderror', (event) => {
|
||||
console.error('❌ Stripe Elements load error:', event.error);
|
||||
showAdBlockerGuidance(event.error.message || 'Failed to load payment form');
|
||||
});
|
||||
|
||||
console.log('✅ Stripe Elements initialized successfully');
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error initializing Stripe Elements:', error);
|
||||
|
||||
// Check if this might be an ad blocker issue
|
||||
const isAdBlockerError = error.message && (
|
||||
error.message.includes('blocked') ||
|
||||
error.message.includes('Failed to fetch') ||
|
||||
error.message.includes('ERR_BLOCKED_BY_CLIENT') ||
|
||||
error.message.includes('network') ||
|
||||
error.message.includes('CORS')
|
||||
);
|
||||
|
||||
if (isAdBlockerError) {
|
||||
showAdBlockerGuidance(error.message || 'Failed to load payment form');
|
||||
} else {
|
||||
// Show generic error for non-ad-blocker issues
|
||||
const errorElement = document.getElementById('payment-errors');
|
||||
if (errorElement) {
|
||||
errorElement.innerHTML = `
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<strong>Payment Form Error:</strong> ${error.message || 'Failed to load payment form'}<br><br>
|
||||
Please refresh the page and try again. If the problem persists, contact support.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
`;
|
||||
errorElement.style.display = 'block';
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Create payment intent on server
|
||||
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;
|
||||
}
|
||||
|
||||
console.log('Form data:', formData);
|
||||
|
||||
const response = await fetch('/company/create-payment-intent', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
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 };
|
||||
}
|
||||
throw new Error(errorData.error || 'Failed to create payment intent');
|
||||
}
|
||||
|
||||
const responseData = await response.json();
|
||||
console.log('✅ Payment intent created:', responseData);
|
||||
|
||||
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);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Confirm payment with Stripe
|
||||
window.confirmStripePayment = async function(clientSecret) {
|
||||
console.log('🔄 Confirming payment...');
|
||||
|
||||
try {
|
||||
// Ensure elements are ready before submitting
|
||||
if (!elements) {
|
||||
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);
|
||||
// Provide more specific error messages
|
||||
if (submitError.type === 'validation_error') {
|
||||
throw new Error('Please check your payment details and try again.');
|
||||
} else if (submitError.type === 'card_error') {
|
||||
throw new Error(submitError.message || 'Card error. Please check your card details.');
|
||||
} else {
|
||||
throw new Error(submitError.message || 'Payment form validation failed.');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Step 1 complete: Elements submitted successfully');
|
||||
console.log('🔄 Step 2: Confirming payment...');
|
||||
|
||||
// Step 2: Confirm payment with Stripe
|
||||
const { error, paymentIntent } = await stripe.confirmPayment({
|
||||
elements,
|
||||
clientSecret: clientSecret,
|
||||
confirmParams: {
|
||||
return_url: `${window.location.origin}/company/payment-success`,
|
||||
},
|
||||
redirect: 'if_required' // Handle success without redirect if possible
|
||||
});
|
||||
|
||||
if (error) {
|
||||
// Payment failed - redirect to failure page
|
||||
console.error('Payment confirmation failed:', error);
|
||||
window.location.href = `${window.location.origin}/company/payment-failure`;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (paymentIntent && paymentIntent.status === 'succeeded') {
|
||||
// Payment succeeded
|
||||
console.log('✅ Payment completed successfully:', paymentIntent.id);
|
||||
|
||||
// Clear saved form data since registration is complete
|
||||
localStorage.removeItem('freezone_company_registration');
|
||||
|
||||
// Redirect to success page with payment details
|
||||
window.location.href = `${window.location.origin}/company/payment-success?payment_intent=${paymentIntent.id}&payment_intent_client_secret=${clientSecret}`;
|
||||
return true;
|
||||
} else if (paymentIntent && paymentIntent.status === 'requires_action') {
|
||||
// Payment requires additional authentication (3D Secure, etc.)
|
||||
console.log('🔐 Payment requires additional authentication');
|
||||
// Stripe will handle the authentication flow automatically
|
||||
return false; // Don't redirect yet
|
||||
} else {
|
||||
// Unexpected status - redirect to failure page
|
||||
console.error('Unexpected payment status:', paymentIntent?.status);
|
||||
window.location.href = `${window.location.origin}/company/payment-failure`;
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Payment confirmation error:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Show comprehensive ad blocker guidance
|
||||
function showAdBlockerGuidance(errorMessage) {
|
||||
const errorElement = document.getElementById('payment-errors');
|
||||
if (!errorElement) return;
|
||||
|
||||
// Detect browser type for specific instructions
|
||||
const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
|
||||
const isFirefox = /Firefox/.test(navigator.userAgent);
|
||||
const isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
|
||||
const isEdge = /Edg/.test(navigator.userAgent);
|
||||
|
||||
let browserSpecificInstructions = '';
|
||||
if (isChrome) {
|
||||
browserSpecificInstructions = `
|
||||
<strong>Chrome Instructions:</strong><br>
|
||||
1. Click the shield icon 🛡️ in the address bar<br>
|
||||
2. Select "Allow" for this site<br>
|
||||
3. Or go to Settings → Privacy → Ad blockers<br>
|
||||
`;
|
||||
} else if (isFirefox) {
|
||||
browserSpecificInstructions = `
|
||||
<strong>Firefox Instructions:</strong><br>
|
||||
1. Click the shield icon 🛡️ in the address bar<br>
|
||||
2. Turn off "Enhanced Tracking Protection" for this site<br>
|
||||
3. Or disable uBlock Origin/AdBlock Plus temporarily<br>
|
||||
`;
|
||||
} else if (isSafari) {
|
||||
browserSpecificInstructions = `
|
||||
<strong>Safari Instructions:</strong><br>
|
||||
1. Go to Safari → Preferences → Extensions<br>
|
||||
2. Temporarily disable ad blocking extensions<br>
|
||||
3. Or add this site to your allowlist<br>
|
||||
`;
|
||||
} else if (isEdge) {
|
||||
browserSpecificInstructions = `
|
||||
<strong>Edge Instructions:</strong><br>
|
||||
1. Click the shield icon 🛡️ in the address bar<br>
|
||||
2. Turn off tracking prevention for this site<br>
|
||||
3. Or disable ad blocking extensions<br>
|
||||
`;
|
||||
}
|
||||
|
||||
errorElement.innerHTML = `
|
||||
<div class="alert alert-warning alert-dismissible" role="alert">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-shield-exclamation fs-1 text-warning me-3 mt-1"></i>
|
||||
<div class="flex-grow-1">
|
||||
<h5 class="alert-heading mb-3">🛡️ Ad Blocker Detected</h5>
|
||||
<p class="mb-3"><strong>Error:</strong> ${errorMessage}</p>
|
||||
|
||||
<p class="mb-3">Your ad blocker or privacy extension is preventing the secure payment form from loading. This is normal security behavior, but we need to process your payment securely through Stripe.</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>🔧 Quick Fix:</h6>
|
||||
${browserSpecificInstructions}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>🔒 Why This Happens:</h6>
|
||||
• Ad blockers block payment tracking<br>
|
||||
• Privacy extensions block third-party scripts<br>
|
||||
• This protects your privacy normally<br>
|
||||
• Stripe needs access for secure payments<br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 p-3 rounded">
|
||||
<h6>✅ Alternative Solutions:</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<strong>1. Incognito/Private Mode</strong><br>
|
||||
<small class="text-muted">Usually has fewer extensions</small>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<strong>2. Different Browser</strong><br>
|
||||
<small class="text-muted">Try Chrome, Firefox, or Safari</small>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<strong>3. Mobile Device</strong><br>
|
||||
<small class="text-muted">Often has fewer blockers</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-center">
|
||||
<button type="button" class="btn btn-primary me-2" onclick="location.reload()">
|
||||
<i class="bi bi-arrow-clockwise me-1"></i>Refresh & Try Again
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="showContactInfo()">
|
||||
<i class="bi bi-headset me-1"></i>Contact Support
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-center">
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-shield-check me-1"></i>
|
||||
We use Stripe for secure payment processing. Your payment information is encrypted and safe.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
errorElement.style.display = 'block';
|
||||
|
||||
// Scroll to error
|
||||
errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
|
||||
// Add visual indication
|
||||
const paymentCard = document.querySelector('#payment-information-section');
|
||||
if (paymentCard) {
|
||||
paymentCard.style.borderColor = '#ffc107';
|
||||
paymentCard.style.borderWidth = '2px';
|
||||
paymentCard.classList.add('border-warning');
|
||||
}
|
||||
}
|
||||
|
||||
// Show contact information
|
||||
function showContactInfo() {
|
||||
alert('Contact Support:\n\nEmail: support@hostbasket.com\nPhone: +1 (555) 123-4567\nLive Chat: Available 24/7\n\nPlease mention "Payment Form Loading Issue" when contacting us.');
|
||||
}
|
||||
|
||||
// Export functions for use by Rust/WASM
|
||||
window.stripeIntegration = {
|
||||
initializeElements: window.initializeStripeElements,
|
||||
createPaymentIntent: window.createPaymentIntent,
|
||||
confirmPayment: window.confirmStripePayment
|
||||
};
|
||||
|
||||
console.log('✅ Stripe integration script ready');
|
Reference in New Issue
Block a user