File Manager

Path: /home/liffinac1/domains/bank.stellerpay.ca/public_html/resources/views/user/

Viewing File: swap.blade.php

@extends('layouts.bankapp')
@section('title', $title)
@section('content')

<div x-data="swapApp()" x-init="init()" class="space-y-6">

    <!-- Alert Messages -->
    <x-danger-alert />
    <x-success-alert />

    <!-- Page Header -->
    <div class="flex items-center justify-between">
        <div>
            <h1 class="text-2xl md:text-3xl font-bold text-[#0D1B23] mb-1">Crypto Swap</h1>
            <p class="text-[#7A7A7A] text-sm">Swap between your main balance and crypto wallets instantly.</p>
        </div>
        <a href="{{ route('swaphistory') }}" class="px-4 py-2 bg-[#DFE5E5] hover:bg-[#C8D1D1]/60 border border-[#C8D1D1] rounded-lg text-sm text-[#4A4A4A] hover:text-[#0D1B23] transition-colors">
            <i data-lucide="history" class="w-4 h-4 inline-block mr-1"></i> History
        </a>
    </div>

    <!-- Balance Cards -->
    <div class="grid grid-cols-2 md:grid-cols-4 gap-3">
        <!-- Main Balance -->
        <div class="bg-white rounded-xl p-4 border border-[#E0E5E5]">
            <div class="flex items-center space-x-2 mb-2">
                <div class="w-8 h-8 rounded-full bg-primary-600/30 flex items-center justify-center">
                    <i data-lucide="wallet" class="w-4 h-4 text-primary-500"></i>
                </div>
                <span class="text-xs font-medium text-[#7A7A7A] uppercase">USD</span>
            </div>
            <p class="text-lg font-bold text-[#0D1B23]">{{ Auth::user()->currency }}{{ number_format(Auth::user()->account_bal, 2, '.', ',') }}</p>
        </div>

        <!-- BTC Balance -->
        <div class="bg-white rounded-xl p-4 border border-[#E0E5E5]">
            <div class="flex items-center space-x-2 mb-2">
                <img src="https://assets.coingecko.com/coins/images/1/standard/bitcoin.png?1696501400" class="w-8 h-8 rounded-full" alt="BTC">
                <span class="text-xs font-medium text-[#7A7A7A] uppercase">BTC</span>
            </div>
            <p class="text-lg font-bold text-[#0D1B23]" x-text="balances.btc + ' BTC'">{{ number_format($cryptoAccount->btc ?? 0, 8) }} BTC</p>
            <p class="text-xs text-[#7A7A7A]" x-text="'≈ $' + usdValues.btc">...</p>
        </div>

        <!-- ETH Balance -->
        <div class="bg-white rounded-xl p-4 border border-[#E0E5E5]">
            <div class="flex items-center space-x-2 mb-2">
                <img src="https://assets.coingecko.com/coins/images/279/standard/ethereum.png?1696501628" class="w-8 h-8 rounded-full" alt="ETH">
                <span class="text-xs font-medium text-[#7A7A7A] uppercase">ETH</span>
            </div>
            <p class="text-lg font-bold text-[#0D1B23]" x-text="balances.eth + ' ETH'">{{ number_format($cryptoAccount->eth ?? 0, 8) }} ETH</p>
            <p class="text-xs text-[#7A7A7A]" x-text="'≈ $' + usdValues.eth">...</p>
        </div>

        <!-- USDT Balance -->
        <div class="bg-white rounded-xl p-4 border border-[#E0E5E5]">
            <div class="flex items-center space-x-2 mb-2">
                <img src="https://assets.coingecko.com/coins/images/325/standard/Tether.png?1696501661" class="w-8 h-8 rounded-full" alt="USDT">
                <span class="text-xs font-medium text-[#7A7A7A] uppercase">USDT</span>
            </div>
            <p class="text-lg font-bold text-[#0D1B23]" x-text="balances.usdt + ' USDT'">{{ number_format($cryptoAccount->usdt ?? 0, 8) }} USDT</p>
            <p class="text-xs text-[#7A7A7A]" x-text="'≈ $' + usdValues.usdt">...</p>
        </div>
    </div>

    <!-- Swap Card -->
    <div class="max-w-lg mx-auto">
        <div class="bg-white rounded-xl border border-[#E0E5E5] overflow-hidden">
            <div class="p-6">
                <h2 class="text-lg font-semibold text-[#0D1B23] mb-5">Swap Assets</h2>

                <!-- From -->
                <div class="mb-1">
                    <label class="block text-xs uppercase tracking-wider font-semibold mb-2 text-[#7A7A7A]">From</label>
                    <div class="flex space-x-3">
                        <select x-model="source" @change="onSourceChange()" class="w-1/3 bg-[#F9F9F9]/60 border border-[#C8D1D1] rounded-lg px-3 py-3 text-[#0D1B23] text-sm focus:outline-none focus:border-primary-500 transition-colors">
                            <option value="usd">USD</option>
                            <option value="btc">BTC</option>
                            <option value="eth">ETH</option>
                            <option value="usdt">USDT</option>
                        </select>
                        <div class="flex-1 relative">
                            <input type="number" x-model="amount" @input="calculateRate()" step="any" min="0"
                                   class="w-full bg-[#F9F9F9]/60 border border-[#C8D1D1] rounded-lg px-3 py-3 text-[#0D1B23] text-sm focus:outline-none focus:border-primary-500 transition-colors"
                                   placeholder="0.00">
                            <button @click="setMaxAmount()" class="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-primary-500 hover:text-primary-400 font-semibold uppercase">Max</button>
                        </div>
                    </div>
                    <p class="text-xs text-[#7A7A7A] mt-1">
                        Available: <span x-text="getAvailable(source)"></span>
                    </p>
                </div>

                <!-- Swap Direction Button -->
                <div class="flex justify-center my-3">
                    <button @click="swapDirection()" class="w-10 h-10 rounded-full bg-[#DFE5E5] hover:bg-[#C8D1D1] border border-[#C8D1D1] flex items-center justify-center transition-colors">
                        <i data-lucide="arrow-up-down" class="w-4 h-4 text-champagne-400"></i>
                    </button>
                </div>

                <!-- To -->
                <div class="mb-4">
                    <label class="block text-xs uppercase tracking-wider font-semibold mb-2 text-[#7A7A7A]">To</label>
                    <div class="flex space-x-3">
                        <select x-model="destination" @change="onDestChange()" class="w-1/3 bg-[#F9F9F9]/60 border border-[#C8D1D1] rounded-lg px-3 py-3 text-[#0D1B23] text-sm focus:outline-none focus:border-primary-500 transition-colors">
                            <option value="usd">USD</option>
                            <option value="btc">BTC</option>
                            <option value="eth">ETH</option>
                            <option value="usdt">USDT</option>
                        </select>
                        <input type="text" :value="receiveDisplay" readonly
                               class="flex-1 bg-[#F9F9F9] border border-[#C8D1D1]/30 rounded-lg px-3 py-3 text-[#4A4A4A] text-sm cursor-not-allowed"
                               placeholder="0.00">
                    </div>
                </div>

                <!-- Rate Display -->
                <div x-show="rateDisplay" x-transition class="bg-[#F9F9F9] rounded-lg p-3 border border-[#E0E5E5] mb-5">
                    <div class="flex justify-between text-xs text-[#7A7A7A]">
                        <span>Exchange Rate</span>
                        <span x-text="rateDisplay" class="text-[#4A4A4A] font-medium"></span>
                    </div>
                </div>

                <!-- Error -->
                <div x-show="errorMsg" x-transition class="bg-[#D55672]/10 border border-[#D55672]/20 rounded-lg p-3 mb-4">
                    <p class="text-sm text-[#D55672]" x-text="errorMsg"></p>
                </div>

                <!-- Success -->
                <div x-show="successMsg" x-transition class="bg-green-500/10 border border-green-500/30 rounded-lg p-3 mb-4">
                    <p class="text-sm text-[#28A745]" x-text="successMsg"></p>
                </div>

                <!-- Submit -->
                <button @click="executeSwap()" :disabled="loading || !canSwap()"
                        class="w-full py-3.5 rounded-lg font-semibold text-sm transition-all duration-200"
                        :class="canSwap() && !loading ? 'bg-primary-600 hover:bg-primary-500 text-white' : 'bg-[#DFE5E5] text-[#7A7A7A] cursor-not-allowed'">
                    <span x-show="!loading">Swap Now</span>
                    <span x-show="loading" class="flex items-center justify-center space-x-2">
                        <svg class="animate-spin h-4 w-4 text-[#0D1B23]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                            <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                            <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
                        </svg>
                        <span>Processing...</span>
                    </span>
                </button>
            </div>
        </div>
    </div>

    <!-- Recent Swap History -->
    @if(count($recentSwaps) > 0)
    <div class="bg-white rounded-xl border border-[#E0E5E5] overflow-hidden">
        <div class="p-4 border-b border-[#E0E5E5] flex justify-between items-center">
            <h3 class="text-sm font-semibold text-[#0D1B23]">Recent Swaps</h3>
            <a href="{{ route('swaphistory') }}" class="text-xs text-primary-500 hover:text-primary-400">View All</a>
        </div>
        <div class="divide-y divide-slate-700/40">
            @foreach($recentSwaps as $swap)
            <div class="p-4 flex items-center justify-between">
                <div class="flex items-center space-x-3">
                    <div class="w-9 h-9 rounded-full bg-[#DFE5E5] flex items-center justify-center">
                        <i data-lucide="arrow-right-left" class="w-4 h-4 text-champagne-400"></i>
                    </div>
                    <div>
                        <p class="text-sm font-medium text-[#0D1B23]">{{ strtoupper($swap->source) }} → {{ strtoupper($swap->dest) }}</p>
                        <p class="text-xs text-[#7A7A7A]">{{ \Carbon\Carbon::parse($swap->created_at)->diffForHumans() }}</p>
                    </div>
                </div>
                <div class="text-right">
                    <p class="text-sm font-semibold text-[#0D1B23]">{{ number_format($swap->quantity, 8) }} {{ strtoupper($swap->dest) }}</p>
                    <p class="text-xs text-[#7A7A7A]">From {{ number_format($swap->amount, $swap->source === 'usd' ? 2 : 8) }} {{ strtoupper($swap->source) }}</p>
                </div>
            </div>
            @endforeach
        </div>
    </div>
    @endif
