🎨 🚀 Overhaul UI/UX with comprehensive design system improvements

 Features added:

- 🔍 Implement functional search in header navigation
- ⚙️ Add basic user settings page
- 📱 Make dashboard fully responsive

🔧 Enhancements:

- 🎭 Standardize CSS with consistent theming across components
- 🧹 Remove unused CSS for better performance
- 📊 Improve dashboard layout and visual hierarchy
- 📦 Redesign last block widget for better usability

💅 This commit introduces a cohesive design system with functional design-token components for a more  polished user experience.
This commit is contained in:
Danny Morabito 2025-03-20 09:46:13 +01:00
parent 5afeb4d01a
commit dc9abee715
Signed by: dannym
GPG key ID: 7CC8056A5A04557E
49 changed files with 4176 additions and 2468 deletions

View file

@ -1,11 +1,16 @@
import { getLastBlockHeight } from '@utils/lastBlockHeight';
import formatDateTime from '@/utils/formatDateTime';
import { type LastBlock, getLastBlock } from '@/utils/lastBlock';
import { LitElement, css, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js';
import '@components/LoadingView';
import '@components/General/Tooltip';
import '@components/General/Fieldset';
@customElement('arx-bitcoin-block-widget')
export class BitcoinBlockWidget extends LitElement {
@state()
private lastBlock: number | null = null;
private lastBlock: LastBlock | null = null;
@state()
private isLoading = true;
@ -13,15 +18,16 @@ export class BitcoinBlockWidget extends LitElement {
@state()
private error: string | null = null;
private REFRESH_INTERVAL = 5000;
private REFRESH_INTERVAL = 30000;
@state()
private timer: number | null = null;
constructor() {
super();
this.loadBlockHeight();
this.timer = window.setInterval(this.loadBlockHeight, this.REFRESH_INTERVAL);
this.loadBlock = this.loadBlock.bind(this);
this.loadBlock();
this.timer = window.setInterval(this.loadBlock, this.REFRESH_INTERVAL);
}
override disconnectedCallback() {
@ -29,70 +35,138 @@ export class BitcoinBlockWidget extends LitElement {
if (this.timer) clearInterval(this.timer);
}
async loadBlockHeight() {
async loadBlock() {
this.isLoading = true;
try {
const response = await getLastBlockHeight();
this.lastBlock = response.height;
const response = await getLastBlock();
this.lastBlock = response;
this.error = null;
} catch (error) {
this.error = 'Failed to load block height';
this.error = 'Failed to load block data';
console.error(error);
} finally {
this.isLoading = false;
}
}
private formatBlockId(id: string): string {
if (!id) return '';
if (id.length <= 16) return id;
return `${id.substring(0, 8)}...${id.substring(id.length - 8)}`;
}
private formatSize(size: number): string {
if (size < 1024) return `${size} Bytes`;
if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`;
return `${(size / (1024 * 1024)).toFixed(2)} MB`;
}
private copyToClipboard(text: string) {
navigator.clipboard.writeText(text).catch((err) => {
console.error('Could not copy text: ', err);
});
}
static override styles = [
css`
.error {
color: #dc3545;
padding: 0.5rem;
border-radius: 4px;
background: #f8d7da;
:host {
display: block;
}
.loading {
.widget-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
margin-bottom: 12px;
border-bottom: var(--border) solid var(--color-base-300);
padding-bottom: 8px;
}
.block-height {
display: flex;
align-items: center;
gap: 0.5rem;
}
.label {
font-weight: 500;
}
.value {
font-size: 1.25rem;
.widget-title {
font-size: 1.2rem;
font-weight: 600;
margin: 0;
color: var(--color-base-content);
display: flex;
align-items: center;
gap: 8px;
}
.bitcoin-icon {
color: var(--color-warning);
font-size: 1.4rem;
}
.error {
color: var(--color-error-content);
padding: 12px;
border-radius: var(--radius-field);
background: var(--color-error);
margin-bottom: 12px;
}
`,
];
override render() {
if (this.error) {
return html`<div class="error">${this.error}</div>`;
}
private renderError() {
return html`<div class="error">${this.error}</div>`;
}
if (this.isLoading) {
return html`
<div class="loading">
<span class="loader"></span>
Loading latest block...
</div>
`;
}
private renderLoading() {
return html`<arx-loading-view></arx-loading-view>`;
}
private renderEmptyState() {
return html`<div class="error">No block data available</div>`;
}
private renderBlockData() {
if (!this.lastBlock) return this.renderEmptyState();
return html`
<div class="block-height">
<span class="label">Last Block:</span>
<span class="value">${this.lastBlock?.toLocaleString()}</span>
<arx-fieldset legend="Height">
<strong>${this.lastBlock.height.toLocaleString()}</strong>
</arx-fieldset>
<arx-fieldset legend="Block ID">
<arx-tooltip
content="Click to copy full ID"
@click=${() => this.copyToClipboard(this.lastBlock!.id)}
>
<strong>${this.formatBlockId(this.lastBlock.id)}</strong>
</arx-tooltip>
</arx-fieldset>
<arx-fieldset legend="Timestamp">
<strong>${formatDateTime(this.lastBlock.timestamp * 1000)}</strong>
</arx-fieldset>
<arx-fieldset legend="Size">
<strong>
${this.formatSize(this.lastBlock.size)}
(${this.lastBlock.tx_count.toLocaleString()} txs)
</strong>
</arx-fieldset>
`;
}
override render() {
return html`
<div class="widget-header">
<h3 class="widget-title">
<span class="bitcoin-icon"></span> Bitcoin Latest Block
</h3>
</div>
${when(
this.error,
() => this.renderError(),
() =>
when(
this.isLoading && !this.lastBlock,
() => this.renderLoading(),
() => this.renderBlockData(),
),
)}
`;
}
}