diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 112c8fb..b903c7f 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect, useCallback, useMemo } from 'react'; +import { useState, useEffect, useCallback, useMemo, type ReactNode } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { Loader2, Plus, Search } from 'lucide-react'; @@ -129,6 +129,17 @@ function formatUtc(date: string) { return `${String(d.getUTCHours()).padStart(2, '0')}:${String(d.getUTCMinutes()).padStart(2, '0')} UTC`; } +function MobileCardRow({ label, children }: { label: string; children: ReactNode }) { + return ( +
+ + {label} + + {children} +
+ ); +} + export default function DashboardPage() { const [contacts, setContacts] = useState([]); const [loading, setLoading] = useState(true); @@ -290,7 +301,7 @@ export default function DashboardPage() {
-
+
@@ -343,19 +354,16 @@ export default function DashboardPage() {
{/* Map + Quick Log */} -
+
{error && (
{error}
)} -
- -
+
+ +
Worldmap @@ -367,7 +375,7 @@ export default function DashboardPage() {
- +

Quick log

@@ -428,7 +436,7 @@ export default function DashboardPage() { onChange={setBandRange} />
-
+
{BAND_ORDER.map((band) => { const count = bandActivity[band] ?? 0; const filled = activityBars(count); @@ -437,7 +445,7 @@ export default function DashboardPage() {
{/* Log + DXpeditions */} -
+
-
-
- +
+
+ setTableSearch(e.target.value)} placeholder="Search callsign, name, grid, frequency…" - className="flex-1 bg-transparent border-0 outline-none text-fg text-[15px] placeholder:text-fg-3" + className="flex-1 min-w-0 bg-transparent border-0 outline-none text-fg text-[15px] placeholder:text-fg-3" /> /
@@ -496,105 +501,168 @@ export default function DashboardPage() {

) : ( - - - - Callsign - When - Band / Mode - Freq - RST - Operator - QSL - - - + <> + {/* Desktop: classic table */} +
+
+ + + Callsign + When + Band / Mode + Freq + RST + Operator + QSL + + + + {loading ? ( + + +
+ + Loading contacts… +
+
+
+ ) : filteredContacts.length === 0 ? ( + + + No contacts match those filters. + + + ) : ( + filteredContacts.map((contact) => ( + handleContactClick(contact)} + > + + + {contact.callsign} + + + + {formatUtc(contact.datetime)} +
+ + {formatRelativeTime(contact.datetime)} + +
+ +
+ {contact.band} + {contact.mode} +
+
+ + + {contact.frequency} + + + + + {contact.rst_sent ?? '-'} / {contact.rst_received ?? '-'} + + + + {contact.name ?? '—'} + {contact.qth ? ( + · {contact.qth} + ) : null} + + +
e.stopPropagation()}> + {qslChip(contact)} + + fetchContacts()} + size="sm" + /> + + +
+
+
+ )) + )} +
+
+
+ + {/* Mobile: stacked QSO cards */} +
{loading ? ( - - -
- - Loading contacts… -
-
-
+
+ + Loading contacts… +
) : filteredContacts.length === 0 ? ( - - - No contacts match those filters. - - +
+ No contacts match those filters. +
) : ( filteredContacts.map((contact) => ( - handleContactClick(contact)} + className="rounded-xl border border-line bg-bg-1 p-3.5 text-left cursor-pointer hover:border-line-hi transition-colors" > - - +
+ {contact.callsign} - - - {formatUtc(contact.datetime)} -
- - {formatRelativeTime(contact.datetime)} + {qslChip(contact)} +
+ + + {formatUtc(contact.datetime)}{' '} + · {formatRelativeTime(contact.datetime)} -
- -
+ + + {contact.band} {contact.mode} -
-
- - - {contact.frequency} - - + + + {contact.frequency} + + {contact.rst_sent ?? '-'} / {contact.rst_received ?? '-'} - - - {contact.name ?? '—'} - {contact.qth ? ( - · {contact.qth} - ) : null} - - -
e.stopPropagation()}> - {qslChip(contact)} - - fetchContacts()} - size="sm" - /> - - -
-
-
+ + + + {contact.name ?? '—'} + {contact.qth ? ( + · {contact.qth} + ) : null} + + + )) )} - - +
+ )} {pagination.pages > 1 && ( diff --git a/src/app/new-contact/page.tsx b/src/app/new-contact/page.tsx index db6dbb0..6290753 100644 --- a/src/app/new-contact/page.tsx +++ b/src/app/new-contact/page.tsx @@ -424,7 +424,7 @@ export default function NewContactPage() {
-
+
-
+
{/* LEFT — form */}
{/* Callsign hero */} - + Verified @@ -550,7 +546,7 @@ export default function NewContactPage() { {/* Band & Mode */} - +

Band & Mode

{formData.frequency ? ( @@ -643,7 +639,7 @@ export default function NewContactPage() { {/* Signal report & details */} - +

Signal report & details

@@ -792,12 +788,12 @@ export default function NewContactPage() {
) : null} -
- -
- -
{/* Preview mockup */} -
+
-
+
@@ -300,7 +300,7 @@ export default function Home() { {/* Features */}
Built for operators @@ -340,9 +340,8 @@ export default function Home() { {/* CTA */}
Free to self-host. Open source. Ready when you are.

-
- -
- {onDelete && ( - - - + + + + Are you sure? + + Are you sure you want to delete this QSO with {contact?.callsign}? This action cannot be undone. + + + {deleteError && ( + + + {deleteError} + + )} + + Cancel + {deleteLoading && } Delete QSO - - - - - Are you sure? - - Are you sure you want to delete this QSO with {contact?.callsign}? This action cannot be undone. - - - {deleteError && ( - - - {deleteError} - - )} - - Cancel - - {deleteLoading && } - Delete QSO - - - - - )} -
-
- - diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 0d6e8a2..6d2eac7 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,9 +1,9 @@ 'use client'; -import React from 'react'; +import React, { useState, useEffect } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; -import { Plus, Search, ChevronDown } from 'lucide-react'; +import { Plus, Search, ChevronDown, Menu, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { BrandLockup } from '@/components/ui/brand-mark'; @@ -51,13 +51,18 @@ export default function Navbar({ actions }: NavbarProps) { // but no longer rendered — pages now use for that. const { user } = useUser(); const pathname = usePathname(); + const [mobileOpen, setMobileOpen] = useState(false); + + useEffect(() => { + setMobileOpen(false); + }, [pathname]); const isActive = (href: string) => href === '/' ? pathname === '/' : pathname?.startsWith(href); return (
+ + -
+ {mobileOpen && ( + + )} + +
{user?.callsign ? (