User can sign out
This commit is contained in:
		@@ -2,9 +2,9 @@
 | 
				
			|||||||
<html lang="en">
 | 
					<html lang="en">
 | 
				
			||||||
  <head>
 | 
					  <head>
 | 
				
			||||||
    <meta charset="UTF-8" />
 | 
					    <meta charset="UTF-8" />
 | 
				
			||||||
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
 | 
					    <link rel="icon" type="image/svg+xml" href="/remote.svg" />
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
				
			||||||
    <title>Vite + React + TS</title>
 | 
					    <title>VirtWebRemote</title>
 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
  <body>
 | 
					  <body>
 | 
				
			||||||
    <div id="root"></div>
 | 
					    <div id="root"></div>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								remote_frontend/public/remote.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								remote_frontend/public/remote.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,0C8.96,0 6.21,1.23 4.22,3.22L5.63,4.63C7.26,3 9.5,2 12,2C14.5,2 16.74,3 18.36,4.64L19.77,3.23C17.79,1.23 15.04,0 12,0M7.05,6.05L8.46,7.46C9.37,6.56 10.62,6 12,6C13.38,6 14.63,6.56 15.54,7.46L16.95,6.05C15.68,4.78 13.93,4 12,4C10.07,4 8.32,4.78 7.05,6.05M12,15A2,2 0 0,1 10,13A2,2 0 0,1 12,11A2,2 0 0,1 14,13A2,2 0 0,1 12,15M15,9H9A1,1 0 0,0 8,10V22A1,1 0 0,0 9,23H15A1,1 0 0,0 16,22V10A1,1 0 0,0 15,9Z" /></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 484 B  | 
@@ -1,6 +1,23 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Menu,
 | 
				
			||||||
 | 
					  MenuButton,
 | 
				
			||||||
 | 
					  MenuItem,
 | 
				
			||||||
 | 
					  MenuList,
 | 
				
			||||||
 | 
					  MenuPopover,
 | 
				
			||||||
 | 
					  MenuTrigger,
 | 
				
			||||||
 | 
					  makeStyles,
 | 
				
			||||||
 | 
					  typographyStyles,
 | 
				
			||||||
 | 
					} from "@fluentui/react-components";
 | 
				
			||||||
import { ServerApi } from "./api/ServerApi";
 | 
					import { ServerApi } from "./api/ServerApi";
 | 
				
			||||||
import { AuthRouteWidget } from "./routes/AuthRouteWidget";
 | 
					import { AuthRouteWidget } from "./routes/AuthRouteWidget";
 | 
				
			||||||
