Can join a family

This commit is contained in:
Pierre HUBERT 2023-06-27 18:52:49 +02:00
parent 817d14ef36
commit 3721f4ba5a
6 changed files with 146 additions and 3 deletions

View File

@ -2,6 +2,14 @@ import { APIClient } from "./ApiClient";
export interface Family {} export interface Family {}
export enum JoinFamilyResult {
TooManyRequests,
InvalidCode,
AlreadyMember,
Error,
Success,
}
export class FamilyApi { export class FamilyApi {
/** /**
* Create a new family * Create a new family
@ -13,4 +21,29 @@ export class FamilyApi {
jsonData: { name: name }, jsonData: { name: name },
}); });
} }
/**
* Join an existing family
*/
static async JoinFamily(code: string): Promise<JoinFamilyResult> {
const res = await APIClient.exec({
method: "POST",
uri: "/family/join",
allowFail: true,
jsonData: { code: code },
});
if (res.status >= 200 && res.status < 300) return JoinFamilyResult.Success;
switch (res.status) {
case 429:
return JoinFamilyResult.TooManyRequests;
case 404:
return JoinFamilyResult.InvalidCode;
case 409:
return JoinFamilyResult.AlreadyMember;
default:
return JoinFamilyResult.Error;
}
}
} }

View File

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

View File

