From 03179647ec7c0e38e6fa89872e85d559c5121196 Mon Sep 17 00:00:00 2001 From: padreug Date: Fri, 27 Jun 2025 00:28:49 +0200 Subject: [PATCH] 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. --- static/js/index.js | 134 ++++++++++++++------------ templates/satmachineclient/index.html | 40 +++----- 2 files changed, 85 insertions(+), 89 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index c6e6209..42ee57c 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -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 + })) } }, diff --git a/templates/satmachineclient/index.html b/templates/satmachineclient/index.html index 6bbc1b2..b5600c1 100644 --- a/templates/satmachineclient/index.html +++ b/templates/satmachineclient/index.html @@ -7,7 +7,7 @@ {% endblock %} {% block page %} -
+
@@ -542,49 +542,32 @@ label="Username (Optional)" placeholder="Enter a friendly name" hint="How you'd like to be identified in the system" - /> + > - - + > - - + > + >