Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions src/components/dashboard/AggregatedBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,43 @@
import { LiInfoCircle, LiQuestionCircle } from 'solar-icon-react/li';
import { BdInfoCircle } from 'solar-icon-react/bd';
import { LiQuestionCircle } from 'solar-icon-react/li';
import { Switch } from '@/components/ui/switch';

/**
* The blue notice shown at the top of every page while aggregated mode is on. It
* explains the mode and carries an Exit toggle that mirrors the top-bar toggle, so a
* miner can leave aggregated mode from wherever the banner is visible.
*
* The two viewports are drawn as separate variants: one row on desktop, and a stack on
* mobile whose body copy is shorter and a size smaller, with the exit control dropping
* below the text and indented to line up with it. The control itself is rendered once
* and moved by layout, so there is never a second switch carrying the same label.
*/
export function AggregatedBanner({ onExit }: { onExit: () => void }) {
return (
<div className="flex items-center justify-between gap-4 bg-info/15 px-6 py-3">
<div className="flex flex-col bg-toast-info px-2 py-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4 sm:px-6">
<div className="flex min-w-0 flex-1 items-start gap-1">
<LiInfoCircle className="mt-0.5 h-5 w-5 shrink-0 text-info" aria-hidden />
<div className="min-w-0">
<p className="text-sm font-bold text-foreground">Viewing Aggregated Dashboard</p>
<p className="text-sm text-foreground">
{/* The icon sits 2px low so it lines up with the title's cap height. */}
<BdInfoCircle className="mt-0.5 h-5 w-5 shrink-0 text-info" aria-hidden />
<div className="flex min-w-0 flex-col">
<p className="text-sm font-bold leading-5 text-foreground">Viewing Aggregated Dashboard</p>
<p className="text-xs leading-4 text-foreground sm:hidden">
You&apos;re viewing combined data across all subaccounts.
</p>
<p className="hidden text-sm leading-5 text-foreground sm:block">
You&apos;re viewing combined workers, earnings, and mining performance across all subaccounts.
</p>
</div>
</div>
<div className="flex shrink-0 items-center gap-2">
<span className="flex items-center gap-1 text-xs text-foreground">

{/* Mobile indents this by the icon column (20px icon + 4px gap) so it aligns
under the copy; desktop drops the indent and sits at the far right. */}
<div className="mt-1 ml-6 flex shrink-0 items-center gap-2 sm:mt-0 sm:ml-0">
<span className="flex items-center gap-1 text-xs font-semibold leading-5 text-foreground">
Exit Aggregated Mode
<LiQuestionCircle className="h-4 w-4 text-placeholder" aria-hidden />
<LiQuestionCircle className="h-4 w-4 text-body-alt" aria-hidden />
</span>
<Switch
size="lg"
checked
onCheckedChange={onExit}
aria-label="Exit aggregated mode"
Expand Down
4 changes: 2 additions & 2 deletions src/components/dashboard/DashboardShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function DashboardShellInner({ children }: { children: ReactNode }) {
{/* Mobile drawer: kept mounted so it can slide rather than pop. */}
<div className={cn('fixed inset-0 z-50 lg:hidden', drawerOpen ? '' : 'pointer-events-none')}>
<div
className={cn('absolute inset-0 bg-black/40 transition-opacity duration-200', drawerOpen ? 'opacity-100' : 'opacity-0')}
className={cn('absolute inset-0 bg-black/40 backdrop-blur-[8px] transition-opacity duration-200', drawerOpen ? 'opacity-100' : 'opacity-0')}
onClick={() => setDrawerOpen(false)}
aria-hidden
/>
Expand All @@ -82,7 +82,7 @@ function DashboardShellInner({ children }: { children: ReactNode }) {
drawerOpen ? 'translate-x-0' : '-translate-x-full',
)}
>
<Sidebar onNavigate={() => setDrawerOpen(false)} />
<Sidebar drawer onNavigate={() => setDrawerOpen(false)} />
</div>
</div>

Expand Down
43 changes: 29 additions & 14 deletions src/components/dashboard/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { cn } from '@/lib/utils';
import { useAuth } from '@/auth';
import { DmndLogo } from '@/components/auth/Logo';
import { Switch } from '@/components/ui/switch';
import { TooltipPill } from '@/components/ui/tooltip-pill';
import { useAggregatedModeContext } from '@/hooks/AggregatedModeProvider';
import { useHasSubaccounts } from '@/hooks/useSubaccounts';
import { useAccountScope } from '@/hooks/useAccountScope';
Expand All @@ -25,20 +26,31 @@ function NavRow({
// The active nav icon is the filled (bold-duotone) glyph; custom icons
// without a duotone variant fall back to their single form.
const Icon = active && item.iconActive ? item.iconActive : item.icon;
const row = (
<span
className={cn(
'flex items-center text-sm leading-5 transition-colors',
collapsed ? 'justify-center rounded-sm px-0 py-2' : 'gap-2 p-2',
active
? 'rounded-[32px] bg-muted font-semibold text-foreground'
: 'rounded-sm text-body-alt hover:bg-muted hover:text-foreground',
)}
>
<Icon className="h-4 w-4 shrink-0" />
{!collapsed && <span className="flex-1">{item.label}</span>}
</span>
);
return (
<Link href={item.href} onClick={onNavigate}>
<span
// When collapsed the label is hidden, so the title gives a hover tooltip.
title={collapsed ? item.label : undefined}
className={cn(
'flex items-center rounded-lg text-sm transition-colors',
collapsed ? 'justify-center px-0 py-2' : 'gap-2.5 px-3 py-2',
active ? 'bg-muted font-medium text-foreground' : 'text-body-alt hover:bg-muted hover:text-foreground',
)}
>
<Icon className="h-[18px] w-[18px] shrink-0" />
{!collapsed && <span className="flex-1">{item.label}</span>}
</span>
{/* Collapsed hides the label, so the design reveals it in the tooltip pill
beside the rail rather than the browser's own title bubble. */}
{collapsed ? (
<TooltipPill label={item.label} side="right" emphasis>
{row}
</TooltipPill>
) : (
row
)}
</Link>
);
}
Expand All @@ -51,10 +63,13 @@ function NavRow({
*/
export function Sidebar({
collapsed = false,
drawer = false,
onToggleCollapse,
onNavigate,
}: {
collapsed?: boolean;
/** The mobile drawer is wider than the docked rail and pads tighter. */
drawer?: boolean;
onToggleCollapse?: () => void;
onNavigate?: () => void;
}) {
Expand All @@ -72,8 +87,8 @@ export function Sidebar({
return (
<div
className={cn(
'flex h-full shrink-0 flex-col border-r border-border bg-background transition-[width] duration-200',
collapsed ? 'w-16' : 'w-60',
'flex h-full shrink-0 flex-col border-r-[0.5px] border-border bg-background transition-[width] duration-200',
drawer ? 'w-[300px]' : collapsed ? 'w-16' : 'w-[236px]',
)}
>
<div className={cn('flex h-16 items-center', collapsed ? 'justify-center px-2' : 'justify-between px-5')}>
Expand Down
28 changes: 20 additions & 8 deletions src/components/dashboard/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,28 +89,40 @@ export function TopBar({ onMenuClick }: { onMenuClick: () => void }) {
{menuOpen && (
<>
<div className="fixed inset-0 z-40" onClick={() => setMenuOpen(false)} aria-hidden />
<div className="absolute right-0 z-50 mt-2 w-48 overflow-hidden rounded-lg border border-border bg-popover p-1 shadow-lg">
<div className="absolute right-0 z-50 mt-2 flex w-[212px] flex-col gap-1 rounded-[12px] bg-background p-3 shadow-[0_25px_50px_-12px_rgba(0,0,0,0.25)]">
{session?.email && (
<p className="truncate px-3 py-2 text-xs text-body-alt">{session.email}</p>
<>
<div className="flex items-center gap-1 rounded-lg px-2 py-1">
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-[#93C5FD] bg-[#3B82F6] text-xs font-semibold leading-5 text-white">
{accountInitials(session.email)}
</span>
<span className="ml-1 flex min-w-0 flex-col">
<span className="truncate text-sm leading-5 text-foreground">{session.email.split('@')[0]}</span>
<span className="truncate text-xs leading-4 text-body-alt">{session.email}</span>
</span>
</div>
<div aria-hidden className="h-[0.5px] bg-border" />
</>
)}
<Link
href="/account"
onClick={() => setMenuOpen(false)}
className="flex items-center gap-2.5 rounded-md px-3 py-2 text-sm text-foreground transition-colors hover:bg-muted"
className="flex items-center gap-1 rounded-lg px-2 py-1 text-sm leading-5 text-body-alt transition-colors hover:bg-muted"
>
<LiSettingsMinimalistic className="h-4 w-4 shrink-0" />
Settings
<LiSettingsMinimalistic className="h-4 w-4 shrink-0 text-[#525252]" />
<span className="ml-1">Settings</span>
</Link>
<div aria-hidden className="h-[0.5px] bg-border" />
<button
type="button"
onClick={() => {
setMenuOpen(false);
signOut();
}}
className="flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-left text-sm text-foreground transition-colors hover:bg-muted"
className="flex w-full items-center gap-1 rounded-lg px-2 py-1 text-left text-xs leading-4 text-body-alt transition-colors hover:bg-muted"
>
<LiLogout3 className="h-4 w-4 shrink-0" />
Logout
<LiLogout3 className="h-4 w-4 shrink-0 text-[#525252]" />
<span className="ml-1">Logout</span>
</button>
</div>
</>
Expand Down
22 changes: 10 additions & 12 deletions src/components/generated-btc/GeneratedBtcFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@ export function GeneratedBtcFilter({
ref={ref}
role="dialog"
aria-label="Filter generated BTC"
className="absolute right-0 top-full z-20 mt-2 w-[360px] max-w-[calc(100vw-2rem)] rounded-3xl border border-border bg-popover px-4 pb-5 pt-4 shadow-xl sm:px-6 sm:pb-6"
className="absolute right-0 top-full z-20 mt-4 w-[574px] max-w-[calc(100vw-2rem)] rounded-3xl border-[0.5px] border-border bg-card px-8 pb-8 pt-4 shadow-[0_25px_50px_-12px_rgba(0,0,0,0.25)]"
>
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-sm font-semibold text-heading">Filter generated BTC</p>
<p className="mt-0.5 text-xs text-body-alt">
<p className="text-base font-bold leading-6 text-foreground">Filter generated BTC</p>
<p className="text-sm leading-5 text-body-alt">
{hasAccounts ? 'Find generated BTC by date or subaccount.' : 'Find generated BTC by date.'}
</p>
</div>
Expand All @@ -163,7 +163,7 @@ export function GeneratedBtcFilter({
setShowCalendar(false);
onReset();
}}
className="rounded-full border border-border px-4 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-muted"
className="inline-flex h-9 items-center rounded-[32px] border-[0.5px] border-black/20 bg-btn-secondary px-5 text-sm leading-5 text-foreground transition-opacity hover:opacity-80"
>
Reset
</button>
Expand All @@ -173,35 +173,33 @@ export function GeneratedBtcFilter({
onApply(draft);
onClose();
}}
className="rounded-full bg-[hsl(var(--btn))] px-4 py-1.5 text-xs font-medium text-[hsl(var(--btn-foreground))] transition-opacity hover:opacity-90"
className="inline-flex h-9 items-center rounded-[32px] border border-black/20 bg-[hsl(var(--btn))] px-5 text-sm leading-5 text-[hsl(var(--btn-foreground))] transition-opacity hover:opacity-90"
>
Apply filter(s)
</button>
</div>
</div>

{hasAccounts ? (
<div className="mt-4 flex gap-4 border-t border-border pt-4 sm:gap-6">
<div className="mt-4 flex gap-6 border-t-[0.5px] border-border pt-4">
<div className="flex w-24 shrink-0 flex-col gap-4">
{CATEGORIES.map(({ key, label, Icon }) => (
<button
key={key}
type="button"
onClick={() => setCategory(key)}
className={cn(
'flex items-center gap-2 text-left text-sm transition-colors',
category === key
? 'font-medium text-foreground underline underline-offset-4'
: 'text-body-alt hover:text-foreground',
'flex items-center gap-1 text-left text-sm leading-5 text-foreground transition-opacity',
category === key ? 'underline underline-offset-4' : 'opacity-50 hover:opacity-80',
)}
>
<Icon className="h-4 w-4 shrink-0" />
<Icon className="h-3.5 w-3.5 shrink-0" />
<span className="whitespace-nowrap">{label}</span>
</button>
))}
</div>

<div className="w-px shrink-0 self-stretch bg-border" aria-hidden />
<div className="w-[0.5px] shrink-0 self-stretch bg-border" aria-hidden />

<div className="flex min-w-0 flex-1 flex-col gap-3">
{category === 'date' &&
Expand Down
32 changes: 20 additions & 12 deletions src/components/generated-btc/GeneratedBtcStatCards.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import type { ReactNode } from 'react';
import { formatHashrate } from '@/lib/utils';
import { Reading } from '@/components/ui/Reading';
import { formatAxisValue, pickHashrateScale } from '@/lib/chartAxis';
import { formatBtc } from '@/lib/generatedBtcTable';

/**
* A stat card. Same shell and type ramp as the home, workers and subaccounts cards so
* the pages cannot drift apart, except that this page sets its unit one step larger
* (18/28) than they do, which is how the frame draws it.
*/
function Card({ title, sub, children }: { title: string; sub: string; children: ReactNode }) {
return (
<div className="rounded-xl border border-border bg-card p-5">
<span className="text-sm text-body-alt">{title}</span>
{children}
<p className="mt-1 text-xs text-body-alt">{sub}</p>
<div className="flex flex-col justify-between gap-6 border-[0.5px] border-border bg-card p-4 lg:p-8">
<span className="text-sm leading-5 text-body-alt">{title}</span>
<div className="flex flex-col gap-1">
{children}
<p className="text-sm leading-5 text-body-alt">{sub}</p>
</div>
</div>
);
}
Expand All @@ -22,21 +30,21 @@ export function GeneratedBtcStatCards({
averageHashrate: number;
activeWorkers: number;
}) {
// One unit for the hashrate figure, matching how every other hashrate reading is set.
const scale = pickHashrateScale([averageHashrate]);

return (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
<Card title="Generated BTC" sub="Total Bitcoin generated">
<p className="mt-2 font-mono text-2xl font-semibold text-heading">
{formatBtc(generated)}
<span className="ml-1 text-base font-normal text-body-alt">BTC</span>
</p>
<Reading value={formatBtc(generated)} unit="BTC" unitSize="lg" />
</Card>

<Card title="Average hashrate" sub="Average hashrate across workers">
<p className="mt-2 font-mono text-2xl font-semibold text-heading">{formatHashrate(averageHashrate)}</p>
<Reading value={formatAxisValue(averageHashrate, scale.divisor)} unit={scale.unit} unitSize="lg" />
</Card>

<Card title="Active workers" sub="Workers that submitted shares">
<p className="mt-2 font-mono text-2xl font-semibold text-heading">{activeWorkers}</p>
<Reading value={activeWorkers} />
</Card>
</div>
);
Expand Down
Loading
Loading