509 lines
20 KiB
HTML
509 lines
20 KiB
HTML
<!--/////////////////////////////////////////////////-->
|
|
<!--//PAGE FOR THE DCA CLIENT EXTENSION IN LNBITS/////-->
|
|
<!--/////////////////////////////////////////////////-->
|
|
|
|
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
|
%} {% block scripts %} {{ window_vars(user) }}
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
|
|
<script src="{{ static_url_for('satmachineclient/static', path='js/index.js') }}"></script>
|
|
{% endblock %} {% block page %}
|
|
<div class="row q-col-gutter-md" id="dcaClient">
|
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
|
|
|
<!-- Loading State -->
|
|
<q-card v-if="loading">
|
|
<q-card-section class="text-center">
|
|
<q-spinner size="2em" />
|
|
<div class="q-mt-md">Loading your DCA dashboard...</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Error State -->
|
|
<q-card v-if="error && !loading" class="bg-negative text-white">
|
|
<q-card-section>
|
|
<q-icon name="error" class="q-mr-sm" />
|
|
${error}
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Dashboard Content -->
|
|
<div v-if="hasData">
|
|
<!-- Hero Card - Bitcoin Stack Progress -->
|
|
<q-card class="q-mb-md bg-gradient-to-r from-orange-1 to-orange-2" style="background: linear-gradient(135deg, #FFF3E0 0%, #FFE0B2 100%);">
|
|
<q-card-section class="text-center">
|
|
<div class="row items-center">
|
|
<div class="col-12 col-md-8">
|
|
<div class="text-h4 text-orange-8 q-mb-sm">
|
|
⚡ ${formatSats(dashboardData.total_sats_accumulated)}
|
|
<q-spinner-gears
|
|
v-if="dashboardData.current_fiat_balance > 0 && dashboardData.dca_status === 'active'"
|
|
color="orange-7"
|
|
size="1.2em"
|
|
class="q-ml-sm"
|
|
/>
|
|
</div>
|
|
<div class="text-h6 text-grey-7 q-mb-xs">
|
|
stacked and growing!
|
|
<span v-if="dashboardData.current_fiat_balance > 0 && dashboardData.dca_status === 'active'" class="text-orange-7 q-ml-xs">
|
|
• actively stacking
|
|
</span>
|
|
</div>
|
|
<div class="text-caption text-grey-6">
|
|
${dashboardData.total_transactions} DCA ${dashboardData.total_transactions === 1 ? 'purchase' : 'purchases'} • <span class="text-weight-bold text-blue-8">${dashboardData.dca_mode}</span> mode
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-4 q-mt-md">
|
|
<q-circular-progress
|
|
show-value
|
|
font-size="12px"
|
|
:value="getMilestoneProgress()"
|
|
size="80px"
|
|
:thickness="0.25"
|
|
color="orange-7"
|
|
track-color="orange-1"
|
|
class="q-ma-md text-orange-8"
|
|
>
|
|
${(getMilestoneProgress() || 0).toFixed(2)}%
|
|
</q-circular-progress>
|
|
<div class="text-body2 text-weight-medium text-grey-7">to ${getNextMilestone().name}</div>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Key Metrics Cards -->
|
|
<div class="row q-col-gutter-md q-mb-md">
|
|
<div class="col-6 col-md-3">
|
|
<q-card class="text-center bg-orange-1" style="min-height: 100px;">
|
|
<q-card-section class="q-pa-md">
|
|
<div class="text-h6 text-orange-8">${formatCurrency(dashboardData.total_fiat_invested)}</div>
|
|
<div class="text-caption text-orange-7 text-weight-medium">Total Invested</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
<div class="col-6 col-md-3">
|
|
<q-card class="text-center bg-blue-1" style="min-height: 100px;">
|
|
<q-card-section class="q-pa-md">
|
|
<div class="text-h6 text-blue-8">${formatCurrency(dashboardData.current_fiat_balance)}</div>
|
|
<div class="text-caption text-blue-7 text-weight-medium">Available Balance</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
<div class="col-6 col-md-3">
|
|
<q-card class="text-center bg-green-1" style="min-height: 100px;">
|
|
<q-card-section class="q-pa-md">
|
|
<div class="text-h5 text-weight-bold text-green-8">${dashboardData.total_transactions}</div>
|
|
<div class="text-caption text-green-7 text-weight-medium">DCA Purchases</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
<div class="col-6 col-md-3">
|
|
<q-card class="text-center bg-purple-1" style="min-height: 100px;">
|
|
<q-card-section class="q-pa-md">
|
|
<div class="text-h6 text-purple-8" v-if="dashboardData.average_cost_basis > 0">
|
|
${Math.round(dashboardData.average_cost_basis)}
|
|
</div>
|
|
<div class="text-h6 text-purple-8" v-else>-</div>
|
|
<div class="text-caption text-purple-7 text-weight-medium">Avg Cost (sats/GTQ)</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
<!-- Bitcoin Performance Card -->
|
|
<q-card class="q-mb-md">
|
|
<q-card-section>
|
|
<div class="row items-center q-mb-md">
|
|
<div class="col">
|
|
<h6 class="text-subtitle2 q-my-none">
|
|
Bitcoin Performance
|
|
</h6>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn
|
|
flat
|
|
dense
|
|
:color="showFiatValues ? 'orange' : 'grey'"
|
|
:icon="showFiatValues ? 'visibility_off' : 'attach_money'"
|
|
@click="showFiatValues = !showFiatValues"
|
|
size="sm"
|
|
>
|
|
${showFiatValues ? 'Hide' : 'Show'} Fiat Values
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Performance Cards (Conditional) -->
|
|
<div v-if="showFiatValues" class="row q-col-gutter-md">
|
|
<div class="col-12 col-md-6">
|
|
<q-card flat class="bg-orange-1">
|
|
<q-card-section class="text-center q-pa-md">
|
|
<div class="text-h5 text-orange-8">${formatCurrencyWithCode(dashboardData.current_sats_fiat_value, dashboardData.currency)}</div>
|
|
<div class="text-caption text-orange-7 q-mb-xs">Current Bitcoin Value</div>
|
|
<div class="text-caption text-grey">at today's ${dashboardData.currency} rate</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<q-card flat :class="(dashboardData.current_sats_fiat_value + dashboardData.current_fiat_balance) > dashboardData.total_fiat_invested ? 'bg-green-1' : 'bg-red-1'">
|
|
<q-card-section class="text-center q-pa-md">
|
|
<div class="text-h5" :class="(dashboardData.current_sats_fiat_value + dashboardData.current_fiat_balance) > dashboardData.total_fiat_invested ? 'text-green-8' : 'text-red-8'">
|
|
${(dashboardData.current_sats_fiat_value + dashboardData.current_fiat_balance) > dashboardData.total_fiat_invested ? '📈 +' : '📉 '}
|
|
${formatCurrencyWithCode((dashboardData.current_sats_fiat_value + dashboardData.current_fiat_balance) - dashboardData.total_fiat_invested, dashboardData.currency)}
|
|
</div>
|
|
<div class="text-caption" :class="(dashboardData.current_sats_fiat_value + dashboardData.current_fiat_balance) > dashboardData.total_fiat_invested ? 'text-green-7' : 'text-red-7'">
|
|
${(dashboardData.current_sats_fiat_value + dashboardData.current_fiat_balance) > dashboardData.total_fiat_invested ? 'Portfolio Growth' : 'Portfolio Change'}
|
|
</div>
|
|
<div class="text-caption text-grey">vs total invested</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
<!-- Pending Deposits Row -->
|
|
<div v-if="dashboardData.pending_fiat_deposits > 0" class="q-mt-md">
|
|
<q-banner rounded class="bg-orange-1 text-orange-9">
|
|
<template v-slot:avatar>
|
|
<q-icon name="schedule" color="orange" size="md" />
|
|
</template>
|
|
<div class="text-subtitle2">
|
|
⏳ <strong>${formatCurrency(dashboardData.pending_fiat_deposits)}</strong> ready to DCA
|
|
</div>
|
|
<div class="text-caption">
|
|
Cash waiting to be inserted into ATM for automatic Bitcoin purchases
|
|
</div>
|
|
</q-banner>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- DCA Status & Strategy -->
|
|
<q-card class="q-mb-md">
|
|
<q-card-section>
|
|
<div class="row items-center q-mb-md">
|
|
<div class="col">
|
|
<h6 class="text-subtitle2 q-my-none">
|
|
DCA Strategy
|
|
</h6>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-chip
|
|
:color="dashboardData.dca_status === 'active' ? 'positive' : 'warning'"
|
|
text-color="white"
|
|
icon="circle"
|
|
size="sm"
|
|
>
|
|
${dashboardData.dca_status}
|
|
</q-chip>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row q-col-gutter-md">
|
|
<div class="col-12 col-md-6">
|
|
<q-card flat class="bg-blue-1">
|
|
<q-card-section class="q-pa-md">
|
|
<div class="text-h6 text-blue-8">
|
|
<strong>${dashboardData.dca_mode}</strong> Mode
|
|
</div>
|
|
<div class="text-caption text-blue-7 q-mt-xs">
|
|
Automatic Bitcoin accumulation strategy
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
<div class="col-12 col-md-6" v-if="dashboardData.average_cost_basis > 0">
|
|
<q-card flat class="bg-purple-1">
|
|
<q-card-section class="q-pa-md">
|
|
<div class="text-body2 text-purple-8">
|
|
<span class="text-h6"><strong>${Math.round(dashboardData.average_cost_basis)}</strong></span> sats/GTQ
|
|
</div>
|
|
<div class="text-caption text-purple-7 q-mt-xs">
|
|
Average cost basis over time
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
</div>
|
|
|
|
<!-- DCA Performance Chart - Always render to ensure canvas is available -->
|
|
<q-card class="q-mb-md">
|
|
<q-card-section>
|
|
<h6 class="text-subtitle2 q-my-none q-mb-md">Bitcoin Accumulation Progress</h6>
|
|
<div class="chart-container" style="position: relative; height: 300px;">
|
|
<canvas ref="dcaChart" style="max-height: 300px;"></canvas>
|
|
</div>
|
|
<div class="row q-mt-sm">
|
|
<div class="col">
|
|
<q-btn-toggle
|
|
v-model="chartTimeRange"
|
|
@update:model-value="loadChartData"
|
|
toggle-color="orange"
|
|
:options="[
|
|
{label: '7D', value: '7d'},
|
|
{label: '30D', value: '30d'},
|
|
{label: '90D', value: '90d'},
|
|
{label: 'ALL', value: 'all'}
|
|
]"
|
|
size="sm"
|
|
flat
|
|
:disable="chartLoading"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Dashboard Content -->
|
|
<div v-if="hasData">
|
|
|
|
<!-- Transaction History -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center q-mb-md">
|
|
<div class="col">
|
|
<h6 class="text-subtitle2 q-my-none">
|
|
DCA Transaction History
|
|
</h6>
|
|
<div class="text-caption text-grey-6">
|
|
Your Bitcoin accumulation journey
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn
|
|
flat
|
|
dense
|
|
icon="refresh"
|
|
@click="refreshAllData"
|
|
:loading="loading"
|
|
size="sm"
|
|
color="orange"
|
|
>
|
|
Refresh
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
|
|
<q-table
|
|
:rows="transactions"
|
|
:columns="transactionColumns"
|
|
row-key="id"
|
|
:pagination="transactionPagination"
|
|
:loading="loading"
|
|
flat
|
|
bordered
|
|
:no-data-label="'🚀 No transactions yet - start your DCA journey!'"
|
|
class="q-mt-md"
|
|
>
|
|
<template v-slot:body-cell-amount_sats="props">
|
|
<q-td :props="props" class="text-center">
|
|
<div class="text-weight-bold text-orange-8">
|
|
${formatSats(props.row.amount_sats)}
|
|
</div>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template v-slot:body-cell-amount_fiat="props">
|
|
<q-td :props="props" class="text-center">
|
|
<div class="text-weight-medium">
|
|
${formatCurrency(props.row.amount_fiat)}
|
|
</div>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template v-slot:body-cell-date="props">
|
|
<q-td :props="props">
|
|
<div class="text-weight-medium">${formatDate(props.value)}</div>
|
|
<div class="text-caption text-grey">
|
|
${formatTime(props.value)}
|
|
</div>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template v-slot:body-cell-status="props">
|
|
<q-td :props="props" class="text-center">
|
|
<q-chip
|
|
:color="props.row.status === 'confirmed' ? 'positive' : props.row.status === 'failed' ? 'negative' : 'warning'"
|
|
text-color="white"
|
|
size="sm"
|
|
:icon="props.row.status === 'confirmed' ? 'check_circle' : props.row.status === 'failed' ? 'error' : 'schedule'"
|
|
>
|
|
${props.row.status}
|
|
</q-chip>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template v-slot:body-cell-type="props">
|
|
<q-td :props="props" class="text-center">
|
|
<q-badge
|
|
:color="props.row.transaction_type === 'flow' ? 'blue' : 'purple'"
|
|
:label="props.row.transaction_type.toUpperCase()"
|
|
/>
|
|
</q-td>
|
|
</template>
|
|
|
|
<template v-slot:no-data="{ message }">
|
|
<div class="full-width row flex-center q-pa-lg">
|
|
<div class="text-center">
|
|
<q-icon name="rocket_launch" size="3em" class="text-orange-5 q-mb-md" />
|
|
<div class="text-h6 text-grey-7 q-mb-sm">${message}</div>
|
|
<div class="text-caption text-grey-5">
|
|
Visit your nearest Lamassu ATM to begin stacking sats automatically
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
|
|
<!-- Welcome Card -->
|
|
<q-card class="bg-gradient-to-br" style="background: linear-gradient(135deg, #F3E5F5 0%, #E1F5FE 100%);">
|
|
<q-card-section>
|
|
<div class="text-center">
|
|
<h6 class="text-subtitle1 q-my-none text-orange-8">
|
|
Bitcoin DCA Dashboard
|
|
</h6>
|
|
<p class="text-body2 text-grey-7 q-mb-none">
|
|
Your automated sat stacking journey with {{SITE_TITLE}}
|
|
</p>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Quick Actions -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<h6 class="text-subtitle2 q-my-none q-mb-md">
|
|
Quick Actions
|
|
</h6>
|
|
<div class="q-gutter-sm">
|
|
<q-btn
|
|
outline
|
|
color="orange"
|
|
icon="refresh"
|
|
label="Refresh Data"
|
|
@click="refreshAllData()"
|
|
:loading="loading"
|
|
class="full-width"
|
|
size="md"
|
|
/>
|
|
<q-btn
|
|
outline
|
|
color="blue"
|
|
icon="download"
|
|
label="Export History"
|
|
class="full-width"
|
|
size="md"
|
|
disable
|
|
>
|
|
<q-tooltip>Export functionality coming soon!</q-tooltip>
|
|
</q-btn>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Stacking Milestones -->
|
|
<q-card v-if="dashboardData">
|
|
<q-card-section>
|
|
<h6 class="text-subtitle2 q-my-none q-mb-md">
|
|
🏆 Stacking Milestones
|
|
</h6>
|
|
<q-list dense>
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<div v-if="dashboardData.total_sats_accumulated >= 10000" class="text-positive text-h6">✅</div>
|
|
<div v-else class="text-grey-5 text-h6">⭕</div>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label class="text-body2">10,000 sats</q-item-label>
|
|
<q-item-label caption>First milestone 🎯</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<div v-if="dashboardData.total_sats_accumulated >= 100000" class="text-positive text-h6">✅</div>
|
|
<div v-else class="text-grey-5 text-h6">⭕</div>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label class="text-body2">100,000 sats</q-item-label>
|
|
<q-item-label caption>Getting serious 🚀</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<div v-if="dashboardData.total_sats_accumulated >= 500000" class="text-positive text-h6">✅</div>
|
|
<div v-else class="text-grey-5 text-h6">⭕</div>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label class="text-body2">500,000 sats</q-item-label>
|
|
<q-item-label caption>Half a million! 🔥</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<div v-if="dashboardData.total_sats_accumulated >= 1000000" class="text-positive text-h6">✅</div>
|
|
<div v-else class="text-grey-5 text-h6">⭕</div>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label class="text-body2">1,000,000 sats</q-item-label>
|
|
<q-item-label caption>True HODLer 💎</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- DCA Tips -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<h6 class="text-subtitle2 q-my-none q-mb-md">
|
|
💡 DCA Tips
|
|
</h6>
|
|
<q-list dense>
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<div class="text-orange text-weight-bold">📈</div>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label class="text-caption">
|
|
Consistency beats timing - small, regular purchases smooth out volatility
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<div class="text-blue text-weight-bold">⏰</div>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label class="text-caption">
|
|
Time in the market beats timing the market
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item>
|
|
<q-item-section avatar>
|
|
<div class="text-green text-weight-bold">🔐</div>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label class="text-caption">
|
|
Each sat purchased is sovereignty gained
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|