import { useState, useEffect } from 'react' import { useWaste } from '../store' interface Props { status: 'disconnected' | 'connecting' | 'connected' } // Parse URL search params for pre-filling the join form. // Supports: // ?invite=waste: waste: invite string (anchor + network name + net hash) // ?n= network name shorthand // ?network= network name // ?net=<64hex> yaw2-style full network hash (joined without plaintext name) // ?a= anchor URL hint (informational — daemon controls its anchor) function parseInviteParams(): { network: string; netHash: string; anchor: string; inviteString: string } { const p = new URLSearchParams(window.location.search) const inviteString = p.get('invite') ?? '' let network = p.get('n') ?? p.get('network') ?? '' let netHash = p.get('net') ?? '' let anchor = p.get('a') ?? p.get('anchor') ?? '' if (inviteString.startsWith('waste:')) { try { // URL-safe base64 → standard base64 const json = JSON.parse(atob(inviteString.slice(6).replace(/-/g, '+').replace(/_/g, '/'))) network = network || json.network || '' netHash = netHash || json.net || '' anchor = anchor || json.anchor || '' } catch { /* ignore bad invite */ } } // URL path: /<16hex>/ — short network ID from anchor-served URL const pathMatch = window.location.pathname.match(/\/([0-9a-f]{16})\/?$/i) if (pathMatch && !netHash && !network) { // Only the short ID — can't join by short ID alone (need full hash for HKDF). // Store it as a hint; the user must supply the network name OR a full 64-char hash. } return { network, netHash, anchor, inviteString } } export function Onboarding({ status }: Props) { const { send, masterAlias, masterId, exportedBackup } = useWaste() const [network, setNetwork] = useState('') const [netHash, setNetHash] = useState('') const [inviteString, setInviteString] = useState('') const [anchorHint, setAnchorHint] = useState('') const [exportPass, setExportPass] = useState('') const [importJson, setImportJson] = useState('') const [importPass, setImportPass] = useState('') const [importStatus, setImportStatus] = useState('') const [showBackup, setShowBackup] = useState(false) useEffect(() => { const { network: n, netHash: nh, anchor: a, inviteString: inv } = parseInviteParams() if (n) setNetwork(n) if (nh) setNetHash(nh) if (a) setAnchorHint(a) if (inv) setInviteString(inv) }, []) function joinNetwork(e: React.FormEvent) { e.preventDefault() const name = network.trim() const hash = netHash.trim() if (!name && !hash) return if (hash.length === 64 && !name) { // yaw2-style: join by full network hash without plaintext name send({ type: 'join_network', network_hash: hash }) } else { send({ type: 'join_network', network_name: name }) } } function exportIdentity(e: React.FormEvent) { e.preventDefault() if (!exportPass.trim()) return setExportBlob('') send({ type: 'export_identity', passphrase: exportPass }) } function importIdentity(e: React.FormEvent) { e.preventDefault() if (!importJson.trim() || !importPass.trim()) return setImportStatus('Verifying…') send({ type: 'import_identity', backup: importJson, passphrase: importPass }) } const shortId = masterId ? masterId.slice(0, 16).replace(/(.{4})/g, '$1 ').trim() : null // ── disconnected / connecting ──────────────────────────────────────────────── if (status !== 'connected') { return (

waste

{status === 'connecting' ? (

Connecting to daemon…

) : ( <>

Daemon not running

Start the daemon to use the web UI:

./launch-web.sh

Or pass a custom alias and network:

ALIAS=alice NETWORK=friends ./launch-web.sh
)}
) } // ── connected — join form ──────────────────────────────────────────────────── return (

waste

{masterAlias && (
{masterAlias} {shortId && {shortId}}
)}
{anchorHint && (

via {anchorHint}

)} {netHash && !network ? ( setNetHash(e.target.value)} placeholder="network hash (64 hex)" autoComplete="off" autoFocus className="mono" style={{ fontSize: '0.72rem' }} /> ) : ( setNetwork(e.target.value)} placeholder="network name" autoComplete="off" autoFocus /> )} {inviteString && (

{inviteString.slice(0, 48)}…

)}
{showBackup && (

Export your identity as an encrypted file. You can import it on any machine or in the TUI with --import-identity.

setExportPass(e.target.value)} placeholder="passphrase for backup" autoComplete="new-password" />
{exportedBackup && (