Can request account deletion

This commit is contained in:
Pierre HUBERT 2023-06-14 16:12:09 +02:00
parent 6f58e767a2
commit 10e5c124fd
5 changed files with 225 additions and 3 deletions

View File

@ -79,4 +79,14 @@ export class UserApi {
return ReplacePasswordResponse.Error; return ReplacePasswordResponse.Error;
} }
} }
/**
* Request account deletion
*/
static async RequestAccountDeletion(): Promise<void> {
await APIClient.exec({
uri: "/user/request_delete",
method: "GET",
});
}
} }

View File

@ -11,6 +11,8 @@ import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css"; import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css"; import "@fontsource/roboto/700.css";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { ConfirmDialogProvider } from "./widgets/ConfirmDialogProvider";
import { AlertDialogProvider } from "./widgets/AlertDialogProvider";
async function init() { async function init() {
try { try {
@ -23,9 +25,13 @@ async function init() {
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<BrowserRouter> <BrowserRouter>
<AlertDialogProvider>
<ConfirmDialogProvider>
<div style={{ height: "100vh" }}> <div style={{ height: "100vh" }}>
<App /> <App />
</div> </div>
</ConfirmDialogProvider>
</AlertDialogProvider>
</BrowserRouter> </BrowserRouter>
</React.StrictMode> </React.StrictMode>
); );

View File

@ -16,6 +16,8 @@ import { ReplacePasswordResponse, User, UserApi } from "../api/UserApi";
import { AsyncWidget } from "../widgets/AsyncWidget"; import { AsyncWidget } from "../widgets/AsyncWidget";
import { PasswordInput } from "../widgets/PasswordInput"; import { PasswordInput } from "../widgets/PasswordInput";
import { formatDate } from "../widgets/TimeWidget"; import { formatDate } from "../widgets/TimeWidget";
import { useConfirm } from "../widgets/ConfirmDialogProvider";
import { useAlert } from "../widgets/AlertDialogProvider";
export function ProfileRoute(): React.ReactElement { export function ProfileRoute(): React.ReactElement {
const [user, setUser] = React.useState<null | User>(null); const [user, setUser] = React.useState<null | User>(null);
@ -41,6 +43,7 @@ export function ProfileRoute(): React.ReactElement {
onUpdate={() => (counter.current += 1)} onUpdate={() => (counter.current += 1)}
/> />
{user?.has_password && <ChangePasswordCard />} {user?.has_password && <ChangePasswordCard />}
<DeleteAccountButton />
</div> </div>
)} )}
/> />
@ -258,3 +261,37 @@ function ChangePasswordCard(): React.ReactElement {
</> </>
); );
} }
function DeleteAccountButton(): React.ReactElement {
const alert = useAlert();
const confirm = useConfirm();
const requestDelete = async () => {
try {
if (
!(await confirm.confirm(
"Voulez-vous initier la suppression de votre compte ?",
"Suppression de compte"
))
)
return;
await UserApi.RequestAccountDeletion();
await alert.alert(
"Demande de suppression de compte enregistrée avec succès. Veuillez consulter votre boîte mail."
);
} catch (e) {
console.error(e);
alert.alert("Echec de la demande de suppression de compte !");
}
};
return (
<div style={{ textAlign: "center" }}>
<Button onClick={requestDelete} color="error">
Supprimer mon compte
</Button>
</div>
);
}

View File

@ -0,0 +1,77 @@
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from "@mui/material";
import React, {
PropsWithChildren,
createContext,
useContext,
useRef,
} from "react";
interface AlertHook {
alert: (message: string, title?: string) => Promise<void>;
}
const AlertContext = createContext<AlertHook | 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: AlertHook = {
alert: (message, title) => {
setTitle(title);
setMessage(message);
setOpen(true);
return new Promise((res) => {
cb.current = res;
});
},
};
return (
<>
<AlertContext.Provider value={hook}>{p.children}</AlertContext.Provider>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
{title && <DialogTitle id="alert-dialog-title">{title}</DialogTitle>}
<DialogContent>
<DialogContentText id="alert-dialog-description">
{message}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} autoFocus>
Ok
</Button>
</DialogActions>
</Dialog>
</>
);
}
export function useAlert(): AlertHook {
return useContext(AlertContext)!;
}

View File

@ -0,0 +1,92 @@
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from "@mui/material";
import React, {
PropsWithChildren,
createContext,
useContext,
useRef,
} from "react";
interface ConfirmHook {
confirm: (
message: string,
title?: string,
confirmButton?: string
) => Promise<boolean>;
}
const ConfirmContext = createContext<ConfirmHook | 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 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: ConfirmHook = {
confirm: (message, title, confirmButton) => {
setTitle(title);
setMessage(message);
setConfirmButton(confirmButton);
setOpen(true);
return new Promise((res) => {
cb.current = res;
});
},
};
return (
<>
<ConfirmContext.Provider value={hook}>
{p.children}
</ConfirmContext.Provider>
<Dialog
open={open}
onClose={() => handleClose(false)}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
{title && <DialogTitle id="alert-dialog-title">{title}</DialogTitle>}
<DialogContent>
<DialogContentText id="alert-dialog-description">
{message}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => handleClose(false)} autoFocus>
Annuler
</Button>
<Button onClick={() => handleClose(true)} color="error">
{confirmButton ?? "Confirmer"}
</Button>
</DialogActions>
</Dialog>
</>
);
}
export function useConfirm(): ConfirmHook {
return useContext(ConfirmContext)!;
}