mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-28 07:32:52 +00:00
123 lines
4.1 KiB
TypeScript
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,
|
|
)
|
|
}
|