diff --git a/ui/src/App.tsx b/ui/src/App.tsx index f55544e..62de770 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,124 +1,49 @@ -import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; -import { LoginPage } from "@ui/pages/login"; -import { SignUpPage } from "@ui/pages/signup"; +import { BrowserRouter as Router, useRoutes } from "react-router-dom"; import { ThemeProvider } from "@ui/contexts/ThemeContext"; import { twMerge } from "tailwind-merge"; -import { ResetPasswordPage } from "@ui/pages/reset-password"; -import { LandingPage } from "@ui/pages/landing"; -import { PublicRoute } from "@ui/components/PublicRoute"; -import { TabloPage } from "@ui/pages/tablo"; import { SessionProvider } from "@ui/contexts/SessionContext"; -import { OAuthSigninPage } from "@ui/pages/oauth-signin"; -import { NotFoundPage } from "@ui/pages/NotFoundPage"; -import { Layout } from "@ui/components/Layout"; -import { DevisPage } from "@ui/pages/devis"; -import { FacturesPage } from "@ui/pages/factures"; -import { PlanningPage } from "@ui/pages/planning"; -import { ChantiersPage } from "@ui/pages/chantiers"; -import { ChatPage } from "@ui/pages/chat"; -import { FeedbackPage } from "@ui/pages/feedback"; -import { SupportPage } from "@ui/pages/support"; import { AllCommunityModule, ModuleRegistry } from "ag-grid-community"; -import ChatProvider from "@ui/providers/ChatProvider"; import { UserStoreProvider } from "@ui/providers/UserStoreProvider"; -import { ProtectedRoute } from "@ui/components/ProtectedRoute"; -import { JoinPage } from "@ui/pages/join"; -import { CreateEventModal } from "@ui/components/CreateEventModal"; import { isProd } from "@ui/utils/helpers"; -import { useEffect } from "react"; -import { datadogRum } from "@datadog/browser-rum"; -import { initRum } from "@ui/lib/rum"; +import { DatadogRumProvider } from "@ui/providers/DatadogRumProvider"; +import { routes } from "@ui/lib/routes"; // Register all Community features ModuleRegistry.registerModules([AllCommunityModule]); -export const App = () => { - useEffect(() => { - initRum(); - datadogRum.startSessionReplayRecording(); - }, [location]); +const AppRoutes = () => { + const element = useRoutes(routes); + return element; +}; +export const App = () => { return ( -
- - }> - }> - } /> - } /> - } /> - } /> - }> - - - } /> - - }> - - - - } /> - - - - } - > - - - - } /> - } /> - - - - + +
+ + -
+ .animate-slide { + animation: slide 24s linear infinite; + } + `} + +
+
diff --git a/ui/src/lib/routes.tsx b/ui/src/lib/routes.tsx new file mode 100644 index 0000000..36a05f1 --- /dev/null +++ b/ui/src/lib/routes.tsx @@ -0,0 +1,139 @@ +import { RouteObject } from "react-router-dom"; +import { ProtectedRoute } from "@ui/components/ProtectedRoute"; +import { Layout } from "@ui/components/Layout"; +import { TabloPage } from "@ui/pages/tablo"; +import { DevisPage } from "@ui/pages/devis"; +import { FacturesPage } from "@ui/pages/factures"; +import { PlanningPage } from "@ui/pages/planning"; +import { NotFoundPage } from "@ui/pages/NotFoundPage"; +import { JoinPage } from "@ui/pages/join"; +import { OAuthSigninPage } from "@ui/pages/oauth-signin"; +import { LandingPage } from "@ui/pages/landing"; +import { LoginPage } from "@ui/pages/login"; +import { SignUpPage } from "@ui/pages/signup"; +import { ResetPasswordPage } from "@ui/pages/reset-password"; +import { PublicRoute } from "@ui/components/PublicRoute"; +import ChatProvider from "@ui/providers/ChatProvider"; +import { CreateEventModal } from "@ui/components/CreateEventModal"; +import { ChantiersPage } from "@ui/pages/chantiers"; +import { ChatPage } from "@ui/pages/chat"; +import { FeedbackPage } from "@ui/pages/feedback"; +import { SupportPage } from "@ui/pages/support"; + +export const routes: RouteObject[] = [ + // Protected routes + { + path: "/", + element: , + children: [ + { + path: "", + element: , + children: [ + { + index: true, + element: , + }, + { + path: "tablo", + element: , + }, + { + path: "devis", + element: , + }, + { + path: "factures", + element: , + }, + { + path: "planning", + element: , + children: [ + { index: true }, + { path: ":tablo_id" }, + { path: "create", element: }, + ], + }, + { + path: "kanban", + element: , + children: [{ index: true }, { path: ":tablo_id" }], + }, + { + path: "chantiers", + element: , + }, + { + path: "chat", + element: ( + + + + ), + children: [{ index: true }, { path: ":channelId" }], + }, + { + path: "feedback", + element: , + }, + { + path: "support", + element: , + }, + ], + }, + ], + }, + // Protected routes with redirect to current page + { + path: "/join/:tablo_name", + element: , + children: [ + { + path: "", + element: , + children: [ + { + index: true, + element: , + }, + ], + }, + ], + }, + // OAuth signin route + { + path: "/login-with-oauth", + element: , + }, + // Landing page + { + path: "/landing", + element: , + }, + // Public routes (authentication pages) + { + path: "/", + element: , + children: [ + { + path: "login", + element: , + }, + { + path: "signup", + element: , + }, + { + path: "reset-password", + element: , + }, + ], + }, + // Catch-all route for 404 + { + path: "*", + element: , + }, +]; diff --git a/ui/src/lib/rum.ts b/ui/src/lib/rum.ts index c3fbb7b..34e568c 100644 --- a/ui/src/lib/rum.ts +++ b/ui/src/lib/rum.ts @@ -15,5 +15,7 @@ export const initRum = () => { sessionReplaySampleRate: 80, defaultPrivacyLevel: "mask-user-input", plugins: [reactPlugin({ router: true })], + trackViewsManually: true, + startSessionReplayRecordingManually: false, }); }; diff --git a/ui/src/providers/DatadogRumProvider.tsx b/ui/src/providers/DatadogRumProvider.tsx new file mode 100644 index 0000000..1780a80 --- /dev/null +++ b/ui/src/providers/DatadogRumProvider.tsx @@ -0,0 +1,47 @@ +import { datadogRum } from "@datadog/browser-rum"; +import { routes } from "@ui/lib/routes"; +import { initRum } from "@ui/lib/rum"; +import { useEffect } from "react"; +import { matchRoutes, RouteMatch, useLocation } from "react-router-dom"; + +function computeViewName(routeMatches: RouteMatch[]) { + let viewName = ""; + for (let index = 0; index < routeMatches.length; index++) { + const routeMatch = routeMatches[index]; + const path = routeMatch.route.path; + // Skip pathless routes + if (!path) { + continue; + } + + if (path.startsWith("/")) { + // Handle absolute child route paths + viewName = path; + } else { + // Handle route paths ending with "/" + viewName += viewName.endsWith("/") ? path : `/${path}`; + } + } + + return viewName || "/"; +} + +export const DatadogRumProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const location = useLocation(); + useEffect(() => { + initRum(); + }, []); + + useEffect(() => { + const routeMatches = matchRoutes(routes, location.pathname); + const viewName = routeMatches && computeViewName(routeMatches); + if (viewName) { + datadogRum.startView({ name: viewName }); + } + }, [location.pathname]); + return <>{children}; +};