diff --git a/geneit_app/src/App.tsx b/geneit_app/src/App.tsx
index 9c8085e..3543794 100644
--- a/geneit_app/src/App.tsx
+++ b/geneit_app/src/App.tsx
@@ -12,6 +12,7 @@ import { ResetPasswordRoute } from "./routes/auth/ResetPasswordRoute";
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
import { BaseLoginPage } from "./widgets/BaseLoginpage";
import { DeleteAccountRoute } from "./routes/DeleteAccountRoute";
+import { FamiliesListRoute } from "./routes/FamiliesListRoute";
interface AuthContext {
signedIn: boolean;
@@ -38,6 +39,7 @@ export function App(): React.ReactElement {
{signedIn ? (
}>
+ } />
} />
} />
diff --git a/geneit_app/src/api/FamilyApi.ts b/geneit_app/src/api/FamilyApi.ts
new file mode 100644
index 0000000..a79e99e
--- /dev/null
+++ b/geneit_app/src/api/FamilyApi.ts
@@ -0,0 +1,16 @@
+import { APIClient } from "./ApiClient";
+
+export interface Family {}
+
+export class FamilyApi {
+ /**
+ * Create a new family
+ */
+ static async CreateFamily(name: string): Promise {
+ await APIClient.exec({
+ method: "POST",
+ uri: "/family/create",
+ jsonData: { name: name },
+ });
+ }
+}
diff --git a/geneit_app/src/api/ServerApi.ts b/geneit_app/src/api/ServerApi.ts
index a16fbec..274dcc5 100644
--- a/geneit_app/src/api/ServerApi.ts
+++ b/geneit_app/src/api/ServerApi.ts
@@ -9,6 +9,7 @@ interface Constraints {
mail_len: LenConstraint;
user_name_len: LenConstraint;
password_len: LenConstraint;
+ family_name_len: LenConstraint;
}
interface OIDCProvider {
diff --git a/geneit_app/src/dialogs/CreateFamilyDialog.tsx b/geneit_app/src/dialogs/CreateFamilyDialog.tsx
new file mode 100644
index 0000000..c2bbb1f
--- /dev/null
+++ b/geneit_app/src/dialogs/CreateFamilyDialog.tsx
@@ -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 (
+
+ );
+}
diff --git a/geneit_app/src/dialogs/TextInputDialog.tsx b/geneit_app/src/dialogs/TextInputDialog.tsx
new file mode 100644
index 0000000..a1309c4
--- /dev/null
+++ b/geneit_app/src/dialogs/TextInputDialog.tsx
@@ -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) => {
+ event.preventDefault();
+ if (!canSubmit) return;
+
+ p.onSubmit(p.value);
+ };
+
+ return (
+
+ );
+}
diff --git a/geneit_app/src/routes/FamiliesListRoute.tsx b/geneit_app/src/routes/FamiliesListRoute.tsx
new file mode 100644
index 0000000..772dd6f
--- /dev/null
+++ b/geneit_app/src/routes/FamiliesListRoute.tsx
@@ -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(null);
+
+ const [createFamily, setCreateFamily] = React.useState(false);
+
+ const load = async () => {
+ // TODO : implement
+ setFamilies([]);
+ };
+
+ const reload = () => {
+ loadKey.current += 1;
+ };
+
+ const onRequestCreateFamily = async () => {
+ setCreateFamily(true);
+ };
+
+ return (
+ (
+ <>
+ {families!!.length === 0 ? (
+
+ ) : (
+
+ )}
+
+ {/** Create family dialog anchor */}
+ setCreateFamily(false)}
+ onCreated={() => {
+ setCreateFamily(false);
+ reload();
+ }}
+ />
+ >
+ )}
+ />
+ );
+}
+
+function NoFamilyWidget(p: {
+ onRequestCreateFamily: () => void;
+}): React.ReactElement {
+ return (
+
+
+ Vous n'appartenez à aucune famille !
+
+
+
+
+
+ {}} />
+
+
+ );
+}
+
+function NoFamilyButton(p: {
+ label: string;
+ onClick: () => void;
+}): React.ReactElement {
+ return (
+
+ );
+}
+
+function HasFamilysWidget(p: {
+ families: Family[];
+ onReload: () => void;
+}): React.ReactElement {
+ return todo has families
;
+}