User can sign out
This commit is contained in:
parent
9407d8f0a8
commit
8a8b1a8846
@ -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>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user