feat(auth): staging wallet partagé — import assisté par fichier + e2e multi-navigateur
Stopgap staging multi-user sur wallet partagé (cf. brief_2026-06-15_shared-wallet-shim). Distribution / import du wallet : - AccessGateScreen : barrière d'accès ON PAR DÉFAUT (désactivable via globalThis.__FESTIPOD_ACCESS_GATE_DISABLED__ pour tests/dev). Fournit le FICHIER .ngw + le mot de passe + un guide en 3 étapes (import assisté sur nextgraph.eu — le broker hébergé n'autorise pas l'import inline pendant l'auth web-app). - sharedWallet.ts + build.ts : fichier copié en /shared-wallet.ngw, mot de passe gravé. - Ancien LoginScreen (/login) retiré ; atterrissage post-login -> /home. - NextGraphContext : dé-piégeage de l'état "connecting" au retour (pageshow/bfcache). Couche multistore stopgap : storeRegistry, isolation, AccountContext, FestipodDataContext. Tests e2e multi-navigateur : - browserPool + world.openBrowser : contextes frais isolés, 2 axes orthogonaux (nb de navigateurs × modèle de wallet own/shared). - @humain : parcours humain complet (télécharge -> importe le fichier sur nextgraph.eu -> Entrer -> pseudo -> accueil). - Bypass de la barrière pour @e2e via context.addInitScript. - Convention @wip exclue via cucumber.json. Docs (concepts) : nextgraph-platform (knowledge_broker-import-constraint, decision_2026-06-17_assisted-wallet-import), bdd-testing (knowledge_multibrowser-harness). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+14
-10
@@ -1,12 +1,13 @@
|
||||
import { RouterProvider, useRouter } from './router';
|
||||
import { ThemeProvider } from '../shared/context/ThemeContext';
|
||||
import { NextGraphProvider } from '../shared/context/NextGraphContext';
|
||||
import { AccountProvider } from '../shared/context/AccountContext';
|
||||
import { FestipodDataProvider } from '../shared/context/FestipodDataContext';
|
||||
import { AuthGate } from './AuthGate';
|
||||
import { ToastContainer } from '../shared/components/sketchy';
|
||||
|
||||
// Auth
|
||||
import { WelcomeScreen } from '../modules/auth/screens/WelcomeScreen';
|
||||
import { LoginScreen } from '../modules/auth/screens/LoginScreen';
|
||||
|
||||
// Home
|
||||
import { HomeScreen } from '../modules/home/screens/HomeScreen';
|
||||
@@ -34,7 +35,6 @@ function AppContent() {
|
||||
|
||||
switch (route.page) {
|
||||
case 'welcome': return <WelcomeScreen />;
|
||||
case 'login': return <LoginScreen />;
|
||||
case 'home': return <HomeScreen />;
|
||||
case 'events': return <EventsScreen />;
|
||||
case 'create-event': return <CreateEventScreen />;
|
||||
@@ -57,14 +57,18 @@ export function App() {
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<NextGraphProvider>
|
||||
<FestipodDataProvider>
|
||||
<RouterProvider>
|
||||
<div className="app-container">
|
||||
<AppContent />
|
||||
<ToastContainer />
|
||||
</div>
|
||||
</RouterProvider>
|
||||
</FestipodDataProvider>
|
||||
<AccountProvider>
|
||||
<FestipodDataProvider>
|
||||
<RouterProvider>
|
||||
<div className="app-container">
|
||||
<AuthGate>
|
||||
<AppContent />
|
||||
</AuthGate>
|
||||
<ToastContainer />
|
||||
</div>
|
||||
</RouterProvider>
|
||||
</FestipodDataProvider>
|
||||
</AccountProvider>
|
||||
</NextGraphProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* AuthGate — the stopgap access flow (see decision_2026-06-15_shared-wallet-login-flow):
|
||||
* 1. Technical access barrier (AccessGateScreen) → opens the SHARED wallet via
|
||||
* the broker redirect (with the wallet file + guide it hands the user).
|
||||
* 2. Perceived app login (ConnexionScreen) → pick a username.
|
||||
* 3. The app.
|
||||
*
|
||||
* The gate is ON BY DEFAULT (Festipod never functions without NextGraph). It is
|
||||
* disabled only when `globalThis.__FESTIPOD_ACCESS_GATE_DISABLED__ === true` —
|
||||
* injected by `build.ts` (from ACCESS_GATE_DISABLED=1) for a no-gate build, or
|
||||
* by the test harness via `context.addInitScript` for @e2e (which exercises the
|
||||
* screens, not the auth flow). Absent → gate ON.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import { useNextGraph } from '../shared/context/NextGraphContext';
|
||||
import { useAccount } from '../shared/context/AccountContext';
|
||||
import { AccessGateScreen } from '../modules/auth/screens/AccessGateScreen';
|
||||
import { ConnexionScreen } from '../modules/auth/screens/ConnexionScreen';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __FESTIPOD_ACCESS_GATE_DISABLED__: boolean | undefined;
|
||||
}
|
||||
const GATE_DISABLED = globalThis.__FESTIPOD_ACCESS_GATE_DISABLED__ === true;
|
||||
|
||||
export function AuthGate({ children }: { children: ReactNode }) {
|
||||
const { status, error, connect } = useNextGraph();
|
||||
const { username } = useAccount();
|
||||
|
||||
// Gate explicitly disabled (no-gate build / @e2e harness) → straight to app.
|
||||
if (GATE_DISABLED) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
// 1. Technical access barrier (real NG login) — until the shared wallet opens.
|
||||
if (status !== 'connected') {
|
||||
return <AccessGateScreen status={status} error={error} onEnter={connect} />;
|
||||
}
|
||||
|
||||
// 2. Perceived app login — until a username is chosen.
|
||||
if (!username) {
|
||||
return <ConnexionScreen />;
|
||||
}
|
||||
|
||||
// 3. The app.
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import React, { createContext, useContext, useState, useEffect, useCallback } fr
|
||||
|
||||
type Route =
|
||||
| { page: 'welcome' }
|
||||
| { page: 'login' }
|
||||
| { page: 'home' }
|
||||
| { page: 'events' }
|
||||
| { page: 'create-event' }
|
||||
@@ -38,7 +37,6 @@ function parsePath(pathname: string): Route {
|
||||
const path = pathname.replace(/\/+$/, '') || '/';
|
||||
|
||||
if (path === '/' || path === '') return { page: 'welcome' };
|
||||
if (path === '/login') return { page: 'login' };
|
||||
if (path === '/home') return { page: 'home' };
|
||||
if (path === '/events') return { page: 'events' };
|
||||
if (path === '/events/new') return { page: 'create-event' };
|
||||
@@ -73,7 +71,6 @@ function parsePath(pathname: string): Route {
|
||||
export function routeToPath(route: Route): string {
|
||||
switch (route.page) {
|
||||
case 'welcome': return '/';
|
||||
case 'login': return '/login';
|
||||
case 'home': return '/home';
|
||||
case 'events': return '/events';
|
||||
case 'create-event': return '/events/new';
|
||||
|
||||
Reference in New Issue
Block a user