diff --git a/remote_frontend/index.html b/remote_frontend/index.html
index e4b78ea..92aa951 100644
--- a/remote_frontend/index.html
+++ b/remote_frontend/index.html
@@ -2,9 +2,9 @@
-
+
- Vite + React + TS
+ VirtWebRemote
diff --git a/remote_frontend/public/remote.svg b/remote_frontend/public/remote.svg
new file mode 100644
index 0000000..027453c
--- /dev/null
+++ b/remote_frontend/public/remote.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/remote_frontend/src/App.tsx b/remote_frontend/src/App.tsx
index bd1141d..005b232 100644
--- a/remote_frontend/src/App.tsx
+++ b/remote_frontend/src/App.tsx
@@ -1,6 +1,23 @@
+import {
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ MenuPopover,
+ MenuTrigger,
+ makeStyles,
+ typographyStyles,
+} from "@fluentui/react-components";
import { ServerApi } from "./api/ServerApi";
import { AuthRouteWidget } from "./routes/AuthRouteWidget";
import { AsyncWidget } from "./widgets/AsyncWidget";
+import { AuthApi } from "./api/AuthApi";
+import { useAlert } from "./hooks/providers/AlertDialogProvider";
+import { useConfirm } from "./hooks/providers/ConfirmDialogProvider";
+
+const useStyles = makeStyles({
+ title: typographyStyles.title2,
+});
export function App() {
return (
@@ -15,7 +32,45 @@ export function App() {
}
function AppInner(): React.ReactElement {
+ const alert = useAlert();
+ const confirm = useConfirm();
+ const styles = useStyles();
+
+ const signOut = async () => {
+ try {
+ if (!(await confirm("Do you really want to sign out?"))) return;
+ await AuthApi.SignOut();
+ } catch (e) {
+ console.error(e);
+ alert("Failed to perform sign out!");
+ }
+ };
+
if (!ServerApi.Config.authenticated && !ServerApi.Config.disable_auth)
return ;
- return <>todo authenticated>;
+
+ return (
+
+
+ VirtWebRemote
+
+
+
+ );
}
diff --git a/remote_frontend/src/api/AuthApi.ts b/remote_frontend/src/api/AuthApi.ts
index a367ca0..25c4ba6 100644
--- a/remote_frontend/src/api/AuthApi.ts
+++ b/remote_frontend/src/api/AuthApi.ts
@@ -25,4 +25,16 @@ export class AuthApi {
window.location.href = "/";
}
+
+ /**
+ * Sign out
+ */
+ static async SignOut(): Promise {
+ await APIClient.exec({
+ uri: "/auth/sign_out",
+ method: "GET",
+ });
+
+ window.location.href = "/";
+ }
}
diff --git a/remote_frontend/src/hooks/providers/AlertDialogProvider.tsx b/remote_frontend/src/hooks/providers/AlertDialogProvider.tsx
new file mode 100644
index 0000000..de5b20a
--- /dev/null
+++ b/remote_frontend/src/hooks/providers/AlertDialogProvider.tsx
@@ -0,0 +1,69 @@
+import {
+ Button,
+ Dialog,
+ DialogActions,
+ DialogBody,
+ DialogContent,
+ DialogSurface,
+ DialogTitle,
+} from "@fluentui/react-components";
+import React, { PropsWithChildren } from "react";
+
+type AlertContext = (message: string, title?: string) => Promise;
+
+const AlertContextK = React.createContext(null);
+
+export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement {
+ const [open, setOpen] = React.useState(false);
+
+ const [title, setTitle] = React.useState(undefined);
+ const [message, setMessage] = React.useState("");
+
+ const cb = React.useRef void)>(null);
+
+ const handleClose = () => {
+ setOpen(false);
+
+ if (cb.current !== null) cb.current();
+ cb.current = null;
+ };
+
+ const hook: AlertContext = (message, title) => {
+ setTitle(title);
+ setMessage(message);
+ setOpen(true);
+
+ return new Promise((res) => {
+ cb.current = res;
+ });
+ };
+
+ return (
+ <>
+ {p.children}
+
+
+ >
+ );
+}
+
+export function useAlert(): AlertContext {
+ return React.useContext(AlertContextK)!;
+}
diff --git a/remote_frontend/src/hooks/providers/ConfirmDialogProvider.tsx b/remote_frontend/src/hooks/providers/ConfirmDialogProvider.tsx
new file mode 100644
index 0000000..8630aea
--- /dev/null
+++ b/remote_frontend/src/hooks/providers/ConfirmDialogProvider.tsx
@@ -0,0 +1,98 @@
+import {
+ Button,
+ Dialog,
+ DialogActions,
+ DialogBody,
+ DialogContent,
+ DialogSurface,
+ DialogTitle,
+} from "@fluentui/react-components";
+import React, { PropsWithChildren } from "react";
+
+type ConfirmContext = (
+ message: string,
+ title?: string,
+ confirmButton?: string,
+ cancelButton?: string
+) => Promise;
+
+const ConfirmContextK = React.createContext(null);
+
+export function ConfirmDialogProvider(
+ p: PropsWithChildren
+): React.ReactElement {
+ const [open, setOpen] = React.useState(false);
+
+ const [title, setTitle] = React.useState(undefined);
+ const [message, setMessage] = React.useState("");
+ const [confirmButton, setConfirmButton] = React.useState(
+ undefined
+ );
+ const [cancelButton, setCancelButton] = React.useState(
+ undefined
+ );
+
+ const cb = React.useRef void)>(null);
+
+ const handleClose = (confirm: boolean) => {
+ setOpen(false);
+
+ if (cb.current !== null) cb.current(confirm);
+ cb.current = null;
+ };
+
+ const hook: ConfirmContext = (
+ message,
+ title,
+ confirmButton,
+ cancelButton
+ ) => {
+ setTitle(title);
+ setMessage(message);
+ setConfirmButton(confirmButton);
+ setCancelButton(cancelButton);
+ setOpen(true);
+
+ return new Promise((res) => {
+ cb.current = res;
+ });
+ };
+
+ return (
+ <>
+
+ {p.children}
+
+
+
+ >
+ );
+}
+
+export function useConfirm(): ConfirmContext {
+ return React.useContext(ConfirmContextK)!;
+}
diff --git a/remote_frontend/src/main.tsx b/remote_frontend/src/main.tsx
index 8dc1b59..2624abb 100644
--- a/remote_frontend/src/main.tsx
+++ b/remote_frontend/src/main.tsx
@@ -6,6 +6,8 @@ import {
FluentProvider,
teamsHighContrastTheme,
} from "@fluentui/react-components";
+import { AlertDialogProvider } from "./hooks/providers/AlertDialogProvider";
+import { ConfirmDialogProvider } from "./hooks/providers/ConfirmDialogProvider";
ReactDOM.createRoot(document.getElementById("root")!).render(
@@ -13,7 +15,11 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
theme={teamsHighContrastTheme}
style={{ display: "flex", flex: 1 }}
>
-
+
+
+
+
+
);