import React, { useCallback, useEffect, useState } from 'react';
import { api } from './api.js';
import { copyToast } from './toast.js';
import Netdisk from './Netdisk.jsx';
// amerc Mansion β a hall of amerc services. First live service: Showcase (reverse proxy).
export default function Mansion({ auth, setRoute }) {
if (!auth.ready) return
;
const loggedIn = !!auth.user;
const handle = loggedIn ? auth.user.handle.toLowerCase().replace(/[^a-z0-9]/g, '') : 'you';
return (
β Chambers of the estate
);
}
function RoomShell({ ico, name, tag, status, desc, feats, children }) {
return (
{ico}
{name}
{tag}
{status === 'live' ? 'β LIVE' : 'SOON'}
{desc}
{feats && }
{children}
);
}
function SoonRoom(props) {
return (
in development
);
}
// File-mapper: agents publish named files; each download is fetched live from
// the broker and piped through β nothing is ever stored on amerc.
function FileMapperRoom({ loggedIn, setRoute }) {
const [maps, setMaps] = useState(null);
const [err, setErr] = useState('');
useEffect(() => {
if (!loggedIn) return;
api('/fm').then((d) => setMaps(d.maps || [])).catch((e) => { setErr(e.message); setMaps([]); });
}, [loggedIn]);
return (
{!loggedIn ? (
setRoute('signin')}>Log in to see your mapped files β
) : (
<>
{err && {err}
}
My mapped files
{maps === null ?
loadingβ¦
: maps.length ? maps.map((f) => (
{f.name}
{f.class_name || `instance #${f.instance_id}`}
copyToast(f.url, 'Link copied')}>copy link
)) :
No mapped files yet. Your broker registers one with POST /api/agents/instances/<id>/fm {'{accesskey, name, fileName, size, mime}'} β it becomes a stable link instantly.
}
>
)}
);
}
// Net-disk: real storage on amerc, 100 MB per account.
function NetdiskRoom({ loggedIn, setRoute }) {
const [usage, setUsage] = useState(null);
const [open, setOpen] = useState(false);
useEffect(() => { if (loggedIn) api('/files/usage').then(setUsage).catch(() => {}); }, [loggedIn, open]);
const pct = usage ? Math.min(100, Math.round((usage.used / usage.quota) * 100)) : 0;
return (
{!loggedIn ? (
setRoute('signin')}>Log in to open your net-disk β
) : (
<>
{usage && (
90 ? '#ff7a8c' : undefined }} />
{(usage.used / 1048576).toFixed(1)} / 100 MB
)}
setOpen((v) => !v)}>{open ? 'Close net-disk' : 'Open net-disk'}
{open &&
}
>
)}
);
}
function ShowcaseRoom({ loggedIn, handle, setRoute }) {
const [list, setList] = useState([]);
const [name, setName] = useState('');
const [created, setCreated] = useState(null);
const [err, setErr] = useState('');
const [loaded, setLoaded] = useState(false);
const load = useCallback(async () => {
if (!loggedIn) return;
try { const d = await api('/showcase'); setList(d.showcases || []); } catch (e) { setErr(e.message); } finally { setLoaded(true); }
}, [loggedIn]);
useEffect(() => { load(); }, [load]);
const register = async (e) => {
e.preventDefault(); setErr('');
try { const r = await api('/showcase', { method: 'POST', body: { name } }); setCreated(r); setName(''); load(); }
catch (e2) { setErr(e2.message); }
};
const del = async (id) => { if (!window.confirm('Delete this showcase?')) return; try { await api(`/showcase/${id}`, { method: 'DELETE' }); load(); } catch (e2) { setErr(e2.message); } };
return (
{!loggedIn ? (
show1 .{handle}.amerc.ai
setRoute('signin')}>Log in to claim a subdomain β
) : (
<>
{err && {err}
}
{created && (
β
{created.host} reserved Β· DNS {created.dns}
{created.tls &&
π {created.tls}
}
public URL copyToast(created.url)}>{created.url} β§
broker WS (kebab connects here) copyToast(created.edgeWs)}>{created.edgeWs} β§
edge token (bind {created.host}) copyToast(created.edgeToken)}>{created.edgeToken.slice(0, 28)}β¦ β§
In the kebab delivery center: connect a broker to the WS with this token, send {`{type:"bind",host:"${created.host}",port:}`} , and your local service is live at the public URL.
)}
My showcases
{!loaded ? (
) : (
<>
{list.map((s) => (
{s.host}
registered {new Date(s.created_at).toLocaleDateString()}
del(s.id)}>delete
))}
{!list.length &&
No showcases yet β register one above.
}
>
)}
>
)}
);
}