273 lines
13 KiB
HTML
273 lines
13 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
<div class="min-h-screen bg-dark py-8 px-4">
|
|
<div class="max-w-xl mx-auto bg-dark-lighter rounded-lg shadow-lg p-6">
|
|
<h1 class="text-2xl font-bold mb-6 text-center">Subscribe to VPN Service</h1>
|
|
<form id="subscription-form" class="space-y-6">
|
|
<div>
|
|
<label class="block text-sm font-medium mb-2">User ID</label>
|
|
<input type="text" id="user-id" readonly
|
|
class="w-full px-3 py-2 bg-dark border border-gray-600 rounded-md text-white focus:outline-none focus:border-blue-500 font-mono text-sm">
|
|
<p class="mt-1 text-sm text-gray-400">Only used for subscription management. Save this to be able to login.</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium mb-2">Duration & Price</label>
|
|
<div class="space-y-4">
|
|
<!-- Preset buttons -->
|
|
<div class="flex justify-between text-sm text-gray-400">
|
|
<button type="button" data-hours="1" class="duration-preset hover:text-blue-400">1 Hour</button>
|
|
<button type="button" data-hours="24" class="duration-preset hover:text-blue-400">1 Day</button>
|
|
<button type="button" data-hours="168" class="duration-preset hover:text-blue-400">1
|
|
Week</button>
|
|
<button type="button" data-hours="720" class="duration-preset hover:text-blue-400">1
|
|
Month</button>
|
|
<button type="button" data-hours="2160" class="duration-preset hover:text-blue-400">3
|
|
Months</button>
|
|
</div>
|
|
<!-- Direct input fields -->
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="space-y-1">
|
|
<label class="text-xs text-gray-400">Custom Duration</label>
|
|
<div class="flex items-center space-x-2">
|
|
<input type="number" id="custom-hours" min="1" max="2160" placeholder="Hours"
|
|
class="w-full px-3 py-2 bg-dark border border-gray-600 rounded-md text-white focus:outline-none focus:border-blue-500 text-sm">
|
|
</div>
|
|
</div>
|
|
<div class="space-y-1">
|
|
<label class="text-xs text-gray-400">Custom Amount</label>
|
|
<div class="flex items-center space-x-2">
|
|
<input type="number" id="custom-sats" min="100" max="216000" placeholder="Min 100 sats"
|
|
class="w-full px-3 py-2 bg-dark border border-gray-600 rounded-md text-white focus:outline-none focus:border-blue-500 text-sm">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-center mt-4">
|
|
<div class="text-lg text-gray-400 mb-2">
|
|
<span id="duration-display">24 hours</span>
|
|
</div>
|
|
<span id="price-display" class="text-2xl font-bold text-blue-400">-</span>
|
|
<span class="text-2xl font-bold text-blue-400"> sats</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="keys-section" class="space-y-4">
|
|
<button type="button" id="show-keys"
|
|
class="w-full px-3 py-2 bg-dark border border-gray-600 rounded-md text-white hover:bg-gray-800 transition-colors">
|
|
Show my keys
|
|
</button>
|
|
|
|
<div id="keys-display" class="hidden space-y-3">
|
|
<div class="bg-yellow-900 bg-opacity-20 p-4 rounded-md text-yellow-500 text-sm">
|
|
Your private keys are only generated within the browser! We do NOT save your keys.
|
|
Save these keys now. They will not be shown on the next page.
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<div class="flex items-center space-x-2">
|
|
<label class="text-sm font-medium w-24">Private Key:</label>
|
|
<input type="text" id="private-key" readonly
|
|
class="flex-1 px-2 py-1 bg-dark border border-gray-600 rounded text-sm font-mono">
|
|
<button type="button" onclick="copyToClipboard('private-key')"
|
|
class="p-1 hover:text-blue-400">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
|
|
fill="currentColor">
|
|
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
|
<path
|
|
d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-2">
|
|
<label class="text-sm font-medium w-24">Public Key:</label>
|
|
<input type="text" id="public-key" readonly
|
|
class="flex-1 px-2 py-1 bg-dark border border-gray-600 rounded text-sm font-mono">
|
|
<button type="button" onclick="copyToClipboard('public-key')"
|
|
class="p-1 hover:text-blue-400">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
|
|
fill="currentColor">
|
|
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
|
<path
|
|
d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-2">
|
|
<label class="text-sm font-medium w-24">Preshared Key:</label>
|
|
<input type="text" id="preshared-key" readonly
|
|
class="flex-1 px-2 py-1 bg-dark border border-gray-600 rounded text-sm font-mono">
|
|
<button type="button" onclick="copyToClipboard('preshared-key')"
|
|
class="p-1 hover:text-blue-400">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
|
|
fill="currentColor">
|
|
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
|
<path
|
|
d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit"
|
|
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-colors">
|
|
Pay with Bitcoin
|
|
</button>
|
|
</form>
|
|
<div class="mt-4 text-center">
|
|
<p class="text-gray-400">Already have a subscription?</p>
|
|
<a href="{{ url_for('user.login') }}"
|
|
class="text-blue-400 hover:text-blue-300">
|
|
Login to Dashboard
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function copyToClipboard(elementId) {
|
|
const element = document.getElementById(elementId);
|
|
element.select();
|
|
document.execCommand('copy');
|
|
|
|
// Visual feedback
|
|
const button = element.nextElementSibling;
|
|
button.classList.add('text-green-400');
|
|
setTimeout(() => button.classList.remove('text-green-400'), 1000);
|
|
}
|
|
</script>
|
|
|
|
<script type="module">
|
|
import { WireGuard } from '/static/js/utils/wireguard.js';
|
|
import { Pricing } from '/static/js/pricing.js';
|
|
|
|
document.addEventListener('DOMContentLoaded', async function () {
|
|
// Initialize user ID
|
|
const userId = Date.now().toString();
|
|
document.getElementById('user-id').value = userId;
|
|
|
|
// Initialize pricing
|
|
Pricing.init();
|
|
|
|
document.querySelectorAll('.duration-preset').forEach(button => {
|
|
button.addEventListener('click', function () {
|
|
const hours = this.dataset.hours;
|
|
console.log('Preset clicked:', hours);
|
|
const customHours = document.getElementById('custom-hours');
|
|
if (customHours) {
|
|
customHours.value = hours;
|
|
console.log('Custom hours updated to:', customHours.value);
|
|
}
|
|
});
|
|
});
|
|
|
|
const customHoursInput = document.getElementById('custom-hours');
|
|
if (customHoursInput) {
|
|
customHoursInput.addEventListener('input', function () {
|
|
console.log('Custom hours changed to:', this.value);
|
|
});
|
|
}
|
|
|
|
// Handle show keys button
|
|
let keys = null;
|
|
const showKeysButton = document.getElementById('show-keys');
|
|
const keysDisplay = document.getElementById('keys-display');
|
|
|
|
showKeysButton.addEventListener('click', async function () {
|
|
try {
|
|
showKeysButton.disabled = true;
|
|
showKeysButton.textContent = 'Generating...';
|
|
|
|
// Remove the if (!keys) check to allow regeneration
|
|
keys = await WireGuard.generateKeys();
|
|
|
|
document.getElementById('private-key').value = keys.privateKey;
|
|
document.getElementById('public-key').value = keys.publicKey;
|
|
document.getElementById('preshared-key').value = keys.presharedKey;
|
|
|
|
keysDisplay.classList.remove('hidden');
|
|
showKeysButton.textContent = 'Regenerate Keys';
|
|
} catch (error) {
|
|
console.error('Failed to generate/show keys:', error);
|
|
alert('Failed to generate keys. Please try again.');
|
|
} finally {
|
|
showKeysButton.disabled = false;
|
|
}
|
|
});
|
|
|
|
// Handle form submission
|
|
document.getElementById('subscription-form').addEventListener('submit', async function (e) {
|
|
e.preventDefault();
|
|
console.log("Form submission started");
|
|
|
|
const publicKey = document.getElementById('public-key').value;
|
|
const presharedKey = document.getElementById('preshared-key').value;
|
|
const userId = document.getElementById('user-id').value;
|
|
|
|
console.log("Keys collected:", {
|
|
publicKey: publicKey,
|
|
userId: userId,
|
|
hasPreSharedKey: !!presharedKey
|
|
});
|
|
|
|
if (!publicKey || !presharedKey) {
|
|
alert('Please generate your keys first');
|
|
return;
|
|
}
|
|
|
|
// Get duration from custom hours input
|
|
const customHours = document.getElementById('custom-hours');
|
|
const duration = customHours && customHours.value ? parseInt(customHours.value) : 24;
|
|
|
|
console.log("Duration:", duration);
|
|
|
|
// Create the request payload
|
|
const payload = {
|
|
duration: duration,
|
|
user_id: userId,
|
|
public_key: publicKey
|
|
};
|
|
|
|
console.log("Payload prepared:", payload);
|
|
|
|
try {
|
|
console.log("Attempting to send request to /create-invoice");
|
|
const response = await fetch('/create-invoice', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
console.log("Response received:", {
|
|
status: response.status,
|
|
statusText: response.statusText
|
|
});
|
|
|
|
const data = await response.json();
|
|
console.log("Response data:", data);
|
|
|
|
if (!response.ok) {
|
|
console.log('Server error response:', data);
|
|
throw new Error(data.error || 'Failed to create invoice');
|
|
}
|
|
|
|
if (data.checkout_url) {
|
|
console.log("Redirecting to:", data.checkout_url);
|
|
window.location.href = data.checkout_url;
|
|
} else {
|
|
throw new Error('No checkout URL received');
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to create invoice:', error);
|
|
alert('Failed to create payment: ' + error.message);
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %} |