From 835b0a02fd9f057d49667a25607371418c9baa52 Mon Sep 17 00:00:00 2001 From: Avi Date: Wed, 26 Aug 2026 14:32:22 -0500 Subject: [PATCH] frontend: list all profiles on Home, pin dev host, widen profiles card - Home now shows every profile with its picture, an inline Active badge, and a working Select button (was only the active profile). - Remove the Relays card from Home (status lives on the Relays screen) and its unused connection-check logic/test. - Pin Vite dev server to 127.0.0.1 so Electron and the dev server agree on the address (fixes white-box window in dev mode). - Make the profiles card full-width to match the publication card below. - Badge now accepts className. --- frontend/src/components/Badge.tsx | 14 +- frontend/src/screens/HomeScreen.tsx | 200 +++++++++----------------- frontend/src/styles.css | 52 ++++++- frontend/src/test/HomeScreen.test.tsx | 15 +- frontend/vite.config.ts | 1 + 5 files changed, 130 insertions(+), 152 deletions(-) diff --git a/frontend/src/components/Badge.tsx b/frontend/src/components/Badge.tsx index b0c3748..265b6a7 100644 --- a/frontend/src/components/Badge.tsx +++ b/frontend/src/components/Badge.tsx @@ -2,6 +2,16 @@ import type { ReactNode } from 'react'; export type BadgeTone = 'neutral' | 'success' | 'warning' | 'danger' | 'info'; -export function Badge({ tone = 'neutral', children }: { tone?: BadgeTone; children: ReactNode }) { - return {children}; +export function Badge({ + tone = 'neutral', + className, + children, +}: { + tone?: BadgeTone; + className?: string; + children: ReactNode; +}) { + return ( + {children} + ); } diff --git a/frontend/src/screens/HomeScreen.tsx b/frontend/src/screens/HomeScreen.tsx index 31c38a3..da4a7f3 100644 --- a/frontend/src/screens/HomeScreen.tsx +++ b/frontend/src/screens/HomeScreen.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useState } from 'react'; import { Alert } from '../components/Alert'; import { Avatar } from '../components/Avatar'; import { Badge } from '../components/Badge'; @@ -6,67 +6,27 @@ import { Button } from '../components/Button'; import { CopyButton } from '../components/CopyButton'; import { EmptyState } from '../components/EmptyState'; import { Icon } from '../components/Icon'; -import { Spinner } from '../components/Spinner'; import { shortenNpub } from '../lib/format'; import type { Screen } from '../lib/navigation'; import { useApp } from '../state/AppProvider'; -type RelayStatus = 'checking' | 'connected' | 'failed'; - interface HomeScreenProps { onNavigate: (screen: Screen) => void; onCreateProfile: () => void; } export function HomeScreen({ onNavigate, onCreateProfile }: HomeScreenProps) { - const { state, relayTest, lastPublish } = useApp(); - const [statuses, setStatuses] = useState>( - {}, - ); - const [testing, setTesting] = useState(false); + const { state, lastPublish, selectProfile } = useApp(); + const [selecting, setSelecting] = useState(null); - const enabled = useMemo( - () => state?.settings.relays.filter((r) => r.enabled) ?? [], - [state?.settings.relays], - ); - - const runCheck = useCallback(async () => { - setTesting(true); - const entries: Record = {}; - for (const relay of enabled) { - entries[relay.url] = { state: 'checking' }; + const onSelect = async (npub: string) => { + setSelecting(npub); + try { + await selectProfile(npub); + } finally { + setSelecting(null); } - setStatuses({ ...entries }); - await Promise.all( - enabled.map(async (relay) => { - try { - const result = await relayTest(relay.url); - entries[relay.url] = { - state: 'connected', - detail: result.latency_ms != null ? `${result.latency_ms} ms` : 'connected', - }; - } catch (error) { - entries[relay.url] = { - state: 'failed', - detail: error instanceof Error ? error.message : String(error), - }; - } - }), - ); - setStatuses({ ...entries }); - setTesting(false); - }, [enabled, relayTest]); - - useEffect(() => { - if (enabled.length > 0) { - void runCheck(); - } else { - setStatuses({}); - } - }, [enabled, runCheck]); - - const connectedCount = Object.values(statuses).filter((s) => s.state === 'connected').length; - const failedCount = Object.values(statuses).filter((s) => s.state === 'failed').length; + }; const active = state?.active_profile ?? null; const shorten = state?.settings.shorten_npub ?? true; @@ -119,94 +79,72 @@ export function HomeScreen({ onNavigate, onCreateProfile }: HomeScreenProps) {
-

Active profile

-
-
- {active ? ( -
- -
- {active.label} - - {shortenNpub(active.npub, shorten)} - -
- -
- ) : ( -

- No profile selected.{' '} - {' '} - to get started. -

- )} -
-
- -
-
-

Relays

+

Your profiles

- {enabled.length === 0 ? ( -

- No relays enabled.{' '} - {' '} - to publish notes. -

- ) : testing && Object.keys(statuses).length === 0 ? ( - - ) : ( -
    - {enabled.map((relay) => { - const status = statuses[relay.url]; - return ( -
  • -
  • - ); - })} -
- )} - {failedCount > 0 && - !testing && - enabled.length > 0 && - Object.keys(statuses).length === enabled.length && ( -

- {connectedCount} of {enabled.length} enabled relays reachable. -

- )} +
    + {state?.profiles.map((profile) => ( +
  • + +
    + + {profile.label} + {profile.is_active && ( + + Active + + )} + + + {shortenNpub(profile.npub, shorten)} + +
    +
    + + {profile.is_active ? ( + + ) : ( + + )} +
    +
  • + ))} +
+
diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 947ee5e..1dd6c1d 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1003,16 +1003,10 @@ select { .home-grid { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: 1fr; gap: 20px; } -@media (max-width: 900px) { - .home-grid { - grid-template-columns: 1fr; - } -} - .active-profile-row { display: flex; align-items: center; @@ -1067,6 +1061,50 @@ select { object-fit: cover; } +.home-profile-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 10px; +} + +.home-profile-row { + display: flex; + align-items: center; + gap: 14px; + padding: 8px; + border-radius: 8px; + border: 1px solid transparent; +} + +.home-profile-row.is-active { + background: var(--token-item-bg, rgba(0, 0, 0, 0.04)); + border-color: var(--token-border, rgba(0, 0, 0, 0.12)); +} + +.home-profile-row .active-profile-meta { + flex: 1; + min-width: 0; +} + +.home-profile-actions { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + +.home-active-badge { + margin-left: 8px; + vertical-align: middle; +} + +.home-add-profile { + margin-top: 14px; +} + .relay-status-list { list-style: none; margin: 0; diff --git a/frontend/src/test/HomeScreen.test.tsx b/frontend/src/test/HomeScreen.test.tsx index 8b8572d..516c4b1 100644 --- a/frontend/src/test/HomeScreen.test.tsx +++ b/frontend/src/test/HomeScreen.test.tsx @@ -1,4 +1,4 @@ -import { screen, waitFor } from '@testing-library/react'; +import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { HomeScreen } from '../screens/HomeScreen'; import { renderWithApp } from './render'; @@ -36,8 +36,8 @@ describe('HomeScreen', () => { renderHome(backend); renderWithApp(); - await screen.findByText('Alice'); - await userEvent.setup().click(screen.getByRole('button', { name: 'Copy full npub' })); + const aliceRow = (await screen.findByText('Alice')).closest('.home-profile-row') as HTMLElement; + await userEvent.setup().click(within(aliceRow).getByRole('button', { name: 'Copy full npub' })); await waitFor(() => { expect(backend.copied).toContain(ALICE); }); @@ -54,13 +54,4 @@ describe('HomeScreen', () => { .click(screen.getByRole('button', { name: /Create your first profile/i })); expect(onCreateProfile).toHaveBeenCalled(); }); - - it('shows live relay connection status from the backend', async () => { - const backend = createFakeBackend(); - renderHome(backend); - renderWithApp(); - - expect(await screen.findAllByText(/Connected/)).not.toHaveLength(0); - expect(screen.getAllByText('wss://relay.damus.io')).not.toHaveLength(0); - }); }); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index d87dd77..4983c5f 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -6,6 +6,7 @@ import type { UserConfig } from 'vite'; export default defineConfig({ plugins: [react()], server: { + host: '127.0.0.1', port: 5173, strictPort: true, },