Rewrite FestipodDataContext over the Refine seam, meeting-point centric
This commit is contained in:
+29
-10
@@ -1,4 +1,5 @@
|
||||
import { RouterProvider, useRouter } from './router';
|
||||
import { useEffect } from 'react';
|
||||
import { RouterProvider, useNavigate, useRouter } from './router';
|
||||
import { ThemeProvider } from '../shared/context/ThemeContext';
|
||||
import { NextGraphProvider } from '../shared/context/NextGraphContext';
|
||||
import { FestipodDataProvider } from '../shared/context/FestipodDataContext';
|
||||
@@ -38,7 +39,7 @@ function AppContent() {
|
||||
case 'home': return <HomeScreen />;
|
||||
case 'events': return <EventsScreen />;
|
||||
case 'create-event': return <CreateEventScreen />;
|
||||
case 'login': return null;
|
||||
case 'login': return <AuthenticatedLoginLanding />;
|
||||
case 'event-detail': return <EventDetailScreen />;
|
||||
case 'update-event': return <UpdateEventScreen />;
|
||||
case 'invite': return <InviteScreen />;
|
||||
@@ -54,26 +55,44 @@ function AppContent() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* What `/login` shows once the gate has let the user through: home. The route
|
||||
* exists for the Solid-OIDC callback (`AuthGate` intercepts `?code` and
|
||||
* `?register_app` before children render); a plain `/login` with nothing left
|
||||
* to do is not a destination — send the user to the app.
|
||||
*/
|
||||
function AuthenticatedLoginLanding() {
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
navigate('/home');
|
||||
}, [navigate]);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function App() {
|
||||
const backendName = initialBackendName();
|
||||
const isNg = backendName === 'nextgraph';
|
||||
|
||||
// THE AUTH GATE ABOVE THE DATA PROVIDER — signing in must have settled before
|
||||
// any data path mounts. The gate renders children only once there is a
|
||||
// session to read through (or an explicit offline-demo choice), so the data
|
||||
// provider's dispatch decision never races a sign-in that has not finished.
|
||||
const appTree = (
|
||||
<FestipodDataProvider>
|
||||
<RouterProvider>
|
||||
<AuthGate>
|
||||
<FestipodDataProvider>
|
||||
<div className="app-container">
|
||||
<AuthGate>
|
||||
<AppContent />
|
||||
</AuthGate>
|
||||
<AppContent />
|
||||
<ToastContainer />
|
||||
</div>
|
||||
</RouterProvider>
|
||||
</FestipodDataProvider>
|
||||
</FestipodDataProvider>
|
||||
</AuthGate>
|
||||
);
|
||||
|
||||
return (
|
||||
<ThemeProvider>
|
||||
{isNg ? <NextGraphProvider>{appTree}</NextGraphProvider> : appTree}
|
||||
<RouterProvider>
|
||||
{isNg ? <NextGraphProvider>{appTree}</NextGraphProvider> : appTree}
|
||||
</RouterProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
+12
-2
@@ -7,7 +7,9 @@
|
||||
* a Pod-provider picker when no session exists, and the `/login` route handling
|
||||
* the OAuth callback plus the ActivityPods app-registration stage. The auth
|
||||
* provider is built with the app description document served by our backend as
|
||||
* `clientId` and `${origin}/login` as `redirectUri`.
|
||||
* `clientId` and `${origin}/login` as `redirectUri`. Every session the flow
|
||||
* establishes is published to `utils/currentSession` — the module store the
|
||||
* data provider's dispatch reads, independent of where each mounts in the tree.
|
||||
*
|
||||
* 2. NEXTGRAPH (suspended at the root, kept for the URL variant). The legacy
|
||||
* `ensureIdentity()` flow: the SDK mounts its own full-screen barrier and owns
|
||||
@@ -26,6 +28,7 @@ import { useEffect, useMemo, useState, type ReactNode } from 'react';
|
||||
import { ensureIdentity } from '@ng-eventually/polyfill';
|
||||
import { startNgSession } from '../shared/utils/ngSession';
|
||||
import { setCurrentPrincipal } from '../shared/utils/currentPrincipal';
|
||||
import { setCurrentSession } from '../shared/utils/currentSession';
|
||||
import { useRouter, useNavigate } from './router';
|
||||
import { initialBackendName } from '../shared/refine/providers';
|
||||
import { getBackendUrl } from '../shared/utils/runtimeConfig';
|
||||
@@ -89,7 +92,9 @@ export function AuthGate({ children }: { children: ReactNode }) {
|
||||
if (stage.stage === 'callback') {
|
||||
const result = await provider.handleCallback();
|
||||
if (cancelled) return;
|
||||
const session = provider.getSession();
|
||||
setCurrentPrincipal(result.webId);
|
||||
setCurrentSession(session ? { webId: session.webId, token: session.token } : undefined);
|
||||
navigate(`/login?register_app=1&redirect=${encodeURIComponent(stage.redirectPath)}`);
|
||||
return;
|
||||
}
|
||||
@@ -99,7 +104,10 @@ export function AuthGate({ children }: { children: ReactNode }) {
|
||||
const registration = session ? await provider.registerApp(session.webId) : undefined;
|
||||
if (cancelled) return;
|
||||
if (registration) {
|
||||
if (session) setCurrentPrincipal(session.webId);
|
||||
if (session) {
|
||||
setCurrentPrincipal(session.webId);
|
||||
setCurrentSession({ webId: session.webId, token: session.token });
|
||||
}
|
||||
navigate(stage.redirectPath);
|
||||
}
|
||||
// If `registerApp` returned `undefined`, the browser left for the Pod's
|
||||
@@ -111,8 +119,10 @@ export function AuthGate({ children }: { children: ReactNode }) {
|
||||
const session = provider.getSession();
|
||||
if (session) {
|
||||
setCurrentPrincipal(session.webId);
|
||||
setCurrentSession({ webId: session.webId, token: session.token });
|
||||
if (!cancelled) setApState('authenticated');
|
||||
} else {
|
||||
setCurrentSession(undefined);
|
||||
if (!cancelled) setApState('unauthenticated');
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,39 +1,13 @@
|
||||
import { authProvider as createActivityPodsAuthProvider } from '@activitypods/refine-providers';
|
||||
import type { AuthProvider } from '@activitypods/refine-providers';
|
||||
import { getRuntimeConfig } from '../../shared/utils/runtimeConfig';
|
||||
import { resolveActivityPodsAuthConfig } from '../../shared/utils/runtimeConfig';
|
||||
|
||||
/**
|
||||
* Resolved ActivityPods auth configuration.
|
||||
*
|
||||
* `clientId` names the app description document the backend serves, as required
|
||||
* by the Solid-OIDC flow. `redirectUri` is fixed to `${origin}/login` because the
|
||||
* app renders its own callback route there.
|
||||
*/
|
||||
export interface ResolvedAuthConfig {
|
||||
clientId: string;
|
||||
redirectUri: string;
|
||||
podProviderUrl: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the auth configuration from runtime values.
|
||||
*
|
||||
* - `backendClientId` wins when explicitly set.
|
||||
* - Otherwise, `backendUrl` is used to derive `${backendUrl}/app`.
|
||||
* - Otherwise, fall back to `${origin}/app` so local dev still typechecks and
|
||||
* can be exercised without a backend.
|
||||
*/
|
||||
export function resolveActivityPodsAuthConfig(origin: string): ResolvedAuthConfig {
|
||||
const cfg = getRuntimeConfig();
|
||||
const backendClientId = cfg.backendClientId?.trim();
|
||||
const backendUrl = cfg.backendUrl?.trim();
|
||||
const clientId = backendClientId || (backendUrl ? `${backendUrl}/app` : `${origin}/app`);
|
||||
return {
|
||||
clientId,
|
||||
redirectUri: `${origin}/login`,
|
||||
podProviderUrl: cfg.podProviderUrl?.trim() || undefined,
|
||||
};
|
||||
}
|
||||
// Re-exported so existing callers (AuthGate, the flow-stage tests) keep their
|
||||
// import surface; the implementation lives in the runtime-config module, the
|
||||
// shared source of truth both the auth gate and the data provider resolve
|
||||
// against — one clientId/redirect for the whole app.
|
||||
export { resolveActivityPodsAuthConfig };
|
||||
export type { ResolvedAuthConfig } from '../../shared/utils/runtimeConfig';
|
||||
|
||||
/** Build an ActivityPods auth provider with the Festipod-specific configuration. */
|
||||
export function buildActivityPodsAuthProvider(origin: string): AuthProvider {
|
||||
|
||||
Reference in New Issue
Block a user