import { useEffect, useRef, useState } from 'react'; import { Button } from '#/components/atoms/button.jsx'; import { Link } from '#/components/atoms/link.jsx'; import { Notice } from '#/components/atoms/notice.jsx'; import { Skeleton } from '#/components/atoms/skeleton.jsx'; import { NavigationProvider, useLocation } from '#/lib/navigation.jsx'; import { completeSignIn } from '#/lib/oauth.js'; import { useSession } from '#/lib/session.jsx'; import { BlobsPage } from '#/pages/blobs.jsx'; import { CredentialPage } from '#/pages/credential.jsx'; import { OpsPage } from '#/pages/ops.jsx'; import { RecordPage } from '#/pages/record.jsx'; import { RecordsPage } from '#/pages/records.jsx'; import { SignInPage } from '#/pages/sign-in.jsx'; import { SpacePage } from '#/pages/space.jsx'; import { SpacesPage } from '#/pages/spaces.jsx'; /** * Where the authorization server sends the browser back. The code is spent * once, so the address bar is cleaned before anything else renders: a reload of * this URL would otherwise present a code the server has already refused. */ function CallbackPage({ navigate }) { const { setSession } = useSession(); const [error, setError] = useState(null); // The exchange consumes the pending sign-in the moment it starts, so it // must run once however many times the effect fires. StrictMode fires it // twice in development, and a second run would find nothing pending and // report a failure over a sign-in that succeeded. The ref survives the // simulated remount; a cleanup-cancelled flag would not, and would also // cancel the first run's redirect. const ran = useRef(false); useEffect(() => { if (ran.current) return; ran.current = true; // Read from the address bar rather than from a prop: the code is spent once // and this effect must not re-run, so it depends on nothing a render makes // anew. completeSignIn(new URLSearchParams(window.location.search)) .then(({ session }) => { setSession(session); navigate('/'); }) .catch((err) => setError(err.message)); }, [navigate, setSession]); if (error) { return (
{error} Start again
); } return ; } /** * Pick the screen for a path, with the space it acts on read from the query. * * Each screen is keyed by what it shows. Both editors here hold a draft in * state, and moving to another record on the same screen would otherwise open * it with the last one's text still in the box. */ function Screen({ path, params }) { const space = params.get('uri'); const repo = params.get('repo'); const collection = params.get('collection'); const rkey = params.get('rkey'); if (!space) return ; if (path === '/space') { return ; } if (path === '/ops') return ; if (path === '/credential') return ; if (path === '/blobs') return ; if (path === '/records') { return ( ); } if (path === '/record') { return ( ); } return ; } export function App() { const { path, params, navigate } = useLocation(); const { session, endSession, writable } = useSession(); return (
{path === '/callback' ? ( ) : !session ? ( ) : ( <>
Spaces {session.handle} {!writable && ( read only )}
)}
); }