diff --git a/src/components/dashboard/UsersManagement.tsx b/src/components/dashboard/UsersManagement.tsx index 5a59e04..7bae268 100644 --- a/src/components/dashboard/UsersManagement.tsx +++ b/src/components/dashboard/UsersManagement.tsx @@ -20,6 +20,7 @@ import useChangeRightsRPC from "@hooks/backend/userService/useChangeRightsRPC"; import useDeleteUserRPC from "@hooks/backend/userService/useDeleteUserRPC"; import DeleteIcon from "@mui/icons-material/Delete"; import { RpcError } from "@protobuf-ts/runtime-rpc"; +import { useTranslation } from 'react-i18next'; interface User { email: string; @@ -29,6 +30,7 @@ interface User { } const UsersManagement: React.FC = () => { + const { t } = useTranslation(); const [selectedRights, setSelectedRights] = useState>( {}, ); @@ -57,14 +59,14 @@ const UsersManagement: React.FC = () => { ...prevSelectedRights, [email]: e.target.value as string, })); - const admin = e.target.value === "Administrateur"; // Cette ligne transforme la valeur en un booléen + const admin = e.target.value === "Administrateur"; await changeRightsClick(email, admin); }} sx={{ width: "150px" }} label="Changer les droitas" > - Utilisateur - Administrateur + User + Admin ); @@ -157,17 +159,17 @@ const UsersManagement: React.FC = () => { return ( - Gestion des utilisateurs + {t('usersManagement.title')} - Inviter un nouvel utilisateur + {t('usersManagement.inviteSection')} { - Liste des utilisateurs + {t('usersManagement.userList')} diff --git a/src/hooks/backend/userService/useFetchHistoryRPC.ts b/src/hooks/backend/userService/useFetchHistoryRPC.ts new file mode 100644 index 0000000..17d68a7 --- /dev/null +++ b/src/hooks/backend/userService/useFetchHistoryRPC.ts @@ -0,0 +1,23 @@ +import React from "react"; +import { transport } from "../../../environment"; +import { HistoryClient } from "@protos/history.client"; +import { HistoryRequest } from "@protos/history"; +import AuthContext from "@contexts/AuthContext"; + +const useFetchHistoryRPC = () => { + const client = React.useMemo(() => new HistoryClient(transport), []); + const { token } = React.useContext(AuthContext); + + const fetchHistory = React.useCallback(async () => { + const request: HistoryRequest = HistoryRequest.create({}); + return await client.getHistory(request, { + meta: { Authorization: `Bearer ${token}` }, + }); + }, [client, token]); + + return { + fetchHistory, + }; +}; + +export default useFetchHistoryRPC; diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 44597d8..d55b8cb 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -91,8 +91,8 @@ "description": "Description", "safe": "Safe", "danger": "Danger", - "dangerHigh": "High danger level", - "dangerMedium": "Medium danger level", + "dangerHigh": "He changed his email", + "dangerMedium": "He logged into his account", "dangerLow": "Low danger level" }, "helpModal": { @@ -109,6 +109,28 @@ "danger": "Danger" }, "AuthContext": { - "logout": "You have been successfully logged out " + "logout": "You have been successfully logged out " + }, + "invitationSignup": { + "choosePassword": "Choose your new password", + "accountCreated": "Your account has been successfully created", + "passwordLabel": "Password", + "usedInvitation": "Invitation already used!", + "createAccount": "Create an account" + }, + "usersManagement": { + "title": "User Management", + "inviteUser": "Invite a new user", + "inviteSection": "Invite a new user", + "emailLabel": "Email new user", + "sendInvitation": "Send Invitation", + "userList": "List of users", + "activated": "Activated", + "pendingActivation": "Pending Activation", + "changeRightsError": "An error occurred while changing user rights.", + "deleteUserError": "An error occurred while deleting the user.", + "inviteSuccess": "User successfully invited!", + "inviteError": "An error occurred while inviting the user.", + "alreadyExists": "An account with this email already exists." } } diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index 3f92133..9a97a3b 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -91,8 +91,8 @@ "description": "Description", "safe": "Sûr", "danger": "Dangereux", - "dangerHigh": "Niveau de danger : élevé", - "dangerMedium": "Niveau de danger : moyen", + "dangerHigh": "Il a change son mail", + "dangerMedium": "Il s'est connecté à son compte", "dangerLow": "Niveau de danger : faible" }, "helpModal": { @@ -110,5 +110,27 @@ }, "AuthContext": { "logout": "Vous avez été déconnecté avec succès" + }, + "invitationSignup": { + "choosePassword": "Choisissez votre nouveau mot de passe", + "accountCreated": "Votre compte a été correctement créé", + "passwordLabel": "Mot de passe", + "usedInvitation": "Invitation déjà utilisée!", + "createAccount": "Créer un compte" + }, + "usersManagement": { + "title": "Gestion des utilisateurs", + "inviteUser": "Inviter un nouvel utilisateur", + "inviteSection": "Inviter un nouvel utilisateur", + "emailLabel": "Email nouvel utilisateur", + "sendInvitation": "Envoyer l'invitation", + "userList": "Liste des utilisateurs", + "activated": "Activé", + "pendingActivation": "En attente d'activation", + "changeRightsError": "Une erreur s'est produite lors du changement de droit de l'utilisateur.", + "deleteUserError": "Une erreur s'est produite lors de la suppression de l'utilisateur.", + "inviteSuccess": "Utilisateur invité avec succès!", + "inviteError": "Une erreur s'est produite lors de l'invitation de l'utilisateur.", + "alreadyExists": "Un compte avec cet email existe déjà." } } diff --git a/src/pages/HistoryPage.tsx b/src/pages/HistoryPage.tsx new file mode 100644 index 0000000..790e21a --- /dev/null +++ b/src/pages/HistoryPage.tsx @@ -0,0 +1,105 @@ +import React, { useState, useEffect, ChangeEvent } from 'react'; +import {Typography, TextField, Chip, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import ErrorIcon from '@mui/icons-material/Error'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import { useTranslation } from 'react-i18next'; +import useFetchHistoryRPC from '../hooks/backend/userService/useFetchHistoryRPC'; +import { CircularProgress } from '@mui/material'; + + +interface Action { + id: number; + user: string; + type: 'danger' | 'safe'; + dangerKey: string; + date: string; + time: string; + emailChanged?: boolean; +} + +const HistoryPage: React.FC = () => { + const [actions, setActions] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + const { t } = useTranslation(); + const { fetchHistory } = useFetchHistoryRPC(); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchData = () => { + setTimeout(() => { + const data: Action[] = [ + { id: 1, user: 'atiteux@dev-id.fr', type: 'safe', dangerKey: 'dangerMedium', date: '21-09-2023', time: '09:43' }, + { id: 2, user: 'atiteuxtest@dev-id.fr', type: 'safe', dangerKey: 'dangerHigh', date: '20-09-2023', time: '15:43' }, + ]; + + data.sort((a, b) => new Date(b.date + 'T' + b.time).getTime() - new Date(a.date + 'T' + a.time).getTime()); + setActions(data); + setLoading(false); + }, 2000); +}; +fetchData(); +}, []); +if (loading) { + return ; +} + return ( + +
+ + {t('historyPage.title')} + +
+ + ) => setSearchTerm(event.target.value)} + /> +
+ + + + + {t('historyPage.date')} + {t('historyPage.time')} + {t('historyPage.type')} + {t('historyPage.user')} + {t('historyPage.description')} + + + + {actions + .filter(action => action.user.includes(searchTerm) || action.type.includes(searchTerm)) + .map(action => ( + + {action.date} + {action.time} + + + : + + } + label={t(`historyPage.${action.type}`)} + style={{ + backgroundColor: action.type === 'safe' ? 'green' : 'red', + color: 'white', + }} + /> + + {action.user} + {t(`historyPage.${action.dangerKey}`)} + + ))} + +
+
+ ); +}; + +export default HistoryPage; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index b03ba73..9675267 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -8,6 +8,8 @@ import BlockManager from '@components/dashboard/BlockManager'; import Others from '@components/dashboard/Others'; import { useTranslation } from "react-i18next"; import UsersManagement from '../components/dashboard/UsersManagement'; +import History from '../pages/HistoryPage'; + const HomePage = () => { const { isLoggedIn, logout } = useContext(AuthContext); @@ -51,6 +53,8 @@ const HomePage = () => { return (
); case 'usersManagement': return() + case 'history': + return (); default: } @@ -65,7 +69,8 @@ const HomePage = () => {
  • handleMenuClick('ipManagement')}>{t('homePage.ipManagement')}
  • handleMenuClick('containerManager')}>{t('homePage.containerManager')}
  • handleMenuClick('incomingConnections')}>{t('homePage.incomingConnections')}
  • -
  • handleMenuClick('usersManagement')}>Gestion des utilisateurs
  • +
  • handleMenuClick('usersManagement')}>{t('usersManagement.title')}
  • +
  • handleMenuClick('history')}>{t('homePage.history')}
  • handleMenuClick('otherFeatures')}>{t('homePage.otherFeatures')}
  • diff --git a/src/pages/Invitation.tsx b/src/pages/Invitation.tsx index ebe5552..298a256 100644 --- a/src/pages/Invitation.tsx +++ b/src/pages/Invitation.tsx @@ -19,13 +19,13 @@ const InvitationSignup: React.FC = () => { const signIn = async () => { try { - setError(null); // Clear any previous errors before attempting the operation again + setError(null); const loginToken = await activateUser(activationToken, password); loginWithToken(loginToken); - toast.success(t("loginPage.loginSuccess")); + toast.success(t('loginPage.loginSuccess')); setSubmitted(true); } catch (err) { - setError("Invitation déjà utilisée!"); // Set the error state + setError(t('invitationSignup.usedInvitation')); // Set the error state } }; @@ -33,14 +33,14 @@ const InvitationSignup: React.FC = () => { - Choisissez votre nouveau mot de passe - {submitted && (Votre compte a été correctement créé)} + {t('invitationSignup.choosePassword')} + {submitted && ({t('invitationSignup.accountCreated')})} {!submitted && ( <> setPassword(e.target.value)} @@ -49,7 +49,7 @@ const InvitationSignup: React.FC = () => { {error} // Display error inline )} )} diff --git a/src/pages/save b/src/pages/save new file mode 100644 index 0000000..e69de29 diff --git a/src/styles.css b/src/styles.css index 3481343..affa45b 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,8 +1,51 @@ +/* +===================================== +STYLES GLOBAUX +===================================== +*/ + body { font-family: 'Roboto', sans-serif; background-color: #f0f2f5; } +.button { + background-color: #2c98f0; + border: none; + color: #ffffff; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + margin-top: 20px; + width: 100%; +} + +.error-message { + color: red; +} + +.dark-mode { + background-color: #333; + color: white; +} + +.dark-mode input, +.dark-mode button { + background-color: #555; + color: white; +} + +.dark-mode input::placeholder { + color: rgba(255, 255, 255, 0.5); +} + +/* +===================================== +STYLES DU FORMULAIRE +===================================== +*/ + .form-container { background-color: #ffffff; padding: 30px; @@ -54,6 +97,12 @@ body { text-align: center; } +/* +===================================== +STYLES DE LA PAGE D'ACCUEIL +===================================== +*/ + .home-container { display: flex; margin: 20px; @@ -169,7 +218,11 @@ body { font-weight: bold; } -/* Page Profile */ +/* +===================================== +STYLES DE LA PAGE PROFILE +===================================== +*/ .profile-image { width: 150px; @@ -196,33 +249,11 @@ body { cursor: pointer; } -.dark-mode { - background-color: #333; - color: white; -} - -.dark-mode input, -.dark-mode button { - background-color: #555; - color: white; -} - -.dark-mode input::placeholder { - color: rgba(255, 255, 255, 0.5); -} - - .button { - background-color: #2c98f0; - border: none; - color: #ffffff; - padding: 10px 20px; - border-radius: 5px; - cursor: pointer; - font-size: 14px; - margin-top: 20px; - width: 100%; - } -/* ContainerManager */ +/* +===================================== +STYLES DU CONTAINER MANAGER +===================================== +*/ .container-manager { padding: 20px; @@ -246,7 +277,69 @@ body { margin-bottom: 10px; } -/* message erreur login */ -.error-message { - color: red; + +/* +===================================== +STYLES DE LA PAGE HISTORIQUE +===================================== +*/ + + +.history-page { + display: flex; + flex-direction: column; + gap: 20px; + padding: 20px; + background-color: #f0f2f5; +} + +.title { + color: #003061; + font-weight: bold; + text-align: center; +} + +.history-card { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 15px; + margin-bottom: 15px; + border-radius: 5px; + box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15); +} + +.history-card:nth-child(even) { + background-color: #f8f9fa; +} + +.history-card:nth-child(odd) { + background-color: #ffffff; +} + +.ip-address { + font-size: 1.25em; + color: #212529; +} + +.ip-time { + font-size: 1em; + color: #6c757d; +} + +.ip-status { + font-size: 0.875em; + font-weight: 500; + color: #fff; + padding: 8px 12px; + border-radius: 20px; +} + +.ip-status-safe { + background-color: #28a745; +} + +.ip-status-danger { + background-color: #dc3545; } \ No newline at end of file