Can create a family from the GUI

This commit is contained in:
Pierre HUBERT 2023-06-27 17:13:12 +02:00
parent 378761f6d5
commit 5b06886c71
6 changed files with 325 additions and 0 deletions

View File

@ -12,6 +12,7 @@ import { ResetPasswordRoute } from "./routes/auth/ResetPasswordRoute";
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage"; import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
import { BaseLoginPage } from "./widgets/BaseLoginpage"; import { BaseLoginPage } from "./widgets/BaseLoginpage";
import { DeleteAccountRoute } from "./routes/DeleteAccountRoute"; import { DeleteAccountRoute } from "./routes/DeleteAccountRoute";
import { FamiliesListRoute } from "./routes/FamiliesListRoute";
interface AuthContext { interface AuthContext {
signedIn: boolean; signedIn: boolean;
@ -38,6 +39,7 @@ export function App(): React.ReactElement {
{signedIn ? ( {signedIn ? (
<Route path="*" element={<BaseAuthenticatedPage />}> <Route path="*" element={<BaseAuthenticatedPage />}>
<Route path="" element={<FamiliesListRoute />} />
<Route path="profile" element={<ProfileRoute />} /> <Route path="profile" element={<ProfileRoute />} />
<Route path="*" element={<NotFoundRoute />} /> <Route path="*" element={<NotFoundRoute />} />
</Route> </Route>

View File

@ -0,0 +1,16 @@
import { APIClient } from "./ApiClient";
export interface Family {}
export class FamilyApi {
/**
* Create a new family
*/
static async CreateFamily(name: string): Promise<void> {
await APIClient.exec({
method: "POST",
uri: "/family/create",
jsonData: { name: name },
});
}
}

View File

@ -9,6 +9,7 @@ interface Constraints {
mail_len: LenConstraint; mail_len: LenConstraint;
user_name_len: LenConstraint; user_name_len: LenConstraint;
password_len: LenConstraint; password_len: LenConstraint;
family_name_len: LenConstraint;
} }
interface OIDCProvider { interface OIDCProvider {

View File

@ -0,0 +1,55 @@
import React from "react";
import { TextInputDialog } from "./TextInputDialog";
import { ServerApi } from "../api/ServerApi";
import { FamilyApi } from "../api/FamilyApi";
export function CreateFamilyDialog(p: {
open: boolean;
onClose: () => void;
onCreated: () => void;
}): React.ReactElement {
const [name, setName] = React.useState("");
const [creating, setCreating] = React.useState(false);
const [error, setError] = React.useState(false);
const cancel = () => {
setName("");
setCreating(false);
setError(false);
p.onClose();
};
const createFamily = async () => {
setCreating(true);
try {
await FamilyApi.CreateFamily(name);
setName("");
p.onCreated();
} catch (e) {
console.error(e);
setError(true);
}
setCreating(false);
};
return (
<TextInputDialog
open={p.open}
title="Creation d'une famille"
text="Veuillez définir le nom que vous souhaitez donner à la famille créée :"
label="Nom de la famille"
minLen={ServerApi.Config.constraints.family_name_len.min}
maxLen={ServerApi.Config.constraints.family_name_len.max}
onClose={cancel}
onCancel={cancel}
submitButton="Créer la famille"
value={name}
onValueChange={setName}
isChecking={creating}
checkingMessage="Création en cours..."
errorIsBlocking={false}
error={error ? "Echec de la création de la famille !" : undefined}
onSubmit={createFamily}
/>
);
}

View File

@ -0,0 +1,147 @@
import {
Box,
Button,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
TextField,
} from "@mui/material";
export function TextInputDialog(p: {
/**
* Minimum amount of text that must be entered before the text can be validated
*/
minLen?: number;
/**
* Max len of text than can be entered
*/
maxLen?: number;
/**
* The title of the dialog
*/
title?: string;
/**
* The message shown above the input dialog
*/
text: string;
/**
* The label of the text field
*/
label: string;
/**
* The current value in the dialog
*/
value: string;
/**
* The type of value
*/
type?: React.HTMLInputTypeAttribute;
/**
* Function called when the value entered by the user is changed
*/
onValueChange: (v: string) => void;
/**
* Specify whether the value is being checked
*/
isChecking?: boolean;
/**
* The message shown while the value is checked
*/
checkingMessage?: string;
/**
* The text of the submit button
*/
submitButton?: string;
/**
* Callback function called when the user click on the submit button
*/
onSubmit: (value: string) => void;
/**
* Callback function called when the user click on the submit button
*/
onCancel: () => void;
/**
* Specify whether the dialog should be open or not
*/
open: boolean;
/**
* Callback function called when the user click outside the dialog
*/
onClose: () => void;
/**
* Error message to show on the input text
*/
error?: string;
/**
* If set to true (the default), when an error is set, the submit button will be disabled
*/
errorIsBlocking?: boolean;
}): React.ReactElement {
const canSubmit =
p.value.length >= (p.minLen ?? 0) &&
(p.errorIsBlocking === false || p.error === undefined);
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (!canSubmit) return;
p.onSubmit(p.value);
};
return (
<Dialog open={p.open} onClose={p.onClose}>
<Box component="form" noValidate onSubmit={handleSubmit}>
{p.title && <DialogTitle>{p.title}</DialogTitle>}
<DialogContent>
<DialogContentText>{p.text}</DialogContentText>
<TextField
disabled={p.isChecking}
autoFocus
margin="dense"
label={p.label}
type={p.type ?? "text"}
fullWidth
variant="standard"
value={p.value}
onChange={(e) => p.onValueChange(e.target.value)}
inputProps={{ maxLength: p.maxLen ?? 255 }}
error={p.error !== undefined}
helperText={p.error}
/>
{p.isChecking && (
<span>
<CircularProgress size={15} />{" "}
{p.checkingMessage ?? "Contrôle en cours..."}
</span>
)}
</DialogContent>
<DialogActions>
<Button onClick={p.onClose}>Annuler</Button>
<Button disabled={!canSubmit} type="submit">
{p.submitButton ?? "Valider"}
</Button>
</DialogActions>
</Box>
</Dialog>
);
}

View File

@ -0,0 +1,104 @@
import React from "react";
import { AsyncWidget } from "../widgets/AsyncWidget";
import { Family } from "../api/FamilyApi";
import { Button, Typography } from "@mui/material";
import { CreateFamilyDialog } from "../dialogs/CreateFamilyDialog";
export function FamiliesListRoute(): React.ReactElement {
const loadKey = React.useRef(1);
const [families, setFamilies] = React.useState<Family[] | null>(null);
const [createFamily, setCreateFamily] = React.useState(false);
const load = async () => {
// TODO : implement
setFamilies([]);
};
const reload = () => {
loadKey.current += 1;
};
const onRequestCreateFamily = async () => {
setCreateFamily(true);
};
return (
<AsyncWidget
loadKey={loadKey.current}
load={load}
errMsg="Echec du chargement de la liste des familles"
build={() => (
<>
{families!!.length === 0 ? (
<NoFamilyWidget onRequestCreateFamily={onRequestCreateFamily} />
) : (
<HasFamilysWidget onReload={reload} families={families!} />
)}
{/** Create family dialog anchor */}
<CreateFamilyDialog
open={createFamily}
onClose={() => setCreateFamily(false)}
onCreated={() => {
setCreateFamily(false);
reload();
}}
/>
</>
)}
/>
);
}
function NoFamilyWidget(p: {
onRequestCreateFamily: () => void;
}): React.ReactElement {
return (
<div style={{ flex: 1, display: "flex", alignItems: "center" }}>
<Typography variant="h3" style={{ flex: 1, textAlign: "center" }}>
Vous n'appartenez à aucune famille !
</Typography>
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<NoFamilyButton
label="Créer une famille"
onClick={p.onRequestCreateFamily}
/>
<NoFamilyButton label="Rejoindre une famille" onClick={() => {}} />
</div>
</div>
);
}
function NoFamilyButton(p: {
label: string;
onClick: () => void;
}): React.ReactElement {
return (
<Button
variant="outlined"
size="large"
style={{ width: "300px", marginBottom: "10px" }}
onClick={p.onClick}
>
{p.label}
</Button>
);
}
function HasFamilysWidget(p: {
families: Family[];
onReload: () => void;
}): React.ReactElement {
return <p>todo has families</p>;
}