Trois surfaces partagent désormais le même design system, Tailwind v4
et React 19 — au lieu d'avoir landing en HTML vanilla, app en React, et
blog en Adonis SSR :
* packages/ui — design system partagé (tokens Tailwind v4 + composants
TSX) extrait depuis apps/web : Brand, Gem, Button, Card, Chip, Eyebrow,
EmptyState. apps/web migre 41 imports vers @rubis/ui.
* apps/landing — nouvelle app Astro 6 SSR (rubis.pro), remplace l'ancienne
landing nginx vanilla. Embarque :
- Landing complète portée en sections React (Hero, Stats, Promise,
HowItWorks, Gamification, Legal, Pricing, FAQ, FinalCTA, Footnotes)
- Pages légales (mentions, confidentialité, CGV) via LegalLayout.astro
- Blog SSR (/blog, /blog/:slug) qui consomme /api/v1/posts
- sitemap.xml, blog/rss.xml, robots.txt en endpoints Astro
- SEO complet (canonical, hreflang, OG, Twitter Card, JSON-LD
Article/BreadcrumbList/Blog/SoftwareApplication)
* apps/api — BlogController réduit à 2 endpoints JSON (GET /api/v1/posts
+ GET /api/v1/posts/:slug). Suppression des templates SSR Adonis
(apps/api/app/blog/), de l'alias #blog/*, des deps react-dom et
@types/react-dom. PostTransformer + PostSummaryTransformer ajoutés.
Le service blog_renderer + le seeder + les 3 articles fondateurs
restent intacts (réutilisés par futurs admin + cron IA).
* Infra :
- Dockerfile.landing (multi-stage Node 22 + tini, Astro standalone)
- k3s/app/landing.yml (Deployment + Service rubis-landing:4321 +
ConfigMap avec API_URL=http://rubis-api.rubis.svc.cluster.local:3333)
- .gitea/workflows/deploy.yml mis à jour pour build rubis-landing
- .gitea/workflows/deploy-web.yml + Dockerfile.web : prennent en
compte packages/ui/ comme dépendance
- Suppression du Dockerfile nginx legacy + k3s/{deployment,service}.yml
- Suppression de landing/ (assets favicons migrés vers
apps/landing/public/)
* Docs : architecture.md (vue d'ensemble + §4bis apps/landing complet,
§3 endpoints JSON blog, layout monorepo), CLAUDE.md (stack technique,
documents associés, déploiement).
Note infra : l'ancien Deployment "rubis" (nginx) et son Service ne sont
PAS supprimés par la CI — à nettoyer manuellement après validation que
Traefik a été repointé sur rubis-landing:4321 dans le repo proxmox.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
70 lines
2.4 KiB
TypeScript
70 lines
2.4 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|
import { z } from "zod";
|
|
import { toast } from "sonner";
|
|
|
|
import type { AuthSession } from "@rubis/shared";
|
|
import { api } from "@/lib/api";
|
|
import { authStore } from "@/lib/auth";
|
|
import { Gem } from "@rubis/ui";
|
|
|
|
/**
|
|
* Callback SSO côté SPA — partagé entre Google et Microsoft. Se charge :
|
|
* 1. d'appeler POST /api/v1/auth/refresh (le cookie httpOnly posé par
|
|
* la callback backend OAuth est auto-envoyé)
|
|
* 2. de stocker l'access token + user dans authStore
|
|
* 3. de naviguer vers `?next=...` (envoyé par le backend selon l'état
|
|
* d'onboarding : "/" pour user complet, "/onboarding/entreprise"
|
|
* pour un nouveau)
|
|
*
|
|
* En cas d'échec du refresh, on renvoie sur /login avec un toast.
|
|
*/
|
|
const searchSchema = z.object({
|
|
next: z.string().default("/"),
|
|
});
|
|
|
|
export const Route = createFileRoute("/auth/sso/complete")({
|
|
validateSearch: searchSchema,
|
|
component: SsoCompletePage,
|
|
});
|
|
|
|
function SsoCompletePage() {
|
|
const { next } = Route.useSearch();
|
|
const navigate = useNavigate();
|
|
// Strict-mode protect : avoid double-firing the refresh in dev.
|
|
const triggered = useRef(false);
|
|
|
|
useEffect(() => {
|
|
if (triggered.current) return;
|
|
triggered.current = true;
|
|
(async () => {
|
|
try {
|
|
const session = await api.post<AuthSession>(
|
|
"/api/v1/auth/refresh",
|
|
undefined,
|
|
{ anonymous: true },
|
|
);
|
|
authStore.setSession(session.accessToken, session.user);
|
|
const firstName = session.user.fullName?.split(" ")[0];
|
|
toast.success(firstName ? `Bonjour ${firstName}.` : "Connecté.");
|
|
// Sécurité : si `next` n'est pas un chemin relatif, on renvoie sur "/".
|
|
const target = next.startsWith("/") && !next.startsWith("//") ? next : "/";
|
|
void navigate({ to: target });
|
|
} catch {
|
|
toast.error("Connexion Google échouée. Réessayez.");
|
|
void navigate({ to: "/login" });
|
|
}
|
|
})();
|
|
}, [next, navigate]);
|
|
|
|
// UI minimale pendant l'aller-retour réseau (max 1-2s en pratique).
|
|
return (
|
|
<main className="min-h-screen bg-cream flex items-center justify-center">
|
|
<div className="flex flex-col items-center gap-3 text-ink-2">
|
|
<Gem size={28} className="animate-pulse" />
|
|
<p className="text-[14px] font-medium">Connexion en cours…</p>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|