mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
e7851e79ff
The mirror answered "dashboard" for a browser that had simply never been told, and RootRedirect took that as the user's choice - so the setting was ignored on every device except the one that set it, and on that one too after a logout cleared the mirror. The setting itself was stored correctly the whole time, which is what made it look like nothing happened. readStartDestination now says "unknown" rather than guessing, and a device in that state waits for the real settings instead. That costs one wait, once per device; afterwards the mirror answers and the launch is as fast as before. Bounded by a timeout, because loadSettings deliberately leaves isLoaded false on a failed request so it can retry - without the floor, a launch with no backend would sit on the spinner forever.
104 lines
3.9 KiB
TypeScript
104 lines
3.9 KiB
TypeScript
import { isTripTabId } from '../constants/tripTabs'
|
|
import type { Settings } from '../types'
|
|
|
|
/**
|
|
* Where opening TREK lands. 'dashboard' is what TREK has always done; with
|
|
* 'active_trip' the app skips straight into the trip the dashboard would
|
|
* feature, on the tab picked below — three clicks and three loads less for
|
|
* someone entering expenses while they're actually travelling.
|
|
*/
|
|
export type StartPage = 'dashboard' | 'active_trip'
|
|
|
|
export const DEFAULT_START_PAGE: StartPage = 'dashboard'
|
|
export const DEFAULT_START_TRIP_TAB = 'plan'
|
|
|
|
/**
|
|
* The route that resolves the destination (RootRedirect in App.tsx). Anywhere
|
|
* that used to navigate to '/dashboard' for lack of a better idea sends people
|
|
* here instead, so the preference is applied in exactly one place.
|
|
*/
|
|
export const START_DESTINATION_ROUTE = '/'
|
|
|
|
/**
|
|
* How long a device that has never seen the preference waits for the settings
|
|
* before giving up and opening the dashboard. Long enough for a normal request,
|
|
* short enough that a launch with no backend isn't stuck staring at a spinner.
|
|
*/
|
|
export const SETTINGS_WAIT_MS = 2500
|
|
|
|
export interface StartDestination {
|
|
page: StartPage
|
|
tab: string
|
|
}
|
|
|
|
// Mirrored in localStorage, not read from the settings store, because the
|
|
// redirect has to be decided on the first paint — waiting for GET /api/settings
|
|
// would put a spinner in front of every launch, including the default one that
|
|
// isn't redirecting anywhere. Same trick as 'app_language' and the theme boot
|
|
// snapshot: the server's answer wins on the next load, this is only what we
|
|
// knew last time.
|
|
const PAGE_KEY = 'trek_start_page'
|
|
const TAB_KEY = 'trek_start_trip_tab'
|
|
|
|
const isStartPage = (value: unknown): value is StartPage =>
|
|
value === 'dashboard' || value === 'active_trip'
|
|
|
|
/**
|
|
* What this device last knew, or null if it has never been told — a fresh
|
|
* browser, another machine, or one that has logged out since.
|
|
*
|
|
* The null case matters: the preference lives on the server, so "this device
|
|
* doesn't know" is not the same as "this user wants the dashboard". Callers
|
|
* have to wait for the real settings rather than assume the default, or the
|
|
* setting is silently ignored on every device that didn't set it.
|
|
*/
|
|
export function readStartDestination(): StartDestination | null {
|
|
try {
|
|
const page = localStorage.getItem(PAGE_KEY)
|
|
if (!isStartPage(page)) return null
|
|
const tab = localStorage.getItem(TAB_KEY)
|
|
return { page, tab: isTripTabId(tab) ? tab : DEFAULT_START_TRIP_TAB }
|
|
} catch {
|
|
// Private-mode Safari and friends throw on access, not just on write.
|
|
return null
|
|
}
|
|
}
|
|
|
|
/** Called whenever the settings store learns a value — load, single or bulk save. */
|
|
export function rememberStartDestination(settings: Partial<Settings>): void {
|
|
try {
|
|
if ('start_page' in settings) {
|
|
const page = settings.start_page
|
|
if (isStartPage(page)) localStorage.setItem(PAGE_KEY, page)
|
|
else localStorage.removeItem(PAGE_KEY)
|
|
}
|
|
if ('start_trip_tab' in settings) {
|
|
const tab = settings.start_trip_tab
|
|
if (isTripTabId(tab)) localStorage.setItem(TAB_KEY, tab)
|
|
else localStorage.removeItem(TAB_KEY)
|
|
}
|
|
} catch {
|
|
// A browser that won't persist this just falls back to the dashboard.
|
|
}
|
|
}
|
|
|
|
/** On logout, so the next account on this browser starts on its own preference. */
|
|
export function forgetStartDestination(): void {
|
|
try {
|
|
localStorage.removeItem(PAGE_KEY)
|
|
localStorage.removeItem(TAB_KEY)
|
|
} catch {
|
|
// Nothing to clean up if storage is unavailable.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The planner URL for a startup redirect. The tab always goes on the query
|
|
* string, including 'plan' — leaving it off would hand the decision back to
|
|
* whichever tab the last session happened to end on.
|
|
*/
|
|
export function tripStartPath(tripId: number, tab: string): string {
|
|
const safeTab = isTripTabId(tab) ? tab : DEFAULT_START_TRIP_TAB
|
|
return `/trips/${tripId}?tab=${safeTab}`
|
|
}
|