Refactor DCA client registration form: Update wallet selection to use walletOptions computed property for better data handling. Change element ID from 'dcaClient' to 'vue' for consistency. Improve error handling and data validation in chart loading and registration processes.
This commit is contained in:
parent
306549a656
commit
03179647ec
2 changed files with 85 additions and 89 deletions
|
|
@ -1,5 +1,5 @@
|
|||
window.app = Vue.createApp({
|
||||
el: '#dcaClient',
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
delimiters: ['${', '}'],
|
||||
data: function () {
|
||||
|
|
@ -14,7 +14,7 @@ window.app = Vue.createApp({
|
|||
fixed_mode_daily_limit: null,
|
||||
username: ''
|
||||
},
|
||||
|
||||
|
||||
// Dashboard state
|
||||
dashboardData: null,
|
||||
transactions: [],
|
||||
|
|
@ -80,17 +80,17 @@ window.app = Vue.createApp({
|
|||
'/satmachineclient/api/v1/registration-status',
|
||||
this.g.user.wallets[0].adminkey
|
||||
)
|
||||
|
||||
|
||||
this.isRegistered = data.is_registered
|
||||
this.registrationChecked = true
|
||||
|
||||
|
||||
if (!this.isRegistered) {
|
||||
this.showRegistrationDialog = true
|
||||
// Pre-fill username and default wallet if available
|
||||
this.registrationForm.username = this.g.user.username || ''
|
||||
this.registrationForm.selectedWallet = this.g.user.wallets[0] || null
|
||||
this.registrationForm.selectedWallet = this.g.user.wallets[0]?.id || null
|
||||
}
|
||||
|
||||
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Error checking registration status:', error)
|
||||
|
|
@ -108,26 +108,32 @@ window.app = Vue.createApp({
|
|||
username: this.registrationForm.username || this.g.user.username || `user_${this.g.user.id.substring(0, 8)}`
|
||||
}
|
||||
|
||||
// Find the selected wallet object to get the adminkey
|
||||
const selectedWallet = this.g.user.wallets.find(w => w.id === this.registrationForm.selectedWallet)
|
||||
if (!selectedWallet) {
|
||||
throw new Error('Selected wallet not found')
|
||||
}
|
||||
|
||||
const { data } = await LNbits.api.request(
|
||||
'POST',
|
||||
'/satmachineclient/api/v1/register',
|
||||
this.registrationForm.selectedWallet.adminkey,
|
||||
selectedWallet.adminkey,
|
||||
registrationData
|
||||
)
|
||||
|
||||
|
||||
this.isRegistered = true
|
||||
this.showRegistrationDialog = false
|
||||
|
||||
|
||||
this.$q.notify({
|
||||
type: 'positive',
|
||||
message: data.message || 'Successfully registered for DCA!',
|
||||
icon: 'check_circle',
|
||||
position: 'top'
|
||||
})
|
||||
|
||||
|
||||
// Load dashboard data after successful registration
|
||||
await this.loadDashboardData()
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error registering client:', error)
|
||||
this.$q.notify({
|
||||
|
|
@ -297,18 +303,18 @@ window.app = Vue.createApp({
|
|||
console.log('Chart already loading, ignoring request')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
this.chartLoading = true
|
||||
|
||||
|
||||
// Destroy existing chart immediately to prevent conflicts
|
||||
if (this.dcaChart) {
|
||||
console.log('Destroying existing chart before loading new data')
|
||||
this.dcaChart.destroy()
|
||||
this.dcaChart = null
|
||||
}
|
||||
|
||||
const {data} = await LNbits.api.request(
|
||||
|
||||
const { data } = await LNbits.api.request(
|
||||
'GET',
|
||||
`/satmachineclient/api/v1/dashboard/analytics?time_range=${this.chartTimeRange}`,
|
||||
this.g.user.wallets[0].adminkey
|
||||
|
|
@ -321,10 +327,10 @@ window.app = Vue.createApp({
|
|||
}
|
||||
|
||||
this.analyticsData = data
|
||||
|
||||
|
||||
// Wait for DOM update and ensure we're still in loading state
|
||||
await this.$nextTick()
|
||||
|
||||
|
||||
// Double-check we're still the active loading request
|
||||
if (this.chartLoading) {
|
||||
this.initDCAChart()
|
||||
|
|
@ -343,13 +349,13 @@ window.app = Vue.createApp({
|
|||
console.log('analyticsData:', this.analyticsData)
|
||||
console.log('dcaChart ref:', this.$refs.dcaChart)
|
||||
console.log('chartLoading state:', this.chartLoading)
|
||||
|
||||
|
||||
// Skip if we're not in a loading state (indicates this is a stale call)
|
||||
if (!this.chartLoading && this.dcaChart) {
|
||||
console.log('Chart already exists and not loading, skipping initialization')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (!this.analyticsData) {
|
||||
console.log('No analytics data available')
|
||||
return
|
||||
|
|
@ -371,7 +377,7 @@ window.app = Vue.createApp({
|
|||
console.error('Chart.js is not loaded')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
console.log('Chart.js version:', Chart.version || 'unknown')
|
||||
console.log('Chart.js available:', typeof Chart)
|
||||
|
||||
|
|
@ -383,45 +389,45 @@ window.app = Vue.createApp({
|
|||
}
|
||||
|
||||
const ctx = this.$refs.dcaChart.getContext('2d')
|
||||
|
||||
|
||||
// Use accumulation_timeline data which is already grouped by day
|
||||
const timelineData = this.analyticsData.accumulation_timeline || []
|
||||
console.log('Timeline data sample:', timelineData.slice(0, 2)) // Debug first 2 records
|
||||
|
||||
|
||||
// If we have timeline data, use it (already grouped by day)
|
||||
if (timelineData.length > 0) {
|
||||
// Calculate running totals from daily data
|
||||
let runningSats = 0
|
||||
const labels = []
|
||||
const cumulativeSats = []
|
||||
|
||||
|
||||
timelineData.forEach(point => {
|
||||
// Ensure sats is a valid number
|
||||
const sats = point.sats || 0
|
||||
const validSats = typeof sats === 'number' ? sats : parseFloat(sats) || 0
|
||||
runningSats += validSats
|
||||
|
||||
|
||||
const date = new Date(point.date)
|
||||
if (!isNaN(date.getTime())) {
|
||||
labels.push(date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
labels.push(date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
}))
|
||||
cumulativeSats.push(runningSats)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
console.log('Timeline chart data:', { labels, cumulativeSats })
|
||||
|
||||
|
||||
this.createChart(labels, cumulativeSats)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Fallback to cost_basis_history but group by date to avoid duplicates
|
||||
console.log('No timeline data, using cost_basis_history as fallback')
|
||||
const chartData = this.analyticsData.cost_basis_history || []
|
||||
console.log('Chart data sample:', chartData.slice(0, 2)) // Debug first 2 records
|
||||
|
||||
|
||||
// Handle empty data case
|
||||
if (chartData.length === 0) {
|
||||
console.log('No chart data available')
|
||||
|
|
@ -429,7 +435,7 @@ window.app = Vue.createApp({
|
|||
const placeholderGradient = ctx.createLinearGradient(0, 0, 0, 300)
|
||||
placeholderGradient.addColorStop(0, 'rgba(255, 149, 0, 0.3)')
|
||||
placeholderGradient.addColorStop(1, 'rgba(255, 149, 0, 0.05)')
|
||||
|
||||
|
||||
// Show placeholder chart with enhanced styling
|
||||
this.dcaChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
|
|
@ -463,7 +469,7 @@ window.app = Vue.createApp({
|
|||
borderWidth: 2,
|
||||
cornerRadius: 8,
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
label: function (context) {
|
||||
return `${context.parsed.y.toLocaleString()} sats`
|
||||
}
|
||||
}
|
||||
|
|
@ -486,7 +492,7 @@ window.app = Vue.createApp({
|
|||
ticks: {
|
||||
color: '#666666',
|
||||
font: { size: 12, weight: '500' },
|
||||
callback: function(value) {
|
||||
callback: function (value) {
|
||||
return value.toLocaleString() + ' sats'
|
||||
}
|
||||
}
|
||||
|
|
@ -498,7 +504,7 @@ window.app = Vue.createApp({
|
|||
this.chartLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Group cost_basis_history by date to eliminate duplicates
|
||||
const groupedData = new Map()
|
||||
chartData.forEach(point => {
|
||||
|
|
@ -513,30 +519,30 @@ window.app = Vue.createApp({
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
const uniqueChartData = Array.from(groupedData.values()).sort((a, b) =>
|
||||
|
||||
const uniqueChartData = Array.from(groupedData.values()).sort((a, b) =>
|
||||
new Date(a.date).getTime() - new Date(b.date).getTime()
|
||||
)
|
||||
|
||||
|
||||
const labels = uniqueChartData.map(point => {
|
||||
// Handle different date formats with enhanced timezone handling
|
||||
let date;
|
||||
if (point.date) {
|
||||
console.log('Raw date from API:', point.date); // Debug the actual date string
|
||||
|
||||
|
||||
// If it's an ISO string with timezone info, parse it correctly
|
||||
if (typeof point.date === 'string' && point.date.includes('T')) {
|
||||
// ISO string - parse and convert to local date
|
||||
date = new Date(point.date);
|
||||
// For display purposes, use the date part only to avoid timezone shifts
|
||||
const localDateStr = date.getFullYear() + '-' +
|
||||
String(date.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getDate()).padStart(2, '0');
|
||||
const localDateStr = date.getFullYear() + '-' +
|
||||
String(date.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getDate()).padStart(2, '0');
|
||||
date = new Date(localDateStr + 'T00:00:00'); // Force local midnight
|
||||
} else {
|
||||
date = new Date(point.date);
|
||||
}
|
||||
|
||||
|
||||
// Check if date is valid
|
||||
if (isNaN(date.getTime())) {
|
||||
date = new Date();
|
||||
|
|
@ -544,9 +550,9 @@ window.app = Vue.createApp({
|
|||
} else {
|
||||
date = new Date();
|
||||
}
|
||||
|
||||
|
||||
console.log('Formatted date:', date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }));
|
||||
|
||||
|
||||
return date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
|
|
@ -557,22 +563,22 @@ window.app = Vue.createApp({
|
|||
const sats = point.cumulative_sats || 0
|
||||
return typeof sats === 'number' ? sats : parseFloat(sats) || 0
|
||||
})
|
||||
|
||||
|
||||
console.log('Final chart data:', { labels, cumulativeSats })
|
||||
console.log('Labels array:', labels)
|
||||
console.log('CumulativeSats array:', cumulativeSats)
|
||||
|
||||
|
||||
// Validate data before creating chart
|
||||
if (labels.length === 0 || cumulativeSats.length === 0) {
|
||||
console.warn('No valid data for chart, skipping creation')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (labels.length !== cumulativeSats.length) {
|
||||
console.warn('Mismatched data arrays:', { labelsLength: labels.length, dataLength: cumulativeSats.length })
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Check for any invalid values in cumulativeSats
|
||||
const hasInvalidValues = cumulativeSats.some(val => val === null || val === undefined || isNaN(val))
|
||||
if (hasInvalidValues) {
|
||||
|
|
@ -585,34 +591,34 @@ window.app = Vue.createApp({
|
|||
|
||||
createChart(labels, cumulativeSats) {
|
||||
console.log('createChart called with loading state:', this.chartLoading)
|
||||
|
||||
|
||||
if (!this.$refs.dcaChart) {
|
||||
console.log('Chart ref not available for createChart')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Skip if we're not in a loading state (indicates this is a stale call)
|
||||
if (!this.chartLoading) {
|
||||
console.log('Not in loading state, skipping createChart')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Destroy existing chart
|
||||
if (this.dcaChart) {
|
||||
console.log('Destroying existing chart in createChart')
|
||||
this.dcaChart.destroy()
|
||||
this.dcaChart = null
|
||||
}
|
||||
|
||||
|
||||
const ctx = this.$refs.dcaChart.getContext('2d')
|
||||
|
||||
|
||||
try {
|
||||
// Create gradient for the area fill
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, 300)
|
||||
gradient.addColorStop(0, 'rgba(255, 149, 0, 0.4)')
|
||||
gradient.addColorStop(0.5, 'rgba(255, 149, 0, 0.2)')
|
||||
gradient.addColorStop(1, 'rgba(255, 149, 0, 0.05)')
|
||||
|
||||
|
||||
// Small delay to ensure Chart.js is fully initialized
|
||||
setTimeout(() => {
|
||||
try {
|
||||
|
|
@ -621,7 +627,7 @@ window.app = Vue.createApp({
|
|||
console.log('Loading state changed during timeout, aborting chart creation')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
this.dcaChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
|
|
@ -662,7 +668,7 @@ window.app = Vue.createApp({
|
|||
cornerRadius: 8,
|
||||
displayColors: false,
|
||||
callbacks: {
|
||||
title: function(context) {
|
||||
title: function (context) {
|
||||
return `📅 ${context[0].label}`
|
||||
},
|
||||
label: function (context) {
|
||||
|
|
@ -741,10 +747,10 @@ window.app = Vue.createApp({
|
|||
async created() {
|
||||
try {
|
||||
this.loading = true
|
||||
|
||||
|
||||
// Check registration status first
|
||||
await this.checkRegistrationStatus()
|
||||
|
||||
|
||||
// Only load dashboard data if registered
|
||||
if (this.isRegistered) {
|
||||
await Promise.all([
|
||||
|
|
@ -768,7 +774,7 @@ window.app = Vue.createApp({
|
|||
console.log('Loading state:', this.loading)
|
||||
console.log('Chart ref available:', !!this.$refs.dcaChart)
|
||||
console.log('Analytics data available:', !!this.analyticsData)
|
||||
|
||||
|
||||
if (this.analyticsData && this.$refs.dcaChart) {
|
||||
console.log('Initializing chart from mounted hook')
|
||||
this.initDCAChart()
|
||||
|
|
@ -781,6 +787,14 @@ window.app = Vue.createApp({
|
|||
computed: {
|
||||
hasData() {
|
||||
return this.dashboardData && !this.loading && this.isRegistered
|
||||
},
|
||||
|
||||
walletOptions() {
|
||||
if (!this.g.user?.wallets) return []
|
||||
return this.g.user.wallets.map(wallet => ({
|
||||
label: `${wallet.name} (${Math.round(wallet.balance_msat / 1000)} sats)`,
|
||||
value: wallet.id
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<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="row q-col-gutter-md" id="vue">
|
||||
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
||||
|
||||
<!-- Loading State -->
|
||||
|
|
@ -542,49 +542,32 @@
|
|||
label="Username (Optional)"
|
||||
placeholder="Enter a friendly name"
|
||||
hint="How you'd like to be identified in the system"
|
||||
/>
|
||||
></q-input>
|
||||
|
||||
<q-select
|
||||
filled
|
||||
dense
|
||||
emit-value
|
||||
v-model="registrationForm.selectedWallet"
|
||||
:options="g.user.wallets"
|
||||
option-label="name"
|
||||
:options="walletOptions"
|
||||
label="DCA Wallet *"
|
||||
hint="Choose which wallet will receive your Bitcoin DCA purchases"
|
||||
>
|
||||
<template v-slot:option="scope">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
${scope.opt.name} (ID: ${scope.opt.id.substring(0, 8)}... • ${Math.round(scope.opt.balance_msat / 1000)} sats)
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
></q-select>
|
||||
|
||||
<q-select
|
||||
filled
|
||||
dense
|
||||
emit-value
|
||||
v-model="registrationForm.dca_mode"
|
||||
:options="[
|
||||
{ label: 'Flow Mode (Recommended)', value: 'flow', description: 'Proportional distribution based on your balance' },
|
||||
{ label: 'Fixed Mode', value: 'fixed', description: 'Set daily purchase limits' }
|
||||
{ label: 'Flow Mode (Recommended)', value: 'flow' },
|
||||
{ label: 'Fixed Mode', value: 'fixed' }
|
||||
]"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
label="DCA Strategy *"
|
||||
hint="Choose how your Bitcoin purchases will be distributed"
|
||||
>
|
||||
<template v-slot:option="scope">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
<q-item-section>
|
||||
<q-item-label>${scope.opt.label} - ${scope.opt.description}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
></q-select>
|
||||
|
||||
<q-input
|
||||
v-if="registrationForm.dca_mode === 'fixed'"
|
||||
|
|
@ -598,7 +581,7 @@
|
|||
:rules="[
|
||||
val => registrationForm.dca_mode !== 'fixed' || (val && val > 0) || 'Daily limit is required for fixed mode'
|
||||
]"
|
||||
/>
|
||||
></q-input>
|
||||
|
||||
<q-banner class="bg-blue-1 text-blue-9 q-mt-md">
|
||||
<template v-slot:avatar>
|
||||
|
|
@ -619,8 +602,7 @@
|
|||
class="full-width"
|
||||
size="lg"
|
||||
>
|
||||
<q-icon name="rocket_launch" class="q-mr-sm" />
|
||||
Start My DCA Journey
|
||||
🚀 Start My DCA Journey
|
||||
</q-btn>
|
||||
</div>
|
||||
</q-form>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue