feat: route pods by URL slug

This commit is contained in:
Yahya Alhinai
2026-06-28 04:07:10 +00:00
parent 563bc6f8fd
commit 16e959072b
2 changed files with 73 additions and 9 deletions
+63 -3
View File
@@ -42,6 +42,26 @@ import { Skeleton } from '@/components/ui/skeleton';
const SESSION_KEY = 'podman.session'; const SESSION_KEY = 'podman.session';
const fmt = new Intl.NumberFormat('en', { notation: 'compact' }); const fmt = new Intl.NumberFormat('en', { notation: 'compact' });
function pathPodId(): string | null {
const [segment] = window.location.pathname.split('/').filter(Boolean);
return segment ? decodeURIComponent(segment) : null;
}
function setPodPath(podId: string, replace = false): void {
const next = `/${encodeURIComponent(podId)}`;
if (window.location.pathname === next) return;
window.history[replace ? 'replaceState' : 'pushState']({}, '', next);
}
function setHomePath(): void {
if (window.location.pathname === '/') return;
window.history.pushState({}, '', '/');
}
function replacePath(path: string): void {
window.history.replaceState({}, '', path || '/');
}
export default function App() { export default function App() {
const [pods, setPods] = useState<Pod[]>([]); const [pods, setPods] = useState<Pod[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -85,13 +105,20 @@ export default function App() {
return n; return n;
}); });
async function connectToPod(podId: string, who: string) { async function connectToPod(podId: string, who: string, replaceRoute = false) {
const previousPath = window.location.pathname;
setPodPath(podId, replaceRoute);
try {
const result = await joinPod(podId, who, who); const result = await joinPod(podId, who, who);
setRoom(result.room); setRoom(result.room);
setDevMode(result.mode === 'dev'); setDevMode(result.mode === 'dev');
setMember(who); setMember(who);
setJoinedPodId(podId); setJoinedPodId(podId);
sessionStorage.setItem(SESSION_KEY, JSON.stringify({ podId, member: who })); sessionStorage.setItem(SESSION_KEY, JSON.stringify({ podId, member: who }));
} catch (e) {
replacePath(previousPath);
throw e;
}
} }
useEffect(() => { useEffect(() => {
@@ -124,8 +151,14 @@ export default function App() {
}, [joinedPodId]); }, [joinedPodId]);
useEffect(() => { useEffect(() => {
const routedPodId = pathPodId();
const raw = sessionStorage.getItem(SESSION_KEY); const raw = sessionStorage.getItem(SESSION_KEY);
if (!raw) return; if (!raw) {
if (routedPodId) {
setError(`Enter your name to join ${routedPodId}.`);
}
return;
}
let saved: { podId: string; member: string }; let saved: { podId: string; member: string };
try { try {
saved = JSON.parse(raw); saved = JSON.parse(raw);
@@ -133,10 +166,11 @@ export default function App() {
sessionStorage.removeItem(SESSION_KEY); sessionStorage.removeItem(SESSION_KEY);
return; return;
} }
const podId = routedPodId ?? saved.podId;
setRestoring(true); setRestoring(true);
void (async () => { void (async () => {
try { try {
await connectToPod(saved.podId, saved.member); await connectToPod(podId, saved.member, !!routedPodId);
} catch { } catch {
sessionStorage.removeItem(SESSION_KEY); sessionStorage.removeItem(SESSION_KEY);
} finally { } finally {
@@ -145,6 +179,30 @@ export default function App() {
})(); })();
}, []); }, []);
useEffect(() => {
const onPopState = () => {
const routedPodId = pathPodId();
if (!routedPodId) {
room?.disconnect();
setRoom(null);
setJoinedPodId(null);
return;
}
const raw = sessionStorage.getItem(SESSION_KEY);
if (!raw) return;
try {
const saved = JSON.parse(raw) as { member: string };
if (saved.member && routedPodId !== joinedPodId) {
void connectToPod(routedPodId, saved.member, true);
}
} catch {
sessionStorage.removeItem(SESSION_KEY);
}
};
window.addEventListener('popstate', onPopState);
return () => window.removeEventListener('popstate', onPopState);
}, [joinedPodId, room]);
async function run(key: string, fn: () => Promise<void>) { async function run(key: string, fn: () => Promise<void>) {
startPending(key); startPending(key);
setError(null); setError(null);
@@ -179,6 +237,7 @@ export default function App() {
run(id, async () => { run(id, async () => {
await api.deletePod(id); await api.deletePod(id);
setPods((cur) => cur.filter((x) => x.id !== id)); setPods((cur) => cur.filter((x) => x.id !== id));
if (pathPodId() === id) setHomePath();
}); });
const handleAddMember = (id: string, name: string) => const handleAddMember = (id: string, name: string) =>
run(id, async () => upsert(await api.addMember(id, name))); run(id, async () => upsert(await api.addMember(id, name)));
@@ -215,6 +274,7 @@ export default function App() {
setRoom(null); setRoom(null);
setJoinedPodId(null); setJoinedPodId(null);
sessionStorage.removeItem(SESSION_KEY); sessionStorage.removeItem(SESSION_KEY);
setHomePath();
void refresh(); void refresh();
} }
+4
View File
@@ -262,6 +262,9 @@ try {
await podCard.getByPlaceholder('Your name').fill(verifyMember); await podCard.getByPlaceholder('Your name').fill(verifyMember);
await podCard.getByRole('button', { name: 'Add and join' }).click(); await podCard.getByRole('button', { name: 'Add and join' }).click();
await page.getByRole('button', { name: 'Share screen' }).waitFor({ timeout: 15_000 }); await page.getByRole('button', { name: 'Share screen' }).waitFor({ timeout: 15_000 });
if (new URL(page.url()).pathname !== `/${verifyPod.id}`) {
throw new Error(`join did not update URL to /${verifyPod.id}: ${page.url()}`);
}
await page.getByRole('heading', { name: 'My stream' }).waitFor({ timeout: 15_000 }); await page.getByRole('heading', { name: 'My stream' }).waitFor({ timeout: 15_000 });
await page.getByRole('heading', { name: 'Team stream' }).waitFor({ timeout: 15_000 }); await page.getByRole('heading', { name: 'Team stream' }).waitFor({ timeout: 15_000 });
@@ -316,6 +319,7 @@ try {
} }
await page.getByRole('button', { name: 'Leave pod' }).click(); await page.getByRole('button', { name: 'Leave pod' }).click();
await page.waitForURL((url) => url.pathname === '/', { timeout: 15_000 });
if (consoleErrors.length) throw new Error(`console errors: ${consoleErrors.join(' | ')}`); if (consoleErrors.length) throw new Error(`console errors: ${consoleErrors.join(' | ')}`);
if (pageErrors.length) throw new Error(`page errors: ${pageErrors.join(' | ')}`); if (pageErrors.length) throw new Error(`page errors: ${pageErrors.join(' | ')}`);