Add webapp skeletons
This commit is contained in:
40
matrixgw_frontend/package-lock.json
generated
40
matrixgw_frontend/package-lock.json
generated
@@ -14,7 +14,8 @@
|
|||||||
"@mui/icons-material": "^7.3.5",
|
"@mui/icons-material": "^7.3.5",
|
||||||
"@mui/material": "^7.3.5",
|
"@mui/material": "^7.3.5",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1"
|
"react-dom": "^19.1.1",
|
||||||
|
"react-router": "^7.9.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "^9.36.0",
|
||||||
@@ -2012,6 +2013,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/cosmiconfig": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||||
@@ -3399,6 +3409,28 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/react-transition-group": {
|
||||||
"version": "4.4.5",
|
"version": "4.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
@@ -3536,6 +3568,12 @@
|
|||||||
"semver": "bin/semver.js"
|
"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": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
"@mui/icons-material": "^7.3.5",
|
"@mui/icons-material": "^7.3.5",
|
||||||
"@mui/material": "^7.3.5",
|
"@mui/material": "^7.3.5",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1"
|
"react-dom": "^19.1.1",
|
||||||
|
"react-router": "^7.9.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "^9.36.0",
|
||||||
|
|||||||
@@ -1,3 +1,61 @@
|
|||||||
export function App(): React.ReactElement {
|
import React from "react";
|
||||||
return <>hello world</>;
|
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<AuthContext | null>(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 ? (
|
||||||
|
<Route path="*" element={<BaseAuthenticatedPage />}>
|
||||||
|
<Route path="" element={<HomeRoute />} />
|
||||||
|
<Route path="*" element={<NotFoundRoute />} />
|
||||||
|
</Route>
|
||||||
|
) : (
|
||||||
|
<Route path="*" element={<BaseLoginPage />}>
|
||||||
|
<Route path="" element={<LoginRoute />} />
|
||||||
|
<Route path="oidc_cb" element={<OIDCCbRoute />} />
|
||||||
|
<Route path="*" element={<NotFoundRoute />} />
|
||||||
|
</Route>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContextK value={context}>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</AuthContextK>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuth(): AuthContext {
|
||||||
|
return React.use(AuthContextK)!;
|
||||||
}
|
}
|
||||||
|
|||||||
3
matrixgw_frontend/src/routes/HomeRoute.tsx
Normal file
3
matrixgw_frontend/src/routes/HomeRoute.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function HomeRoute(): React.ReactElement {
|
||||||
|
return <p>Todo home route</p>;
|
||||||
|
}
|
||||||
23
matrixgw_frontend/src/routes/NotFoundRoute.tsx
Normal file
23
matrixgw_frontend/src/routes/NotFoundRoute.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Button } from "@mui/material";
|
||||||
|
import { RouterLink } from "../widgets/RouterLink";
|
||||||
|
|
||||||
|
export function NotFoundRoute(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
textAlign: "center",
|
||||||
|
flex: "1",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1>404 Not found</h1>
|
||||||
|
<p>The page you requested was not found!</p>
|
||||||
|
<RouterLink to="/">
|
||||||
|
<Button>Go back home</Button>
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
matrixgw_frontend/src/routes/auth/LoginRoute.tsx
Normal file
3
matrixgw_frontend/src/routes/auth/LoginRoute.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function LoginRoute(): React.ReactElement {
|
||||||
|
return <>LoginRoute</>;
|
||||||
|
}
|
||||||
53
matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx
Normal file
53
matrixgw_frontend/src/routes/auth/OIDCCbRoute.tsx
Normal file
@@ -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 (
|
||||||
|
<AuthSingleMessage message="Failed to finalize OpenID authentication!" />
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CircularProgress />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx
Normal file
3
matrixgw_frontend/src/widgets/BaseAuthenticatedPage.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function BaseAuthenticatedPage(): React.ReactElement {
|
||||||
|
return <p>todo authenticated</p>;
|
||||||
|
}
|
||||||
16
matrixgw_frontend/src/widgets/RouterLink.tsx
Normal file
16
matrixgw_frontend/src/widgets/RouterLink.tsx
Normal file
@@ -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 (
|
||||||
|
<Link
|
||||||
|
to={p.to}
|
||||||
|
target={p.target}
|
||||||
|
style={{ color: "inherit", textDecoration: "inherit" }}
|
||||||
|
>
|
||||||
|
{p.children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
matrixgw_frontend/src/widgets/auth/AuthSingleMessage.tsx
Normal file
13
matrixgw_frontend/src/widgets/auth/AuthSingleMessage.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Button } from "@mui/material";
|
||||||
|
import { Link } from "react-router";
|
||||||
|
|
||||||
|
export function AuthSingleMessage(p: { message: string }): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p style={{ textAlign: "center" }}>{p.message}</p>
|
||||||
|
<Link to={"/"}>
|
||||||
|
<Button>Go back home</Button>
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx
Normal file
3
matrixgw_frontend/src/widgets/auth/BaseLoginPage.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function BaseLoginPage(): React.ReactElement {
|
||||||
|
return <p>Todo login page route</p>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user