From fd3f06c16795047b15ad8df44eef581f1e623e68 Mon Sep 17 00:00:00 2001 From: iamdoubz Date: Wed, 28 Oct 2026 09:59:34 -0500 Subject: [PATCH 1/4] web: answer calls and navigate on a phone On an iPhone the incoming call dimmed the screen with no buttons: iOS zooms into fields under 16px (the 14px login inputs) and stays zoomed, and the My Phone tabs were wider than the screen, so the centered dialog was drawn outside what the phone showed. Fields are 16px on touch screens and buttons at least 44px. The My Phone tabs and header links fold into a menu button below tablet width. A ringing call gets its own screen: full screen on a phone, a card on wider screens, with round Decline and Answer buttons. Esc still declines. --- apps/web/src/components/ui.tsx | 19 +++- apps/web/src/index.css | 10 ++ apps/web/src/pages/admin/AdminLayout.tsx | 4 +- apps/web/src/pages/user/MePage.tsx | 74 +++++++++---- apps/web/src/softphone/SoftphoneDock.tsx | 127 ++++++++++++++++++----- 5 files changed, 183 insertions(+), 51 deletions(-) diff --git a/apps/web/src/components/ui.tsx b/apps/web/src/components/ui.tsx index 7cbbb01..914c6b4 100644 --- a/apps/web/src/components/ui.tsx +++ b/apps/web/src/components/ui.tsx @@ -32,12 +32,29 @@ export function Button({ return (
diff --git a/apps/web/src/pages/user/MePage.tsx b/apps/web/src/pages/user/MePage.tsx index f6a315a..0daff77 100644 --- a/apps/web/src/pages/user/MePage.tsx +++ b/apps/web/src/pages/user/MePage.tsx @@ -1,12 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // My Phone (05 §4): Home, Calls, Voicemail, Contacts (11), Settings, People, and the softphone. import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { Link, NavLink, Route, Routes, useNavigate } from 'react-router'; import type { AuthUser } from '@selfphoneng/shared'; import { meApi, signOut } from '../../api/client.ts'; import { ThemeToggle } from '../../components/ThemeToggle.tsx'; -import { Alert, Brand, Button } from '../../components/ui.tsx'; +import { Alert, Brand, Button, MenuIcon } from '../../components/ui.tsx'; import { subscribeLive, useLive } from '../../live/store.ts'; import { SoftphoneDock } from '../../softphone/SoftphoneDock.tsx'; import { PresencePanel } from '../../softphone/PresencePanel.tsx'; @@ -36,15 +36,24 @@ export function MePage({ user }: { user: AuthUser }) { const toasts = useLive((s) => s.toasts); const offers = useContactShares().data?.length ?? 0; + const [menu, setMenu] = useState(false); + const close = () => setMenu(false); + async function logout() { + await signOut(); // may leave for the OpenID provider's sign-out page (12 §3.5) + qc.clear(); + nav('/login', { replace: true }); + } + const tab = ({ isActive }: { isActive: boolean }) => - `rounded-md px-3 py-2 text-sm font-medium ${isActive ? 'bg-zinc-100 dark:bg-zinc-800' : 'hover:bg-zinc-100 dark:hover:bg-zinc-800'}`; + `rounded-md px-3 py-2 text-sm font-medium pointer-coarse:py-3 ${isActive ? 'bg-zinc-100 dark:bg-zinc-800' : 'hover:bg-zinc-100 dark:hover:bg-zinc-800'}`; + const row = tab({ isActive: false }); return (
-
+

-
+
{user.role === 'admin' && ( Admin @@ -54,32 +63,40 @@ export function MePage({ user }: { user: AuthUser }) { About -
+
+ {/* On phones the tabs and the header links fold into the menu button. */} {toasts.map((t) => (
)} - dialogCall && run(softphone.decline(dialogCall.id))} - title="Incoming call" - footer={ - dialogCall && ( - <> - - - - ) - } - > - {dialogCall && ( -

- {nameOf(dialogCall)} - {nameOf(dialogCall) !== dialogCall.remote && ( - - {dialogCall.remote.length > 6 ? formatNumber(dialogCall.remote) : dialogCall.remote} - - )} -

- )} - {dialogCall && } -
+ {dialogCall && ( + run(softphone.answer(dialogCall.id))} + onDecline={() => run(softphone.decline(dialogCall.id))} + /> + )} ); } +// Feather's phone; turned 135° it's the hang-up handset. +const PHONE = + 'M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z'; + +function RoundButton({ + label, + color, + hangUp = false, + onClick, +}: { + label: string; + color: string; + hangUp?: boolean; + onClick: () => void; +}) { + return ( + + ); +} + +/** The ringing screen: full screen on a phone, a card on wider screens. Esc declines. */ +function IncomingCall({ + call, + name, + onAnswer, + onDecline, +}: { + call: CallInfo; + name: string; + onAnswer: () => void; + onDecline: () => void; +}) { + const ref = useRef(null); + useEffect(() => { + const d = ref.current; + if (d && !d.open) d.showModal(); + }, []); + return ( + { + e.preventDefault(); + onDecline(); + }} + className="m-0 h-dvh max-h-none w-full max-w-none bg-white p-0 text-zinc-900 backdrop:bg-black/60 sm:m-auto sm:h-auto sm:max-w-sm sm:rounded-2xl sm:shadow-xl dark:bg-zinc-900 dark:text-zinc-100" + > +
+
+

+ Incoming call +

+

{name}

+ {name !== call.remote && ( +

+ {call.remote.length > 6 ? formatNumber(call.remote) : call.remote} +

+ )} + +
+
+ + +
+
+
+ ); +} + function Keypad({ onKey, label }: { onKey: (k: string) => void; label: string }) { return (
From 5db4914fa785fd70b6949999bb9a4b20b9cc4f6c Mon Sep 17 00:00:00 2001 From: iamdoubz Date: Wed, 28 Oct 2026 10:00:20 -0500 Subject: [PATCH 2/4] web: theme colors from the logo, readable in both themes Pages used zinc and sky classes with every dark: variant paired by hand, and several pairs failed WCAG AA: white on the sky-600 buttons (4.1:1), zinc-500 secondary text on dark backgrounds (3.7-4.1:1), the 10px zinc-400 labels (2.6:1) and the dashboard's green "Connected" (3.3:1). index.css now defines theme tokens once, light and dark side by side (page, surface, subtle, line, line-strong, fg, muted, primary and its soft/line/ink shades, link, focus), exposed to Tailwind with @theme inline. Pages use those names, so a palette change is one file. Blue #2563eb from the logo for buttons and links, the logo's navy #0b1120 for the dark page. Every text pair is at least 4.5:1 in both themes, most 7:1. Cards and lists sit on the surface color with a light shadow on an off-white page, at rounded-xl; native controls follow the theme (color-scheme). Zinc and sky stay only as data colors: status dots and chart swatches. --- apps/web/src/auth/RequireAuth.tsx | 2 +- apps/web/src/components/DestinationPicker.tsx | 2 +- apps/web/src/components/Dialog.tsx | 8 +- apps/web/src/components/ParkedCalls.tsx | 10 +- apps/web/src/components/PhoneAppSetup.tsx | 6 +- apps/web/src/components/SignInCard.tsx | 8 +- apps/web/src/components/SpamNote.tsx | 2 +- apps/web/src/components/ui.tsx | 14 +-- apps/web/src/index.css | 54 +++++++++ apps/web/src/main.tsx | 2 +- apps/web/src/pages/AboutPage.tsx | 8 +- apps/web/src/pages/ChangePasswordPage.tsx | 2 +- apps/web/src/pages/LoginPage.tsx | 10 +- apps/web/src/pages/SetupPage.tsx | 31 +++--- apps/web/src/pages/admin/AccountPage.tsx | 4 +- apps/web/src/pages/admin/AdminLayout.tsx | 21 ++-- apps/web/src/pages/admin/BlockedPage.tsx | 8 +- apps/web/src/pages/admin/CallsPage.tsx | 22 ++-- apps/web/src/pages/admin/ConferencesPage.tsx | 6 +- apps/web/src/pages/admin/DashboardPage.tsx | 33 +++--- apps/web/src/pages/admin/IvrsPage.tsx | 14 +-- apps/web/src/pages/admin/NumbersPage.tsx | 14 +-- apps/web/src/pages/admin/OfficeHoursPage.tsx | 30 ++--- apps/web/src/pages/admin/OutboundPage.tsx | 24 ++-- apps/web/src/pages/admin/PagingPage.tsx | 8 +- apps/web/src/pages/admin/PhonesPage.tsx | 32 +++--- apps/web/src/pages/admin/QueuesPage.tsx | 34 +++--- apps/web/src/pages/admin/RecordingsPage.tsx | 25 ++--- apps/web/src/pages/admin/ReportsPage.tsx | 28 ++--- apps/web/src/pages/admin/RingGroupsPage.tsx | 10 +- apps/web/src/pages/admin/SettingsPage.tsx | 105 +++++++++--------- apps/web/src/pages/admin/SoundsPage.tsx | 28 ++--- apps/web/src/pages/admin/SystemPage.tsx | 53 +++++---- apps/web/src/pages/admin/TrunksPage.tsx | 31 +++--- apps/web/src/pages/admin/UserDrawer.tsx | 22 ++-- apps/web/src/pages/admin/UsersPage.tsx | 26 ++--- apps/web/src/pages/user/ContactsPage.tsx | 43 +++---- apps/web/src/pages/user/MePage.tsx | 32 +++--- apps/web/src/pages/user/MeSettingsPage.tsx | 31 +++--- apps/web/src/pages/user/MyCallsPage.tsx | 12 +- apps/web/src/pages/user/VoicemailPage.tsx | 22 ++-- apps/web/src/softphone/PresencePanel.tsx | 6 +- apps/web/src/softphone/SoftphoneDock.tsx | 14 +-- 43 files changed, 453 insertions(+), 444 deletions(-) diff --git a/apps/web/src/auth/RequireAuth.tsx b/apps/web/src/auth/RequireAuth.tsx index ad88152..21c1de3 100644 --- a/apps/web/src/auth/RequireAuth.tsx +++ b/apps/web/src/auth/RequireAuth.tsx @@ -26,7 +26,7 @@ export function RequireAuth({ }) { const me = useMe(); const loc = useLocation(); - if (me.isPending) return

Loading…

; + if (me.isPending) return

Loading…

; if (me.isError) { if (me.error instanceof ApiError && me.error.status === 401) return ; diff --git a/apps/web/src/components/DestinationPicker.tsx b/apps/web/src/components/DestinationPicker.tsx index 8a5f64f..d73d964 100644 --- a/apps/web/src/components/DestinationPicker.tsx +++ b/apps/web/src/components/DestinationPicker.tsx @@ -143,7 +143,7 @@ export function DestinationPicker({

)} {isIdKind(kind) && all[kind] && targets(kind).length === 0 && ( -

Nothing to pick yet ({what}).

+

Nothing to pick yet ({what}).

)} ); diff --git a/apps/web/src/components/Dialog.tsx b/apps/web/src/components/Dialog.tsx index bec6fa3..f9b7bd8 100644 --- a/apps/web/src/components/Dialog.tsx +++ b/apps/web/src/components/Dialog.tsx @@ -28,17 +28,17 @@ export function Dialog({ const shape = drawer ? 'ml-auto mr-0 h-dvh max-h-dvh w-full max-w-md' - : 'm-auto w-[calc(100%-2rem)] max-w-md rounded-lg'; + : 'm-auto w-[calc(100%-2rem)] max-w-md rounded-xl'; return ( e.target === ref.current && onClose()} aria-labelledby="dialog-title" - className={`${shape} bg-white p-0 text-zinc-900 shadow-xl backdrop:bg-black/40 dark:bg-zinc-900 dark:text-zinc-100`} + className={`${shape} bg-surface p-0 text-fg shadow-xl backdrop:bg-black/40`} >
-
+

{title}

@@ -48,7 +48,7 @@ export function Dialog({
{children}
{footer && ( -
+
{footer}
)} diff --git a/apps/web/src/components/ParkedCalls.tsx b/apps/web/src/components/ParkedCalls.tsx index ce099a1..585f247 100644 --- a/apps/web/src/components/ParkedCalls.tsx +++ b/apps/web/src/components/ParkedCalls.tsx @@ -15,13 +15,13 @@ export function ParkedCalls({ onPickUp }: { onPickUp?: (slot: string) => void }) Parked calls {parked.length === 0 ? ( -

+

None. Transfer a call to 700 to park it; dial the slot to pick it up.

) : (
    {parked.map((p) => (
  • void }) {p.slot} {p.callerName ? `${p.callerName} ` : ''} - {fmtNumber(p.callerNumber)} - {p.parkedBy && · parked by {p.parkedBy}} + {fmtNumber(p.callerNumber)} + {p.parkedBy && · parked by {p.parkedBy}} {since(p.since, now)} {onPickUp && (
)} {qr.error && {qr.error.message}} -

+

On a mobile phone, apps can miss calls while they're in the background (iPhones and battery-saving Android phones stop them). For calls you can't miss, use a desk phone or this browser, or forward unanswered calls to your mobile number. diff --git a/apps/web/src/components/SignInCard.tsx b/apps/web/src/components/SignInCard.tsx index 398e392..ec29db4 100644 --- a/apps/web/src/components/SignInCard.tsx +++ b/apps/web/src/components/SignInCard.tsx @@ -56,11 +56,11 @@ export function SignInCard() {

Password

{ways.password ? ( - + Change password ) : ( -

+

You have no password here. An admin can give you one with Reset password.

)} @@ -92,7 +92,7 @@ export function SignInCard() { ) : ( <> -

+

Connect your {oidc.label} account to sign in with it as well.

@@ -133,7 +133,7 @@ function DirectoryConnect() { }} >

Company directory

-

+

Connect your directory account to sign in with its password as well.

diff --git a/apps/web/src/components/SpamNote.tsx b/apps/web/src/components/SpamNote.tsx index 85e5670..0110f99 100644 --- a/apps/web/src/components/SpamNote.tsx +++ b/apps/web/src/components/SpamNote.tsx @@ -44,7 +44,7 @@ export function SpamNote({ check }: { check: SpamCheck | null | undefined }) { > {high ? 'Likely spam' : 'Possible spam'} - {spamLine(check)} + {spamLine(check)} ); } diff --git a/apps/web/src/components/ui.tsx b/apps/web/src/components/ui.tsx index 914c6b4..7671fe7 100644 --- a/apps/web/src/components/ui.tsx +++ b/apps/web/src/components/ui.tsx @@ -3,15 +3,13 @@ import { useId, type ComponentProps, type ReactNode } from 'react'; import { APP_NAME } from '@selfphoneng/shared'; -const focus = - 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500'; +const focus = 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus'; const variants = { - primary: 'bg-sky-600 text-white hover:bg-sky-700 disabled:bg-sky-600/50', - secondary: - 'border border-zinc-300 bg-white hover:bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-900 dark:hover:bg-zinc-800', + primary: 'bg-primary text-white hover:bg-primary-hover disabled:bg-primary/50', + secondary: 'border border-line-strong bg-surface hover:bg-subtle', danger: 'bg-red-600 text-white hover:bg-red-700 disabled:bg-red-600/50', - ghost: 'hover:bg-zinc-100 dark:hover:bg-zinc-800', + ghost: 'hover:bg-subtle', }; /** The icon (public/icon-192.png, cut from public/logo.png) and the product name. */ @@ -55,7 +53,7 @@ export function MenuIcon() { ); } -export const inputClass = `w-full rounded-md border border-zinc-300 bg-white px-3 py-2 text-sm dark:border-zinc-700 dark:bg-zinc-900 ${focus} aria-invalid:border-red-500`; +export const inputClass = `w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm ${focus} aria-invalid:border-red-500`; /** Label + control + error text. `children` gets the id and aria props to spread on the control. */ export function Field({ @@ -84,7 +82,7 @@ export function Field({ {(error || hint) && (

{error ?? hint}

diff --git a/apps/web/src/index.css b/apps/web/src/index.css index aa20774..7375522 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1,6 +1,60 @@ @import 'tailwindcss'; @custom-variant dark (&:where(.dark, .dark *)); +/* Theme colors: the logo's blue and navy. Pages use these names (bg-surface, text-muted, + border-line...), not palette colors, so light and dark are paired here once. Every text pair + is at least 4.5:1 in both themes. */ +:root { + color-scheme: light; + --sp-page: #f8fafc; + --sp-surface: #ffffff; + --sp-subtle: #f1f5f9; + --sp-line: #e2e8f0; + --sp-line-strong: #cbd5e1; + --sp-fg: #0f172a; + --sp-muted: #475569; + --sp-primary: #2563eb; + --sp-primary-hover: #1d4ed8; + --sp-primary-soft: #eff6ff; + --sp-primary-line: #bfdbfe; + --sp-primary-ink: #1e3a8a; + --sp-link: #1d4ed8; + --sp-focus: #2563eb; +} +.dark { + color-scheme: dark; + --sp-page: #0b1120; + --sp-surface: #131c2e; + --sp-subtle: #1e293b; + --sp-line: #253349; + --sp-line-strong: #334155; + --sp-fg: #f1f5f9; + --sp-muted: #94a3b8; + --sp-primary: #2563eb; + --sp-primary-hover: #1d4ed8; + --sp-primary-soft: #172554; + --sp-primary-line: #1e3a8a; + --sp-primary-ink: #dbeafe; + --sp-link: #60a5fa; + --sp-focus: #60a5fa; +} +@theme inline { + --color-page: var(--sp-page); + --color-surface: var(--sp-surface); + --color-subtle: var(--sp-subtle); + --color-line: var(--sp-line); + --color-line-strong: var(--sp-line-strong); + --color-fg: var(--sp-fg); + --color-muted: var(--sp-muted); + --color-primary: var(--sp-primary); + --color-primary-hover: var(--sp-primary-hover); + --color-primary-soft: var(--sp-primary-soft); + --color-primary-line: var(--sp-primary-line); + --color-primary-ink: var(--sp-primary-ink); + --color-link: var(--sp-link); + --color-focus: var(--sp-focus); +} + /* iOS zooms into any field under 16px on focus and stays zoomed, which left the incoming call dialog off screen. Unlayered, so it beats text-sm. */ @media (pointer: coarse) { diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 3476200..6e4ae61 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -104,7 +104,7 @@ const router = createBrowserRouter([ const queryClient = new QueryClient(); -document.body.className = 'bg-white text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100'; +document.body.className = 'bg-page text-fg'; createRoot(document.getElementById('root')!).render( diff --git a/apps/web/src/pages/AboutPage.tsx b/apps/web/src/pages/AboutPage.tsx index c971742..2c421b3 100644 --- a/apps/web/src/pages/AboutPage.tsx +++ b/apps/web/src/pages/AboutPage.tsx @@ -6,7 +6,7 @@ import { Link } from 'react-router'; import { aboutApi } from '../api/client.ts'; import { Alert, Brand } from '../components/ui.tsx'; -const linkClass = 'text-sky-700 underline dark:text-sky-400'; +const linkClass = 'text-link underline'; export function AboutPage() { const about = useQuery({ queryKey: ['about'], queryFn: aboutApi.get }); @@ -44,18 +44,18 @@ export function AboutPage() { Code v{a.version} - + selfphone.tar.gz, {Math.max(1, Math.round(a.source.bytes / 1e6))} MB, built{' '} {new Date(a.source.builtAt).toLocaleString()}
) : ( a && ( -

+

This is a development build: the source archive is only in builds made with Docker. The project's repository is below.

diff --git a/apps/web/src/pages/ChangePasswordPage.tsx b/apps/web/src/pages/ChangePasswordPage.tsx index e63a76d..106f36a 100644 --- a/apps/web/src/pages/ChangePasswordPage.tsx +++ b/apps/web/src/pages/ChangePasswordPage.tsx @@ -44,7 +44,7 @@ export function ChangePasswordPage() { return ( {forced && ( -

+

You signed in with a temporary password. Choose a new one to continue.

)} diff --git a/apps/web/src/pages/LoginPage.tsx b/apps/web/src/pages/LoginPage.tsx index 3358746..fb7b816 100644 --- a/apps/web/src/pages/LoginPage.tsx +++ b/apps/web/src/pages/LoginPage.tsx @@ -13,12 +13,12 @@ import { Alert, Button, Field, inputClass } from '../components/ui.tsx'; export function AuthCard({ title, children }: { title: string; children: React.ReactNode }) { return (
-
+
{APP_NAME}

{title}

{children}
- + About SelfPhoneNG · source code
@@ -73,12 +73,12 @@ export function LoginPage() { {/* A full page load: the browser goes to the provider and comes back signed in. */} Sign in with {oidc.label} {showForm && ( -

+

or with your password

)} @@ -87,7 +87,7 @@ export function LoginPage() { {!showForm && ( @@ -109,7 +109,7 @@ function Nav({ hint?: string; }) { return ( -
+
@@ -118,7 +118,7 @@ function Nav({ {skip} )} - {hint && {hint}} + {hint && {hint}}
); } @@ -196,7 +196,7 @@ function CompanyForm({ )} -
+
@@ -343,7 +343,7 @@ function NetworkForm(p: { I've done this
-
+
@@ -399,7 +399,7 @@ function TrunkStep({ next }: { next: () => void }) { const ok = status?.label === 'Registered' || status?.label === 'Reachable'; return ( <> -

+

Connect your voip.ms sub-account. Follow the checklist, save, and wait for{' '} Registered.

@@ -457,7 +457,7 @@ function NumbersStep({ next }: { next: () => void }) { ); return ( <> -

+

Add the phone numbers (DIDs) you have at voip.ms. You choose where their calls go in step 9.

{trunks.data[0]!.hasApiPassword && } @@ -465,8 +465,7 @@ function NumbersStep({ next }: { next: () => void }) {
    {dids.data.map((d) => (
  • - ✓ {formatNumber(d.number)}{' '} - {d.label && {d.label}} + ✓ {formatNumber(d.number)} {d.label && {d.label}}
  • ))}
@@ -554,7 +553,7 @@ function UsersStep({ next }: { next: () => void }) { return ( <> -

+

Add the people who get a phone (up to 10 here; more later under Users & Extensions). Each gets the extension you type (any free number, like 1), or the next free one, and a temporary password, shown once. @@ -614,7 +613,7 @@ function UsersStep({ next }: { next: () => void }) { {save.isError && {save.error.message}} {created.length > 0 && ( -

+

Give each person their temporary password (shown only now):

@@ -624,7 +623,7 @@ function UsersStep({ next }: { next: () => void }) { {c.number} {c.name} - {c.email} + {c.email} {c.password} ))} @@ -682,7 +681,7 @@ function RoutingStep({ next }: { next: () => void }) { const incomplete = Object.values(dest).some((d) => d === null); return ( <> -

+

A digital receptionist ("press 1 for sales…") needs a recorded greeting: build it under Digital receptionist later and pick it here or on the Phone numbers page.

@@ -715,7 +714,7 @@ function RoutingStep({ next }: { next: () => void }) { })} {everyone.isError && {everyone.error.message}} {save.isError && {save.error.message}} -
+
@@ -753,6 +752,6 @@ function Loading({ q }: { q: { isError: boolean; error: Error | null } }): React return q.isError ? ( {q.error!.message} ) : ( -

Loading…

+

Loading…

); } diff --git a/apps/web/src/pages/admin/AccountPage.tsx b/apps/web/src/pages/admin/AccountPage.tsx index 2343d94..b4023c4 100644 --- a/apps/web/src/pages/admin/AccountPage.tsx +++ b/apps/web/src/pages/admin/AccountPage.tsx @@ -73,7 +73,7 @@ function TwoStep() {

Two-step sign-in

-

+

Besides your password, signing in asks for a code from an authenticator app on your phone (any app that does TOTP, such as Aegis, 2FAS, Google or Microsoft Authenticator).

@@ -115,7 +115,7 @@ function TwoStep() { height={240} className="rounded bg-white" /> -

+

Or type this key: {setup.data.secret}

diff --git a/apps/web/src/pages/admin/AdminLayout.tsx b/apps/web/src/pages/admin/AdminLayout.tsx index 11bbcf7..24171ae 100644 --- a/apps/web/src/pages/admin/AdminLayout.tsx +++ b/apps/web/src/pages/admin/AdminLayout.tsx @@ -60,11 +60,11 @@ export function AdminLayout({ user }: { user: AuthUser }) { end={p.path === ''} onClick={() => setNavOpen(false)} className={({ isActive }) => - `flex items-center justify-between rounded-md px-3 py-1.5 text-sm ${isActive ? 'bg-sky-100 font-medium text-sky-900 dark:bg-sky-950 dark:text-sky-100' : 'hover:bg-zinc-200/60 dark:hover:bg-zinc-800'} ${p.milestone ? 'text-zinc-500' : ''}` + `flex items-center justify-between rounded-md px-3 py-1.5 text-sm ${isActive ? 'bg-primary-soft font-medium text-primary-ink' : 'hover:bg-subtle'} ${p.milestone ? 'text-muted' : ''}` } > {p.label} - {p.milestone && {p.milestone}} + {p.milestone && {p.milestone}} ))} @@ -74,7 +74,7 @@ export function AdminLayout({ user }: { user: AuthUser }) { return (
-