Pendant de cecbddc (landing). Retire le système react-i18next ajouté en 254f65b — un seul brief utilisateur en français suffit pour l'audience TPE-PME française visée en V1. - Désinstalle i18next + react-i18next (≈ 13 kB gzip économisés). - Supprime apps/web/src/i18n/ (fr.ts, en.ts, index.ts, types.ts) et le LanguageSwitcher de /parametres. - Inline les chaînes FR dans les composants impactés : main.tsx (FallbackError), shell (AppSidebar, MobileTabBar, UserMenu), auth (login, signup, onboarding/compte), dashboard (_app/index), /parametres. - Met à jour signup : « 30 jours gratuits » → « 14 jours gratuits » pour s'aligner sur la landing. Côté UX visible : plus de switcher langue, plus de détection navigator.language, plus de localStorage["rubis:locale"]. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
125 lines
3.7 KiB
TypeScript
125 lines
3.7 KiB
TypeScript
// Sentry init AVANT tout autre import non-essentiel pour capturer même
|
|
// les erreurs de bootstrap (cf. apps/web/src/lib/sentry.ts).
|
|
import "./lib/sentry";
|
|
|
|
import { StrictMode } from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import * as Sentry from "@sentry/react";
|
|
|
|
import { routeTree } from "./routeTree.gen";
|
|
import { env } from "./lib/env";
|
|
import { api } from "./lib/api";
|
|
import { authStore } from "./lib/auth";
|
|
import type { AuthSession } from "@rubis/shared";
|
|
|
|
import "./styles/app.css";
|
|
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
staleTime: 30_000,
|
|
gcTime: 5 * 60_000,
|
|
retry: 1,
|
|
refetchOnWindowFocus: false,
|
|
},
|
|
mutations: {
|
|
retry: 0,
|
|
},
|
|
},
|
|
});
|
|
|
|
const router = createRouter({
|
|
routeTree,
|
|
context: { queryClient },
|
|
defaultPreload: "intent",
|
|
defaultPreloadStaleTime: 0,
|
|
});
|
|
|
|
declare module "@tanstack/react-router" {
|
|
interface Register {
|
|
router: typeof router;
|
|
}
|
|
}
|
|
|
|
async function enableMocking(): Promise<void> {
|
|
// import.meta.env.DEV est un booléen statique : Vite tree-shake la branche
|
|
// entière (et le chunk MSW avec) quand on build en mode production.
|
|
if (!import.meta.env.DEV || !env.VITE_USE_MOCKS) return;
|
|
const { worker } = await import("./mocks/browser");
|
|
await worker.start({
|
|
onUnhandledRequest: "bypass",
|
|
serviceWorker: {
|
|
url: "/mockServiceWorker.js",
|
|
},
|
|
});
|
|
// eslint-disable-next-line no-console
|
|
console.info(
|
|
"%c[MSW]%c Mocks API actifs — VITE_USE_MOCKS=true",
|
|
"background:#9F1239;color:white;padding:2px 6px;border-radius:3px;font-weight:600",
|
|
"color:#8A7F76",
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Tente de récupérer la session à partir du refresh token (cf. ADR-017).
|
|
* Si le serveur (ou MSW) confirme une session valide → on rehydrate l'authStore
|
|
* AVANT le 1er render, ce qui évite le flash redirect /login pour les users
|
|
* déjà connectés.
|
|
*
|
|
* En mode dev avec MSW, la "session" persistée est en localStorage (cf.
|
|
* mocks/sessionStore). En prod réel, c'est le cookie httpOnly côté Adonis.
|
|
*/
|
|
async function bootstrapSession(): Promise<void> {
|
|
try {
|
|
const session = await api.post<AuthSession>(
|
|
"/api/v1/auth/refresh",
|
|
undefined,
|
|
{ anonymous: true },
|
|
);
|
|
authStore.setSession(session.accessToken, session.user);
|
|
} catch {
|
|
// Pas de session valide → on reste anonyme. _app guard redirigera vers /login.
|
|
}
|
|
}
|
|
|
|
function FallbackError() {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-cream px-6">
|
|
<div className="text-center max-w-md">
|
|
<h1 className="font-display text-3xl mb-3 text-ink">Quelque chose a coincé.</h1>
|
|
<p className="text-ink-2 mb-6">On a noté, on regarde. Rechargez la page pour réessayer.</p>
|
|
<button
|
|
onClick={() => location.reload()}
|
|
className="bg-rubis hover:bg-rubis-deep text-white px-5 py-2.5 rounded-md font-medium transition-colors"
|
|
>
|
|
Recharger
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function render(): void {
|
|
const rootEl = document.getElementById("root");
|
|
if (!rootEl) throw new Error("#root introuvable dans index.html");
|
|
createRoot(rootEl).render(
|
|
<StrictMode>
|
|
<Sentry.ErrorBoundary fallback={<FallbackError />}>
|
|
<QueryClientProvider client={queryClient}>
|
|
<RouterProvider router={router} />
|
|
</QueryClientProvider>
|
|
</Sentry.ErrorBoundary>
|
|
</StrictMode>,
|
|
);
|
|
}
|
|
|
|
async function init(): Promise<void> {
|
|
await enableMocking();
|
|
await bootstrapSession();
|
|
render();
|
|
}
|
|
|
|
void init();
|