Files

123 lines
4.1 KiB
TypeScript

import type { ReactNode } from 'react'
import { useSyncExternalStore } from 'react'
import { Code2, MessageSquare } from '@hanzogui/lucide-icons-2'
import { dev } from '@hanzo/app/lib/host'
/** The two the rail offers. Ask, or build.
*
* Chat's href is SLASHLESS, and now that is simply the address: /chat is the
* export's own room, served the way /dev is. It carried a trailing slash while
* the SPA held that path, because that app's index declares
* `<base href="/chat/">`. The export writes `chat.html` and no
* `chat/index.html`, so the slashed form has nothing behind it on any plane.
* One spelling everywhere; see components/home/nav-data.ts `CHAT`. It also
* means `owns()`/ROOMS below match the href as well as `useRoute()`. */
export const MODES = [
{ href: '/chat', label: 'Chat', Icon: MessageSquare, mounted: true },
{ href: dev(), label: 'Dev', Icon: Code2 },
]
/**
* The rooms — every route under app/(app), which draws its own sidebar and
* brings its own field. Add a room there, add it here.
*
* This was the same list as MODES until the app grew four more rooms, and the
* two read as one list because they coincided: the rail happened to offer
* everything the workspace owned. It does not any more. A visitor is offered
* Chat and Dev; the workspace answers for six.
*/
const ROOMS = [
'/home',
'/chat',
'/dev',
'/board',
'/drive',
'/inbox',
'/work',
// The rooms added after this list was last read. Each one already brought its
// own rail and — in Bots' case — its own field, so the site composer drew a
// SECOND one underneath: /bots shipped with "Message Sales Outbound" and "Ask
// Hanzo anything" stacked, two ways to say one thing.
'/bots',
'/meet',
'/cal',
'/contacts',
// Settings draws its own column and panels, and asks for nothing in a field.
'/settings',
// The member door: the rooms' veil while it sends the reader on.
'/dashboard',
// Projects workspace brings its own rail, sidebar, and threads pane.
'/projects',
]
/**
* Whether a path is a room's own screen.
*
* Site chrome steps aside there rather than draw a second copy of what the room
* already has: the room brings its own rail with the account at its foot, and
* carries help and preferences in its own Settings. The help corner
* (./help.tsx) reads it.
*
* A SEGMENT, never a string prefix, so a later /development keeps its chrome.
*/
export const owns = (path: string): boolean =>
ROOMS.some((room) => path === room || path.startsWith(`${room}/`))
/**
* The pages a person passes through on the way in or on the way to pay.
* They carry the mark and nothing else: no site menus, no footer.
* The beta landing keeps the chrome. Once a track is chosen, `setBetaRunning`
* steps the menus aside and the form's Back stands in the bar.
*/
const BARE = ['/login', '/signup', '/forget', '/join', '/pay', '/checkout', '/onboarding', '/auth']
/** Whether a path is one of those pages. A segment match, like `owns`. */
export const bare = (path: string): boolean => {
const p = path.length > 1 ? path.replace(/\/+$/, '') : path
return BARE.some((route) => p === route || p.startsWith(`${route}/`))
}
/** The beta application is underway (a track is chosen, checkout has not returned). */
let running = false
const hear = new Set<() => void>()
export function setBetaRunning(on: boolean) {
if (running === on) return
running = on
hear.forEach((fn) => fn())
}
export function betaRunning(): boolean {
return running
}
export function subscribeBeta(fn: () => void) {
hear.add(fn)
return () => hear.delete(fn)
}
/**
* The step back a focused page offers in the shared bar (@hanzo/app `Chrome`),
* drawn right after the brand. The page sets it while it has a step to go back to
* and clears it on the way out; the bar only draws what it holds.
*/
let step: ReactNode = null
const hearStep = new Set<() => void>()
export function setBack(next: ReactNode) {
if (step === next) return
step = next
hearStep.forEach((fn) => fn())
}
export function useBack(): ReactNode {
return useSyncExternalStore(
(fn) => {
hearStep.add(fn)
return () => hearStep.delete(fn)
},
() => step,
() => null,
)
}