From bf9bd338be98bb138e8f71cec436ffaac268cccb Mon Sep 17 00:00:00 2001 From: Ayush Date: Tue, 28 Jul 2026 20:50:08 +0530 Subject: [PATCH 1/3] UI: Centralize alerts, field errors, and status dot, refactored the ui to maintain re-usability --- src/components/data/ConnectionStatus.tsx | 18 +-- src/components/layout/Shell.tsx | 13 +- .../mining/AdvancedMiningConfigForm.tsx | 9 +- src/components/pools/PoolIdentityFields.tsx | 16 +- src/components/pools/PoolPriorityEditor.tsx | 3 +- src/components/settings/ConfigurationTab.tsx | 28 ++-- src/components/setup/SetupWizard.tsx | 149 +++++++++--------- .../setup/steps/BitcoinPrereqStep.tsx | 24 +-- src/components/setup/steps/BitcoinSetup.tsx | 62 +++----- src/components/setup/steps/HashrateStep.tsx | 33 ++-- src/components/setup/steps/JdcConfigStep.tsx | 25 ++- .../setup/steps/MiningIdentityStep.tsx | 21 ++- src/components/setup/steps/PoolConfigStep.tsx | 2 +- src/components/setup/steps/ReviewStart.tsx | 23 +-- .../setup/steps/TranslatorConfigStep.tsx | 20 +-- src/components/ui/alert.tsx | 90 +++++++++++ src/components/ui/field-error.tsx | 25 +++ src/components/ui/status-dot.tsx | 45 ++++++ src/pages/UnifiedDashboard.tsx | 97 ++++++------ 19 files changed, 391 insertions(+), 312 deletions(-) create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/field-error.tsx create mode 100644 src/components/ui/status-dot.tsx diff --git a/src/components/data/ConnectionStatus.tsx b/src/components/data/ConnectionStatus.tsx index 164ec85f..77a8ffca 100644 --- a/src/components/data/ConnectionStatus.tsx +++ b/src/components/data/ConnectionStatus.tsx @@ -1,4 +1,5 @@ import { cn } from '@/lib/utils'; +import { StatusDot } from '@/components/ui/status-dot'; type ConnectionState = 'connected' | 'connecting' | 'disconnected' | 'error'; @@ -17,23 +18,18 @@ export function ConnectionStatus({ label, className, }: ConnectionStatusProps) { - const stateConfig: Record = { - connected: { color: 'bg-green-500', text: 'Connected' }, - connecting: { color: 'bg-yellow-500 animate-pulse', text: 'Connecting' }, - disconnected: { color: 'bg-muted-foreground', text: 'Disconnected' }, - error: { color: 'bg-red-500', text: 'Error' }, + const stateConfig: Record = { + connected: { status: 'connected', text: 'Connected' }, + connecting: { status: 'connecting', text: 'Connecting' }, + disconnected: { status: 'idle', text: 'Disconnected' }, + error: { status: 'disconnected', text: 'Error' }, }; const config = stateConfig[state]; return (
-
+ {label || config.text} diff --git a/src/components/layout/Shell.tsx b/src/components/layout/Shell.tsx index 08e023d3..0129c394 100644 --- a/src/components/layout/Shell.tsx +++ b/src/components/layout/Shell.tsx @@ -7,6 +7,7 @@ import type { AppMode, AppFeatures } from '@/types/api'; import { getAppFeatures } from '@/types/api'; import { useUiConfig } from '@/hooks/useUiConfig'; import { PoolIcon } from '@/components/ui/pool-icon'; +import { StatusDot } from '@/components/ui/status-dot'; function useTheme() { const [isDark, setIsDark] = useState(() => { @@ -173,20 +174,12 @@ export function Shell({ <> {/* Mobile: dot + uptime only (no status text to save space) */} - + Uptime: {formatUptime(uptime ?? 0)} {/* Desktop: dot + full status text + uptime */} - + {connectionStatus === 'connected' ? ( {connectedStatusLabel} diff --git a/src/components/mining/AdvancedMiningConfigForm.tsx b/src/components/mining/AdvancedMiningConfigForm.tsx index 77b2f865..df6d56ca 100644 --- a/src/components/mining/AdvancedMiningConfigForm.tsx +++ b/src/components/mining/AdvancedMiningConfigForm.tsx @@ -6,6 +6,7 @@ import { } from '@sv2-ui/shared'; import { Switch } from '@/components/ui/switch'; +import { FieldError } from '@/components/ui/field-error'; export interface AdvancedMiningConfigValues { sharesPerMinute: string; @@ -124,9 +125,7 @@ export function AdvancedMiningConfigForm({ className="h-9 w-full rounded-lg border border-input bg-background px-3 text-sm outline-none transition-all focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/15" /> {!sharesPerMinuteValid ? ( -

- Enter a value greater than 0. -

+ ) : (

Target share rate for variable difficulty. @@ -151,9 +150,7 @@ export function AdvancedMiningConfigForm({ className="h-9 w-full rounded-lg border border-input bg-background px-3 text-sm outline-none transition-all focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/15" /> {!downstreamExtranonce2SizeValid ? ( -

- Enter a whole number from 1 to 65,535. -

+ ) : (

Bytes assigned to downstream SV1 miners. diff --git a/src/components/pools/PoolIdentityFields.tsx b/src/components/pools/PoolIdentityFields.tsx index a446a456..e652bf56 100644 --- a/src/components/pools/PoolIdentityFields.tsx +++ b/src/components/pools/PoolIdentityFields.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef, useState } from 'react'; -import { AlertCircle } from 'lucide-react'; +import { Alert } from '@/components/ui/alert'; +import { FieldError } from '@/components/ui/field-error'; import { shouldAggregateTranslatorChannels, type BitcoinNetwork, @@ -64,13 +65,12 @@ export function PoolIdentityFields({ (required) {showBraiinsUsernameWarning && ( -

-
+ )} - {error &&

{error}

} +

{miningMode === 'solo' ? 'Bitcoin address used by the pool for solo mining payouts.' @@ -147,9 +147,7 @@ function SriPoolIdentityFields({ autoComplete="off" className="w-full h-10 px-3 rounded-lg border border-input bg-background focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/15 outline-none transition-all font-mono text-sm" /> - {getBitcoinAddressError(payoutAddress, network) && ( -

{getBitcoinAddressError(payoutAddress, network)}

- )} +

Used with worker and donation settings to build this pool identity.

@@ -215,7 +213,7 @@ function SriPoolIdentityFields({

- {identityError &&

{identityError}

} +
); } diff --git a/src/components/pools/PoolPriorityEditor.tsx b/src/components/pools/PoolPriorityEditor.tsx index f64275fb..a8effaa9 100644 --- a/src/components/pools/PoolPriorityEditor.tsx +++ b/src/components/pools/PoolPriorityEditor.tsx @@ -1,6 +1,7 @@ import { useRef, useState } from 'react'; import { ArrowDown, ArrowUp, GripVertical, X } from 'lucide-react'; import { DEFAULT_POOL_PORT, type MiningMode, type PoolConfig } from '@sv2-ui/shared'; +import { FieldError } from '@/components/ui/field-error'; import { PoolIcon } from '@/components/ui/pool-icon'; import { createEmptyCustomPool, @@ -380,7 +381,7 @@ function CustomPoolFields({ pubkeyError ? 'border-destructive focus-visible:border-destructive' : 'border-input focus-visible:border-primary' }`} /> - {pubkeyError &&

{pubkeyError}

} + diff --git a/src/components/settings/ConfigurationTab.tsx b/src/components/settings/ConfigurationTab.tsx index d7f77073..e8cd61c4 100644 --- a/src/components/settings/ConfigurationTab.tsx +++ b/src/components/settings/ConfigurationTab.tsx @@ -55,7 +55,8 @@ import { Check, X, } from 'lucide-react'; - +import { FieldError } from '@/components/ui/field-error'; +import { StatusDot } from '@/components/ui/status-dot'; const SETUP_TARGET_STEP_STORAGE_KEY = 'sv2-ui-setup-target-step'; type EditingField = null | 'pools' | 'mode' | 'signature' | 'hashrate' | 'telemetry' | 'advanced'; @@ -356,10 +357,10 @@ export function ConfigurationTab() { const updateEditPoolIdentity = (index: number, nextPool: PoolConfig) => { setEditPools((currentPools) => currentPools ? normalizePoolPriorityIdentities( - currentPools.map((pool, poolIndex) => poolIndex === index ? nextPool : pool), - currentPools[0], - activeMiningMode, - ) + currentPools.map((pool, poolIndex) => poolIndex === index ? nextPool : pool), + currentPools[0], + activeMiningMode, + ) : null); }; @@ -370,7 +371,7 @@ export function ConfigurationTab() {
-
+

{isRunning ? 'Services Running' : 'Services Stopped'}

@@ -525,11 +526,10 @@ export function ConfigurationTab() { key={m} type="button" onClick={() => setEditMode(m)} - className={`px-4 py-2 rounded-lg border text-sm font-medium transition-all ${ - editMode === m + className={`px-4 py-2 rounded-lg border text-sm font-medium transition-all ${editMode === m ? 'border-primary bg-primary/[0.04] text-primary' : 'border-border bg-card hover:border-primary/45' - }`} + }`} > {m === 'jd' ? 'Job Declaration (Custom Templates)' : 'Pool Templates'} @@ -687,8 +687,8 @@ export function ConfigurationTab() { placeholder="Miner signature" className="w-full h-10 px-3 rounded-lg border border-input bg-background font-mono text-sm focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/15 outline-none transition-all" /> - {editSignature && getIdentifierError(editSignature) && ( -

{getIdentifierError(editSignature)}

+ {editSignature && ( + )}

Miner-chosen tag shown in coinbase transactions on block explorers. @@ -766,9 +766,7 @@ export function ConfigurationTab() { autoComplete="off" className="w-full h-9 px-3 rounded-lg border border-input bg-background font-mono text-sm focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/15 outline-none transition-all" /> - {minerTelemetryCidrError && ( -

{minerTelemetryCidrError}

- )} +

Private LAN subnet where miners expose their web/API interface.

@@ -977,4 +975,4 @@ function PoolSummary({
); -} +} \ No newline at end of file diff --git a/src/components/setup/SetupWizard.tsx b/src/components/setup/SetupWizard.tsx index bb1ac6d0..dd31186f 100644 --- a/src/components/setup/SetupWizard.tsx +++ b/src/components/setup/SetupWizard.tsx @@ -1,9 +1,10 @@ import { useState, useCallback, useEffect, useRef } from 'react'; import { useLocation } from 'wouter'; -import { ArrowLeft, AlertCircle, Sun, Moon } from 'lucide-react'; +import { ArrowLeft, Sun, Moon } from 'lucide-react'; import { SetupStep, SetupData, initialSetupData } from './types'; import { shouldAggregateTranslatorChannelsForPools } from './poolRules'; import { BITCOIN_MESSAGES } from '@/lib/messages'; +import { Alert } from '@/components/ui/alert'; function useTheme() { const [isDark, setIsDark] = useState(() => { @@ -89,7 +90,6 @@ export function SetupWizard() { const [currentStep, setCurrentStep] = useState('mining-mode'); const [data, setData] = useState(initialSetupData); const [isReconfiguring, setIsReconfiguring] = useState(false); - const [isSetupReview, setIsSetupReview] = useState(false); const [loadingConfig, setLoadingConfig] = useState(true); const [bitcoinSetupNotice, setBitcoinSetupNotice] = useState(null); const { results: discoveredNodes, isLoading: isDiscovering, retry: retryDiscovery } = useBitcoinRpcDiscovery(); @@ -110,7 +110,6 @@ export function SetupWizard() { const setupReviewRequested = window.sessionStorage.getItem(SETUP_REVIEW_STORAGE_KEY) === 'true'; window.sessionStorage.removeItem(SETUP_TARGET_STEP_STORAGE_KEY); window.sessionStorage.removeItem(SETUP_REVIEW_STORAGE_KEY); - setIsSetupReview(setupReviewRequested); if (targetStep === 'bitcoin' && config.bitcoin) { nextConfig = { @@ -252,85 +251,81 @@ export function SetupWizard() { return ( <> -
- {/* Header */} -
- - -
- {nonModeSteps.map((_, idx) => ( -
- ))} -
+
+ {/* Header */} +
+ + +
+ {nonModeSteps.map((_, idx) => ( +
+ ))} +
- -
+ +
- {/* Step content */} -
-
-
- {isReconfiguring && currentStepIndex === 1 && ( -
-
- )} - {currentStep === 'template-mode' && } - {currentStep === 'pool' && } - {currentStep === 'bitcoin-prereq' && ( - - )} - {currentStep === 'bitcoin' && ( - setBitcoinSetupNotice(null)} - discoveredNodes={discoveredNodes} - /> - )} - {currentStep === 'hashrate' && } - {currentStep === 'identity' && } - {currentStep === 'review' && ( - - )} + {/* Step content */} +
+
+
+ {isReconfiguring && currentStepIndex === 1 && ( + + Reconfiguring SV2 setup — this will replace your current configuration. + + )} + {currentStep === 'template-mode' && } + {currentStep === 'pool' && } + {currentStep === 'bitcoin-prereq' && ( + + )} + {currentStep === 'bitcoin' && ( + setBitcoinSetupNotice(null)} + discoveredNodes={discoveredNodes} + /> + )} + {currentStep === 'hashrate' && } + {currentStep === 'identity' && } + {currentStep === 'review' && ( + + )} +
-
- {flashOverlay} + {flashOverlay} ); } diff --git a/src/components/setup/steps/BitcoinPrereqStep.tsx b/src/components/setup/steps/BitcoinPrereqStep.tsx index 5c605ef3..ae497141 100644 --- a/src/components/setup/steps/BitcoinPrereqStep.tsx +++ b/src/components/setup/steps/BitcoinPrereqStep.tsx @@ -12,7 +12,8 @@ import { } from '@sv2-ui/shared'; import { BITCOIN_MESSAGES } from '@/lib/messages'; import { StepProps, BitcoinConfig } from '../types'; -import { Copy, Check, ExternalLink, Loader2, RotateCw, CheckCircle2, AlertCircle } from 'lucide-react'; +import { Check, Loader2, AlertCircle, CheckCircle2, RotateCw, Copy, ExternalLink } from 'lucide-react'; +import { Alert } from '@/components/ui/alert'; import type { BitcoinRpcDiscoveryResult } from '@/hooks/useBitcoinRpcDiscovery'; import { useHostEnv } from '@/hooks/useHostEnv'; import { BitcoinNetworkSelector } from '../BitcoinNetworkSelector'; @@ -285,12 +286,6 @@ export function BitcoinPrereqStep({ data, updateData, onNext, discoveredNodes, i description: `${detectedNodeSummary}. Continue to configure the connection.`, }; - const readinessClasses = { - neutral: 'border-border bg-muted/50 text-muted-foreground', - warning: 'border-warning/20 bg-warning/[0.08] text-warning', - destructive: 'border-destructive/20 bg-destructive/[0.08] text-destructive', - success: 'border-success/20 bg-success/10 text-success', - }[readiness.tone]; const canConfigureManually = !hostOsLoading && !isDiscovering @@ -380,17 +375,14 @@ export function BitcoinPrereqStep({ data, updateData, onNext, discoveredNodes, i />
-
- {readiness.icon} -
-

{readiness.title}

-

{readiness.description}

-
-
+

{readiness.title}

+

{readiness.description}

+ {(canConfigureManually || canRetry) && (
diff --git a/src/components/setup/steps/BitcoinSetup.tsx b/src/components/setup/steps/BitcoinSetup.tsx index 98c78ae5..9ea20c7f 100644 --- a/src/components/setup/steps/BitcoinSetup.tsx +++ b/src/components/setup/steps/BitcoinSetup.tsx @@ -12,7 +12,9 @@ import { import type { BitcoinCoreVersion, OperatingSystem, BitcoinNetwork } from '@sv2-ui/shared'; import { BITCOIN_MESSAGES } from '@/lib/messages'; import { StepProps, BitcoinConfig } from '../types'; -import { Apple, Terminal, Pencil, Check, Loader2, AlertCircle, CheckCircle2, RotateCw } from 'lucide-react'; +import { Apple, Terminal, Pencil, Check, Loader2, RotateCw } from 'lucide-react'; +import { Alert } from '@/components/ui/alert'; +import { FieldError } from '@/components/ui/field-error'; import { UmbrelIcon } from '../icons/UmbrelIcon'; import { useBitcoinSocketValidation } from '@/hooks/useBitcoinSocketValidation'; import type { BitcoinRpcDiscoveryResult } from '@/hooks/useBitcoinRpcDiscovery'; @@ -106,19 +108,14 @@ export function BitcoinSetup({ data, updateData, onNext, notice, onDismissNotice
{discoveryApplied && ( -
-
+ )}
@@ -184,14 +181,9 @@ export function BitcoinSetup({ data, updateData, onNext, notice, onDismissNotice {BITCOIN_MESSAGES.versionLabel} {notice && ( -
-
+ )} - {getBitcoinAddressError(coinbaseAddress, network) && ( -

{getBitcoinAddressError(coinbaseAddress, network)}

- )} +

Bitcoin address that receives solo mining rewards

diff --git a/src/components/setup/steps/PoolConfigStep.tsx b/src/components/setup/steps/PoolConfigStep.tsx index c7ee8868..1b8ee284 100644 --- a/src/components/setup/steps/PoolConfigStep.tsx +++ b/src/components/setup/steps/PoolConfigStep.tsx @@ -214,4 +214,4 @@ function SelectedPoolSummary({
); -} +} \ No newline at end of file diff --git a/src/components/setup/steps/ReviewStart.tsx b/src/components/setup/steps/ReviewStart.tsx index 487d3e77..9ccc20ab 100644 --- a/src/components/setup/steps/ReviewStart.tsx +++ b/src/components/setup/steps/ReviewStart.tsx @@ -1,7 +1,8 @@ import React, { useState, useEffect } from "react"; import { SetupStep, StepProps } from "../types"; -import { Loader2, AlertCircle } from "lucide-react"; +import { Loader2 } from "lucide-react"; import { useQueryClient } from "@tanstack/react-query"; +import { Alert } from "@/components/ui/alert"; import { MinerConnectionInfo } from "../MinerConnectionInfo"; import { shouldAggregateTranslatorChannelsForPools } from "../poolRules"; import { isBitcoinSocketError } from "@/lib/bitcoinSocketErrors"; @@ -189,17 +190,9 @@ export function ReviewStart({ data, onComplete, onGoToStep }: ReviewStartProps)
{error && ( -
-
{isAggregatedTproxy && ( -
+ Translator aggregation is enabled for Braiins compatibility. The Translator Proxy will aggregate all SV1 workers into one single SV2 upstream channel, so the Braiins Pool dashboard will not track workers individually. -
+ )}
diff --git a/src/components/setup/steps/TranslatorConfigStep.tsx b/src/components/setup/steps/TranslatorConfigStep.tsx index dfee1bd1..8191d7da 100644 --- a/src/components/setup/steps/TranslatorConfigStep.tsx +++ b/src/components/setup/steps/TranslatorConfigStep.tsx @@ -1,8 +1,9 @@ import { useState, useEffect } from 'react'; import { StepProps, TranslatorConfig } from '../types'; import { Switch } from '@/components/ui/switch'; -import { Info } from 'lucide-react'; + import { TRANSLATOR_PORT } from '@/lib/ports'; +import { Alert } from '@/components/ui/alert'; export function TranslatorConfigStep({ data, updateData, onNext }: StepProps) { const isSoloMode = data.miningMode === 'solo'; @@ -36,17 +37,12 @@ export function TranslatorConfigStep({ data, updateData, onNext }: StepProps) {

-
-
-
-
+ +

+ The Translator Proxy bridges your SV1 mining hardware to the SV2 {isSoloMode ? 'solo pool' : 'pool'}. + Your miners will connect to the Translator on port {TRANSLATOR_PORT}. +

+
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 00000000..3bae18d7 --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,90 @@ +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/lib/utils'; +import { AlertCircle, CheckCircle2, Info } from 'lucide-react'; + +const alertVariants = cva( + 'relative w-full rounded-xl border p-4 text-sm flex gap-3 text-left', + { + variants: { + variant: { + neutral: 'bg-muted/50 border-border text-muted-foreground', + warning: 'bg-yellow-500/10 border-yellow-500/20 text-yellow-600 dark:text-yellow-500', + destructive: 'bg-red-500/10 border-red-500/20 text-red-600 dark:text-red-500', + success: 'bg-green-500/10 border-green-500/20 text-green-600 dark:text-green-500', + info: 'bg-blue-500/10 border-blue-500/20 text-blue-600 dark:text-blue-500', + }, + }, + defaultVariants: { + variant: 'neutral', + }, + } +); + +export interface AlertProps + extends React.HTMLAttributes, + VariantProps { + icon?: React.ReactNode; +} + +const Alert = React.forwardRef( + ({ className, variant, icon, children, role, ...props }, ref) => { + // Auto-assign ARIA roles based on variant if not explicitly provided + const defaultRole = variant === 'destructive' || variant === 'warning' ? 'alert' : 'status'; + + // Auto-assign default icons if not explicitly overridden (can pass icon={null} to remove) + let DefaultIcon = null; + if (icon === undefined) { + if (variant === 'destructive' || variant === 'warning') DefaultIcon = AlertCircle; + else if (variant === 'success') DefaultIcon = CheckCircle2; + else if (variant === 'info') DefaultIcon = Info; + } + + return ( +
+ {(icon !== undefined ? icon : DefaultIcon) && ( + + {icon !== undefined ? icon : DefaultIcon && + )} +
+ {children} +
+
+ ); + } +); +Alert.displayName = 'Alert'; + +// Reusable alert title component +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = 'AlertTitle'; + +// Reusable alert description component +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = 'AlertDescription'; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/src/components/ui/field-error.tsx b/src/components/ui/field-error.tsx new file mode 100644 index 00000000..ec4c1892 --- /dev/null +++ b/src/components/ui/field-error.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { cn } from '@/lib/utils'; + +export interface FieldErrorProps extends React.HTMLAttributes { + message?: string | null; +} + +const FieldError = React.forwardRef( + ({ className, message, ...props }, ref) => { + if (!message) return null; + + return ( +

+ {message} +

+ ); + } +); +FieldError.displayName = 'FieldError'; + +export { FieldError }; diff --git a/src/components/ui/status-dot.tsx b/src/components/ui/status-dot.tsx new file mode 100644 index 00000000..e266f91d --- /dev/null +++ b/src/components/ui/status-dot.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/lib/utils'; + +const statusDotVariants = cva( + 'shrink-0 rounded-full', + { + variants: { + status: { + connected: 'bg-green-500', + connecting: 'bg-yellow-500 animate-pulse', + disconnected: 'bg-red-500', + idle: 'bg-muted-foreground', + }, + size: { + default: 'h-2.5 w-2.5', + sm: 'h-2 w-2', + lg: 'h-3 w-3', + }, + }, + defaultVariants: { + status: 'idle', + size: 'default', + }, + } +); + +export interface StatusDotProps + extends React.HTMLAttributes, + VariantProps {} + +const StatusDot = React.forwardRef( + ({ className, status, size, ...props }, ref) => { + return ( +
+ ); + } +); +StatusDot.displayName = 'StatusDot'; + +export { StatusDot }; diff --git a/src/pages/UnifiedDashboard.tsx b/src/pages/UnifiedDashboard.tsx index ec2c07ba..6bc8f9a6 100644 --- a/src/pages/UnifiedDashboard.tsx +++ b/src/pages/UnifiedDashboard.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'; import { Link } from 'wouter'; import { useQueryClient } from '@tanstack/react-query'; import { AlertTriangle, Search, Play, Trash2 } from 'lucide-react'; +import { Alert, AlertTitle } from '@/components/ui/alert'; import { InfoPopover } from '@/components/ui/info-popover'; import { MinerConnectionInfo } from '@/components/setup/MinerConnectionInfo'; import { Shell } from '@/components/layout/Shell'; @@ -117,10 +118,10 @@ export function UnifiedDashboard() { const { data: translatorOk, isLoading: translatorHealthLoading, isError: translatorHealthError } = useTranslatorHealth(); const { data: jdcOk, isLoading: jdcHealthLoading, isError: jdcHealthError } = useJdcHealth(isJdMode); const translatorHealthy = translatorOk === true && !translatorHealthError; - const jdcHealthy = jdcOk === true && !jdcHealthError; - const translatorDown = !translatorHealthLoading && !translatorHealthy; - const jdcDown = isJdMode && !jdcHealthLoading && !jdcHealthy; - const showError = poolError || translatorDown || jdcDown; + const jdcHealthy = jdcOk === true && !jdcHealthError; + const translatorDown = !translatorHealthLoading && !translatorHealthy; + const jdcDown = isJdMode && !jdcHealthLoading && !jdcHealthy; + const showError = poolError || translatorDown || jdcDown; const configuredButStopped = isOrchestrated && isConfigured && !isRunning; const configurationIssue = configurationIssues[0] ?? null; const canReviewConfiguration = configurationIssue?.code !== 'saved-setup-unavailable'; @@ -293,10 +294,10 @@ export function UnifiedDashboard() { // Total hashrate: // - JD mode: from SV2 client telemetry when available, falling back to vardiff totals // - Translator-only mode: from SV1 client telemetry when available, falling back to vardiff totals - const totalHashrate = isJdMode + const totalHashrate = isJdMode ? (sv2Clients && sv1Data - ? (sv2TotalHashrate ?? 0) + sv1TotalHashrate - : (poolGlobal?.sv2_clients?.total_hashrate ?? 0)) + ? (sv2TotalHashrate ?? 0) + sv1TotalHashrate + : (poolGlobal?.sv2_clients?.total_hashrate ?? 0)) : (sv1Data ? sv1TotalHashrate : (poolGlobal?.sv1_clients?.total_hashrate ?? 0)); // Scope hashrate history to the active pool + mode so stale samples from a @@ -617,13 +618,15 @@ export function UnifiedDashboard() { {/* Start Mining Banner (configured but stopped) */} {!configurationIssue && configuredButStopped && showError && ( -
-
- {autoStarting || isStarting ? ( -
- ) : ( - - )} + + ) : ( + + )} + > +
{autoStarting ? 'Mining services are starting...' @@ -631,27 +634,26 @@ export function UnifiedDashboard() { ? 'Starting mining services...' : 'Mining services are stopped.'} + {!autoStarting && !isStarting && ( + + )}
- {!autoStarting && !isStarting && ( - - )} -
+ )} {/* Connection Error Banner (not configured or unknown error) */} {!configurationIssue && (startError || (showError && !configuredButStopped && diagnostics.length === 0)) && ( -
- - + +

{startError || 'Cannot connect to pool. Make sure mining services are running.'} - -

+

+ )} {/* log-derived Diagnostic Banners */} @@ -661,34 +663,29 @@ export function UnifiedDashboard() { diagnostic.code === BITCOIN_CORE_DISCONNECTED_CODE; return ( -
-
- +
- {diagnostic.title} + {diagnostic.title} {diagnostic.message} {diagnostic.recommendation && ( {diagnostic.recommendation} )}
+ {showBitcoinSetupButton && ( + window.sessionStorage.setItem(SETUP_TARGET_STEP_STORAGE_KEY, 'bitcoin')} + className="inline-flex h-9 shrink-0 items-center justify-center rounded-full bg-red-600 px-4 font-medium text-white transition-colors hover:bg-red-700 sm:ml-4" + > + Open Bitcoin Setup + + )}
- {showBitcoinSetupButton && ( - window.sessionStorage.setItem(SETUP_TARGET_STEP_STORAGE_KEY, 'bitcoin')} - className="inline-flex h-9 shrink-0 items-center justify-center rounded-full bg-red-500 px-4 font-medium text-white transition-colors hover:bg-red-600 sm:ml-4" - > - Open Bitcoin Setup - - )} -
+ ); })} @@ -816,7 +813,7 @@ export function UnifiedDashboard() { title="Best Difficulty" value={hasBestDiffSource ? formatDifficulty(bestDiff) : '-'} /> -
+
{/* Miner Connection Info */}
@@ -909,4 +906,4 @@ export function UnifiedDashboard() { )} ); -} +} \ No newline at end of file From 591394b790e56e0206e14d66c17d07e2f6ff140e Mon Sep 17 00:00:00 2001 From: Ayush Date: Fri, 7 Aug 2026 21:50:35 +0530 Subject: [PATCH 2/3] Fix icon vertical alignment in Start Mining banner --- src/pages/UnifiedDashboard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/UnifiedDashboard.tsx b/src/pages/UnifiedDashboard.tsx index 6bc8f9a6..3efe3ea0 100644 --- a/src/pages/UnifiedDashboard.tsx +++ b/src/pages/UnifiedDashboard.tsx @@ -620,6 +620,7 @@ export function UnifiedDashboard() { {!configurationIssue && configuredButStopped && showError && ( ) : ( From 176860ed2aa5b32b38fd4f1ab4c4048c14b0fb96 Mon Sep 17 00:00:00 2001 From: Ayush Date: Fri, 7 Aug 2026 21:50:53 +0530 Subject: [PATCH 3/3] Restore isSetupReview state and conditional banner logic --- src/components/setup/SetupWizard.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/setup/SetupWizard.tsx b/src/components/setup/SetupWizard.tsx index dd31186f..6d13b76b 100644 --- a/src/components/setup/SetupWizard.tsx +++ b/src/components/setup/SetupWizard.tsx @@ -90,6 +90,7 @@ export function SetupWizard() { const [currentStep, setCurrentStep] = useState('mining-mode'); const [data, setData] = useState(initialSetupData); const [isReconfiguring, setIsReconfiguring] = useState(false); + const [isSetupReview, setIsSetupReview] = useState(false); const [loadingConfig, setLoadingConfig] = useState(true); const [bitcoinSetupNotice, setBitcoinSetupNotice] = useState(null); const { results: discoveredNodes, isLoading: isDiscovering, retry: retryDiscovery } = useBitcoinRpcDiscovery(); @@ -110,6 +111,7 @@ export function SetupWizard() { const setupReviewRequested = window.sessionStorage.getItem(SETUP_REVIEW_STORAGE_KEY) === 'true'; window.sessionStorage.removeItem(SETUP_TARGET_STEP_STORAGE_KEY); window.sessionStorage.removeItem(SETUP_REVIEW_STORAGE_KEY); + setIsSetupReview(setupReviewRequested); if (targetStep === 'bitcoin' && config.bitcoin) { nextConfig = { @@ -290,7 +292,9 @@ export function SetupWizard() {
{isReconfiguring && currentStepIndex === 1 && ( - Reconfiguring SV2 setup — this will replace your current configuration. + {isSetupReview + ? 'Review your setup to continue mining. Your saved settings are prefilled.' + : 'Reconfiguring SV2 setup — this will replace your current configuration.'} )} {currentStep === 'template-mode' && }