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 // ?a= anchor URL hint 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 { 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 */ } } return { network, netHash, anchor, inviteString } } const DEFAULT_ANCHOR = (() => { const cfg = (window as unknown as { WASTE_CONFIG?: { signalURL?: string } })['WASTE_CONFIG'] return cfg?.signalURL ?? localStorage.getItem('waste_anchor_url') ?? '' })() const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' export function Onboarding({ status }: Props) { const { send, connect, connectBrowser, adapterMode, masterAlias, masterId, exportedBackup } = useWaste() const [network, setNetwork] = useState('') const [netHash, setNetHash] = useState('') const [inviteString, setInviteString] = useState('') const [anchorUrl, setAnchorUrl] = useState(DEFAULT_ANCHOR) const [nick, setNick] = useState(localStorage.getItem('waste_nick') || '') const [exportPass, setExportPass] = useState('') const [importJson, setImportJson] = useState('') const [importPass, setImportPass] = useState('') const [importStatus, setImportStatus] = useState('') const [showBackup, setShowBackup] = useState(false) const [daemonUrl, setDaemonUrl] = useState(localStorage.getItem('waste_daemon_ws') || 'ws://127.0.0.1:17338') useEffect(() => { const { network: n, netHash: nh, anchor: a, inviteString: inv } = parseInviteParams() if (n) setNetwork(n) if (nh) setNetHash(nh) if (a) setAnchorUrl(a) if (inv) setInviteString(inv) }, []) function joinNetwork(e: React.FormEvent) { e.preventDefault() const name = network.trim() const hash = netHash.trim() if (!name && !hash) return // In browser mode: persist anchor URL choice and pass it via localStorage if (adapterMode === 'browser') { localStorage.setItem('waste_anchor_url', anchorUrl) if (nick.trim()) localStorage.setItem('waste_nick', nick.trim()) } if (hash.length === 64 && !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 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 }) } function switchToDaemon() { localStorage.setItem('waste_daemon_ws', daemonUrl) connect(daemonUrl) } function switchToBrowser() { connectBrowser() } const shortId = masterId ? masterId.slice(0, 16).replace(/(.{4})/g, '$1 ').trim() : null // ── disconnected / connecting ──────────────────────────────────────────────── if (status !== 'connected') { return (

waste

{status === 'connecting' ? (

{adapterMode === 'browser' ? 'Loading crypto…' : 'Connecting to daemon…'}

) : ( <>

Daemon not running

Start the daemon to use the web UI:

./launch-web.sh

Or use browser mode (no install required):

)}
) } // ── connected — join form ──────────────────────────────────────────────────── return (

waste

{masterAlias && (
{masterAlias} {shortId && {shortId}} {adapterMode === 'browser' ? 'browser mode' : 'daemon mode'}
)}
{adapterMode === 'browser' && ( setNick(e.target.value)} placeholder="your name (optional)" autoComplete="nickname" style={{ marginBottom: '0.4rem' }} /> )} {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 /> )} {adapterMode === 'browser' && ( setAnchorUrl(e.target.value)} placeholder="signal server (wss://…)" autoComplete="url" className="mono" style={{ fontSize: '0.78rem' }} /> )} {inviteString && (

{inviteString.slice(0, 48)}…

)}
{/* Mode switcher — only shown when on localhost */} {isLocal && adapterMode !== null && (
{adapterMode === 'daemon' ? ( ) : (
setDaemonUrl(e.target.value)} placeholder="ws://127.0.0.1:17338" className="mono" style={{ fontSize: '0.78rem' }} />
)}
)}
{showBackup && (

Export your identity as an encrypted file. You can import it on any device — browser or TUI.

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