Can update profile information
This commit is contained in:
		
							
								
								
									
										11
									
								
								geneit_app/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								geneit_app/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -22,6 +22,7 @@
 | 
				
			|||||||
        "@types/node": "^16.18.34",
 | 
					        "@types/node": "^16.18.34",
 | 
				
			||||||
        "@types/react": "^18.2.8",
 | 
					        "@types/react": "^18.2.8",
 | 
				
			||||||
        "@types/react-dom": "^18.2.4",
 | 
					        "@types/react-dom": "^18.2.4",
 | 
				
			||||||
 | 
					        "date-and-time": "^3.0.1",
 | 
				
			||||||
        "jotai": "^2.1.1",
 | 
					        "jotai": "^2.1.1",
 | 
				
			||||||
        "react": "^18.2.0",
 | 
					        "react": "^18.2.0",
 | 
				
			||||||
        "react-dom": "^18.2.0",
 | 
					        "react-dom": "^18.2.0",
 | 
				
			||||||
@@ -6846,6 +6847,11 @@
 | 
				
			|||||||
        "node": ">=10"
 | 
					        "node": ">=10"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/date-and-time": {
 | 
				
			||||||
 | 
					      "version": "3.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Az1hy3IPuh7LR4duPGlgkUzZXtjoWH8pd+XVLpLXMNd+DD48sRdcsWIPbqdHCXg4izb9lPzrKtqUXPXevS9g4Q=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/debug": {
 | 
					    "node_modules/debug": {
 | 
				
			||||||
      "version": "4.3.4",
 | 
					      "version": "4.3.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
 | 
				
			||||||
@@ -22546,6 +22552,11 @@
 | 
				
			|||||||
        "whatwg-url": "^8.0.0"
 | 
					        "whatwg-url": "^8.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "date-and-time": {
 | 
				
			||||||
 | 
					      "version": "3.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-Az1hy3IPuh7LR4duPGlgkUzZXtjoWH8pd+XVLpLXMNd+DD48sRdcsWIPbqdHCXg4izb9lPzrKtqUXPXevS9g4Q=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "debug": {
 | 
					    "debug": {
 | 
				
			||||||
      "version": "4.3.4",
 | 
					      "version": "4.3.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@
 | 
				
			|||||||
    "@types/node": "^16.18.34",
 | 
					    "@types/node": "^16.18.34",
 | 
				
			||||||
    "@types/react": "^18.2.8",
 | 
					    "@types/react": "^18.2.8",
 | 
				
			||||||
    "@types/react-dom": "^18.2.4",
 | 
					    "@types/react-dom": "^18.2.4",
 | 
				
			||||||
 | 
					    "date-and-time": "^3.0.1",
 | 
				
			||||||
    "jotai": "^2.1.1",
 | 
					    "jotai": "^2.1.1",
 | 
				
			||||||
    "react": "^18.2.0",
 | 
					    "react": "^18.2.0",
 | 
				
			||||||
    "react-dom": "^18.2.0",
 | 
					    "react-dom": "^18.2.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,8 +47,7 @@ export class APIClient {
 | 
				
			|||||||
    // Handle expired tokens
 | 
					    // Handle expired tokens
 | 
				
			||||||
    if (res.status === 412) {
 | 
					    if (res.status === 412) {
 | 
				
			||||||
      AuthApi.RemoveAuthToken();
 | 
					      AuthApi.RemoveAuthToken();
 | 
				
			||||||
      // eslint-disable-next-line no-self-assign
 | 
					      window.location.href = "/";
 | 
				
			||||||
      window.location.href = window.location.href;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!args.allowFail && !res.ok)
 | 
					    if (!args.allowFail && !res.ok)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,4 +23,17 @@ export class UserApi {
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
    ).data;
 | 
					    ).data;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Update user profile
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async UpdateProfile(name: string): Promise<void> {
 | 
				
			||||||
 | 
					    await APIClient.exec({
 | 
				
			||||||
 | 
					      uri: "/user/update_profile",
 | 
				
			||||||
 | 
					      method: "POST",
 | 
				
			||||||
 | 
					      jsonData: {
 | 
				
			||||||
 | 
					        name: name,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,129 @@
 | 
				
			|||||||
import React, { useRef } from "react";
 | 
					import React, { useRef } from "react";
 | 
				
			||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
				
			||||||
import { User, UserApi } from "../api/UserApi";
 | 
					import { User, UserApi } from "../api/UserApi";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Alert,
 | 
				
			||||||
 | 
					  Box,
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  Card,
 | 
				
			||||||
 | 
					  CardActions,
 | 
				
			||||||
 | 
					  CardContent,
 | 
				
			||||||
 | 
					  Checkbox,
 | 
				
			||||||
 | 
					  FormControlLabel,
 | 
				
			||||||
 | 
					  TextField,
 | 
				
			||||||
 | 
					  Typography,
 | 
				
			||||||
 | 
					} from "@mui/material";
 | 
				
			||||||
 | 
					import { TimeWidget, formatDate } from "../widgets/TimeWidget";
 | 
				
			||||||
 | 
					import { ServerApi } from "../api/ServerApi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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);
 | 
				
			||||||
 | 
					  const [newName, setNewName] = React.useState("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [error, setError] = React.useState<string | null>(null);
 | 
				
			||||||
 | 
					  const [success, setSuccess] = React.useState<string | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const load = async () => {
 | 
					  const load = async () => {
 | 
				
			||||||
    setUser(await UserApi.GetUserInfo());
 | 
					    const u = await UserApi.GetUserInfo();
 | 
				
			||||||
 | 
					    setUser(u);
 | 
				
			||||||
 | 
					    setNewName(u.name);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const counter = useRef(0);
 | 
					  const counter = useRef(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const updateProfile = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      setSuccess(null);
 | 
				
			||||||
 | 
					      setError(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await UserApi.UpdateProfile(newName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      counter.current += 1;
 | 
				
			||||||
 | 
					      setSuccess("Informations du profil mises à jour avec succès !");
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.error(e);
 | 
				
			||||||
 | 
					      setError("Echec de la mise à jour du profil !");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <AsyncWidget
 | 
					    <AsyncWidget
 | 
				
			||||||
      loadKey={counter.current}
 | 
					      loadKey={counter.current}
 | 
				
			||||||
      load={load}
 | 
					      load={load}
 | 
				
			||||||
      errMsg="Echec du chargement des informations du compte utilisateur !"
 | 
					      errMsg="Echec du chargement des informations du compte utilisateur !"
 | 
				
			||||||
      build={() => (
 | 
					      build={() => (
 | 
				
			||||||
        <>
 | 
					        <div style={{ maxWidth: "500px", margin: "auto" }}>
 | 
				
			||||||
          <p>ready !!! {user!.name}</p>
 | 
					          <Typography variant="h3">Profil</Typography>
 | 
				
			||||||
        </>
 | 
					
 | 
				
			||||||
 | 
					          {error && <Alert severity="error">{error}</Alert>}
 | 
				
			||||||
 | 
					          {success && <Alert severity="success">{success}</Alert>}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <Card style={{ marginTop: "10px" }}>
 | 
				
			||||||
 | 
					            <CardContent>
 | 
				
			||||||
 | 
					              <Typography gutterBottom variant="h5" component="div">
 | 
				
			||||||
 | 
					                Paramètres du compte
 | 
				
			||||||
 | 
					              </Typography>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <Box
 | 
				
			||||||
 | 
					                component="form"
 | 
				
			||||||
 | 
					                sx={{
 | 
				
			||||||
 | 
					                  "& .MuiTextField-root": { m: 1 },
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                noValidate
 | 
				
			||||||
 | 
					                autoComplete="off"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <TextField
 | 
				
			||||||
 | 
					                  disabled
 | 
				
			||||||
 | 
					                  fullWidth
 | 
				
			||||||
 | 
					                  label="Identifiant"
 | 
				
			||||||
 | 
					                  value={user?.id}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <TextField
 | 
				
			||||||
 | 
					                  disabled
 | 
				
			||||||
 | 
					                  fullWidth
 | 
				
			||||||
 | 
					                  label="Création du compte"
 | 
				
			||||||
 | 
					                  value={formatDate(user!.time_create)}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <TextField
 | 
				
			||||||
 | 
					                  disabled
 | 
				
			||||||
 | 
					                  fullWidth
 | 
				
			||||||
 | 
					                  label="Activation du compte"
 | 
				
			||||||
 | 
					                  value={formatDate(user!.time_activate)}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <TextField
 | 
				
			||||||
 | 
					                  disabled
 | 
				
			||||||
 | 
					                  fullWidth
 | 
				
			||||||
 | 
					                  label="Adresse mail"
 | 
				
			||||||
 | 
					                  value={user?.email}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <TextField
 | 
				
			||||||
 | 
					                  fullWidth
 | 
				
			||||||
 | 
					                  label="Nom d'utilisateur"
 | 
				
			||||||
 | 
					                  value={newName}
 | 
				
			||||||
 | 
					                  onChange={(e) => setNewName(e.target.value)}
 | 
				
			||||||
 | 
					                  inputProps={{
 | 
				
			||||||
 | 
					                    maxLength: ServerApi.Config.constraints.user_name_len.max,
 | 
				
			||||||
 | 
					                  }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <FormControlLabel
 | 
				
			||||||
 | 
					                  disabled
 | 
				
			||||||
 | 
					                  control={<Checkbox checked={user!.admin} />}
 | 
				
			||||||
 | 
					                  label="Compte administrateur"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </Box>
 | 
				
			||||||
 | 
					            </CardContent>
 | 
				
			||||||
 | 
					            <CardActions>
 | 
				
			||||||
 | 
					              <Button onClick={updateProfile} style={{ marginLeft: "auto" }}>
 | 
				
			||||||
 | 
					                Enregistrer
 | 
				
			||||||
 | 
					              </Button>
 | 
				
			||||||
 | 
					            </CardActions>
 | 
				
			||||||
 | 
					          </Card>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										63
									
								
								geneit_app/src/widgets/TimeWidget.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								geneit_app/src/widgets/TimeWidget.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					import { Tooltip } from "@mui/material";
 | 
				
			||||||
 | 
					import date from "date-and-time";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function formatDate(time: number): string {
 | 
				
			||||||
 | 
					  const t = new Date();
 | 
				
			||||||
 | 
					  t.setTime(1000 * time);
 | 
				
			||||||
 | 
					  return date.format(t, "DD/MM/YYYY HH:mm:ss");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function timeDiff(a: number, b: number): string {
 | 
				
			||||||
 | 
					  let diff = b - a;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (diff === 0) return "maintenant";
 | 
				
			||||||
 | 
					  if (diff === 1) return "1 seconde";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (diff < 60) {
 | 
				
			||||||
 | 
					    return `${diff} secondes`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  diff = Math.floor(diff / 60);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (diff === 1) return "1 minute";
 | 
				
			||||||
 | 
					  if (diff < 24) {
 | 
				
			||||||
 | 
					    return `${diff} minutes`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  diff = Math.floor(diff / 60);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (diff === 1) return "1 heure";
 | 
				
			||||||
 | 
					  if (diff < 24) {
 | 
				
			||||||
 | 
					    return `${diff} heures`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const diffDays = Math.floor(diff / 24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (diffDays === 1) return "1 jour";
 | 
				
			||||||
 | 
					  if (diffDays < 31) {
 | 
				
			||||||
 | 
					    return `${diffDays} jours`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  diff = Math.floor(diffDays / 31);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (diff < 12) {
 | 
				
			||||||
 | 
					    return `${diff} mois`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const diffYears = Math.floor(diffDays / 365);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (diffYears === 1) return "1 an";
 | 
				
			||||||
 | 
					  return `${diffYears} ans`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function timeDiffFromNow(time: number): string {
 | 
				
			||||||
 | 
					  return timeDiff(time, Math.floor(new Date().getTime() / 1000));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function TimeWidget(p: { time: number }): React.ReactElement {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Tooltip title={formatDate(p.time)}>
 | 
				
			||||||
 | 
					      <span>{timeDiffFromNow(p.time)}</span>
 | 
				
			||||||
 | 
					    </Tooltip>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user