diff --git a/frontend/src/components/layouts/AppLayout.tsx b/frontend/src/components/layouts/AppLayout.tsx index beced9ff3..a6b06cac1 100644 --- a/frontend/src/components/layouts/AppLayout.tsx +++ b/frontend/src/components/layouts/AppLayout.tsx @@ -9,6 +9,7 @@ import { useCommandPaletteContext, } from "src/contexts/CommandPaletteContext"; import { useBanner } from "src/hooks/useBanner"; +import { useDocumentTitle } from "src/hooks/useDocumentTitle"; import { useInfo } from "src/hooks/useInfo"; import { useNotifyReceivedPayments } from "src/hooks/useNotifyReceivedPayments"; import { useRemoveSuccessfulChannelOrder } from "src/hooks/useRemoveSuccessfulChannelOrder"; @@ -21,6 +22,7 @@ function AppLayoutInner() { useRemoveSuccessfulChannelOrder(); useNotifyReceivedPayments(); + useDocumentTitle(); if (!info) { return null; diff --git a/frontend/src/hooks/useDocumentTitle.ts b/frontend/src/hooks/useDocumentTitle.ts new file mode 100644 index 000000000..7349cd340 --- /dev/null +++ b/frontend/src/hooks/useDocumentTitle.ts @@ -0,0 +1,49 @@ +import React from "react"; +import { useMatches } from "react-router-dom"; + +/** + * Custom hook to manage document.title based on route handles. + * This ensures the browser's history entries include a proper title + * (fixes: back gesture / long-press back showing empty/incorrect titles). + * + * Looks for a `title` property in route handles (string or function), + * falling back to "Alby Hub" if not present. + */ +export function useDocumentTitle() { + const matches = useMatches(); + + React.useEffect(() => { + try { + // Extract title from route handles. Use a typed helper to get the title + // from the handle object (if present). + const getTitleFromHandle = (handle: unknown): string | null => { + if (handle && typeof handle === "object") { + const h = handle as { title?: unknown }; + if (typeof h.title === "string") { + return h.title; + } + if (typeof h.title === "function") { + try { + return (h.title as () => string)(); + } catch (err) { + return null; + } + } + } + return null; + }; + + // Find the last (most specific) route with a title, or default to "Alby Hub" + const routeTitle = + matches + .map((m) => getTitleFromHandle(m.handle)) + .filter(Boolean) + .pop() || "Alby Hub"; + + // Set document title + document.title = routeTitle; + } catch (err) { + console.error("Failed to compute page title", err); + } + }, [matches]); +} diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx index 5ed1436a1..57e2faef9 100644 --- a/frontend/src/routes.tsx +++ b/frontend/src/routes.tsx @@ -102,7 +102,7 @@ const routes = [ { path: "home", element: , - handle: { crumb: () => "Dashboard" }, + handle: { crumb: () => "Dashboard", title: "Dashboard" }, children: [ { index: true, @@ -113,7 +113,7 @@ const routes = [ { path: "wallet", element: , - handle: { crumb: () => "Wallet" }, + handle: { crumb: () => "Wallet", title: "Wallet" }, children: [ { index: true, @@ -121,7 +121,7 @@ const routes = [ }, { path: "swap", - handle: { crumb: () => "Swap" }, + handle: { crumb: () => "Swap", title: "Swap" }, children: [ { index: true, @@ -143,24 +143,30 @@ const routes = [ }, { path: "receive", - handle: { crumb: () => "Receive" }, + handle: { crumb: () => "Receive", title: "Receive" }, children: [ { index: true, element: , }, { - handle: { crumb: () => "Receive On-chain" }, + handle: { + crumb: () => "Receive On-chain", + title: "Receive On-chain", + }, path: "onchain", element: , }, { - handle: { crumb: () => "Invoice" }, + handle: { crumb: () => "Invoice", title: "Invoice" }, path: "invoice", element: , }, { - handle: { crumb: () => "BOLT-12 Offer" }, + handle: { + crumb: () => "BOLT-12 Offer", + title: "BOLT-12 Offer", + }, path: "offer", element: , }, @@ -168,7 +174,7 @@ const routes = [ }, { path: "send", - handle: { crumb: () => "Send" }, + handle: { crumb: () => "Send", title: "Send" }, children: [ { index: true, @@ -203,24 +209,27 @@ const routes = [ { path: "sign-message", element: , - handle: { crumb: () => "Sign Message" }, + handle: { crumb: () => "Sign Message", title: "Sign Message" }, }, { path: "node-alias", element: , - handle: { crumb: () => "Node Alias" }, + handle: { crumb: () => "Node Alias", title: "Node Alias" }, }, { path: "withdraw", element: , - handle: { crumb: () => "Withdraw On-Chain Balance" }, + handle: { + crumb: () => "Withdraw On-Chain Balance", + title: "Withdraw On-Chain Balance", + }, }, ], }, { path: "settings", element: , - handle: { crumb: () => "Settings" }, + handle: { crumb: () => "Settings", title: "Settings" }, children: [ { path: "", @@ -233,22 +242,25 @@ const routes = [ { path: "about", element: , - handle: { crumb: () => "About" }, + handle: { crumb: () => "About", title: "About" }, }, { path: "auto-unlock", element: , - handle: { crumb: () => "Auto Unlock" }, + handle: { crumb: () => "Auto Unlock", title: "Auto Unlock" }, }, { path: "change-unlock-password", element: , - handle: { crumb: () => "Unlock Password" }, + handle: { + crumb: () => "Unlock Password", + title: "Change Unlock Password", + }, }, { path: "backup", element: , - handle: { crumb: () => "Backup" }, + handle: { crumb: () => "Backup", title: "Backup" }, }, { path: "node-migrate", @@ -273,7 +285,7 @@ const routes = [ { path: "apps", element: , - handle: { crumb: () => "Connections" }, + handle: { crumb: () => "Connections", title: "Connections" }, children: [ { index: true, @@ -286,7 +298,7 @@ const routes = [ { path: "new", element: , - handle: { crumb: () => "New App" }, + handle: { crumb: () => "New App", title: "New Connection" }, }, { path: "cleanup", @@ -297,7 +309,7 @@ const routes = [ { path: "sub-wallets", element: , - handle: { crumb: () => "Sub-wallets" }, + handle: { crumb: () => "Sub-wallets", title: "Sub-wallets" }, children: [ { @@ -317,7 +329,7 @@ const routes = [ { path: "internal-apps", element: , - handle: { crumb: () => "Connections" }, + handle: { crumb: () => "Connections", title: "Internal Apps" }, children: [ { path: "buzzpay", @@ -356,7 +368,7 @@ const routes = [ { path: "appstore", element: , - handle: { crumb: () => "App Store" }, + handle: { crumb: () => "App Store", title: "App Store" }, children: [ { path: ":appStoreId", @@ -367,7 +379,7 @@ const routes = [ { path: "channels", element: , - handle: { crumb: () => "Node" }, + handle: { crumb: () => "Node", title: "Channels" }, children: [ { index: true, @@ -375,7 +387,10 @@ const routes = [ }, { path: "first", - handle: { crumb: () => "Your First Channel" }, + handle: { + crumb: () => "Your First Channel", + title: "Your First Channel", + }, children: [ { index: true, @@ -393,7 +408,7 @@ const routes = [ }, { path: "auto", - handle: { crumb: () => "New Channel" }, + handle: { crumb: () => "New Channel", title: "New Channel" }, children: [ { index: true, @@ -412,34 +427,43 @@ const routes = [ { path: "outgoing", element: , - handle: { crumb: () => "Open Channel with On-Chain" }, + handle: { + crumb: () => "Open Channel with On-Chain", + title: "Open Channel with On-Chain", + }, }, { path: "incoming", element: , - handle: { crumb: () => "Open Channel with Lightning" }, + handle: { + crumb: () => "Open Channel with Lightning", + title: "Open Channel with Lightning", + }, }, { path: "order", element: , - handle: { crumb: () => "Current Order" }, + handle: { crumb: () => "Current Order", title: "Current Order" }, }, { path: "onchain/buy-bitcoin", element: , - handle: { crumb: () => "Buy Bitcoin" }, + handle: { crumb: () => "Buy Bitcoin", title: "Buy Bitcoin" }, }, { path: "onchain/deposit-bitcoin", element: , - handle: { crumb: () => "Deposit Bitcoin" }, + handle: { + crumb: () => "Deposit Bitcoin", + title: "Deposit Bitcoin", + }, }, ], }, { path: "peers", element: , - handle: { crumb: () => "Peers" }, + handle: { crumb: () => "Peers", title: "Peers" }, children: [ { index: true, @@ -448,7 +472,7 @@ const routes = [ { path: "new", element: , - handle: { crumb: () => "Connect Peer" }, + handle: { crumb: () => "Connect Peer", title: "Connect Peer" }, }, ], }, @@ -459,7 +483,7 @@ const routes = [ { path: "review-earn", element: , - handle: { crumb: () => "Review & Earn" }, + handle: { crumb: () => "Review & Earn", title: "Review & Earn" }, }, ], },