@ -39,7 +39,7 @@ export function CreateFamilyDialog(p: {
return ( return (
<TextInputDialog <TextInputDialog
open={p.open} open={p.open}
title="Creation d'une famille" title="Création d'une famille"
text="Veuillez définir le nom que vous souhaitez donner à la famille créée :" text="Veuillez définir le nom que vous souhaitez donner à la famille créée :"
label="Nom de la famille" label="Nom de la famille"
minLen={ServerApi.Config.constraints.family_name_len.min} minLen={ServerApi.Config.constraints.family_name_len.min}

View File

@ -0,0 +1,81 @@
import React from "react";
import { TextInputDialog } from "./TextInputDialog";
import { ServerApi } from "../api/ServerApi";
import { FamilyApi, JoinFamilyResult } from "../api/FamilyApi";
import { useAlert } from "../widgets/AlertDialogProvider";
export function JoinFamilyDialog(p: {
open: boolean;
onClose: () => void;
onJoined: () => void;
}): React.ReactElement {
const [code, setCode] = React.useState("");
const [joining, setJoining] = React.useState(false);
const [error, setError] = React.useState<string>();
const alert = useAlert();
const cancel = () => {
setCode("");
setJoining(false);
setError(undefined);
p.onClose();
};
const joinFamily = async () => {
setJoining(true);
setError(undefined);
try {
const res = await FamilyApi.JoinFamily(code);
switch (res) {
case JoinFamilyResult.Success:
setCode("");
p.onJoined();
await alert.alert("La famille a été rejointe avec succès !");
break;
case JoinFamilyResult.TooManyRequests:
setError("Trop de tentatives, veuillez réessayer ultérieurement...");
break;
case JoinFamilyResult.InvalidCode:
setError("Le code spécifié est invalide !");
break;
case JoinFamilyResult.AlreadyMember:
setError("Vous êtes déjà membre de cette famille !");
break;
case JoinFamilyResult.Error:
setError("Erreur lors de la tentative de jonction à la famille !");
break;
}
} catch (e) {
console.error(e);
setError("Echec de l'appel au serveur !");
}
setJoining(false);
};
return (
<TextInputDialog
open={p.open}
title="Rejoindre une famille"
text="Veuillez spécifier le code d'invitation de la famille :"
label="Code de la famille"
minLen={ServerApi.Config.constraints.invitation_code_len.min}
maxLen={ServerApi.Config.constraints.invitation_code_len.max}
onClose={cancel}
onCancel={cancel}
submitButton="Rejoindre la famille"
value={code}
onValueChange={setCode}
isChecking={joining}
checkingMessage="Jointure en cours..."
errorIsBlocking={false}
error={error}
onSubmit={joinFamily}
/>
);
}

View File

@ -3,6 +3,7 @@ import { AsyncWidget } from "../widgets/AsyncWidget";
import { Family } from "../api/FamilyApi"; import { Family } from "../api/FamilyApi";
import { Button, Typography } from "@mui/material"; import { Button, Typography } from "@mui/material";
import { CreateFamilyDialog } from "../dialogs/CreateFamilyDialog"; import { CreateFamilyDialog } from "../dialogs/CreateFamilyDialog";
import { JoinFamilyDialog } from "../dialogs/JoinFamilyDialog";
export function FamiliesListRoute(): React.ReactElement { export function FamiliesListRoute(): React.ReactElement {
const loadKey = React.useRef(1); const loadKey = React.useRef(1);
@ -10,6 +11,7 @@ export function FamiliesListRoute(): React.ReactElement {
const [families, setFamilies] = React.useState<Family[] | null>(null); const [families, setFamilies] = React.useState<Family[] | null>(null);
const [createFamily, setCreateFamily] = React.useState(false); const [createFamily, setCreateFamily] = React.useState(false);
const [joinFamily, setJoinFamily] = React.useState(false);
const load = async () => { const load = async () => {
// TODO : implement // TODO : implement
@ -24,6 +26,10 @@ export function FamiliesListRoute(): React.ReactElement {
setCreateFamily(true); setCreateFamily(true);
}; };
const onRequestJoinFamily = async () => {
setJoinFamily(true);
};
return ( return (
<AsyncWidget <AsyncWidget
loadKey={loadKey.current} loadKey={loadKey.current}
@ -32,7 +38,10 @@ export function FamiliesListRoute(): React.ReactElement {
build={() => ( build={() => (
<> <>
{families!!.length === 0 ? ( {families!!.length === 0 ? (
<NoFamilyWidget onRequestCreateFamily={onRequestCreateFamily} /> <NoFamilyWidget
onRequestCreateFamily={onRequestCreateFamily}
onRequestJoinFamily={onRequestJoinFamily}
/>
) : ( ) : (
<HasFamilysWidget onReload={reload} families={families!} /> <HasFamilysWidget onReload={reload} families={families!} />
)} )}
@ -46,6 +55,16 @@ export function FamiliesListRoute(): React.ReactElement {
reload(); reload();
}} }}
/> />
{/** Join family dialog anchor */}
<JoinFamilyDialog
open={joinFamily}
onClose={() => setJoinFamily(false)}
onJoined={() => {
setJoinFamily(false);
reload();
}}
/>
</> </>
)} )}
/> />
@ -54,6 +73,7 @@ export function FamiliesListRoute(): React.ReactElement {
function NoFamilyWidget(p: { function NoFamilyWidget(p: {
onRequestCreateFamily: () => void; onRequestCreateFamily: () => void;
onRequestJoinFamily: () => void;
}): React.ReactElement { }): React.ReactElement {
return ( return (
<div style={{ flex: 1, display: "flex", alignItems: "center" }}> <div style={{ flex: 1, display: "flex", alignItems: "center" }}>
@ -74,7 +94,10 @@ function NoFamilyWidget(p: {
onClick={p.onRequestCreateFamily} onClick={p.onRequestCreateFamily}
/> />
<NoFamilyButton label="Rejoindre une famille" onClick={() => {}} /> <NoFamilyButton
label="Rejoindre une famille"
onClick={p.onRequestJoinFamily}
/>
</div> </div>
</div> </div>
); );

View File

@ -23,6 +23,7 @@ pub struct StaticConstraints {
pub user_name_len: SizeConstraint, pub user_name_len: SizeConstraint,
pub password_len: SizeConstraint, pub password_len: SizeConstraint,
pub family_name_len: SizeConstraint, pub family_name_len: SizeConstraint,
pub invitation_code_len: SizeConstraint,
} }
impl Default for StaticConstraints { impl Default for StaticConstraints {
@ -32,6 +33,10 @@ impl Default for StaticConstraints {
user_name_len: SizeConstraint::new(3, 30), user_name_len: SizeConstraint::new(3, 30),
password_len: SizeConstraint::new(8, 255), password_len: SizeConstraint::new(8, 255),
family_name_len: SizeConstraint::new(3, 30), family_name_len: SizeConstraint::new(3, 30),
invitation_code_len: SizeConstraint::new(
FAMILY_INVITATION_CODE_LEN,
FAMILY_INVITATION_CODE_LEN,
),
} }
} }
} }