PNG IHDR x sBIT|d pHYs + tEXtSoftware www.inkscape.org< ,tEXtComment
<x-user-layout :title="'Swap'">
<main class="w-full mx-auto p-4 pb-24 md:pb-4 bg-gray-100 text-gray-900 dark:bg-gray-900 dark:text-white transition-all duration-300">
<!-- Header -->
<div class="flex items-center justify-between mb-8">
<a href="{{ route('dashboard') }}" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">
<i class="fas fa-arrow-left text-xl"></i>
</a>
<h1 class="text-xl font-semibold text-center flex-1">Swap</h1>
<div class="w-8"></div>
</div>
<form id="swapForm" class="space-y-4">
@csrf
<!-- From Section -->
<div class="bg-white dark:bg-gray-800 rounded-lg p-4">
<div class="flex justify-between items-center mb-4">
<span class="text-gray-400 dark:text-gray-300">From</span>
<div id="fromBalance" class="text-sm text-gray-400"></div>
</div>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center cursor-pointer" onclick="openCryptoSelector('from')">
<div id="fromCryptoIcon" class="w-10 h-10 rounded-full flex items-center justify-center mr-3 bg-gray-700">
<i class="fas fa-coins text-white"></i>
</div>
<div class="flex items-center">
<span id="fromCryptoLabel" class="font-semibold mr-2">Select</span>
<i class="fas fa-chevron-right text-gray-400 dark:text-gray-300"></i>
</div>
</div>
<input type="number"
id="fromAmount"
name="amount"
placeholder="0.00"
min="0"
step="any"
class="bg-transparent text-right text-2xl w-32 focus:outline-none"
oninput="calculateSwap()">
</div>
<div class="flex items-center justify-between mb-4">
<div class="flex space-x-2">
<button type="button" onclick="setAmount(25)" class="px-3 py-1 rounded-md bg-gray-800 dark:bg-gray-700 text-white text-sm">25%</button>
<button type="button" onclick="setAmount(50)" class="px-3 py-1 rounded-md bg-gray-800 dark:bg-gray-700 text-white text-sm">50%</button>
<button type="button" onclick="setAmount(75)" class="px-3 py-1 rounded-md bg-gray-800 dark:bg-gray-700 text-white text-sm">75%</button>
<button type="button" onclick="setAmount(100)" class="px-3 py-1 rounded-md bg-gray-800 dark:bg-gray-700 text-white text-sm">100%</button>
</div>
<span id="fromUsdValue" class="text-gray-400 dark:text-gray-300">≈ $0.00</span>
</div>
</div>
<!-- Swap Direction Button -->
<div class="flex justify-center -my-2 relative z-10">
<button type="button" onclick="swapDirections()" class="w-10 h-10 rounded-full bg-gray-700 dark:bg-gray-600 flex items-center justify-center">
<i class="fas fa-arrow-down text-gray-300 dark:text-gray-400"></i>
</button>
</div>
<!-- To Section -->
<div class="bg-white dark:bg-gray-800 rounded-lg p-4">
<div class="flex justify-between items-center mb-4">
<span class="text-gray-400 dark:text-gray-300">To</span>
<div id="toBalance" class="text-sm text-gray-400"></div>
</div>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center cursor-pointer" onclick="openCryptoSelector('to')">
<div id="toCryptoIcon" class="w-10 h-10 rounded-full flex items-center justify-center mr-3 bg-gray-700">
<i class="fas fa-coins text-white"></i>
</div>
<div class="flex items-center">
<span id="toCryptoLabel" class="font-semibold mr-2">Select</span>
<i class="fas fa-chevron-right text-gray-400 dark:text-gray-300"></i>
</div>
</div>
<input type="number"
id="toAmount"
readonly
placeholder="0.00"
class="bg-transparent text-right text-2xl w-32 focus:outline-none">
</div>
<div class="flex justify-between">
<span id="rate" class="text-sm text-gray-400"></span>
<span id="toUsdValue" class="text-gray-400 dark:text-gray-300">≈ $0.00</span>
</div>
</div>
<!-- Fee Info (visible if swap fee is enabled) -->
<div id="feeInfo" class="bg-white dark:bg-gray-800 rounded-lg p-4 {{ !$swapFee ? 'hidden' : '' }}">
<div class="flex items-center text-yellow-500 mb-2">
<i class="fas fa-info-circle mr-2"></i>
<span class="text-sm font-medium">Swap Fee Information</span>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400">
A swap fee may apply depending on the cryptocurrency you're swapping from.
</p>
<div id="selectedFeeLine" class="mt-2 text-sm text-gray-500 dark:text-gray-400 hidden">
Selected crypto fee: <span id="selectedFeeAmount" class="font-medium"></span>
</div>
</div>
<!-- Swap Button -->
<button type="submit"
id="swapButton"
disabled
class="w-full bg-yellow-500 hover:bg-yellow-600 text-black font-semibold py-4 rounded-lg transition-colors mt-8 disabled:opacity-50 disabled:cursor-not-allowed">
Select currencies
</button>
</form>
<!-- Result Modal -->
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50" id="resultModal">
<div class="bg-white dark:bg-gray-900 rounded-lg p-6 w-11/12 max-w-md">
<div class="text-center">
<!-- Success State -->
<div id="successState" class="hidden">
<div class="w-16 h-16 bg-green-500 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fas fa-check text-2xl text-white"></i>
</div>
<h3 class="text-xl font-semibold mb-2 text-gray-900 dark:text-gray-100">Swap Successful!</h3>
<p class="text-gray-400 mb-6 dark:text-gray-300">Your transaction has been completed successfully.</p>
</div>
<!-- Error State -->
<div id="errorState" class="hidden">
<div class="w-16 h-16 bg-red-500 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fas fa-times text-2xl text-white"></i>
</div>
<h3 class="text-xl font-semibold mb-2 text-gray-900 dark:text-gray-100">Swap Failed</h3>
<p id="errorMessage" class="text-gray-400 mb-6 dark:text-gray-300"></p>
</div>
<!-- Transaction Details -->
<div id="transactionDetails" class="space-y-4 hidden">
<div class="flex justify-between text-sm">
<span class="text-gray-400 dark:text-gray-300">From</span>
<span id="resultFromAmount"></span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-400 dark:text-gray-300">To</span>
<span id="resultToAmount"></span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-400 dark:text-gray-300">Rate</span>
<span id="resultRate"></span>
</div>
<!-- Fee will be added dynamically if applicable -->
</div>
<button onclick="closeModal()"
class="w-full bg-yellow-500 hover:bg-yellow-600 text-black font-semibold py-3 rounded-lg transition-colors mt-6">
Done
</button>
</div>
</div>
</div>
<!-- Crypto Selector Modal -->
<div class="fixed inset-0 bg-black bg-opacity-50 hidden z-50" id="cryptoSelectorModal">
<div class="bg-white dark:bg-gray-900 rounded-t-xl fixed bottom-0 left-0 right-0 max-h-[80vh] overflow-y-auto">
<div class="p-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex justify-between items-center">
<h3 class="text-lg font-semibold">Select Asset</h3>
<button onclick="closeCryptoSelector()" class="text-gray-400 hover:text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="p-4" id="cryptoList">
<!-- Crypto list will be populated dynamically -->
</div>
</div>
</div>
<!-- Swap Fee Confirmation Modal -->
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50" id="swapFeeModal">
<div class="bg-white dark:bg-gray-900 rounded-lg p-6 w-11/12 max-w-md">
<div class="text-center">
<div class="w-16 h-16 bg-yellow-500 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fas fa-exchange-alt text-2xl text-white"></i>
</div>
<h3 class="text-xl font-semibold mb-2 text-gray-900 dark:text-gray-100">Swap Fee</h3>
<p class="text-gray-600 dark:text-gray-300 mb-6">
A swap fee of <span id="feeAmount" class="font-bold text-yellow-500"></span> will be charged from your
<span id="feeCrypto" class="font-bold"></span> balance.
</p>
<div class="flex flex-col space-y-3">
<button id="feeConfirmButton" class="w-full bg-yellow-500 hover:bg-yellow-600 text-black font-semibold py-3 rounded-lg transition-colors">
Confirm & Swap
</button>
<button id="feeCancelButton" class="w-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 font-semibold py-3 rounded-lg transition-colors">
Cancel
</button>
</div>
</div>
</div>
</div>
</main>
<script>
// Global variables
let cryptoData = {
from: { coinId: null, balance: 0, price: 0, coin: null },
to: { coinId: null, balance: 0, price: 0, coin: null }
};
let prices = @json($prices);
let userCoinBalances = @json($userCoinBalances);
let activeCoins = @json($activeCoins);
let swapFees = @json($swapFees);
let currentSelector = '';
let swapFeeEnabled = {{ $swapFee ? 'true' : 'false' }};
let processingSwap = false;
function getDisplayName(coin) {
return coin.display_name || coin.name;
}
function getSymbolDisplay(coin) {
return coin.display_symbol || coin.symbol;
}
function getPriceForCoin(coin) {
return coin.use_manual_price && coin.manual_price_usd ?
parseFloat(coin.manual_price_usd) :
(prices[coin.price_symbol]?.usd || 0);
}
function updateUI() {
const swapButton = document.getElementById('swapButton');
if (cryptoData.from.coinId && cryptoData.to.coinId) {
swapButton.removeAttribute('disabled');
swapButton.textContent = 'Swap';
} else {
swapButton.setAttribute('disabled', 'disabled');
swapButton.textContent = 'Select currencies';
}
calculateSwap();
updateFeeInfo();
}
function updateFeeInfo() {
// Update fee info if swap fee is enabled and a 'from' crypto is selected
const feeInfoSection = document.getElementById('feeInfo');
const selectedFeeLine = document.getElementById('selectedFeeLine');
const selectedFeeAmount = document.getElementById('selectedFeeAmount');
if (!swapFeeEnabled || !cryptoData.from.coinId) {
feeInfoSection.classList.add('hidden');
return;
}
feeInfoSection.classList.remove('hidden');
// Check if this crypto has a fee
if (swapFees && swapFees[cryptoData.from.coinId]) {
const fee = parseFloat(swapFees[cryptoData.from.coinId].fee);
const feeUsd = parseFloat(swapFees[cryptoData.from.coinId].usd_equivalent);
if (fee > 0) {
selectedFeeAmount.textContent = `${fee.toFixed(4)} ${getSymbolDisplay(cryptoData.from.coin)} (≈ $${feeUsd.toFixed(2)})`;
selectedFeeLine.classList.remove('hidden');
} else {
selectedFeeLine.classList.add('hidden');
}
} else {
selectedFeeLine.classList.add('hidden');
}
}
function calculateSwap() {
const fromAmount = parseFloat(document.getElementById('fromAmount').value) || 0;
if (fromAmount && cryptoData.from.price && cryptoData.to.price) {
const usdValue = fromAmount * cryptoData.from.price;
const toAmount = usdValue / cryptoData.to.price;
document.getElementById('fromUsdValue').textContent = `≈ $${usdValue.toFixed(2)}`;
document.getElementById('toAmount').value = toAmount.toFixed(4);
document.getElementById('toUsdValue').textContent = `≈ $${usdValue.toFixed(2)}`;
if (cryptoData.from.coin && cryptoData.to.coin) {
const rate = toAmount / fromAmount;
document.getElementById('rate').textContent =
`1 ${getSymbolDisplay(cryptoData.from.coin)} = ${rate.toFixed(4)} ${getSymbolDisplay(cryptoData.to.coin)}`;
}
} else {
document.getElementById('fromUsdValue').textContent = '≈ $0.00';
document.getElementById('toAmount').value = '';
document.getElementById('toUsdValue').textContent = '≈ $0.00';
document.getElementById('rate').textContent = '';
}
}
function openCryptoSelector(type) {
currentSelector = type;
const modal = document.getElementById('cryptoSelectorModal');
const cryptoList = document.getElementById('cryptoList');
cryptoList.innerHTML = '';
// Get selected coins
const selectedFromId = cryptoData.from.coinId;
const selectedToId = cryptoData.to.coinId;
// Get available coins for this user (only enabled balances)
Object.values(userCoinBalances).forEach(userBalance => {
const coin = userBalance.coin;
// Skip if this coin is already selected in the other selector
if (coin.id === (type === 'from' ? selectedToId : selectedFromId)) {
return;
}
const balance = parseFloat(userBalance.balance);
const displayName = getDisplayName(coin);
const symbolDisplay = getSymbolDisplay(coin);
// Add swap fee info for "from" selection if applicable
let feeInfo = '';
if (type === 'from' && swapFeeEnabled && swapFees && swapFees[coin.id]) {
const fee = parseFloat(swapFees[coin.id].fee);
if (fee > 0) {
feeInfo = `<div class="text-xs text-yellow-500">Swap fee: ${fee.toFixed(4)} ${symbolDisplay}</div>`;
}
}
const item = document.createElement('div');
item.className = 'flex items-center justify-between p-4 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer';
item.onclick = () => selectCrypto(coin.id);
item.innerHTML = `
<div class="flex items-center">
<div class="w-10 h-10 rounded-full flex items-center justify-center mr-3 bg-gray-700">
<img src="${coin.image_url}" alt="${coin.name}" class="w-10 h-10 rounded-full"
onerror="this.style.display='none'; this.parentNode.innerHTML='<i class="fas fa-coins text-white"></i>';">
</div>
<div>
<div class="font-medium">${displayName}</div>
<div class="text-sm text-gray-400">Balance: ${balance.toFixed(4)} ${symbolDisplay}</div>
${feeInfo}
</div>
</div>
`;
cryptoList.appendChild(item);
});
modal.classList.remove('hidden');
}
function closeCryptoSelector() {
document.getElementById('cryptoSelectorModal').classList.add('hidden');
currentSelector = '';
}
function selectCrypto(coinId) {
// Convert coinId to string to match object keys
const userBalance = userCoinBalances[String(coinId)];
if (!userBalance) {
return;
}
const coin = userBalance.coin;
const balance = parseFloat(userBalance.balance);
const price = getPriceForCoin(coin);
// Update cryptoData
cryptoData[currentSelector] = {
coinId: coinId,
balance: balance,
price: price,
coin: coin
};
// Update UI elements
document.getElementById(`${currentSelector}CryptoIcon`).className =
`w-10 h-10 rounded-full flex items-center justify-center mr-3 bg-gray-700`;
document.getElementById(`${currentSelector}CryptoIcon`).innerHTML =
`<img src="${coin.image_url}" alt="${coin.name}" class="w-10 h-10 rounded-full"
onerror="this.style.display='none'; this.parentNode.innerHTML='<i class="fas fa-coins text-white"></i>';">`;
document.getElementById(`${currentSelector}CryptoLabel`).textContent = getDisplayName(coin);
document.getElementById(`${currentSelector}Balance`).textContent =
`Available: ${balance.toFixed(4)} ${getSymbolDisplay(coin)}`;
closeCryptoSelector();
updateUI();
}
function swapDirections() {
const temp = {...cryptoData.from};
cryptoData.from = {...cryptoData.to};
cryptoData.to = temp;
// Swap UI elements
const fromIcon = document.getElementById('fromCryptoIcon').innerHTML;
const fromLabel = document.getElementById('fromCryptoLabel').textContent;
const fromIconClass = document.getElementById('fromCryptoIcon').className;
const fromBalance = document.getElementById('fromBalance').textContent;
document.getElementById('fromCryptoIcon').innerHTML = document.getElementById('toCryptoIcon').innerHTML;
document.getElementById('fromCryptoLabel').textContent = document.getElementById('toCryptoLabel').textContent;
document.getElementById('fromCryptoIcon').className = document.getElementById('toCryptoIcon').className;
document.getElementById('fromBalance').textContent = document.getElementById('toBalance').textContent;
document.getElementById('toCryptoIcon').innerHTML = fromIcon;
document.getElementById('toCryptoLabel').textContent = fromLabel;
document.getElementById('toCryptoIcon').className = fromIconClass;
document.getElementById('toBalance').textContent = fromBalance;
calculateSwap();
updateFeeInfo();
}
function setAmount(percentage) {
if (!cryptoData.from.coinId) return;
const maxAmount = cryptoData.from.balance;
const amount = (maxAmount * percentage) / 100;
document.getElementById('fromAmount').value = amount.toFixed(8);
calculateSwap();
}
function showSuccess(data) {
const modal = document.getElementById('resultModal');
document.getElementById('successState').classList.remove('hidden');
document.getElementById('errorState').classList.add('hidden');
document.getElementById('transactionDetails').classList.remove('hidden');
document.getElementById('resultFromAmount').textContent =
`${parseFloat(data.from_amount).toFixed(4)} ${data.from_crypto}`;
document.getElementById('resultToAmount').textContent =
`${parseFloat(data.to_amount).toFixed(4)} ${data.to_crypto}`;
document.getElementById('resultRate').textContent =
`1 ${data.from_crypto} = ${parseFloat(data.rate).toFixed(8)} ${data.to_crypto}`;
// Add fee display if there was a fee
if (data.fee && parseFloat(data.fee) > 0) {
// Check if fee element already exists
let feeContainer = document.getElementById('feeContainer');
if (!feeContainer) {
// Create new fee element
feeContainer = document.createElement('div');
feeContainer.id = 'feeContainer';
feeContainer.className = 'flex justify-between text-sm';
feeContainer.innerHTML = `
<span class="text-gray-400 dark:text-gray-300">Fee</span>
<span id="resultFee">${parseFloat(data.fee).toFixed(4)} ${data.from_crypto} (≈ $${parseFloat(data.fee_usd).toFixed(2)})</span>
`;
document.getElementById('transactionDetails').appendChild(feeContainer);
} else {
// Update existing fee element
const feeElement = document.getElementById('resultFee');
if (feeElement) {
feeElement.textContent = `${parseFloat(data.fee).toFixed(4)} ${data.from_crypto} (≈ $${parseFloat(data.fee_usd).toFixed(2)})`;
}
}
}
modal.classList.remove('hidden');
}
function showError(message) {
const modal = document.getElementById('resultModal');
document.getElementById('successState').classList.add('hidden');
document.getElementById('errorState').classList.remove('hidden');
document.getElementById('transactionDetails').classList.add('hidden');
document.getElementById('errorMessage').textContent = message;
modal.classList.remove('hidden');
}
function closeModal() {
document.getElementById('resultModal').classList.add('hidden');
window.location.reload(); // Refresh to update balances
}
// Process the actual swap
async function processSwap(amount) {
processingSwap = true;
try {
// Show loading state
const swapButton = document.getElementById('swapButton');
const originalText = swapButton.textContent;
swapButton.textContent = 'Processing...';
swapButton.disabled = true;
const response = await fetch('{{ route("swap.process") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value
},
body: JSON.stringify({
from_coin_id: cryptoData.from.coinId,
to_coin_id: cryptoData.to.coinId,
amount: amount
})
});
const result = await response.json();
// Restore button state
swapButton.textContent = originalText;
swapButton.disabled = false;
if (result.success) {
showSuccess(result.data);
} else {
showError(result.message);
}
} catch (error) {
console.error('Swap error:', error);
showError('An error occurred while processing your swap');
// Restore button state
const swapButton = document.getElementById('swapButton');
swapButton.textContent = 'Swap';
swapButton.disabled = false;
} finally {
processingSwap = false;
}
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
// Form submission with swap fee handling
document.getElementById('swapForm').addEventListener('submit', async function(e) {
e.preventDefault();
if (processingSwap) return; // Prevent double submission
if (!cryptoData.from.coinId || !cryptoData.to.coinId) {
showError('Please select currencies');
return;
}
const amount = parseFloat(document.getElementById('fromAmount').value);
if (!amount || amount <= 0) {
showError('Please enter a valid amount');
return;
}
if (amount > cryptoData.from.balance) {
showError('Insufficient balance');
return;
}
// Check for swap fee
if (swapFeeEnabled && swapFees && swapFees[cryptoData.from.coinId]) {
const fee = parseFloat(swapFees[cryptoData.from.coinId].fee);
const feeUsd = parseFloat(swapFees[cryptoData.from.coinId].usd_equivalent);
if (fee > 0) {
// Calculate total needed
const totalNeeded = amount + fee;
// Check if user has enough balance for amount + fee
if (cryptoData.from.balance < totalNeeded) {
showError(`Insufficient balance to cover swap fee: ${cryptoData.from.balance.toFixed(4)} available, ${totalNeeded.toFixed(4)} needed (${amount.toFixed(4)} + ${fee.toFixed(4)} fee)`);
return;
}
// Show fee confirmation modal
document.getElementById('feeAmount').textContent = `${fee.toFixed(4)} ${getSymbolDisplay(cryptoData.from.coin)} (≈ $${feeUsd.toFixed(2)})`;
document.getElementById('feeCrypto').textContent = getSymbolDisplay(cryptoData.from.coin);
document.getElementById('swapFeeModal').classList.remove('hidden');
// Set up the confirm button to continue with the swap
document.getElementById('feeConfirmButton').onclick = function() {
document.getElementById('swapFeeModal').classList.add('hidden');
processSwap(amount);
};
// Set up the cancel button
document.getElementById('feeCancelButton').onclick = function() {
document.getElementById('swapFeeModal').classList.add('hidden');
};
return;
}
}
// If no fee or fee is not enabled, proceed directly
processSwap(amount);
});
// Initialize event listeners for inputs, selectors, etc.
document.getElementById('fromAmount').addEventListener('input', calculateSwap);
// Initialize fee info section
updateFeeInfo();
});
</script>
</x-user-layout>
b IDATxytVսϓ22 A@IR:hCiZ[v*E:WũZA ^dQeQ @ !jZ'>gsV仿$|?g)&x-E