From 9ed711777c11e16c524685a8903c742ff26dfbc8 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 4 Nov 2025 19:43:51 +0100 Subject: [PATCH] Add webapp skeletons --- matrixgw_frontend/package-lock.json | 40 +++++++++++- matrixgw_frontend/package.json | 3 +- matrixgw_frontend/src/App.tsx | 62 ++++++++++++++++++- matrixgw_frontend/src/routes/HomeRoute.tsx | 3 + .../src/routes/NotFoundRoute.tsx | 23 +++++++ .../src/routes/auth/LoginRoute.tsx | 3 + .../src/routes/auth/OIDCCbRoute.tsx | 53 ++++++++++++++++ .../src/widgets/BaseAuthenticatedPage.tsx | 3 + matrixgw_frontend/src/widgets/RouterLink.tsx | 16 +++++ .../src/widgets/auth/AuthSingleMessage.tsx | 13 ++++ .../src/widgets/auth/BaseLoginPage.tsx | 3 + 11 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 matrixgw_frontend/src/routes/HomeRoute.tsx create mode 100644 matrixgw_frontend/src/routes/NotFoundRoute.tsx create mode 100644 matrixgw_frontend/src/routes/auth/LoginRoute.tsx create mode 100644 matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx create mode 100644 matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx create mode 100644 matrixgw_frontend/src/widgets/RouterLink.tsx create mode 100644 matrixgw_frontend/src/widgets/auth/AuthSingleMessage.tsx create mode 100644 matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx diff --git a/matrixgw_frontend/package-lock.json b/matrixgw_frontend/package-lock.json index 5f2de0a..e80d450 100644 --- a/matrixgw_frontend/package-lock.json +++ b/matrixgw_frontend/package-lock.json @@ -14,7 +14,8 @@ "@mui/icons-material": "^7.3.5", "@mui/material": "^7.3.5", "react": "^19.1.1", - "react-dom": "^19.1.1" + "react-dom": "^19.1.1", + "react-router": "^7.9.5" }, "devDependencies": { "@eslint/js": "^9.36.0", @@ -2012,6 +2013,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -3399,6 +3409,28 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.5.tgz", + "integrity": "sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -3536,6 +3568,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/matrixgw_frontend/package.json b/matrixgw_frontend/package.json index 2025306..3207e3c 100644 --- a/matrixgw_frontend/package.json +++ b/matrixgw_frontend/package.json @@ -16,7 +16,8 @@ "@mui/icons-material": "^7.3.5", "@mui/material": "^7.3.5", "react": "^19.1.1", - "react-dom": "^19.1.1" + "react-dom": "^19.1.1", + "react-router": "^7.9.5" }, "devDependencies": { "@eslint/js": "^9.36.0", diff --git a/matrixgw_frontend/src/App.tsx b/matrixgw_frontend/src/App.tsx index 20351d8..4b0d983 100644 --- a/matrixgw_frontend/src/App.tsx +++ b/matrixgw_frontend/src/App.tsx @@ -1,3 +1,61 @@ -export function App(): React.ReactElement { - return <>hello world; +import React from "react"; +import { + createBrowserRouter, + createRoutesFromElements, + Route, + RouterProvider, +} from "react-router"; +import { AuthApi } from "./api/AuthApi"; +import { ServerApi } from "./api/ServerApi"; +import { LoginRoute } from "./routes/auth/LoginRoute"; +import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute"; +import { HomeRoute } from "./routes/HomeRoute"; +import { NotFoundRoute } from "./routes/NotFoundRoute"; +import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage"; +import { BaseLoginPage } from "./widgets/auth/BaseLoginPage"; + +interface AuthContext { + signedIn: boolean; + setSignedIn: (signedIn: boolean) => void; +} + +const AuthContextK = React.createContext(null); + +export function App(): React.ReactElement { + const [signedIn, setSignedIn] = React.useState(AuthApi.SignedIn); + + const context: AuthContext = { + signedIn: signedIn, + setSignedIn: (s) => { + setSignedIn(s); + location.reload(); + }, + }; + + const router = createBrowserRouter( + createRoutesFromElements( + signedIn || ServerApi.Config.auth_disabled ? ( + }> + } /> + } /> + + ) : ( + }> + } /> + } /> + } /> + + ) + ) + ); + + return ( + + + + ); +} + +export function useAuth(): AuthContext { + return React.use(AuthContextK)!; } diff --git a/matrixgw_frontend/src/routes/HomeRoute.tsx b/matrixgw_frontend/src/routes/HomeRoute.tsx new file mode 100644 index 0000000..b93b24f --- /dev/null +++ b/matrixgw_frontend/src/routes/HomeRoute.tsx @@ -0,0 +1,3 @@ +export function HomeRoute(): React.ReactElement { + return

Todo home route

; +} diff --git a/matrixgw_frontend/src/routes/NotFoundRoute.tsx b/matrixgw_frontend/src/routes/NotFoundRoute.tsx new file mode 100644 index 0000000..72812e6 --- /dev/null +++ b/matrixgw_frontend/src/routes/NotFoundRoute.tsx @@ -0,0 +1,23 @@ +import { Button } from "@mui/material"; +import { RouterLink } from "../widgets/RouterLink"; + +export function NotFoundRoute(): React.ReactElement { + return ( +
+

404 Not found

+

The page you requested was not found!

+ + + +
+ ); +} diff --git a/matrixgw_frontend/src/routes/auth/LoginRoute.tsx b/matrixgw_frontend/src/routes/auth/LoginRoute.tsx new file mode 100644 index 0000000..6190120 --- /dev/null +++ b/matrixgw_frontend/src/routes/auth/LoginRoute.tsx @@ -0,0 +1,3 @@ +export function LoginRoute(): React.ReactElement { + return <>LoginRoute; +} diff --git a/matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx b/matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx new file mode 100644 index 0000000..90c67f8 --- /dev/null +++ b/matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx @@ -0,0 +1,53 @@ +import { CircularProgress } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; +import { useNavigate, useSearchParams } from "react-router"; +import { AuthApi } from "../../api/AuthApi"; +import { useAuth } from "../../App"; +import { AuthSingleMessage } from "../../widgets/auth/AuthSingleMessage"; + +/** + * OpenID login callback route + */ +export function OIDCCbRoute(): React.ReactElement { + const auth = useAuth(); + const navigate = useNavigate(); + + const [error, setError] = useState(false); + + const [searchParams] = useSearchParams(); + const code = searchParams.get("code"); + const state = searchParams.get("state"); + + const count = useRef(""); + + useEffect(() => { + const load = async () => { + try { + if (count.current === code) { + return; + } + count.current = code!; + + await AuthApi.FinishOpenIDLogin(code!, state!); + navigate("/"); + auth.setSignedIn(true); + } catch (e) { + console.error(e); + setError(true); + } + }; + + load(); + }); + + if (error) + return ( + + ); + + return ( + <> + + + ); +} diff --git a/matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx b/matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx new file mode 100644 index 0000000..79607a4 --- /dev/null +++ b/matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx @@ -0,0 +1,3 @@ +export function BaseAuthenticatedPage(): React.ReactElement { + return

todo authenticated

; +} diff --git a/matrixgw_frontend/src/widgets/RouterLink.tsx b/matrixgw_frontend/src/widgets/RouterLink.tsx new file mode 100644 index 0000000..c946ed6 --- /dev/null +++ b/matrixgw_frontend/src/widgets/RouterLink.tsx @@ -0,0 +1,16 @@ +import { type PropsWithChildren } from "react"; +import { Link } from "react-router"; + +export function RouterLink( + p: PropsWithChildren<{ to: string; target?: React.HTMLAttributeAnchorTarget }> +): React.ReactElement { + return ( + + {p.children} + + ); +} diff --git a/matrixgw_frontend/src/widgets/auth/AuthSingleMessage.tsx b/matrixgw_frontend/src/widgets/auth/AuthSingleMessage.tsx new file mode 100644 index 0000000..02423df --- /dev/null +++ b/matrixgw_frontend/src/widgets/auth/AuthSingleMessage.tsx @@ -0,0 +1,13 @@ +import { Button } from "@mui/material"; +import { Link } from "react-router"; + +export function AuthSingleMessage(p: { message: string }): React.ReactElement { + return ( + <> +

{p.message}

+ + + + + ); +} diff --git a/matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx b/matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx new file mode 100644 index 0000000..866dd64 --- /dev/null +++ b/matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx @@ -0,0 +1,3 @@ +export function BaseLoginPage(): React.ReactElement { + return

Todo login page route

; +}