Can create a family from the GUI
This commit is contained in:
		@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								geneit_app/src/api/FamilyApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								geneit_app/src/api/FamilyApi.ts
									
									
									
									
									
										Normal 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 },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										55
									
								
								geneit_app/src/dialogs/CreateFamilyDialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								geneit_app/src/dialogs/CreateFamilyDialog.tsx
									
									
									
									
									
										Normal 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}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										147
									
								
								geneit_app/src/dialogs/TextInputDialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								geneit_app/src/dialogs/TextInputDialog.tsx
									
									
									
									
									
										Normal 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>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										104
									
								
								geneit_app/src/routes/FamiliesListRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								geneit_app/src/routes/FamiliesListRoute.tsx
									
									
									
									
									
										Normal 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>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user