mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
/chat signed out is the buying agent (components/sales/Guest, Agent): the
question the home composer carried in ?q= is asked of cloud's
POST /v1/ai/sales, the reply is drawn as text, and each next step the
server built is a control: a call (the lead is filed first, then the
calendar opens on it), the sign-up, a plan's card checkout, Add funds. A
quiet line says it is a limited preview and how many messages are left; at
the ceiling the sign-up takes the composer's place. A conversation's own
address still signs in, and a signed-in reader keeps the app.
/buying-specialist is the same agent; its keyword script, its typed prices
and its claims to see an account are gone. The model pages' no-key card is
the real lane, POST /v1/chat/public, not a guest key that answers 401.
Measured in the site's names: chat_started, cta_clicked {cta: kind, plan,
agent: sales}, sales_contacted. The floating privacy button is hidden as
room.css meant: the rule names its tag to outrank gui's atomic class.
61 lines
2.5 KiB
TypeScript
61 lines
2.5 KiB
TypeScript
'use client'
|
|
|
|
// /chat, and by the edge every conversation under it (lib/edge.ts `APP`): the
|
|
// app in Chat. A conversation is its path, `/chat/<id>` (@hanzo/app lib/host
|
|
// `talk`, `place`).
|
|
//
|
|
// signed in the app (app/_web.tsx), drawn here as on `/`. A conversation
|
|
// at the address it had before, `/chat?thread=<id>`, is moved to
|
|
// its path first, once, so Back does not land on it (`moved`).
|
|
// signed out at /chat itself, the buying agent (components/sales/Guest): a
|
|
// limited free preview that needs no account, asked the question
|
|
// `?q=` carried in from the home page's composer. A conversation's
|
|
// own address is a signed-in reader's, so there it is sign in
|
|
// first (destination.ts `enter`); the address is written down on
|
|
// the way out, so the sign-in returns here and the first case
|
|
// takes over.
|
|
|
|
import { useEffect, useState } from 'react'
|
|
import dynamic from 'next/dynamic'
|
|
import { useIam } from '@hanzo/iam/react'
|
|
import { enter } from '@hanzo/app/lib/destination'
|
|
import { CHAT, talk } from '@hanzo/app/lib/host'
|
|
import { Guest } from '@/components/sales/Guest'
|
|
|
|
/** The app, fetched only for a signed-in reader. */
|
|
const App = dynamic(() => import('../../_web'), { ssr: false })
|
|
|
|
/** The path a retired Chat address names; null when it is current. */
|
|
export function moved(pathname: string, q: URLSearchParams): string | null {
|
|
if (pathname !== CHAT) return null
|
|
const thread = q.get('thread')
|
|
return thread ? talk(thread) : null
|
|
}
|
|
|
|
/** The question a signed-out reader at /chat itself is asked, or null where the
|
|
* address is a conversation's and only a signed-in reader may open it. */
|
|
export function guest(pathname: string, q: URLSearchParams): string | null {
|
|
if (pathname.replace(/\/$/, '') !== CHAT || q.has('thread')) return null
|
|
return q.get('q') ?? ''
|
|
}
|
|
|
|
export function Door() {
|
|
const { isLoading, isAuthenticated } = useIam()
|
|
const [door, setDoor] = useState<{ app: true } | { ask: string } | null>(null)
|
|
|
|
useEffect(() => {
|
|
if (isLoading) return
|
|
const q = new URLSearchParams(window.location.search)
|
|
if (!isAuthenticated) {
|
|
const ask = guest(window.location.pathname, q)
|
|
return ask === null ? enter() : setDoor({ ask })
|
|
}
|
|
const to = moved(window.location.pathname, q)
|
|
if (to) window.history.replaceState(null, '', to)
|
|
setDoor({ app: true })
|
|
}, [isLoading, isAuthenticated])
|
|
|
|
if (!door) return null
|
|
return 'app' in door ? <App /> : <Guest seed={door.ask} source="chat" />
|
|
}
|