</div>

<script>
function swapApp() {
    return {
        source: 'usd',
        destination: 'btc',
        amount: '',
        receiveAmount: 0,
        receiveDisplay: '',
        rateDisplay: '',
        loading: false,
        errorMsg: '',
        successMsg: '',
        calcTimeout: null,
        prices: {},
        balances: {
            usd: '{{ number_format(Auth::user()->account_bal, 2, ".", "") }}',
            btc: '{{ number_format($cryptoAccount->btc ?? 0, 8, ".", "") }}',
            eth: '{{ number_format($cryptoAccount->eth ?? 0, 8, ".", "") }}',
            usdt: '{{ number_format($cryptoAccount->usdt ?? 0, 8, ".", "") }}',
        },
        usdValues: { btc: '0.00', eth: '0.00', usdt: '0.00' },

        init() {
            this.fetchPrices();
            setTimeout(() => lucide.createIcons(), 100);
        },

        async fetchPrices() {
            try {
                const resp = await fetch('{{ route("getliveprices") }}');
                const data = await resp.json();
                if (data.status === 200) {
                    this.prices = data.data;
                    // Calculate USD values
                    this.usdValues.btc = (parseFloat(this.balances.btc) * (this.prices.btc || 0)).toFixed(2);
                    this.usdValues.eth = (parseFloat(this.balances.eth) * (this.prices.eth || 0)).toFixed(2);
                    this.usdValues.usdt = (parseFloat(this.balances.usdt) * (this.prices.usdt || 0)).toFixed(2);
                }
            } catch (e) {
                console.error('Price fetch failed', e);
            }
        },

        getAvailable(coin) {
            if (coin === 'usd') return '{{ Auth::user()->currency }}' + parseFloat(this.balances.usd).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
            return parseFloat(this.balances[coin]).toFixed(8) + ' ' + coin.toUpperCase();
        },

        setMaxAmount() {
            this.amount = this.source === 'usd' ? parseFloat(this.balances.usd) : parseFloat(this.balances[this.source]);
            this.calculateRate();
        },

        swapDirection() {
            let tmp = this.source;
            this.source = this.destination;
            this.destination = tmp;
            this.calculateRate();
        },

        onSourceChange() {
            if (this.source === this.destination) {
                // pick a different destination
                const options = ['usd', 'btc', 'eth', 'usdt'];
                this.destination = options.find(o => o !== this.source) || 'usd';
            }
            this.calculateRate();
        },

        onDestChange() {
            if (this.source === this.destination) {
                const options = ['usd', 'btc', 'eth', 'usdt'];
                this.source = options.find(o => o !== this.destination) || 'usd';
            }
            this.calculateRate();
        },

        calculateRate() {
            this.errorMsg = '';
            this.successMsg = '';
            clearTimeout(this.calcTimeout);

            if (!this.amount || parseFloat(this.amount) <= 0) {
                this.receiveDisplay = '';
                this.rateDisplay = '';
                return;
            }

            this.calcTimeout = setTimeout(async () => {
                try {
                    const resp = await fetch(`{{ route('calculateswap') }}?source=${this.source}&destination=${this.destination}&amount=${this.amount}`);
                    const data = await resp.json();
                    if (data.status === 200) {
                        this.receiveAmount = data.data.receive_amount;
                        const decimals = this.destination === 'usd' ? 2 : 8;
                        this.receiveDisplay = parseFloat(data.data.receive_amount).toFixed(decimals) + ' ' + this.destination.toUpperCase();
                        this.rateDisplay = data.data.rate_display;
                    } else {
                        this.errorMsg = data.message || 'Failed to get rate';
                    }
                } catch (e) {
                    this.errorMsg = 'Network error. Please try again.';
                }
            }, 400);
        },

        canSwap() {
            return this.amount && parseFloat(this.amount) > 0 && this.receiveAmount > 0 && this.source !== this.destination;
        },

        async executeSwap() {
            if (!this.canSwap()) return;
            this.loading = true;
            this.errorMsg = '';
            this.successMsg = '';

            try {
                const resp = await fetch('{{ route("exchangenow") }}', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf_token"]').getAttribute('content')
                    },
                    body: JSON.stringify({
                        source: this.source,
                        destination: this.destination,
                        amount: parseFloat(this.amount),
                        quantity: this.receiveAmount
                    })
                });
                const data = await resp.json();
                if (data.status === 200) {
                    this.successMsg = data.success;
                    this.amount = '';
                    this.receiveDisplay = '';
                    this.rateDisplay = '';
                    this.receiveAmount = 0;
                    // Refresh balances
                    await this.refreshBalances();
                } else {
                    this.errorMsg = data.message || 'Swap failed. Please try again.';
                }
            } catch (e) {
                this.errorMsg = 'Network error. Please try again.';
            } finally {
                this.loading = false;
            }
        },

        async refreshBalances() {
            try {
                const resp = await fetch('{{ route("getallbalances") }}');
                const data = await resp.json();
                if (data.status === 200) {
                    this.balances = {
                        usd: parseFloat(data.data.usd).toFixed(2),
                        btc: parseFloat(data.data.btc).toFixed(8),
                        eth: parseFloat(data.data.eth).toFixed(8),
                        usdt: parseFloat(data.data.usdt).toFixed(8),
                    };
                    // Recalculate USD values
                    this.usdValues.btc = (parseFloat(this.balances.btc) * (this.prices.btc || 0)).toFixed(2);
                    this.usdValues.eth = (parseFloat(this.balances.eth) * (this.prices.eth || 0)).toFixed(2);
                    this.usdValues.usdt = (parseFloat(this.balances.usdt) * (this.prices.usdt || 0)).toFixed(2);
                }
            } catch (e) {
                console.error('Balance refresh failed', e);
            }
        }
    }
}
</script>
@endsection