diff --git a/client/src/App.css b/client/src/App.css
index c1a8522..05b0c43 100644
--- a/client/src/App.css
+++ b/client/src/App.css
@@ -742,8 +742,21 @@ html.scheme-dark .themed-select-option.is-selected {
background: rgba(148, 163, 184, 0.08);
border: 1px solid rgba(148, 163, 184, 0.18);
color: var(--app-text-muted);
- cursor: default;
+ cursor: pointer;
user-select: none;
+ font-family: inherit;
+ transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
+}
+
+.skipper-badge:hover {
+ background: rgba(148, 163, 184, 0.14);
+ border-color: rgba(148, 163, 184, 0.28);
+ color: var(--app-text);
+}
+
+.skipper-badge:focus-visible {
+ outline: 2px solid var(--app-accent, #38bdf8);
+ outline-offset: 2px;
}
.skipper-badge__name {
@@ -800,6 +813,128 @@ html.scheme-dark .themed-select-option.is-selected {
padding-bottom: calc(32px + env(safe-area-inset-bottom, 0px));
}
+.profile-main {
+ max-width: 900px;
+ margin: 0 auto;
+ padding: 0 24px 48px;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+.profile-back-btn {
+ margin-right: 12px;
+}
+
+.profile-dl {
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.profile-dl-row {
+ display: grid;
+ grid-template-columns: minmax(120px, 180px) 1fr;
+ gap: 8px 16px;
+ align-items: center;
+}
+
+.profile-dl-row dt {
+ margin: 0;
+ font-size: 13px;
+ color: var(--app-text-muted);
+}
+
+.profile-dl-row dd {
+ margin: 0;
+ font-size: 14px;
+ word-break: break-all;
+}
+
+.profile-user-id {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.profile-user-id code {
+ font-size: 12px;
+ background: rgba(148, 163, 184, 0.08);
+ padding: 4px 8px;
+ border-radius: 6px;
+}
+
+.profile-copy-btn {
+ flex-shrink: 0;
+}
+
+.profile-section-header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 8px;
+}
+
+.profile-section-header h3 {
+ margin: 0;
+ font-size: 16px;
+}
+
+.profile-section-desc,
+.profile-pin-status,
+.profile-empty {
+ margin: 0 0 12px;
+ font-size: 13px;
+ color: var(--app-text-muted);
+ line-height: 1.5;
+}
+
+.profile-pin-form {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.profile-passkey-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.profile-passkey-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 10px 12px;
+ border-radius: 10px;
+ background: rgba(148, 163, 184, 0.06);
+ border: 1px solid rgba(148, 163, 184, 0.12);
+}
+
+.profile-passkey-id {
+ display: block;
+ font-family: ui-monospace, monospace;
+ font-size: 13px;
+}
+
+.profile-passkey-transports {
+ display: block;
+ font-size: 11px;
+ color: var(--app-text-muted);
+ margin-top: 2px;
+}
+
+@media (max-width: 640px) {
+ .profile-dl-row {
+ grid-template-columns: 1fr;
+ }
+}
+
.account-danger-zone {
border-top: 1px solid rgba(239, 68, 68, 0.2);
padding-top: 24px;
diff --git a/client/src/App.tsx b/client/src/App.tsx
index c30b015..e2524ad 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'
import './App.css'
import { DialogProvider } from './components/ModalDialog.tsx'
import AuthOnboarding from './components/AuthOnboarding.tsx'
+import UserProfilePage from './components/UserProfilePage.tsx'
import LogbookDashboard from './components/LogbookDashboard.tsx'
import VesselForm from './components/VesselForm.tsx'
import CrewForm from './components/CrewForm.tsx'
@@ -61,6 +62,7 @@ function App() {
const [online, setOnline] = useState(navigator.onLine)
const [isSyncing, setIsSyncing] = useState(false)
const [isAcceptingInvite, setIsAcceptingInvite] = useState(false)
+ const [showUserProfile, setShowUserProfile] = useState(false)
// Viewer mode for read-only shared links
const [isViewerMode, setIsViewerMode] = useState(false)
@@ -361,6 +363,7 @@ function App() {
setIsAuthenticated(false)
setActiveLogbookId(null)
setActiveLogbookTitle(null)
+ setShowUserProfile(false)
setTourSelectedEntryId(null)
setDemoHighlightEntryId(null)
localStorage.removeItem('active_logbook_id')
@@ -442,10 +445,18 @@ function App() {
return (
{pwaInstallBanner}
-
+ {showUserProfile ? (
+ setShowUserProfile(false)}
+ onLogout={handleLogout}
+ />
+ ) : (
+ setShowUserProfile(true)}
+ />
+ )}
)
}
diff --git a/client/src/components/LogbookDashboard.tsx b/client/src/components/LogbookDashboard.tsx
index e2fb9ef..6c66c2d 100644
--- a/client/src/components/LogbookDashboard.tsx
+++ b/client/src/components/LogbookDashboard.tsx
@@ -8,7 +8,6 @@ import BetaBadge from './BetaBadge.tsx'
import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js'
import { logoutUser } from '../services/auth.js'
import { useDialog } from './ModalDialog.tsx'
-import AccountDangerZone from './AccountDangerZone.tsx'
import { BookOpen, Plus, Trash2, LogOut, Languages, RefreshCw, Ship, User, Wifi, WifiOff } from 'lucide-react'
import DisclaimerHeaderButton from './DisclaimerHeaderButton.tsx'
import FeedbackHeaderButton from './FeedbackHeaderButton.tsx'
@@ -16,9 +15,10 @@ import FeedbackHeaderButton from './FeedbackHeaderButton.tsx'
interface LogbookDashboardProps {
onSelectLogbook: (id: string, title: string) => void
onLogout: () => void
+ onOpenProfile: () => void
}
-export default function LogbookDashboard({ onSelectLogbook, onLogout }: LogbookDashboardProps) {
+export default function LogbookDashboard({ onSelectLogbook, onLogout, onOpenProfile }: LogbookDashboardProps) {
const { t, i18n } = useTranslation()
const { showConfirm } = useDialog()
const [logbooks, setLogbooks] = useState([])
@@ -210,14 +210,16 @@ export default function LogbookDashboard({ onSelectLogbook, onLogout }: LogbookD
{/* Skipper profile */}
-
{username}
-
+
{/* Lang toggle */}