import { AsyncWidget } from "./widgets/AsyncWidget";
 | 
					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() {
 | 
					export function App() {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@@ -15,7 +32,45 @@ export function App() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function AppInner(): React.ReactElement {
 | 
					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)
 | 
					  if (!ServerApi.Config.authenticated && !ServerApi.Config.disable_auth)
 | 
				
			||||||
    return <AuthRouteWidget />;
 | 
					    return <AuthRouteWidget />;
 | 
				
			||||||
  return <>todo authenticated</>;
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      style={{
 | 
				
			||||||
 | 
					        width: "100%",
 | 
				
			||||||
 | 
					        maxWidth: "1000px",
 | 
				
			||||||
 | 
					        margin: "50px auto",
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <div style={{ display: "flex", justifyContent: "space-between" }}>
 | 
				
			||||||
 | 
					        <span className={styles.title}>VirtWebRemote</span>
 | 
				
			||||||
 | 
					        <Menu>
 | 
				
			||||||
 | 
					          <MenuTrigger disableButtonEnhancement>
 | 
				
			||||||
 | 
					            <MenuButton>Account</MenuButton>
 | 
				
			||||||
 | 
					          </MenuTrigger>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <MenuPopover>
 | 
				
			||||||
 | 
					            <MenuList>
 | 
				
			||||||
 | 
					              <MenuItem onClick={signOut}>Sign out</MenuItem>
 | 
				
			||||||
 | 
					            </MenuList>
 | 
				
			||||||
 | 
					          </MenuPopover>
 | 
				
			||||||
 | 
					        </Menu>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,4 +25,16 @@ export class AuthApi {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    window.location.href = "/";
 | 
					    window.location.href = "/";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Sign out
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async SignOut(): Promise<void> {
 | 
				
			||||||
 | 
					    await APIClient.exec({
 | 
				
			||||||
 | 
					      uri: "/auth/sign_out",
 | 
				
			||||||
 | 
					      method: "GET",
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.location.href = "/";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										69
									
								
								remote_frontend/src/hooks/providers/AlertDialogProvider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								remote_frontend/src/hooks/providers/AlertDialogProvider.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -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<void>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertContextK = React.createContext<AlertContext | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function AlertDialogProvider(p: PropsWithChildren): React.ReactElement {
 | 
				
			||||||
 | 
					  const [open, setOpen] = React.useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [title, setTitle] = React.useState<string | undefined>(undefined);
 | 
				
			||||||
 | 
					  const [message, setMessage] = React.useState("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const cb = React.useRef<null | (() => 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 (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <AlertContextK.Provider value={hook}>{p.children}</AlertContextK.Provider>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <Dialog
 | 
				
			||||||
 | 
					        open={open}
 | 
				
			||||||
 | 
					        onOpenChange={(_event, data) => {
 | 
				
			||||||
 | 
					          if (!data.open) setOpen(data.open);
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <DialogSurface>
 | 
				
			||||||
 | 
					          <DialogBody>
 | 
				
			||||||
 | 
					            {title && <DialogTitle>{title}</DialogTitle>}
 | 
				
			||||||
 | 
					            <DialogContent>{message}</DialogContent>
 | 
				
			||||||
 | 
					            <DialogActions>
 | 
				
			||||||
 | 
					              <Button onClick={handleClose} autoFocus appearance="secondary">
 | 
				
			||||||
 | 
					                Ok
 | 
				
			||||||
 | 
					              </Button>
 | 
				
			||||||
 | 
					            </DialogActions>
 | 
				
			||||||
 | 
					          </DialogBody>
 | 
				
			||||||
 | 
					        </DialogSurface>
 | 
				
			||||||
 | 
					      </Dialog>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useAlert(): AlertContext {
 | 
				
			||||||
 | 
					  return React.useContext(AlertContextK)!;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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<boolean>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ConfirmContextK = React.createContext<ConfirmContext | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ConfirmDialogProvider(
 | 
				
			||||||
 | 
					  p: PropsWithChildren
 | 
				
			||||||
 | 
					): React.ReactElement {
 | 
				
			||||||
 | 
					  const [open, setOpen] = React.useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [title, setTitle] = React.useState<string | undefined>(undefined);
 | 
				
			||||||
 | 
					  const [message, setMessage] = React.useState("");
 | 
				
			||||||
 | 
					  const [confirmButton, setConfirmButton] = React.useState<string | undefined>(
 | 
				
			||||||
 | 
					    undefined
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const [cancelButton, setCancelButton] = React.useState<string | undefined>(
 | 
				
			||||||
 | 
					    undefined
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const cb = React.useRef<null | ((a: boolean) => 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 (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <ConfirmContextK.Provider value={hook}>
 | 
				
			||||||
 | 
					        {p.children}
 | 
				
			||||||
 | 
					      </ConfirmContextK.Provider>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <Dialog
 | 
				
			||||||
 | 
					        open={open}
 | 
				
			||||||
 | 
					        onOpenChange={(_event, data) => {
 | 
				
			||||||
 | 
					          if (!data.open) setOpen(false);
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <DialogSurface>
 | 
				
			||||||
 | 
					          <DialogBody>
 | 
				
			||||||
 | 
					            {title && <DialogTitle>{title}</DialogTitle>}
 | 
				
			||||||
 | 
					            <DialogContent>{message}</DialogContent>
 | 
				
			||||||
 | 
					            <DialogActions>
 | 
				
			||||||
 | 
					              <Button
 | 
				
			||||||
 | 
					                onClick={() => handleClose(false)}
 | 
				
			||||||
 | 
					                autoFocus
 | 
				
			||||||
 | 
					                appearance="secondary"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                {cancelButton ?? "Cancel"}
 | 
				
			||||||
 | 
					              </Button>
 | 
				
			||||||
 | 
					              <Button onClick={() => handleClose(true)} appearance="primary">
 | 
				
			||||||
 | 
					                {confirmButton ?? "Confirm"}
 | 
				
			||||||
 | 
					              </Button>
 | 
				
			||||||
 | 
					            </DialogActions>
 | 
				
			||||||
 | 
					          </DialogBody>
 | 
				
			||||||
 | 
					        </DialogSurface>
 | 
				
			||||||
 | 
					      </Dialog>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useConfirm(): ConfirmContext {
 | 
				
			||||||
 | 
					  return React.useContext(ConfirmContextK)!;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -6,6 +6,8 @@ import {
 | 
				
			|||||||
  FluentProvider,
 | 
					  FluentProvider,
 | 
				
			||||||
  teamsHighContrastTheme,
 | 
					  teamsHighContrastTheme,
 | 
				
			||||||
} from "@fluentui/react-components";
 | 
					} from "@fluentui/react-components";
 | 
				
			||||||
 | 
					import { AlertDialogProvider } from "./hooks/providers/AlertDialogProvider";
 | 
				
			||||||
 | 
					import { ConfirmDialogProvider } from "./hooks/providers/ConfirmDialogProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
 | 
					ReactDOM.createRoot(document.getElementById("root")!).render(
 | 
				
			||||||
  <React.StrictMode>
 | 
					  <React.StrictMode>
 | 
				
			||||||
@@ -13,7 +15,11 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
 | 
				
			|||||||
      theme={teamsHighContrastTheme}
 | 
					      theme={teamsHighContrastTheme}
 | 
				
			||||||
      style={{ display: "flex", flex: 1 }}
 | 
					      style={{ display: "flex", flex: 1 }}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <App />
 | 
					      <AlertDialogProvider>
 | 
				
			||||||
 | 
					        <ConfirmDialogProvider>
 | 
				
			||||||
 | 
					          <App />
 | 
				
			||||||
 | 
					        </ConfirmDialogProvider>
 | 
				
			||||||
 | 
					      </AlertDialogProvider>
 | 
				
			||||||
    </FluentProvider>
 | 
					    </FluentProvider>
 | 
				
			||||||
  </React.StrictMode>
 | 
					  </React.StrictMode>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user