From d98f97097638d0ad0f1d13c02bb50527b340b3e6 Mon Sep 17 00:00:00 2001 From: Khadija Tagemouati Date: Sat, 5 Nov 2022 14:17:01 +0100 Subject: [PATCH 01/47] =?UTF-8?q?Traduction=20des=20=C3=A9lections?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/backend/src/Server.ts | 1 + .../src/components/utils/FillElectionInfo.tsx | 6 + web/frontend/src/index.tsx | 49 ++-- web/frontend/src/language/de.json | 260 +++++++++++++++++- web/frontend/src/language/en.json | 11 +- web/frontend/src/language/fr.json | 260 +++++++++++++++++- .../pages/ballot/components/BallotDisplay.tsx | 15 +- web/frontend/src/pages/election/Show.tsx | 4 +- .../election/components/AddQuestionModal.tsx | 160 +++++++---- .../election/components/ElectionForm.tsx | 54 +++- .../election/components/SubjectComponent.tsx | 59 +++- .../components/utils/useQuestionForm.ts | 37 ++- web/frontend/src/types/.prettierrc | 7 + web/frontend/src/types/JSONparser.ts | 50 +++- web/frontend/src/types/configuration.ts | 15 + web/frontend/src/types/getObjectType.ts | 23 +- 16 files changed, 907 insertions(+), 104 deletions(-) create mode 100644 web/frontend/src/types/.prettierrc diff --git a/web/backend/src/Server.ts b/web/backend/src/Server.ts index 525b0d5ce..e3d01bd49 100644 --- a/web/backend/src/Server.ts +++ b/web/backend/src/Server.ts @@ -116,6 +116,7 @@ app.get('/api/control_key', (req, res) => { const role = usersDB.get(sciper) || ''; + req.session.userid = parseInt(sciper, 10); req.session.lastname = lastname; req.session.firstname = firstname; diff --git a/web/frontend/src/components/utils/FillElectionInfo.tsx b/web/frontend/src/components/utils/FillElectionInfo.tsx index 32f4ab50a..23f98e5f1 100644 --- a/web/frontend/src/components/utils/FillElectionInfo.tsx +++ b/web/frontend/src/components/utils/FillElectionInfo.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { ID } from 'types/configuration'; import { ElectionInfo, LightElectionInfo, Results, Status } from 'types/election'; +import { election } from './Endpoints'; const useFillElectionInfo = (electionData: ElectionInfo) => { const [id, setId] = useState(''); @@ -16,6 +17,10 @@ const useFillElectionInfo = (electionData: ElectionInfo) => { useEffect(() => { if (electionData !== null) { + const title = JSON.parse(electionData.Configuration.MainTitle); + electionData.Configuration.TitleEn = title.en; + electionData.Configuration.TitleFr = title.fr; + electionData.Configuration.TitleDe = title.de; setId(electionData.ElectionID); setStatus(electionData.Status); setPubKey(electionData.Pubkey); @@ -26,6 +31,7 @@ const useFillElectionInfo = (electionData: ElectionInfo) => { setConfigObj(electionData.Configuration); setVoters(electionData.Voters); + if (electionData.Result.length > 0) { setIsResultSet(true); } diff --git a/web/frontend/src/index.tsx b/web/frontend/src/index.tsx index 6f2f9d518..eccfeae33 100644 --- a/web/frontend/src/index.tsx +++ b/web/frontend/src/index.tsx @@ -7,6 +7,8 @@ import App from 'layout/App'; import reportWebVitals from 'reportWebVitals'; import ShortUniqueId from 'short-unique-id'; +import { useTranslation } from 'react-i18next'; + import * as endpoints from 'components/utils/Endpoints'; const flashTimeout = 4000; @@ -104,30 +106,33 @@ export const ProxyContext = createContext(defaultProxyState); // A small elements to display that the page is loading, should be something // more elegant in the future and be its own component. -const Loading: FC = () => ( -
-
-
- - - - +const Loading: FC = () => { + const { t } = useTranslation(); + return ( +
+
+
+ + + + +
+

{t('loading')}

-

App is loading...

-
-); + ); +}; const Failed: FC = ({ children }) => (
diff --git a/web/frontend/src/language/de.json b/web/frontend/src/language/de.json index 85b497987..1c9b2acd2 100644 --- a/web/frontend/src/language/de.json +++ b/web/frontend/src/language/de.json @@ -4,6 +4,264 @@ "about": "This is the About text", "en": "🇺🇸 Englisch", "fr": "🇫🇷 Französisch", - "de": "🇩🇪 Deutsch" + "de": "🇩🇪 Deutsch", + "navBarStatus": "Umfragen", + "navBarHome" : "Homepage", + "navBarCreate" : "Erstellen", + "vote":"Abstimmung", + "elections": "Umfragen", + "navBarResult": "Ergebnisse", + "navBarAbout": "Über", + "navBarAdmin": "Admin", + "admin": "Admin", + "previous": "Vorherige", + "next": "Nächster", + "confirmDeleteUserSciper": "Bestätigen Sie das Löschen der Rolle für den Benutzer sciper", + "404Title": "Seite nicht gefunden", + "403Title": "Verbotene Seite", + "401Title": "unbefugt Seite", + "404Description": "The page you are looking for does not exist.", + "403Description": "You are not authorized to access this page.", + "goHome": "Go to home page", + "results": "results", + "showing": "Showing", + "saveQuestion": "Save", + "addRank": "Add rank", + "editrank": "Edit rank", + "removerank": "Remove rank", + "addSelect": "Add select", + "editselect": "Edit select", + "removeselect": "Remove select", + "addText": "Add text", + "edittext": "Edit text", + "removetext": "Remove text", + "subject": "Subject", + "choices": "Choices", + "answers": "Answers", + "enterMaxLength": "Enter the MaxLength", + "maxChoices": "Max number of choices", + "minChoices": "Min number of choices", + "enterMinN": "Enter the MinN", + "enterMaxN": "Enter the MaxN", + "enterRegex": "Enter your regex", + "enterTitle": "Enter your Title", + "mainProperties": "Main properties", + "additionalProperties": "Additional properties", + "removeSubject": "Remove subject", + "addSubject": "Add subject", + "addQuestionrank": "Rank", + "addQuestionselect": "Select", + "addQuestiontext": "Text", + "importFile": "Import JSON file", + "enterSciper": "Please give the sciper of the user", + "adminDetails": "Add or remove roles of users from the admin table", + "navBarCreateElection": "Create election", + "homeTitle": "Welcome to our e-voting platform!", + "homeWhatsNew": "What's new", + "homeJustShippedVersion": "Just shipped version", + "homeText": "Use the navigation bar above to reach the page you want.", + "loginText": "You need to login to access the content of {{from}}", + "notLoggedInActionText1": "You need to ", + "notLoggedInActionText2": "login", + "notLoggedInActionText3": " to perform these actions.", + "loginCallback": "We are proceeding with the authentication. You should be redirected...", + "logout": "Logout", + "namePlaceHolder": "Enter the name", + "addCandidate": "Add a candidate", + "addUser": "Add user", + "role": "Role", + "roles": "Roles", + "edit": "Edit", + "nothingToAdd": "There is nothing to add.", + "duplicateCandidate": "This candidate has already been added.", + "add": "Add", + "exportJSON": "Export as JSON", + "delete": "Delete", + "combineShares": "Combine shares", + "createElec": "Create election", + "clearElection": "Clear election", + "elecName": "Election title", + "confirmRemovesubject": "Do you really want to remove this subject?", + "confirmRemovetext": "Do you really want to remove this text?", + "confirmRemoverank": "Do you really want to remove this rank?", + "confirmRemoveselect": "Do you really want to remove this select?", + "upload": "Choose a json file from your computer:", + "notJson": "The file needs to have the .json extension.", + "noFile": "No file found", + "createElecDesc": "Create a new election by filling out the information below or by", + "uploadJSON": "uploading a JSON file", + "enterMainTitleLg1": "Enter the Main Title in Deutsch", + "enterMainTitleLg": "Enter the Main Title in Englisch", + "enterMainTitleLg2" : "Enter the Main Title in Französisch", + "enterSubjectTitleLg1": "Enter the Subject Title", + "enterSubjectTitleLg": "Enter the Subject Title in Englisch", + "enterSubjectTitleLg2": "Enter the Subject Title", + "errorCandidates": "You must add at least one candidate!", + "errorNewCandidate": "Are you sure you don't want to add ", + "errorRetrievingElections": "An error seems to have occurred while retrieving all the elections from our server. Contact the administrator of this website. Error: ", + "errorRetrievingElection": "An error seems to have occurred while retrieving the election from our server. Contact the administrator of this website. Error: ", + "errorRetrievingProxy": "An error seems to have occurred while retrieving the addresses of the proxies from our server. Contact the administrator of this website. ", + "errorRetrievingNodes": "An error seems to have occurred while retrieving the status of the nodes from our server. Contact the administrator of this website. ", + "errorRetrievingKey": "An error seems to have occurred while retrieving the public key from our server. Contact the administrator of this website.", + "errorServerDown": "One of our servers seems to be down. Contact the administrator of this website.", + "electionSuccess": "Your election was successfully submitted!", + "electionFail": "Election creation failed!", + "clickElection": "Click on the election name to display additional details.", + "noElection": "No election were retrieved!", + "listElection": "This page lists all the elections that have ever been created.", + "loading": "Loading...", + "electionDetails": "Election details", + "status": "Status", + "startDate": "Start date:", + "candidates": "Candidates:", + "title": "Title", + "back": "Back", + "open": "Open", + "close": "Close", + "cancel": "Cancel", + "canceled": "Canceled", + "action": "Action", + "login": "Login", + "loggedIn": "You are logged in.", + "notLoggedIn": "You are not logged in.", + "logOutSuccessful": "Logout successful.", + "logOutError": "Failed to log out: {{error}}", + "confirmCloseElection": "Are you sure you want to close this election?", + "confirmCancelElection": "Are you sure you want to cancel this election?", + "confirmDeleteElection": "Are you sure you want to delete this election? This action cannot be reversed.", + "createElection": "Create election", + "statusInitial": "Created", + "statusInitializedNodes": "Nodes initialized", + "initializeNode": "Initialize Nodes", + "initialized": "Initialized", + "initializing": "Initializing...", + "settingUp": "Setting up...", + "statusSetup": "Setup", + "setupNode": "Setup Node", + "statusOpen": "Open", + "failed": "Failed", + "dealing": "Dealing", + "responding": "Responding", + "certifying": "Certifying", + "certified": "Certified", + "opening": "Opening...", + "statusClose": "Closed", + "closing": "Closing...", + "shuffling": "Shuffling...", + "statusShuffle": "Ballots shuffled", + "decrypting": "Decrypting...", + "statusDecrypted": "Ballots decrypted", + "statusPubSharesSubmitted": "PubShares submitted", + "combine": "Combine", + "combining": "Combining...", + "statusResultAvailable": " Results available", + "statusCancel": "Canceled", + "canceling": "Cancelling...", + "errorAction": "An error occurred while trying to perform this action. Please contact the administrator of this website. Error: {{error}}", + "noActionAvailable": "Nothing to be done", + "alreadyVoted": "You have already voted for", + "alreadyVoted2": "on this election.", + "changeVote": "You can change your vote by simply casting a new vote.", + "pickCandidate": "Pick a candidate:", + "voteSuccess": "Your vote was successfully submitted!", + "voteSuccessful": "Vote successful", + "errorTitle": "Error", + "actionChange": "Action Change", + "notification": "Notification", + "successCreateElection": "Election successfully created! ElectionID: ", + "errorIncorrectConfSchema": "Incorrect election configuration, please fill it completely: ", + "successAddUser": "User successfully added!", + "errorAddUser": "Error while adding the user", + "successRemoveUser": "User successfully removed!", + "errorRemoveUser": "Error while removing the user", + "errorFetchingUsers": "Error while fetching the users", + "voteFailure": "Your ballot hasn't been taken into account. It might be that the election has been closed or cancelled. Try refreshing the page.", + "ballotFailure": "An error occurred while sending your ballot. Please contact the administrator of this website. ", + "incompleteBallot": "Some answers are not complete.", + "selectMin": "Select {{minSelect}} {{singularPlural}}. ", + "selectMax": "Select at most {{maxSelect}} {{singularPlural}}. ", + "selectBetween": "Select between {{minSelect}} and {{maxSelect}} answers. ", + "minSelectError": "You need to select at least {{min}} {{singularPlural}}. ", + "maxSelectError": "You cannot select more than {{max}} answers. ", + "fillText": "Fill {{minText}} {{singularPlural}}. ", + "minText": "Fill at least {{minText}} {{singularPlural}}. ", + "minTextError": "You need to fill at least {{minText}} {{singularPlural}}. ", + "maxTextChars": "Answer must be less than {{maxLength}} characters long. ", + "regexpCheck": "Answer must be of the form: {{regexp}}. ", + "singularAnswer": "answer", + "pluralAnswers": "answers", + "rankRange": "Answer must be between 1 and {{max}}. ", + "castVote": "Cast vote", + "voteExplanation": "You may cast a ballot as many time as you want while the election is open. Only your last vote will be taken into account.", + "noVote": "There is currently nothing to vote on.", + "voteAllowed": "You are allowed to vote on the election(s) below. Click on an election title to display its ballot and vote.", + "displayResults": "The results of the election(s) listed below are available. Click on an election title to access them.", + "noResultsAvailable": "There is currently no available results.", + "resultExplanation1": "Results for select and text question are given in percentage of the number of votes for a candidate divided by the number of ballots cast. ", + "resultExplanation2": "Results of rank question corresponds to the percentage of the score a candidate has. Each voter gives candidates points by ranking them from 1 to N (lower is better). ", + "resultExplanation3": "The score corresponds to the sum of the points a candidate got and is divided by the total number of points attributed across all ballots, and subtracted to one.", + "shuffle": "Shuffle", + "decrypt": "Decrypt", + "seeResult": "See results", + "totalNumberOfVotes": "Total number of votes : {{votes}}", + "notEnoughBallot": "The operation failed because less than two ballots have been casted.", + "operationFailure": "The operation failed. Try refreshing the page.", + "shuffleFail": "The shuffle operation failed.", + "voteImpossible": "Vote Impossible", + "notFoundVoteImpossible": "Go back to election table", + "voteImpossibleDescription": "The election is not open for voting anymore.", + "yes": "Yes", + "no": "No", + "download": "Export results in JSON format", + "rowsPerPage": "Elections per page", + "of": " of ", + "about0": "The following diagram pictures the d-voting system from a deployment point of view. It describes the components and their interactions.", + "about1": "This website hosts the interface of an evoting system. This system runs smart contracts, handled by a set of Byzantine fault-tolerant nodes.", + "about2": "When an administrator creates an election, the election parameters are saved on a blockchain and so are every following transaction (closing/cancelling election, casting a vote etc...). ", + "about3": "A distributed key is generated at election creation time so that when a user votes, his/her vote is encrypted with the key guarantying the anonymity of the vote. However the system doesn't enforce the anonymity of the voter.", + "about4": "When an election is closed, the nodes shuffle the ballots and check its correctness before decrypting the shuffle and publish the result of the election on a smart contract.", + "end": "The end", + "save": "Save", + "contributors": "Our contributors", + "nodeSetup": "Node setup", + "inputNodeSetup": "Choose which node to start the setup on:", + "inputProxyAddressError": "Error: the address of a proxy cannot be empty.", + "node": "Node", + "nodes": "Nodes", + "DKGStatuses": "DKG Statuses", + "proxies": "Proxies", + "filterByStatus": "Filter by status", + "all": "All", + "resetFilter": "Reset filter", + "showingNOverMOfXResults": "Showing {{n}}/{{m}} of {{x}} results.", + "addProxy": "Add proxy", + "editProxy": "Edit the proxy address", + "proxy": "Proxy", + "confirmDeleteProxy": "Do confirm deleting this node address", + "nodeDetails": "Add, edit or remove the mapping between a node address and its proxy address.", + "inputNodeProxyError": "Error: the address of the node and the proxy cannot be empty.", + "proxySuccessfullyEdited": "The proxy address was successfully modified !", + "nodeProxySuccessfullyAdded": "The node and proxy addresses were successfully added !", + "proxySuccessfullyDeleted": "The node and proxy addresses were successfully deleted !", + "addNodeProxyError": "An error occurred while trying to add the node and proxy addresses. Error: ", + "editProxyError": "An error occurred while trying to edit the proxy address. Error: ", + "removeProxyError": "An error occurred while trying to remove the node and proxy addresses. Error: ", + "enterNodeProxy": "Please enter the addresses of the node and the proxy", + "invalidProxyError": "Error: the address you entered is not a valid URL.", + "learnMore": "Learn more about D-Voting platform", + "aboutPlatform": "About the Platform", + "whatMakesUsDifferent": "What makes us different", + "numVotes": "Number of ballot cast: {{num}}", + "userID": "User ID of voters", + "nodeUnreachable": "Timeout: the node ({{node}}) could not be reached. ", + "proxyUnreachable": "Timeout: the address of the proxy for the node ({{node}}) could not be resolved. ", + "error": "Error: ", + "actionLoading": "Action loading...", + "statusLoading": "Status loading...", + "actionNotAvailable": "Action not available", + "uninitialized": "Uninitialized", + "actionTextVoter1": "The election is not open yet, you can come back later to vote once it is open.", + "actionTextVoter2": "The results of the election are not available yet.", + "choice": "Choix" } } diff --git a/web/frontend/src/language/en.json b/web/frontend/src/language/en.json index c815ac01c..2a8bbdc02 100644 --- a/web/frontend/src/language/en.json +++ b/web/frontend/src/language/en.json @@ -86,8 +86,12 @@ "noFile": "No file found", "createElecDesc": "Create a new election by filling out the information below or by", "uploadJSON": "uploading a JSON file", - "enterMainTitle": "Enter the Main Title", - "enterSubjectTitle": "Enter the Subject Title", + "enterMainTitleLg": "Enter the Main Title in English", + "enterMainTitleLg1": "Enter the Main Title in French", + "enterMainTitleLg2": "Enter the Main Title in Deutsch", + "enterSubjectTitleLg1": "Enter the Subject Title in French", + "enterSubjectTitleLg": "Enter the Subject Title in English", + "enterSubjectTitleLg2": "Enter the Subject Title in Deutsch", "errorCandidates": "You must add at least one candidate!", "errorNewCandidate": "Are you sure you don't want to add ", "errorRetrievingElections": "An error seems to have occurred while retrieving all the elections from our server. Contact the administrator of this website. Error: ", @@ -256,6 +260,7 @@ "actionNotAvailable": "Action not available", "uninitialized": "Uninitialized", "actionTextVoter1": "The election is not open yet, you can come back later to vote once it is open.", - "actionTextVoter2": "The results of the election are not available yet." + "actionTextVoter2": "The results of the election are not available yet.", + "choice": "Choix" } } diff --git a/web/frontend/src/language/fr.json b/web/frontend/src/language/fr.json index 35cc5355e..1294ddcc3 100644 --- a/web/frontend/src/language/fr.json +++ b/web/frontend/src/language/fr.json @@ -4,6 +4,264 @@ "about": "This is the About text", "en": "🇺🇸 Anglais", "fr": "🇫🇷 Français", - "de": "🇩🇪 Allemand" + "de": "🇩🇪 Allemand", + "navBarStatus": "Sondages", + "navBarHome" : "Acceuil", + "navBarCreate" : "Créer", + "vote":"Vote", + "elections": "Sondages", + "navBarResult": "Résultats", + "navBarAbout": "À propos", + "navBarAdmin": "Admin", + "admin": "Admin", + "previous": "Précédent", + "next": "Suivant", + "confirmDeleteUserSciper": "Confirmez la suppression du rôle pour l'utilisateur sciper", + "404Title": "Page introuvable", + "403Title": "Page interdite", + "401Title": "Page non autorisée", + "404Description": "La page que vous cherchez n'existe pas.", + "403Description": "Vous n'êtes pas autorisé à accéder à cette page.", + "goHome": "Allez sur la page d'acceuil", + "results": "résultats", + "showing": "Montrer", + "saveQuestion": "Enregistrer", + "addRank": "Ajouter un rang", + "editrank": "Modifier un rang", + "removerank": "Enlever un rang", + "addSelect": "Ajouter la sélection", + "editselect": "Modifier la sélection", + "removeselect": "Supprimer la sélection", + "addText": "Ajouter un texte", + "edittext": "Modifier un texte", + "removetext": "Supprimer un texte", + "subject": "Sujet", + "choices": "Choix", + "answers": "Réponses", + "enterMaxLength": "Entrer la longueur max", + "maxChoices": "Max nombre de choix", + "minChoices": "Min nombre de choix", + "enterMinN": "Entrer le MinN", + "enterMaxN": "Entrer le MaxN", + "enterRegex": "Entrer votre regex", + "enterTitle": "Entrer votre Titre", + "mainProperties": "Principales propriétés", + "additionalProperties": "Propriétés additionnels", + "removeSubject": "Suprimer un sujet", + "addSubject": "Ajouter un sujet", + "addQuestionrank": "Rang", + "addQuestionselect": "Sélectionner", + "addQuestiontext": "Texte", + "importFile": "Importer le fichier JSON ", + "enterSciper": "Merci de donner le sciper de l'utilisateur", + "adminDetails": "Ajouter ou supprimer les rôles des utilisateurs du tableau de l'admin", + "navBarCreateElection": "Créer un sondage", + "homeTitle": "Bienvenue dans notre platforme de e-voting!", + "homeWhatsNew": "Quoi de neuf", + "homeJustShippedVersion": "Version tout juste expédiée", + "homeText": "Utiliser la barre de navigation ci-dessus pour accèder à la page que vous voulez.", + "loginText": "Vous devez vous connecter pour accèder au contenu de {{from}}", + "notLoggedInActionText1": "Vous devez vous ", + "notLoggedInActionText2": "connecter ", + "notLoggedInActionText3": " pour effectuer ces actions.", + "loginCallback": "Nous procédons à l'authentification. Vous devriez être redirigé...", + "logout": "Déconnexion", + "namePlaceHolder": "Entrer le nom", + "addCandidate": "Ajouter un candidat", + "addUser": "Ajouter un utilisateur", + "role": "Rôle", + "roles": "Rôles", + "edit": "Editer", + "nothingToAdd": "Il y a rien à ajouter.", + "duplicateCandidate": "Ce candidat a déjà été ajouter.", + "add": "Ajouter", + "exportJSON": "Exporter en JSON", + "delete": "Supprimer", + "combineShares": "Combiner les actions", + "createElec": "Créer un sondage", + "clearElection": "Effacer un sondage", + "elecName": "Titre du sondage", + "confirmRemovesubject": "Voulez vous vraiment supprimer ce sujet?", + "confirmRemovetext": "Voulez vous vraiment supprimer ce texte?", + "confirmRemoverank": "Voulez vous vraiment supprimer ce rang?", + "confirmRemoveselect": "Voulez vous vraiment supprimer cette selection?", + "upload": "Choisi un fichier json sur votre ordinateur:", + "notJson": "Le fichier doit avoir l'extension .json.", + "noFile": "Aucun fichier trouvé", + "createElecDesc": "Crée un nouveau sondage en remplissant les informations ci-dessous ou en", + "uploadJSON": "téléchargent un fichier JSON", + "enterMainTitleLg1": "Entrer le Titre principal en Français", + "enterMainTitleLg": "Entrer le Titre principal en Anglais", + "enterMainTitleLg2": "Entrer le Titre principal en Allemand", + "enterSubjectTitleLg1": "Entrer le Titre du sujet en Français", + "enterSubjectTitleLg": "Entrer le Titre du sujet en Anglais", + "enterSubjectTitleLg2": "Entrer le Titre du sujet en Allemand", + "errorCandidates": "Vous devez ajouter au moins un candidat!", + "errorNewCandidate": "Vous êtes de sûr de ne pas vouloir ajouter ", + "errorRetrievingElections": "Une erreur semble s'être produite lors de la récupération des sondages sur notre serveur. Contactez l'administrateur de ce site. Erreur: ", + "errorRetrievingElection": "Une erreur semble s'être produite lors de la récupération du sondage sur notre serveur. Contactez l'administrateur de ce site. Erreur:", + "errorRetrievingProxy": "Une erreur semble s'ếtre produite lors de la récupération des addresses du proxy sur notre serveur. Contactez l'administrateur de ce site.", + "errorRetrievingNodes": "Une erreur semble s'être produite lors de la récupération des statuts des noeux sur notre serveur. Contactez l'administrateur de ce site.", + "errorRetrievingKey": "Une erreur semble s'être produite lors de la récupération de la clé public sur notre serveur. Contactez l'administrateur de ce site.", + "errorServerDown": "Un de nos serveurs semble être en panne. Contactez l'administrateur de ce site.", + "electionSuccess": "Votre sondage à été soumise avec succès!", + "electionFail": "La création du sondage a échoué!", + "clickElection": "Clique sur le nom du sondage pour afficher des détails supplémentaires.", + "noElection": "Aucun sondage n'a été récupéré!", + "listElection": "Cette page liste toutes les sondages qui ont été créé.", + "loading": "Chargement...", + "electionDetails": "Les détails du sondage", + "status": "Statuts", + "startDate": "Date de début:", + "candidates": "Candidats:", + "title": "Titre", + "back": "Retour", + "open": "Ouvrir", + "close": "Fermer", + "cancel": "Annuler", + "canceled": "Annulé", + "action": "Action", + "login": "Connexion", + "loggedIn": "Vous êtes connectés.", + "notLoggedIn": "Vous n'êtes pas connectés.", + "logOutSuccessful": "Déconnexion réussie.", + "logOutError": "Echec de la déconnexion: {{error}}", + "confirmCloseElection": "Êtes vous sûr de vouloir fermer ce sondage?", + "confirmCancelElection": " Êtes vous sûr de vouloir annuler ce sondage?", + "confirmDeleteElection": "Êtes vous sûr de vouloir supprimer ce sondage? Cette action est irrévocable.", + "createElection": "Créer un sondage", + "statusInitial": "Créé", + "statusInitializedNodes": "Noeuds initialisés", + "initializeNode": "Initialiser les noeuds", + "initialized": "Initializé", + "initializing": "Initialisation...", + "settingUp": "Mise en place...", + "statusSetup": "Configuration", + "setupNode": "Configuration d'un noeud", + "statusOpen": "Ouvert", + "failed": "Échoué", + "dealing": "Traitement", + "responding": "Repondant", + "certifying": "Certifiant", + "certified": "Certifié", + "opening": "Ouverture...", + "statusClose": "Fermé", + "closing": "Fermeture...", + "shuffling": "Mélangé...", + "statusShuffle": "Les ballots ont été mélangé", + "decrypting": "Décryptage...", + "statusDecrypted": "Les ballots ont été décrypté", + "statusPubSharesSubmitted": "PubShares ont été soumis", + "combine": "Combiner", + "combining": "Combiné...", + "statusResultAvailable": " Résultats disponible", + "statusCancel": "Annulé", + "canceling": "Annulation...", + "errorAction": "Une erreur s'est produite lors de l'éxecution de cette action. Merci de contacter l'administrateur de ce site. Erreur: {{error}}", + "noActionAvailable": "Rien à faire", + "alreadyVoted": "Vous avez déjà voté pour", + "alreadyVoted2": "sur ce sondage.", + "changeVote": "Vous pouvez changer votre vote en effectuant simplement un nouveau vote.", + "pickCandidate": "Choisi un candidat:", + "voteSuccess": "Votre vote a été soumis avec succès!", + "voteSuccessful": "Vote réussi", + "errorTitle": "Erreur", + "actionChange": "Changement d'action", + "notification": "Notification", + "successCreateElection": "Sondage créé avec succès! FormID: ", + "errorIncorrectConfSchema": "Configuration incorrect du sondage, merci de le remplir complétement: ", + "successAddUser": "Utilisateur ajouté avec succès!", + "errorAddUser": "Erreur lors de l'ajout de l'utilisateur", + "successRemoveUser": "Utilisateur supprimé avec succès!", + "errorRemoveUser": "Erreur lors de la suppression de l'utilisateur", + "errorFetchingUsers": "Erreur lors de la récupération des utilisateurs", + "voteFailure": "Votre ballot n'a pas été pris en compte. C'est possible que le sondage a été fermé ou annulé. Essayez de rafraichir la page.", + "ballotFailure": "Une erreur est survenu lors de l'envoi de votre ballot. Merci de contacter l'administrateur de ce site. ", + "incompleteBallot": "Certaines réponses ne sont pas complète.", + "selectMin": "Selectionnez {{minSelect}} {{singularPlural}}. ", + "selectMax": "Selectionnez au moins {{maxSelect}} {{singularPlural}}. ", + "selectBetween": "Selectionnez entre {{minSelect}} et {{maxSelect}} réponses. ", + "minSelectError": "Vous devez sélectionné au moins {{min}} {{singularPlural}}. ", + "maxSelectError": "Vous pouvez pas sélectionné plus de {{max}} réponses. ", + "fillText": "Remplissez {{minText}} {{singularPlural}}. ", + "minText": "Remplissez au moins {{minText}} {{singularPlural}}. ", + "minTextError": "Vous devez remplir au moins {{minText}} {{singularPlural}}. ", + "maxTextChars": "Les réponses doivent être au maximum long de {{maxLength}} charactères. ", + "regexpCheck": "Les réponses doivent être sous la forme: {{regexp}}. ", + "singularAnswer": "réponse", + "pluralAnswers": "réponses", + "rankRange": "La réponse doit être entre 1 et {{max}}. ", + "castVote": "Voter", + "voteExplanation": "Vous pouvez voter autant de fois que vous le souhaitez tant que le sondage est ouverte. Seul votre dernier vote sera pris en compte.", + "noVote": "Il y a actuellement rien à voter.", + "voteAllowed": "Vous êtes autorisés à voter sur les sondages ci-dessous. Cliquez sur le titre d'un sondage pour afficher son bulletin de vote et voter.", + "displayResults": "Les résultats de(s) sondage(s) listé(s) ci-dessous sont disponibles. Clique sur le titre d'un sondage pour y avoir accès.", + "noResultsAvailable": "Il y a actuellement aucuns résultats disponibles.", + "resultExplanation1": "Les résultats des questions sélectives et textuelles sont donnés en pourcentage du nombre de voix pour un candidat divisé par le nombre de bulletins de vote. ", + "resultExplanation2": "Les résultats de la question de classement correspondent au pourcentage de la note d'un candidat. Chaque électeur donne des points aux candidats en les classant de 1 à N (le plus bas est le mieux). ", + "resultExplanation3": "Le score correspond à la somme des points obtenus par un candidat et est divisé par le nombre total de points attribués sur l'ensemble des bulletins, puis soustrait à un", + "shuffle": "Mélanfez", + "decrypt": "Décryptez", + "seeResult": "Voir les résultats", + "totalNumberOfVotes": "Nombre total de votes : {{votes}}", + "notEnoughBallot": "L'opération a échoué parce que moins 2 votes ont été enregistré.", + "operationFailure": "L'opération a échoué.Essayez de rafraichir la page.", + "shuffleFail": "L'opération de mélange a échoué", + "voteImpossible": "Vote Impossible", + "notFoundVoteImpossible": "Retournez à l'onglet des sondages", + "voteImpossibleDescription": "Le sondage n'est plus ouvert au vote.", + "yes": "Oui", + "no": "Non", + "download": "Exportez les résultats sous un format JSON", + "rowsPerPage": "Sondages par page", + "of": " de ", + "about0": "Le diagram suivant représente le système du d-voting du point de vue du déploiement. Il décrit les composants et leurs interactions.", + "about1": "Ce site web héberge l'interface d'un système de vote. Ce système exécute des contrats intelligents, gérés par un ensemble de nœuds byzantins tolérants aux pannes.", + "about2": "Lorsqu'un administrateur crée un sondage, les paramètres du sondage sont sauvegardés sur une blockchain ainsi que toutes les transactions suivantes (fermeture/annulation du sondage, vote, etc.). ", + "about3": "Une clé distribuée est générée au moment de la création de l'élection de sorte que lorsqu'un utilisateur vote, son vote est crypté avec la clé garantissant l'anonymat du vote. Cependant, le système ne garantit pas l'anonymat de l'électeur.", + "about4": "Lorsqu'un sondage est clôturée, les nœuds mélangent les bulletins de vote et vérifient leur exactitude avant de décrypter le mélange et de publier le résultat du sondage sur un contrat intelligent.", + "end": "Fin", + "save": "Enregistrer", + "contributors": "Nos contributeurs", + "nodeSetup": "Configuration du noeud", + "inputNodeSetup": "Choisi un noeud pour commencer la configuration dessus:", + "inputProxyAddressError": "Erreur: l'adresse d'un proxy ne peut pas être vide.", + "node": "Noeud", + "nodes": "Noeuds", + "DKGStatuses": "Les statuts du DKG", + "proxies": "Proxies", + "filterByStatus": "Filtrer par statuts", + "all": "Tout", + "resetFilter": "Rénitialiser le filtre", + "showingNOverMOfXResults": "Montrer {{n}}/{{m}} de {{x}} résultats.", + "addProxy": "Ajouter un proxy", + "editProxy": "Modifier l'adresse du proxy", + "proxy": "Proxy", + "confirmDeleteProxy": "Confirmez la suppression de l'adresse de ce nœud", + "nodeDetails": "Ajouter, modifier ou supprimer le mappage entre l'adresse d'un nœud et son adresse proxy.", + "inputNodeProxyError": "Erreur: l'adresse du noeud et du proxy ne peuvent pas être vide.", + "proxySuccessfullyEdited": "L'adresse du proxy a été modifié avec succès!", + "nodeProxySuccessfullyAdded": "Les addresses du noeud et du proxy ont été ajouté avec succès !", + "proxySuccessfullyDeleted": "Les addresses du noeud et du proxy ont été supprimé avec succès !", + "addNodeProxyError": "Une erreur est survénue lors de l'ajout des adresses du noeud et du proxy. Erreur: ", + "editProxyError": "Une erreur est survénue lors de la tentation d'édition de l'adresses du proxy. Erreur: ", + "removeProxyError": "Une error est survénue lors de la tentation de la suppression des adresses du noeud et du proxy. Erreur: ", + "enterNodeProxy": "Merci d'entrer les adresses du noeud et du proxy", + "invalidProxyError": "Erreur: l'adresse que vous avez entré n'est pas un URL valide.", + "learnMore": "Apprends plus sur la platforme de D-voting", + "aboutPlatform": "À propos de la Platforme", + "whatMakesUsDifferent": "Qu'est ce qui nous rend différent", + "numVotes": "Nombre de bulletin enregistré: {{num}}", + "userID": "Utilisateurs ID pour les électeurs", + "nodeUnreachable": "Timeout: le noeud ({{node}}) n'a pas pu être atteint. ", + "proxyUnreachable": "Timeout: l'adresse du proxy pour le noeud ({{node}}) n'a pas pu être résolue. ", + "error": "Erreur: ", + "actionLoading": "Chargement de l'action...", + "statusLoading": "Chargement du statut...", + "actionNotAvailable": "Action pas disponible", + "uninitialized": "Non initialisé", + "actionTextVoter1": "TLe sondage n'est pas encore ouvert, vous pouvez revenir plus tard pour voter lorsque ce sera ouvert.", + "actionTextVoter2": "Les résultats du sondage ne sont pas encore disponible.", + "choice": "Choix" } } diff --git a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx index a25eba752..a878f7b9d 100644 --- a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx +++ b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import { Answers, Configuration, ID, RANK, SELECT, SUBJECT, TEXT } from 'types/configuration'; import * as types from 'types/configuration'; import Rank, { handleOnDragEnd } from './Rank'; @@ -19,6 +19,17 @@ const BallotDisplay: FC = ({ setAnswers, userErrors, }) => { + const [titles,setTitles]= useState({}); + useEffect (()=> { + try{ + console.log('config', configuration.MainTitle) + const ts = JSON.parse(configuration.MainTitle) + setTitles(ts) + }catch(e){ + console.log('error',e) + } + + }, [configuration]) const SubjectElementDisplay = (element: types.SubjectElement) => { return (
@@ -62,7 +73,7 @@ const BallotDisplay: FC = ({ handleOnDragEnd(dropRes, answers, setAnswers)}>

- {configuration.MainTitle} + {titles.en}

{configuration.Scaffold.map((subject: types.Subject) => SubjectTree(subject))} diff --git a/web/frontend/src/pages/election/Show.tsx b/web/frontend/src/pages/election/Show.tsx index c946db97a..a3105bbb1 100644 --- a/web/frontend/src/pages/election/Show.tsx +++ b/web/frontend/src/pages/election/Show.tsx @@ -14,6 +14,7 @@ import useGetResults from './components/utils/useGetResults'; import UserIDTable from './components/UserIDTable'; import DKGStatusTable from './components/DKGStatusTable'; import LoadingButton from './components/LoadingButton'; +//import { json } from 'node:stream/consumers'; const ElectionShow: FC = () => { const { t } = useTranslation(); @@ -51,6 +52,7 @@ const ElectionShow: FC = () => { const ongoingItem = 'ongoingAction' + electionID; const nodeToSetupItem = 'nodeToSetup' + electionID; + // called by a DKG row const notifyDKGState = (node: string, info: InternalDKGInfo) => { if ( @@ -208,7 +210,7 @@ const ElectionShow: FC = () => { {!loading ? ( <>
- {configObj.MainTitle} + {configObj.TitleEn}
Election ID : {electionId}
diff --git a/web/frontend/src/pages/election/components/AddQuestionModal.tsx b/web/frontend/src/pages/election/components/AddQuestionModal.tsx index 2a01a8540..a5cc5dd9b 100644 --- a/web/frontend/src/pages/election/components/AddQuestionModal.tsx +++ b/web/frontend/src/pages/election/components/AddQuestionModal.tsx @@ -1,7 +1,6 @@ import { FC, Fragment, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import { Dialog, Transition } from '@headlessui/react'; - import { useTranslation } from 'react-i18next'; import { CheckIcon, MinusCircleIcon, PlusCircleIcon } from '@heroicons/react/outline'; import { @@ -16,6 +15,9 @@ import { ranksSchema, selectsSchema, textsSchema } from '../../../schema/configu import useQuestionForm from './utils/useQuestionForm'; import DisplayTypeIcon from './DisplayTypeIcon'; +import { availableLanguages } from 'language/Configuration'; + +//import TranslatableInput from './TranslatableInput'; type AddQuestionModalProps = { question: RankQuestion | SelectQuestion | TextQuestion; open: boolean; @@ -42,8 +44,8 @@ const AddQuestionModal: FC = ({ deleteChoice, updateChoice, } = useQuestionForm(question); - - const { Title, MaxN, MinN, Choices } = values; + const [language, setLanguage] = useState('en'); + const { Title, TitleDe, TitleFr, MaxN, MinN, Choices, ChoicesFr, ChoicesDe } = values; const [errors, setErrors] = useState([]); const handleSave = async () => { @@ -79,7 +81,20 @@ const AddQuestionModal: FC = ({ break; } }; + /*const handleValueChange(value, lang, stateName) { + const state = this.state[stateName]; + state[lang] = value; + this.setState({ + [stateName]: state + }); + } + + const handleLanguageChange(editingLanguage) { + this.setState({ + editingLanguage + }); + }*/ const handleDeleteChoice = (index: number) => (e) => { switch (Type) { case RANK: @@ -124,6 +139,49 @@ const AddQuestionModal: FC = ({ return; } }; + type renderQuestionModalProps = { + lg : string; + }; + const RenderQuestionModal : FC = ({lg }) => { + return(
+ {Choices.map((choice: string, idx: number) => ( +
+ + + +
+ {Choices.length > 1 && ( + + )} + {idx === Choices.length - 1 && ( + + )} +
+
+ ))} +
) + } return ( @@ -170,17 +228,55 @@ const AddQuestionModal: FC = ({
+
+
+ {availableLanguages.map( + (lang) => + language !== lang && ( + + ) + )} +
+
{t('mainProperties')}
- + {language === 'en' && ( + + )} + {language === 'fr' && ( + + )} + {language === 'de' && ( + + )}
{errors @@ -192,41 +288,13 @@ const AddQuestionModal: FC = ({ -
- {Choices.map((choice: string, idx: number) => ( -
- -
- {Choices.length > 1 && ( - - )} - {idx === Choices.length - 1 && ( - - )} -
-
- ))} -
+ {language === 'en' && ( )} + + {language === 'fr' && ( )} + + {language === 'de' && ()} + +
{errors .filter((err) => err.startsWith('Choices')) diff --git a/web/frontend/src/pages/election/components/ElectionForm.tsx b/web/frontend/src/pages/election/components/ElectionForm.tsx index c42608265..910e466db 100644 --- a/web/frontend/src/pages/election/components/ElectionForm.tsx +++ b/web/frontend/src/pages/election/components/ElectionForm.tsx @@ -20,6 +20,8 @@ import RemoveElementModal from './RemoveElementModal'; import { useConfiguration } from 'components/utils/useConfiguration'; import BallotDisplay from 'pages/ballot/components/BallotDisplay'; +import { availableLanguages } from 'language/Configuration'; + // notifyParent must be used by the child to tell the parent if the subject's // schema changed. @@ -45,7 +47,13 @@ const ElectionForm: FC = () => { const [marshalledConf, setMarshalledConf] = useState(marshalConfig(conf)); const { configuration: previewConf, answers, setAnswers } = useConfiguration(marshalledConf); - const { MainTitle, Scaffold } = conf; + const { MainTitle, Scaffold, TitleLg1,TitleLg2} = conf; //ScaffoldLg1, , ScaffoldLg2 + + const [language, setLanguage] = useState('en'); + //const [titleChanging1, setTitleChanging1] = useState(true); + // const [titleChanging2, setTitleChanging2] = useState(true); + //const TitleLg1 = ''; + //const TitleLg2 = ''; useEffect(() => { setMarshalledConf(marshalConfig(conf)); @@ -134,17 +142,53 @@ const ElectionForm: FC = () => { return (
-
+
{titleChanging ? ( <> - +
+ {availableLanguages.map( + (lang) => + language !== lang && ( + + ) + )} +
+
+ {language === 'en' && ( + setConf({ ...conf, MainTitle: e.target.value })} name="MainTitle" type="text" - placeholder={t('enterMainTitle')} - className="ml-3 px-1 w-60 text-lg border rounded-md" + placeholder={t('enterMainTitleLg')} + className="m-3 px-1 w-100 text-lg border rounded-md" /> + )} + {language === 'fr' && ( + setConf({ ...conf, TitleLg1: e.target.value })} + name="MainTitle1" + type="text" + placeholder={t('enterMainTitleLg1')} + className="m-3 px-1 w-100 text-lg border rounded-md" + />)} + {language === 'de' && ( + setConf({ ...conf, TitleLg2: e.target.value })} + name="MainTitle2" + type="text" + placeholder={t('enterMainTitleLg2')} + className="m-3 px-1 w-100 text-lg border rounded-md" + />)}
+ ) + )} +
+
+ {language === 'en' && ( + setSubject({ ...subject, Title: e.target.value })} name="Title" type="text" - placeholder={t('enterSubjectTitle')} - className={`w-60 px-1 border rounded-md ${ + placeholder={t('enterSubjectTitleLg')} + className={`m-3 px-1 w-100 border rounded-md ${ + nestedLevel === 0 ? 'text-lg' : 'text-md' + } `} + /> + )} + {language === 'fr' &&( + setSubject({ ...subject, TitleFr: e.target.value })} + name="Title" + type="text" + placeholder={t('enterSubjectTitleLg1')} + className={`m-3 px-1 w-100 border rounded-md ${ nestedLevel === 0 ? 'text-lg' : 'text-md' } `} + /> + )} + {language === 'de' &&( + setSubject({ ...subject, TitleDe: e.target.value })} + name="Title" + type="text" + placeholder={t('enterSubjectTitleLg2')} + className={`m-3 px-1 w-100 border rounded-md ${ + nestedLevel === 0 ? 'text-lg' : 'text-md' + } `} /> + )} +
- ) + )}
From 347735ecd4033c81e60ce5e0c34963547d43fdcb Mon Sep 17 00:00:00 2001 From: Khadija Tagemouati Date: Mon, 14 Nov 2022 00:56:23 +0100 Subject: [PATCH 04/47] Resolve conflicts with main --- web/frontend/src/language/de.json | 24 +-- web/frontend/src/language/fr.json | 30 +-- .../pages/ballot/components/BallotDisplay.tsx | 16 +- .../src/pages/ballot/components/Rank.tsx | 17 +- .../src/pages/ballot/components/Select.tsx | 18 +- .../src/pages/ballot/components/Text.tsx | 16 +- web/frontend/src/pages/form/Result.tsx | 21 ++- .../form/components/AddQuestionModal.tsx | 176 +++++++++++++++--- .../src/pages/form/components/FormForm.tsx | 35 ++-- .../form/components/SubjectComponent.tsx | 17 +- .../form/components/utils/useQuestionForm.ts | 83 ++++++--- web/frontend/src/types/JSONparser.ts | 12 +- web/frontend/src/types/configuration.ts | 5 +- web/frontend/src/types/getObjectType.ts | 4 +- 14 files changed, 343 insertions(+), 131 deletions(-) diff --git a/web/frontend/src/language/de.json b/web/frontend/src/language/de.json index 1c9b2acd2..36bda5cf6 100644 --- a/web/frontend/src/language/de.json +++ b/web/frontend/src/language/de.json @@ -9,7 +9,7 @@ "navBarHome" : "Homepage", "navBarCreate" : "Erstellen", "vote":"Abstimmung", - "elections": "Umfragen", + "forms": "Umfragen", "navBarResult": "Ergebnisse", "navBarAbout": "Über", "navBarAdmin": "Admin", @@ -20,14 +20,14 @@ "404Title": "Seite nicht gefunden", "403Title": "Verbotene Seite", "401Title": "unbefugt Seite", - "404Description": "The page you are looking for does not exist.", - "403Description": "You are not authorized to access this page.", - "goHome": "Go to home page", - "results": "results", - "showing": "Showing", - "saveQuestion": "Save", - "addRank": "Add rank", - "editrank": "Edit rank", + "404Description": "Die gesuchte Seite existiert nicht.", + "403Description": "Sie sind nicht berechtigt, auf diese Seite zuzugreifen.", + "goHome": "Zur Homepage", + "results": "ergebnisse", + "showing": "zeigen", + "saveQuestion": "Speichern", + "addRank": "Rang hinzufügen", + "editrank": "Rang bearbeiten", "removerank": "Remove rank", "addSelect": "Add select", "editselect": "Edit select", @@ -104,13 +104,13 @@ "errorRetrievingNodes": "An error seems to have occurred while retrieving the status of the nodes from our server. Contact the administrator of this website. ", "errorRetrievingKey": "An error seems to have occurred while retrieving the public key from our server. Contact the administrator of this website.", "errorServerDown": "One of our servers seems to be down. Contact the administrator of this website.", - "electionSuccess": "Your election was successfully submitted!", - "electionFail": "Election creation failed!", + "formSuccess": "Your election was successfully submitted!", + "formFail": "Election creation failed!", "clickElection": "Click on the election name to display additional details.", "noElection": "No election were retrieved!", "listElection": "This page lists all the elections that have ever been created.", "loading": "Loading...", - "electionDetails": "Election details", + "formDetails": "Election details", "status": "Status", "startDate": "Start date:", "candidates": "Candidates:", diff --git a/web/frontend/src/language/fr.json b/web/frontend/src/language/fr.json index 1294ddcc3..a7335391d 100644 --- a/web/frontend/src/language/fr.json +++ b/web/frontend/src/language/fr.json @@ -9,7 +9,7 @@ "navBarHome" : "Acceuil", "navBarCreate" : "Créer", "vote":"Vote", - "elections": "Sondages", + "forms": "Sondages", "navBarResult": "Résultats", "navBarAbout": "À propos", "navBarAdmin": "Admin", @@ -55,7 +55,7 @@ "importFile": "Importer le fichier JSON ", "enterSciper": "Merci de donner le sciper de l'utilisateur", "adminDetails": "Ajouter ou supprimer les rôles des utilisateurs du tableau de l'admin", - "navBarCreateElection": "Créer un sondage", + "navBarCreateForm": "Créer un sondage", "homeTitle": "Bienvenue dans notre platforme de e-voting!", "homeWhatsNew": "Quoi de neuf", "homeJustShippedVersion": "Version tout juste expédiée", @@ -79,7 +79,7 @@ "delete": "Supprimer", "combineShares": "Combiner les actions", "createElec": "Créer un sondage", - "clearElection": "Effacer un sondage", + "clearForm": "Effacer un sondage", "elecName": "Titre du sondage", "confirmRemovesubject": "Voulez vous vraiment supprimer ce sujet?", "confirmRemovetext": "Voulez vous vraiment supprimer ce texte?", @@ -99,18 +99,18 @@ "errorCandidates": "Vous devez ajouter au moins un candidat!", "errorNewCandidate": "Vous êtes de sûr de ne pas vouloir ajouter ", "errorRetrievingElections": "Une erreur semble s'être produite lors de la récupération des sondages sur notre serveur. Contactez l'administrateur de ce site. Erreur: ", - "errorRetrievingElection": "Une erreur semble s'être produite lors de la récupération du sondage sur notre serveur. Contactez l'administrateur de ce site. Erreur:", + "errorRetrievingForm": "Une erreur semble s'être produite lors de la récupération du sondage sur notre serveur. Contactez l'administrateur de ce site. Erreur:", "errorRetrievingProxy": "Une erreur semble s'ếtre produite lors de la récupération des addresses du proxy sur notre serveur. Contactez l'administrateur de ce site.", "errorRetrievingNodes": "Une erreur semble s'être produite lors de la récupération des statuts des noeux sur notre serveur. Contactez l'administrateur de ce site.", "errorRetrievingKey": "Une erreur semble s'être produite lors de la récupération de la clé public sur notre serveur. Contactez l'administrateur de ce site.", "errorServerDown": "Un de nos serveurs semble être en panne. Contactez l'administrateur de ce site.", - "electionSuccess": "Votre sondage à été soumise avec succès!", - "electionFail": "La création du sondage a échoué!", - "clickElection": "Clique sur le nom du sondage pour afficher des détails supplémentaires.", - "noElection": "Aucun sondage n'a été récupéré!", - "listElection": "Cette page liste toutes les sondages qui ont été créé.", + "formSuccess": "Votre sondage à été soumise avec succès!", + "formFail": "La création du sondage a échoué!", + "clickForm": "Clique sur le nom du sondage pour afficher des détails supplémentaires.", + "noForm": "Aucun sondage n'a été récupéré!", + "listForm": "Cette page liste toutes les sondages qui ont été créé.", "loading": "Chargement...", - "electionDetails": "Les détails du sondage", + "formDetails": "Les détails du sondage", "status": "Statuts", "startDate": "Date de début:", "candidates": "Candidats:", @@ -126,10 +126,10 @@ "notLoggedIn": "Vous n'êtes pas connectés.", "logOutSuccessful": "Déconnexion réussie.", "logOutError": "Echec de la déconnexion: {{error}}", - "confirmCloseElection": "Êtes vous sûr de vouloir fermer ce sondage?", - "confirmCancelElection": " Êtes vous sûr de vouloir annuler ce sondage?", - "confirmDeleteElection": "Êtes vous sûr de vouloir supprimer ce sondage? Cette action est irrévocable.", - "createElection": "Créer un sondage", + "confirmCloseForm": "Êtes vous sûr de vouloir fermer ce sondage?", + "confirmCancelForm": " Êtes vous sûr de vouloir annuler ce sondage?", + "confirmDeleteForm": "Êtes vous sûr de vouloir supprimer ce sondage? Cette action est irrévocable.", + "createForm": "Créer un sondage", "statusInitial": "Créé", "statusInitializedNodes": "Noeuds initialisés", "initializeNode": "Initialiser les noeuds", @@ -168,7 +168,7 @@ "errorTitle": "Erreur", "actionChange": "Changement d'action", "notification": "Notification", - "successCreateElection": "Sondage créé avec succès! FormID: ", + "successCreateForm": "Sondage créé avec succès! FormID: ", "errorIncorrectConfSchema": "Configuration incorrect du sondage, merci de le remplir complétement: ", "successAddUser": "Utilisateur ajouté avec succès!", "errorAddUser": "Erreur lors de l'ajout de l'utilisateur", diff --git a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx index 8ee177735..4efeb24ae 100644 --- a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx +++ b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx @@ -8,6 +8,7 @@ import { DragDropContext } from 'react-beautiful-dnd'; import { default as i18n } from 'i18next'; + type BallotDisplayProps = { configuration: Configuration; answers: Answers; @@ -54,7 +55,9 @@ const BallotDisplay: FC = ({ return (

- {subject.Title} + {i18n.language == 'en' && subject.Title} + {i18n.language == 'fr' && subject.TitleFr} + {i18n.language == 'de' && subject.TitleDe}

{subject.Order.map((id: ID) => (
@@ -69,8 +72,13 @@ const BallotDisplay: FC = ({ ))}
); - }; - + } + try{ + console.log("subjects", configuration.Scaffold); + }catch(e){ + console.log("erreur",e) + } + return ( handleOnDragEnd(dropRes, answers, setAnswers)}>
@@ -78,8 +86,10 @@ const BallotDisplay: FC = ({ {i18n.language == 'en' && titles.en} {i18n.language == 'fr' && titles.fr} {i18n.language == 'de' && titles.de} +
+ {configuration.Scaffold.map((subject: types.Subject) => SubjectTree(subject))}
{userErrors}
diff --git a/web/frontend/src/pages/ballot/components/Rank.tsx b/web/frontend/src/pages/ballot/components/Rank.tsx index a6b6e5d1a..dcbf5f9c0 100644 --- a/web/frontend/src/pages/ballot/components/Rank.tsx +++ b/web/frontend/src/pages/ballot/components/Rank.tsx @@ -2,6 +2,7 @@ import { FC } from 'react'; import { Draggable, DropResult, Droppable } from 'react-beautiful-dnd'; import { Answers, ID, RankQuestion } from 'types/configuration'; import { answersFrom } from 'types/getObjectType'; +import { default as i18n } from 'i18next'; export const handleOnDragEnd = ( result: DropResult, @@ -78,14 +79,26 @@ const Rank: FC = ({ rank, answers }) => { return (
-

{rank.Title}

+

+ {i18n.language == 'en' && rank.Title} + {i18n.language == 'fr' && rank.TitleFr} + {i18n.language == 'de' && rank.TitleDe} +

<> {(provided) => (
    {Array.from(answers.RankAnswers.get(rank.ID).entries()).map( - ([rankIndex, choiceIndex]) => choiceDisplay(rank.Choices[choiceIndex], rankIndex) + ([rankIndex, choiceIndex]) => { + if(i18n.language == 'en') + return choiceDisplay(rank.Choices[choiceIndex], rankIndex) + else if (i18n.language == 'fr') + return choiceDisplay(rank.ChoicesFr[choiceIndex], rankIndex) + else if (i18n.language == 'de') + return choiceDisplay(rank.ChoicesDe[choiceIndex], rankIndex) + } + )} {provided.placeholder}
diff --git a/web/frontend/src/pages/ballot/components/Select.tsx b/web/frontend/src/pages/ballot/components/Select.tsx index d4f2b2c9d..99383f95a 100644 --- a/web/frontend/src/pages/ballot/components/Select.tsx +++ b/web/frontend/src/pages/ballot/components/Select.tsx @@ -2,7 +2,7 @@ import { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { Answers, SelectQuestion } from 'types/configuration'; import { answersFrom } from 'types/getObjectType'; - +import { default as i18n } from 'i18next'; type SelectProps = { select: SelectQuestion; answers: Answers; @@ -75,12 +75,22 @@ const Select: FC = ({ select, answers, setAnswers }) => { return (
-

{select.Title}

+

+ {i18n.language == 'en' && select.Title} + {i18n.language == 'fr' && select.TitleFr} + {i18n.language == 'de' && select.TitleDe}

{hintDisplay()}
{Array.from(answers.SelectAnswers.get(select.ID).entries()).map( - ([choiceIndex, isChecked]) => - choiceDisplay(isChecked, select.Choices[choiceIndex], choiceIndex) + ([choiceIndex, isChecked]) =>{ + if(i18n.language == 'en' ) + return choiceDisplay(isChecked, select.Choices[choiceIndex], choiceIndex) + else if(i18n.language == 'fr') + return choiceDisplay(isChecked, select.ChoicesFr[choiceIndex], choiceIndex) + else if(i18n.language == 'de') + return choiceDisplay(isChecked, select.ChoicesDe[choiceIndex], choiceIndex) + } + )}
{answers.Errors.get(select.ID)}
diff --git a/web/frontend/src/pages/ballot/components/Text.tsx b/web/frontend/src/pages/ballot/components/Text.tsx index a5911e89c..143862c50 100644 --- a/web/frontend/src/pages/ballot/components/Text.tsx +++ b/web/frontend/src/pages/ballot/components/Text.tsx @@ -2,6 +2,7 @@ import { FC, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Answers, TextQuestion } from 'types/configuration'; import { answersFrom } from 'types/getObjectType'; +import { default as i18n } from 'i18next'; type TextProps = { text: TextQuestion; @@ -98,11 +99,20 @@ const Text: FC = ({ text, answers, setAnswers }) => { return (
-

{text.Title}

+

+ {i18n.language == 'en' && text.Title} + {i18n.language == 'fr' && text.TitleFr} + {i18n.language == 'de' && text.TitleDe}

{hintDisplay()} -
+ {i18n.language == 'en' && (
{text.Choices.map((choice, index) => choiceDisplay(choice, index))} -
+
)} + {i18n.language == 'fr' && (
+ {text.ChoicesFr.map((choice, index) => choiceDisplay(choice, index))} +
)} + {i18n.language == 'de' && (
+ {text.ChoicesDe.map((choice, index) => choiceDisplay(choice, index))} +
)}
{answers.Errors.get(text.ID)}
); diff --git a/web/frontend/src/pages/form/Result.tsx b/web/frontend/src/pages/form/Result.tsx index 8fb79e389..b07d164cd 100644 --- a/web/frontend/src/pages/form/Result.tsx +++ b/web/frontend/src/pages/form/Result.tsx @@ -4,6 +4,7 @@ import { DownloadedResults, RankResults, SelectResults, TextResults } from 'type import SelectResult from './components/SelectResult'; import RankResult from './components/RankResult'; import TextResult from './components/TextResult'; +import { default as i18n } from 'i18next'; import { ID, RANK, @@ -88,7 +89,17 @@ const FormResult: FC = () => { return { rankRes, selectRes, textRes }; }; - + const [titles,setTitles]= useState({}); + useEffect (()=> { + try{ + console.log('config', configuration.MainTitle) + const ts = JSON.parse(configuration.MainTitle) + setTitles(ts) + }catch(e){ + console.log('error',e) + } + + }, [configuration]) useEffect(() => { if (result !== null) { const { rankRes, selectRes, textRes } = groupResultsByID(); @@ -158,7 +169,9 @@ const FormResult: FC = () => { }); const data = { - Title: configuration.MainTitle, + TitleEn: i18n.language == 'en' && titles.en, + TitleFr: i18n.language == 'fr' && titles.fr, + TitleDe: i18n.language == 'en' && titles.de, NumberOfVotes: result.length, Results: dataToDownload, }; @@ -229,7 +242,9 @@ const FormResult: FC = () => { {t('totalNumberOfVotes', { votes: result.length })}

- {configuration.MainTitle} + {i18n.language == 'en' && titles.en} + {i18n.language == 'fr' && titles.fr} + {i18n.language == 'de' && titles.de}

diff --git a/web/frontend/src/pages/form/components/AddQuestionModal.tsx b/web/frontend/src/pages/form/components/AddQuestionModal.tsx index a5cc5dd9b..c6b027281 100644 --- a/web/frontend/src/pages/form/components/AddQuestionModal.tsx +++ b/web/frontend/src/pages/form/components/AddQuestionModal.tsx @@ -77,7 +77,7 @@ const AddQuestionModal: FC = ({ handleChange('addChoiceRank')(e); break; default: - addChoice(); + addChoice(language); break; } }; @@ -139,18 +139,19 @@ const AddQuestionModal: FC = ({ return; } }; - type renderQuestionModalProps = { + /*type renderQuestionModalProps = { lg : string; + nameC: string[]; }; - const RenderQuestionModal : FC = ({lg }) => { + const RenderQuestionModal : FC = ({lg ,nameC}) => { return(
- {Choices.map((choice: string, idx: number) => ( + {nameC.map((choice: string, idx: number) => (
= ({ />
- {Choices.length > 1 && ( + {nameC.length > 1 && ( )} - {idx === Choices.length - 1 && ( + {idx === nameC.length - 1 && (
))}
) - } - + }*/ + //{language === 'en' && ( )} + //{language === 'fr' && ( )} + //{language === 'de' && ()} return ( = ({
-
-
+
+
{availableLanguages.map( - (lang) => - language !== lang && ( - - ) - )} -
-
+ (lang, index) => + + + + )} + +
{t('mainProperties')}
@@ -288,13 +292,125 @@ const AddQuestionModal: FC = ({ - {language === 'en' && ( )} - - {language === 'fr' && ( )} - - {language === 'de' && ()} + + + {language === 'en' && ( +
+ {Choices.map((choice: string, idx: number) => ( +
+ + - +
+ {Choices.length > 1 && ( + + )} + {idx === Choices.length - 1 && ( + + )} +
+
+ ))} +
)} + {language === 'fr' && ( +
+ {ChoicesFr.map((choice: string, idx: number) => ( +
+ + + +
+ {ChoicesFr.length > 1 && ( + + )} + {idx === ChoicesFr.length - 1 && ( + + )} +
+
+ ))} +
)} + {language === 'de' && ( +
+ {ChoicesDe.map((choice: string, idx: number) => ( +
+ + + +
+ {ChoicesDe.length > 1 && ( + + )} + {idx === ChoicesDe.length - 1 && ( + + )} +
+
+ ))} +
)}
{errors .filter((err) => err.startsWith('Choices')) diff --git a/web/frontend/src/pages/form/components/FormForm.tsx b/web/frontend/src/pages/form/components/FormForm.tsx index 50eb843c8..46aaa7059 100644 --- a/web/frontend/src/pages/form/components/FormForm.tsx +++ b/web/frontend/src/pages/form/components/FormForm.tsx @@ -47,13 +47,13 @@ const FormForm: FC = () => { const [marshalledConf, setMarshalledConf] = useState(marshalConfig(conf)); const { configuration: previewConf, answers, setAnswers } = useConfiguration(marshalledConf); - const { MainTitle, Scaffold, TitleLg1,TitleLg2} = conf; //ScaffoldLg1, , ScaffoldLg2 + const { MainTitle, Scaffold, TitleLg1,TitleLg2,} = conf; const [language, setLanguage] = useState('en'); //const [titleChanging1, setTitleChanging1] = useState(true); // const [titleChanging2, setTitleChanging2] = useState(true); //const TitleLg1 = ''; - //const TitleLg2 = ''; + //const TitleLg2 = '';ScaffoldFr, ScaffoldDe useEffect(() => { setMarshalledConf(marshalConfig(conf)); @@ -123,11 +123,16 @@ const FormForm: FC = () => { newSubjects[newSubjects.findIndex((s) => s.ID === subject.ID)] = subject; setConf({ ...conf, Scaffold: newSubjects }); }; - +// languages: string const addSubject = () => { - const newSubjects = [...Scaffold]; - newSubjects.push(newSubject()); - setConf({ ...conf, Scaffold: newSubjects }); + const newSubjectsEn = [...Scaffold]; + //const newSubjectsFr = [...ScaffoldFr]; + //const newSubjectsDe = [...ScaffoldDe]; + // if (languages === 'en'){ + newSubjectsEn.push(newSubject()); + setConf({ ...conf, Scaffold: newSubjectsEn }); + //} + }; const handleConfirmRemoveSubject = () => { @@ -142,23 +147,25 @@ const FormForm: FC = () => { return (
-
+
{titleChanging ? ( <>
-
+
{availableLanguages.map( - (lang) => - +
+ + )} -
+
{language === 'en' && ( = ({
-
+
{availableLanguages.map( (lang) => - language !== lang && ( - - ) +
+ + + )} -
+
{language === 'en' && ( { - setState({ ...state, Choices: [...Choices, ''], ChoicesFr: [...ChoicesFr, ''], ChoicesDe: [...ChoicesDe, ''], MaxN: Choices.length + 1 }); + const addChoice = (lang) => { + switch (lang){ + case 'en': + setState({...state,Choices:[...Choices,''],MaxN: Choices.length + 1}) + break; + case 'fr': + setState({...state,ChoicesFr:[...ChoicesFr,''],MaxN: ChoicesFr.length + 1}) + break; + case 'de': + setState({...state,ChoicesDe:[...ChoicesDe,''],MaxN: ChoicesDe.length + 1}); + break; + default : + setState({...state,Choices:[...Choices,''],MaxN: Choices.length + 1}) + + } }; // remove a choice from the choices array @@ -90,29 +103,47 @@ const useQuestionForm = (initState: RankQuestion | SelectQuestion | TextQuestion }}}; // update the choice at the given index - const updateChoice = (index: number) => (e) => { + const updateChoice = (index: number,lang: string) => (e) => { e.persist(); - setState({ - ...state, - Choices: Choices.map((item: string, idx: number) => { - if (idx === index) { - return e.target.value; - } - return item; - }), - ChoicesFr: ChoicesFr.map((item: string, idx: number) => { - if (idx === index) { - return e.target.value; - } - return item; - }), - ChoicesDe: ChoicesDe.map((item: string, idx: number) => { - if (idx === index) { - return e.target.value; - } - return item; - }), - }); + switch (lang){ + case 'en' : + setState({...state,Choices: Choices.map((item: string, idx: number) => { + if (idx === index) { + return e.target.value; + } + return item; + })}) + break + case 'fr' : + setState({ + ...state, + ChoicesFr: ChoicesFr.map((item: string, idx: number) => { + if (idx === index) { + return e.target.value; + } + return item; + })}) + break + case 'de' : + setState({ + ...state, + ChoicesDe: ChoicesDe.map((item: string, idx: number) => { + if (idx === index) { + return e.target.value; + } + return item; + }), + }) + break + default: + setState({...state,Choices: Choices.map((item: string, idx: number) => { + if (idx === index) { + return e.target.value; + } + return item; + })}) + } + ; }; return { state, handleChange, addChoice, deleteChoice, updateChoice }; diff --git a/web/frontend/src/types/JSONparser.ts b/web/frontend/src/types/JSONparser.ts index afa4a2131..2b03f4c39 100644 --- a/web/frontend/src/types/JSONparser.ts +++ b/web/frontend/src/types/JSONparser.ts @@ -112,18 +112,18 @@ const unmarshalConfig = (json: any): types.Configuration => { MainTitle: json.MainTitle, Scaffold: [], TitleLg1: json.TitleLg1, - //ScaffoldLg1: [], + // ScaffoldFr: [], TitleLg2: json.TitleLg2, - //ScaffoldLg2: [], + //ScaffoldDe: [], }; for (const subject of json.Scaffold) { conf.Scaffold.push(unmarshalSubject(subject)); } - /*for (const subject of json.ScaffoldLg1) { - conf.ScaffoldLg1.push(unmarshalSubject(subject)); + /*for (const subject of json.ScaffoldFr) { + conf.ScaffoldFr.push(unmarshalSubject(subject)); } - for (const subject of json.ScaffoldLg2) { - conf.ScaffoldLg2.push(unmarshalSubject(subject)); + for (const subject of json.ScaffoldDe) { + conf.ScaffoldDe.push(unmarshalSubject(subject)); }*/ return conf; }; diff --git a/web/frontend/src/types/configuration.ts b/web/frontend/src/types/configuration.ts index d1ea9c0aa..2bf3aad50 100644 --- a/web/frontend/src/types/configuration.ts +++ b/web/frontend/src/types/configuration.ts @@ -56,9 +56,8 @@ interface Configuration { Scaffold: Subject[]; TitleLg1: string; TitleLg2: string; - //ScaffoldLg1: Subject[]; - - //ScaffoldLg2: Subject[]; + // ScaffoldFr: Subject[]; + //ScaffoldDe: Subject[]; } // Answers describes the current answers for each type of question diff --git a/web/frontend/src/types/getObjectType.ts b/web/frontend/src/types/getObjectType.ts index 635c27299..acdd7e708 100644 --- a/web/frontend/src/types/getObjectType.ts +++ b/web/frontend/src/types/getObjectType.ts @@ -9,9 +9,9 @@ const emptyConfiguration = (): types.Configuration => { MainTitle: '', Scaffold: [], TitleLg1: '', - //ScaffoldLg1: [], + //ScaffoldFr: [], TitleLg2: '', - //ScaffoldLg2: [], + //ScaffoldDe: [], }; }; From c55d0c0a1177ec3fe6d7badbe8161760b85c4855 Mon Sep 17 00:00:00 2001 From: Khadija Tagemouati Date: Mon, 14 Nov 2022 00:57:42 +0100 Subject: [PATCH 05/47] affichage du sondage dans la bonne langue From e5689effd51be75503ebf463130cce816acba2f6 Mon Sep 17 00:00:00 2001 From: Khadija Tagemouati Date: Thu, 24 Nov 2022 20:15:09 +0100 Subject: [PATCH 06/47] Parse subjects part1 --- web/backend/src/Server.ts | 3 +- .../src/components/utils/FillFormInfo.tsx | 1 + web/frontend/src/language/de.json | 8 +- web/frontend/src/language/fr.json | 2 +- web/frontend/src/mocks/mockData.ts | 22 ++-- web/frontend/src/pages/ballot/Show.tsx | 2 + .../pages/ballot/components/BallotDisplay.tsx | 55 +++++---- .../src/pages/ballot/components/Rank.tsx | 29 ++--- .../src/pages/ballot/components/Select.tsx | 27 +++-- .../src/pages/ballot/components/Text.tsx | 25 ++-- web/frontend/src/pages/form/Result.tsx | 1 + .../form/components/AddQuestionModal.tsx | 2 + .../src/pages/form/components/FormForm.tsx | 109 +++++++++--------- .../src/pages/form/components/FormRow.tsx | 2 +- .../src/pages/form/components/Question.tsx | 8 +- .../form/components/SubjectComponent.tsx | 96 +++++++-------- .../form/components/utils/useQuestionForm.ts | 27 +++-- web/frontend/src/types/JSONparser.ts | 18 ++- web/frontend/src/types/configuration.ts | 7 +- web/frontend/src/types/getObjectType.ts | 30 +++-- 20 files changed, 258 insertions(+), 216 deletions(-) diff --git a/web/backend/src/Server.ts b/web/backend/src/Server.ts index 562a6192b..f15c4e1a1 100644 --- a/web/backend/src/Server.ts +++ b/web/backend/src/Server.ts @@ -162,7 +162,7 @@ app.get('/api/personal_info', (req, res) => { sciper: req.session.userid, lastname: req.session.lastname, firstname: req.session.firstname, - role: req.session.role, + role: 'admin', islogged: true, }); } else { @@ -177,6 +177,7 @@ app.get('/api/personal_info', (req, res) => { }); function isAuthorized(roles: string[], req: express.Request): boolean { + return true; if (!req.session || !req.session.userid) { return false; } diff --git a/web/frontend/src/components/utils/FillFormInfo.tsx b/web/frontend/src/components/utils/FillFormInfo.tsx index a59cb9758..624c26f41 100644 --- a/web/frontend/src/components/utils/FillFormInfo.tsx +++ b/web/frontend/src/components/utils/FillFormInfo.tsx @@ -21,6 +21,7 @@ const useFillFormInfo = (formData: FormInfo) => { formData.Configuration.TitleEn = title.en; formData.Configuration.TitleFr = title.fr; formData.Configuration.TitleDe = title.de; + console.log('ok') setId(formData.FormID); setStatus(formData.Status); setPubKey(formData.Pubkey); diff --git a/web/frontend/src/language/de.json b/web/frontend/src/language/de.json index 36bda5cf6..de1671062 100644 --- a/web/frontend/src/language/de.json +++ b/web/frontend/src/language/de.json @@ -28,10 +28,10 @@ "saveQuestion": "Speichern", "addRank": "Rang hinzufügen", "editrank": "Rang bearbeiten", - "removerank": "Remove rank", - "addSelect": "Add select", - "editselect": "Edit select", - "removeselect": "Remove select", + "removerank": "Rang entfernen", + "addSelect": "Auswahl hinzufügen", + "editselect": "Bearbeiten auswählen", + "removeselect": "Auswahl entfernen", "addText": "Add text", "edittext": "Edit text", "removetext": "Remove text", diff --git a/web/frontend/src/language/fr.json b/web/frontend/src/language/fr.json index a7335391d..3c8bb212b 100644 --- a/web/frontend/src/language/fr.json +++ b/web/frontend/src/language/fr.json @@ -200,7 +200,7 @@ "resultExplanation1": "Les résultats des questions sélectives et textuelles sont donnés en pourcentage du nombre de voix pour un candidat divisé par le nombre de bulletins de vote. ", "resultExplanation2": "Les résultats de la question de classement correspondent au pourcentage de la note d'un candidat. Chaque électeur donne des points aux candidats en les classant de 1 à N (le plus bas est le mieux). ", "resultExplanation3": "Le score correspond à la somme des points obtenus par un candidat et est divisé par le nombre total de points attribués sur l'ensemble des bulletins, puis soustrait à un", - "shuffle": "Mélanfez", + "shuffle": "Mélangez", "decrypt": "Décryptez", "seeResult": "Voir les résultats", "totalNumberOfVotes": "Nombre total de votes : {{votes}}", diff --git a/web/frontend/src/mocks/mockData.ts b/web/frontend/src/mocks/mockData.ts index e99d440b1..493daa8f9 100644 --- a/web/frontend/src/mocks/mockData.ts +++ b/web/frontend/src/mocks/mockData.ts @@ -23,35 +23,35 @@ const mockRoster: string[] = [ ]; const mockForm1: any = { - MainTitle: 'Life on the campus', + MainTitle: '{ "en" : "Life on the campus", "fr" : "Vie sur le campus", "de" : "Vie sur le campus"}', Scaffold: [ { ID: (0xa2ab).toString(), - Title: 'Rate the course', + Title: '{ "en" : "Rate the course", "fr" : "Note la course", "de" : "Rate the course"}', Order: [(0x3fb2).toString(), (0x41e2).toString(), (0xcd13).toString(), (0xff31).toString()], Subjects: [ { - Title: "Let's talk about the food", + Title: '{ "en" : "Let s talk about the food", "fr" : "Parlons de la nourriture", "de" : "Let s talk about food"}', ID: (0xff31).toString(), Order: [(0xa319).toString(), (0x19c7).toString()], Subjects: [], Texts: [], Selects: [ { - Title: 'Select your ingredients', + Title: '{ "en" : "Select your ingredients", "fr" : "Choisi tes ingrédients", "de" : "Select your ingredients"}', ID: (0xa319).toString(), MaxN: 2, MinN: 1, - Choices: ['tomato', 'salad', 'onion'], + Choices: {'en': ['tomato', 'salad', 'onion'], 'fr': ['tomate','salade','oignon'], 'de': ['tomato', 'salad', 'onion']}, }, ], Ranks: [ { - Title: 'Rank the cafeteria', + Title: '{ "en" : "Rank the cafeteria", "fr" : "Ordonne les cafet", "de" : "Rank the cafeteria"}', ID: (0x19c7).toString(), MaxN: 3, MinN: 3, - Choices: ['BC', 'SV', 'Parmentier'], + Choices: {'en': ['BC', 'SV', 'Parmentier'], 'fr':['BC', 'SV', 'Parmentier'], 'de' : ['BC', 'SV', 'Parmentier']}, }, ], }, @@ -63,25 +63,25 @@ const mockForm1: any = { ID: (0x3fb2).toString(), MaxN: 1, MinN: 1, - Choices: ['1', '2', '3', '4', '5'], + Choices: {'en': ['1', '2', '3', '4', '5'],'fr': ['1', '2', '3', '4', '5'],'de': ['1', '2', '3', '4', '5']}, }, { Title: 'How did you find the teaching ?', ID: (0x41e2).toString(), MaxN: 1, MinN: 1, - Choices: ['bad', 'normal', 'good'], + Choices: {'en' : ['bad', 'normal', 'good'], 'fr': ['mauvais', 'normal', 'super'],'de' : ['bad', 'normal', 'good']}, }, ], Texts: [ { - Title: 'Who were the two best TAs ?', + Title: '{ "en" : Who were the two best TAs ?, "fr" : "Quels sont les deux meilleurs TA ? "de" : Who were the two best TAs ?} ', ID: (0xcd13).toString(), MaxLength: 20, MaxN: 2, MinN: 1, Regex: '', - Choices: ['TA1', 'TA2'], + Choices: {'en':['TA1', 'TA2'],'fr':['TA1', 'TA2'],'de': ['TA1', 'TA2']}, }, ], }, diff --git a/web/frontend/src/pages/ballot/Show.tsx b/web/frontend/src/pages/ballot/Show.tsx index 8d2864ba5..65998b31e 100644 --- a/web/frontend/src/pages/ballot/Show.tsx +++ b/web/frontend/src/pages/ballot/Show.tsx @@ -21,6 +21,7 @@ import BallotDisplay from './components/BallotDisplay'; import FormClosed from './components/FormClosed'; import Loading from 'pages/Loading'; import RedirectToModal from 'components/modal/RedirectToModal'; +import { default as i18n } from 'i18next'; const Ballot: FC = () => { const { t } = useTranslation(); @@ -139,6 +140,7 @@ const Ballot: FC = () => { answers={answers} setAnswers={setAnswers} userErrors={userErrors} + language={i18n.language} />
diff --git a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx index 4efeb24ae..3dd0ca461 100644 --- a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx +++ b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx @@ -5,15 +5,13 @@ import Rank, { handleOnDragEnd } from './Rank'; import Select from './Select'; import Text from './Text'; import { DragDropContext } from 'react-beautiful-dnd'; -import { default as i18n } from 'i18next'; - - type BallotDisplayProps = { configuration: Configuration; answers: Answers; setAnswers: (answers: Answers) => void; userErrors: string; + language: string; }; const BallotDisplay: FC = ({ @@ -21,31 +19,32 @@ const BallotDisplay: FC = ({ answers, setAnswers, userErrors, + language, }) => { - const [titles,setTitles]= useState({}); - useEffect (()=> { - try{ - console.log('config', configuration.MainTitle) - const ts = JSON.parse(configuration.MainTitle) - setTitles(ts) - }catch(e){ - console.log('error',e) + const [titles, setTitles] = useState({}); + useEffect(() => { + try { + console.log('config', configuration.MainTitle); + const ts = JSON.parse(configuration.MainTitle); + setTitles(ts); + } catch (e) { + console.log('error', e); } - - }, [configuration]) + }, [configuration]); const SubjectElementDisplay = (element: types.SubjectElement) => { return (
- {element.Type === RANK && } + {element.Type === RANK && } {element.Type === SELECT && ( -
setLanguage(lang)}> - {t(lang)} -
- - - - )} - -
+
+ {availableLanguages.map((lang, index) => ( + + ))} +
+
{language === 'en' && ( - setConf({ ...conf, MainTitle: e.target.value })} - name="MainTitle" - type="text" - placeholder={t('enterMainTitleLg')} - className="m-3 px-1 w-100 text-lg border rounded-md" - /> + setConf({ ...conf, MainTitle: e.target.value })} + name="MainTitle" + type="text" + placeholder={t('enterMainTitleLg')} + className="m-3 px-1 w-100 text-lg border rounded-md" + /> )} {language === 'fr' && ( - setConf({ ...conf, TitleLg1: e.target.value })} - name="MainTitle1" - type="text" - placeholder={t('enterMainTitleLg1')} - className="m-3 px-1 w-100 text-lg border rounded-md" - />)} + setConf({ ...conf, TitleFr: e.target.value })} + name="MainTitle1" + type="text" + placeholder={t('enterMainTitleLg1')} + className="m-3 px-1 w-100 text-lg border rounded-md" + /> + )} {language === 'de' && ( - setConf({ ...conf, TitleLg2: e.target.value })} - name="MainTitle2" - type="text" - placeholder={t('enterMainTitleLg2')} - className="m-3 px-1 w-100 text-lg border rounded-md" - />)} + setConf({ ...conf, TitleDe: e.target.value })} + name="MainTitle2" + type="text" + placeholder={t('enterMainTitleLg2')} + className="m-3 px-1 w-100 text-lg border rounded-md" + /> + )}
diff --git a/web/frontend/src/pages/form/components/FormRow.tsx b/web/frontend/src/pages/form/components/FormRow.tsx index 4a9b51589..9c76cbc1d 100644 --- a/web/frontend/src/pages/form/components/FormRow.tsx +++ b/web/frontend/src/pages/form/components/FormRow.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import { LightFormInfo } from 'types/form'; import { Link } from 'react-router-dom'; import FormStatus from './FormStatus'; diff --git a/web/frontend/src/pages/form/components/Question.tsx b/web/frontend/src/pages/form/components/Question.tsx index ed6ab6bff..8e8a3ec59 100644 --- a/web/frontend/src/pages/form/components/Question.tsx +++ b/web/frontend/src/pages/form/components/Question.tsx @@ -7,7 +7,7 @@ import { RankQuestion, SelectQuestion, TextQuestion } from 'types/configuration' import SubjectDropdown from './SubjectDropdown'; import AddQuestionModal from './AddQuestionModal'; import DisplayTypeIcon from './DisplayTypeIcon'; - +import { default as i18n } from 'i18next'; type QuestionProps = { question: RankQuestion | SelectQuestion | TextQuestion; notifyParent(question: RankQuestion | SelectQuestion | TextQuestion): void; @@ -15,7 +15,7 @@ type QuestionProps = { }; const Question: FC = ({ question, notifyParent, removeQuestion }) => { - const { Title, Type } = question; + const { Title, Type ,TitleFr, TitleDe} = question; const [openModal, setOpenModal] = useState(false); const dropdownContent: { @@ -53,7 +53,9 @@ const Question: FC = ({ question, notifyParent, removeQuestion })
- {Title.length ? Title : `Enter ${Type} title`} + {i18n.language === 'en' && (Title.length ? Title : `Enter ${Type} title`)} + {i18n.language === 'fr' && (TitleFr.length ? TitleFr : `Enter ${Type} title`)} + {i18n.language === 'de' && (TitleDe.length ? TitleDe : `Enter ${Type} title`)}
diff --git a/web/frontend/src/pages/form/components/SubjectComponent.tsx b/web/frontend/src/pages/form/components/SubjectComponent.tsx index 727495a53..73962336e 100644 --- a/web/frontend/src/pages/form/components/SubjectComponent.tsx +++ b/web/frontend/src/pages/form/components/SubjectComponent.tsx @@ -20,8 +20,7 @@ import { PencilIcon } from '@heroicons/react/solid'; import AddQuestionModal from './AddQuestionModal'; import { useTranslation } from 'react-i18next'; import RemoveElementModal from './RemoveElementModal'; -import { availableLanguages } from 'language/Configuration'; - +import { default as i18n } from 'i18next'; const MAX_NESTED_SUBJECT = 1; type SubjectComponentProps = { @@ -29,13 +28,14 @@ type SubjectComponentProps = { removeSubject: () => void; subjectObject: types.Subject; nestedLevel: number; + language: string; }; - const SubjectComponent: FC = ({ notifyParent, removeSubject, subjectObject, nestedLevel, + language, }) => { const { t } = useTranslation(); const emptyElementToRemove = { ID: '', Type: '' }; @@ -56,7 +56,7 @@ const SubjectComponent: FC = ({ const [components, setComponents] = useState([]); const { Title, Order, Elements, TitleFr, TitleDe } = subject; - const [language, setLanguage] = useState('en'); + //const [language, setLanguage] = useState('en'); // When a property changes, we notify the parent with the new subject object useEffect(() => { // We only notify the parent when the subject is mounted @@ -167,6 +167,7 @@ const SubjectComponent: FC = ({ subjectObject={sub} nestedLevel={nestedLevel + 1} key={sub.ID} + language={language} /> ); case RANK: @@ -303,62 +304,43 @@ const SubjectComponent: FC = ({
{titleChanging ? ( -
-
-
- {availableLanguages.map( - (lang) => - - - - )} -
-
{language === 'en' && ( setSubject({ ...subject, Title: e.target.value })} - name="Title" - type="text" - placeholder={t('enterSubjectTitleLg')} - className={`m-3 px-1 w-100 border rounded-md ${ - nestedLevel === 0 ? 'text-lg' : 'text-md' - } `} - /> + value={Title} + onChange={(e) => setSubject({ ...subject, Title: e.target.value })} + name="Title" + type="text" + placeholder={t('enterSubjectTitleLg')} + className={`m-3 px-1 w-120 border rounded-md ${ + nestedLevel === 0 ? 'text-lg' : 'text-md' + } `} + /> )} - {language === 'fr' &&( - setSubject({ ...subject, TitleFr: e.target.value })} - name="Title" - type="text" - placeholder={t('enterSubjectTitleLg1')} - className={`m-3 px-1 w-100 border rounded-md ${ - nestedLevel === 0 ? 'text-lg' : 'text-md' - } `} - /> + {language === 'fr' && ( + setSubject({ ...subject, TitleFr: e.target.value })} + name="Title" + type="text" + placeholder={t('enterSubjectTitleLg1')} + className={`m-3 px-1 w-120 border rounded-md ${ + nestedLevel === 0 ? 'text-lg' : 'text-md' + } `} + /> + )} + {language === 'de' && ( + setSubject({ ...subject, TitleDe: e.target.value })} + name="Title" + type="text" + placeholder={t('enterSubjectTitleLg2')} + className={`m-3 px-1 w-120 border rounded-md ${ + nestedLevel === 0 ? 'text-lg' : 'text-md' + } `} + /> )} - {language === 'de' &&( - setSubject({ ...subject, TitleDe: e.target.value })} - name="Title" - type="text" - placeholder={t('enterSubjectTitleLg2')} - className={`m-3 px-1 w-100 border rounded-md ${ - nestedLevel === 0 ? 'text-lg' : 'text-md' - } `} - /> - )} -
)} - {idx === Choices.length - 1 && ( + {idx === Choices.get('en').length - 1 && (
)} {language === 'fr' && (
- {ChoicesFr.map((choice: string, idx: number) => ( + {Choices.get('fr').map((choice: string, idx: number) => (
= ({ />
- {ChoicesFr.length > 1 && ( + {Choices.get('fr').length > 1 && ( )} - {idx === ChoicesFr.length - 1 && ( + {idx === Choices.get('fr').length - 1 && (
)} {language === 'de' && (
- {ChoicesDe.map((choice: string, idx: number) => ( + {Choices.get('de').map((choice: string, idx: number) => (
= ({ />
- {ChoicesDe.length > 1 && ( + {Choices.get('de').length > 1 && ( )} - {idx === ChoicesDe.length - 1 && ( + {idx === Choices.get('de').length - 1 && ( )} - {idx === Choices.get('en').length - 1 && ( + {idx === Choices.length - 1 && (
)} {language === 'fr' && (
- {Choices.get('fr').map((choice: string, idx: number) => ( + {ChoicesFr.map((choice: string, idx: number) => (
= ({ />
- {Choices.get('fr').length > 1 && ( + {ChoicesFr.length > 1 && ( )} - {idx === Choices.get('fr').length - 1 && ( + {idx === ChoicesFr.length - 1 && (
)} {language === 'de' && (
- {Choices.get('de').map((choice: string, idx: number) => ( + {ChoicesDe.map((choice: string, idx: number) => (
= ({ />
- {Choices.get('de').length > 1 && ( + {ChoicesDe.length > 1 && ( )} - {idx === Choices.get('de').length - 1 && ( + {idx === ChoicesDe.length - 1 && ( - )} - {idx === nameC.length - 1 && ( - - )} -
-
- ))} -
) + {Choices.get('en').map((choice: string, idx: number) => ( +
+ +
+ {Choices.get('en').length > 1 && ( + + )} + {idx === Choices.get('en').length - 1 && ( + + )} +
+
+ ))} +
+ )} + }*/ - //{language === 'en' && ( )} - //{language === 'fr' && ( )} - //{language === 'de' && ()} + //{language === 'en' && ( )} + //{language === 'fr' && ( )} + //{language === 'de' && ()} return ( = ({
-
-
- {availableLanguages.map( - (lang, index) => - - - - )} -
-
+
+
+ {availableLanguages.map((lang, index) => ( + + ))} +
+
{t('mainProperties')}
@@ -278,7 +266,7 @@ const AddQuestionModal: FC = ({ placeholder={'Entrer votre Titre'} className="my-1 px-1 w-60 ml-1 border rounded-md" /> - )} + )} {language === 'de' && ( = ({
+ {language=== 'en' && ( = ({ type="text" placeholder={t('enterHint')} className="my-1 px-1 w-60 ml-1 border rounded-md" - /> + />)} + {language=== 'fr' && ( + )} + {language=== 'de' && ( + )}
- - {language === 'en' && ( -
- {Choices.map((choice: string, idx: number) => ( -
- - -
- {Choices.length > 1 && ( - + {language === 'en' && ( +
+ {Choices.get('en').map((choice: string, idx: number) => ( +
+ +
+ {Choices.get('en').length > 1 && ( + + )} + {idx === Choices.get('en').length - 1 && ( + + )} +
+
+ ))} +
)} - {idx === Choices.length - 1 && ( - + {language === 'fr' && ( +
+ {Choices.get('fr').map((choice: string, idx: number) => ( +
+ +
+ {Choices.get('fr').length > 1 && ( + + )} + {idx === Choices.get('fr').length - 1 && ( + + )} +
+
+ ))} +
)} -
-
- ))} -
)} - {language === 'fr' && ( -
- {ChoicesFr.map((choice: string, idx: number) => ( -
- + {language === 'de' && ( +
+ {Choices.get('de').map((choice: string, idx: number) => ( +
-
- {ChoicesFr.length > 1 && ( + {Choices.get('de').length > 1 && ( - )} - {idx === ChoicesFr.length - 1 && ( + )} + {idx === Choices.get('de').length - 1 && ( - )} + )}
-
+
))} -
)} - {language === 'de' && ( -
- {ChoicesDe.map((choice: string, idx: number) => ( -
- - - -
- {ChoicesDe.length > 1 && ( - - )} - {idx === ChoicesDe.length - 1 && ( - - )} -
-
- ))} -
)} +
+ )}
{errors .filter((err) => err.startsWith('Choices')) diff --git a/web/frontend/src/pages/form/components/FormRow.tsx b/web/frontend/src/pages/form/components/FormRow.tsx index 9c76cbc1d..4a9b51589 100644 --- a/web/frontend/src/pages/form/components/FormRow.tsx +++ b/web/frontend/src/pages/form/components/FormRow.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect, useState } from 'react'; +import React, { FC } from 'react'; import { LightFormInfo } from 'types/form'; import { Link } from 'react-router-dom'; import FormStatus from './FormStatus'; diff --git a/web/frontend/src/pages/form/components/Question.tsx b/web/frontend/src/pages/form/components/Question.tsx index 8e8a3ec59..20cbe8270 100644 --- a/web/frontend/src/pages/form/components/Question.tsx +++ b/web/frontend/src/pages/form/components/Question.tsx @@ -15,7 +15,7 @@ type QuestionProps = { }; const Question: FC = ({ question, notifyParent, removeQuestion }) => { - const { Title, Type ,TitleFr, TitleDe} = question; + const { Title, Type, TitleFr, TitleDe } = question; const [openModal, setOpenModal] = useState(false); const dropdownContent: { diff --git a/web/frontend/src/pages/form/components/SubjectComponent.tsx b/web/frontend/src/pages/form/components/SubjectComponent.tsx index 73962336e..9624180d4 100644 --- a/web/frontend/src/pages/form/components/SubjectComponent.tsx +++ b/web/frontend/src/pages/form/components/SubjectComponent.tsx @@ -20,7 +20,6 @@ import { PencilIcon } from '@heroicons/react/solid'; import AddQuestionModal from './AddQuestionModal'; import { useTranslation } from 'react-i18next'; import RemoveElementModal from './RemoveElementModal'; -import { default as i18n } from 'i18next'; const MAX_NESTED_SUBJECT = 1; type SubjectComponentProps = { diff --git a/web/frontend/src/pages/form/components/utils/countResult.ts b/web/frontend/src/pages/form/components/utils/countResult.ts index 7c7973955..af10a95ed 100644 --- a/web/frontend/src/pages/form/components/utils/countResult.ts +++ b/web/frontend/src/pages/form/components/utils/countResult.ts @@ -8,7 +8,7 @@ const countRankResult = (rankResult: number[][], rank: RankQuestion) => { const resultsInPercent: string[] = []; const minIndices: number[] = []; // the maximum score achievable is (number of choices - 1) * number of ballots - let min = (rank.Choices.length - 1) * rankResult.length; + let min = (rank.Choices.get('en').length - 1) * rankResult.length; const results = rankResult.reduce((a, b) => { return a.map((value, index) => { diff --git a/web/frontend/src/pages/form/components/utils/useQuestionForm.ts b/web/frontend/src/pages/form/components/utils/useQuestionForm.ts index 8c6c5032f..592bc8bf1 100644 --- a/web/frontend/src/pages/form/components/utils/useQuestionForm.ts +++ b/web/frontend/src/pages/form/components/utils/useQuestionForm.ts @@ -1,16 +1,21 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { RankQuestion, SelectQuestion, TextQuestion } from 'types/configuration'; // form hook that handles the form state for all types of questions const useQuestionForm = (initState: RankQuestion | SelectQuestion | TextQuestion) => { const [state, setState] = useState(initState); - const { MinN, Choices,ChoicesDe,ChoicesFr } = state; + const { MinN, Choices } = state; // depending on the type of the Exception in the question, the form state is // updated accordingly const handleChange = (Exception?: string, optionnalValues?: number) => (e?: React.ChangeEvent) => { const { value, type, name } = e.target; + const obj = Object.fromEntries(Choices) + const newChoices = new Map(Object.entries(obj)) + newChoices.set('en', [...newChoices.get('en'),'']) + newChoices.set('fr', [...newChoices.get('fr'),'']) + newChoices.set('de', [...newChoices.get('de'),'']) switch (Exception) { case 'RankMinMax': setState({ ...state, MinN: Number(value), MaxN: Number(value) }); @@ -18,29 +23,25 @@ const useQuestionForm = (initState: RankQuestion | SelectQuestion | TextQuestion case 'addChoiceRank': setState({ ...state, - Choices: [...Choices, ''], - ChoicesFr: [...ChoicesFr, ''], - ChoicesDe: [...ChoicesDe, ''], - MaxN: Math.max(Choices.length + 1,ChoicesFr.length + 1, ChoicesDe.length + 1), - MinN: Math.min(Choices.length + 1,ChoicesFr.length + 1, ChoicesDe.length + 1), + Choices: newChoices, + MaxN: Math.max(Choices.get('en').length + 1, Choices.get('fr').length + 1, Choices.get('de').length + 1), + MinN: Math.min(Choices.get('en').length + 1, Choices.get('fr').length + 1, Choices.get('de').length + 1), }); break; case 'deleteChoiceRank': - const filteredChoices = Choices.filter( + const filteredChoices = Choices.get('en').filter( (item: string, idx: number) => idx !== optionnalValues ); - const filteredChoicesFr = ChoicesFr.filter( + const filteredChoicesFr = Choices.get('fr').filter( (item: string, idx: number) => idx !== optionnalValues ); - const filteredChoicesDe = ChoicesDe.filter( + const filteredChoicesDe = Choices.get('de').filter( (item: string, idx: number) => idx !== optionnalValues ); - + const newState = {'en': filteredChoices, 'fr': filteredChoicesFr, 'de': filteredChoicesDe} setState({ ...state, - Choices: filteredChoices, - ChoicesFr: filteredChoicesFr, - ChoicesDe: filteredChoicesDe, + Choices: new Map(Object.entries(newState)), MaxN: filteredChoices.length, MinN: filteredChoices.length, }); @@ -68,94 +69,113 @@ const useQuestionForm = (initState: RankQuestion | SelectQuestion | TextQuestion // updates the choices array when the user adds a new choice const addChoice = (lang) => { - switch (lang){ - case 'en': - setState({...state,Choices:[...Choices,''],MaxN: Choices.length + 1}) - break; - case 'fr': - setState({...state,ChoicesFr:[...ChoicesFr,''],MaxN: ChoicesFr.length + 1}) - break; - case 'de': - setState({...state,ChoicesDe:[...ChoicesDe,''],MaxN: ChoicesDe.length + 1}); - break; - default : - setState({...state,Choices:[...Choices,''],MaxN: Choices.length + 1}) - + const obj = Object.fromEntries(Choices) + const newChoices = new Map(Object.entries(obj)) + switch (lang) { + case 'en': + setState({ ...state, Choices:newChoices.set('en', [...newChoices.get('en'),'']) , MaxN: Choices.get('en').length + 1 }); + break; + case 'fr': + setState({ ...state, Choices :newChoices.set('fr', [...newChoices.get('fr'),'']) , MaxN: Choices.get('fr').length + 1 }); + break; + case 'de': + setState({ ...state, Choices :newChoices.set('de', [...newChoices.get('de'),'']) , MaxN: Choices.get('de').length + 1 }); + break; + default: + setState({ ...state, Choices:newChoices.set('en', [...newChoices.get('en'),'']) , MaxN: Choices.get('en').length + 1 }); } }; // remove a choice from the choices array const deleteChoice = (index: number) => { - if (Choices.length > MinN) { - const filteredChoices = Choices.filter((item: string, idx: number) => idx !== index); + if (Choices.get('en').length > MinN) { + const filteredChoices = Choices.get('en').filter((item: string, idx: number) => idx !== index); + setState({ + ...state, + Choices: Choices.set('en', filteredChoices), + MaxN: Math.max(filteredChoices.length + 1, Choices.get('fr').length + 1, Choices.get('de').length + 1), + }); + } + if (Choices.get('fr').length > MinN) { + const filteredChoicesFr = Choices.get('fr').filter((item: string, idx: number) => idx !== index); setState({ ...state, - Choices: filteredChoices, - MaxN: Math.max(filteredChoices.length + 1,ChoicesFr.length + 1, ChoicesDe.length + 1), + Choices: Choices.set('fr', filteredChoicesFr), + MaxN: Math.max(Choices.get('en').length + 1, filteredChoicesFr.length + 1, Choices.get('de').length + 1), }); } - if (ChoicesFr.length > MinN) { - const filteredChoicesFr = ChoicesFr.filter((item: string, idx: number) => idx !== index); + if (Choices.get('de').length > MinN) { + const filteredChoicesDe = Choices.get('de').filter((item: string, idx: number) => idx !== index); + setState({ + ...state, + Choices: Choices.set('de', filteredChoicesDe) , + MaxN: Math.max(Choices.get('en').length + 1, Choices.get('de').length + 1, filteredChoicesDe.length + 1), + }); + } + }; + useEffect (() => { + console.log('Choices',[...Choices.entries()]) + },[Choices]) + // update the choice at the given index + const updateChoice = (index: number, lang: string) => (e) => { + e.persist(); + const obj = Object.fromEntries(Choices) + const newChoices = new Map(Object.entries(obj)) + switch (lang) { + case 'en': + const choice = newChoices.get('en').map((item: string, idx: number) => { + console.log(e.target.value) + console.log(index) + if (idx === index) { + return e.target.value; + } + return item; + }) + newChoices.set('en',choice) setState({ - ...state, - ChoicesFr: filteredChoicesFr, - MaxN: Math.max(Choices.length + 1,filteredChoicesFr.length + 1, ChoicesDe.length + 1), - }); - } - if (ChoicesDe.length > MinN) { - const filteredChoicesDe = ChoicesDe.filter((item: string, idx: number) => idx !== index); + ...state, + Choices: newChoices, + }); + console.log('new', [...newChoices.entries()]) + break; + case 'fr': setState({ - ...state, - ChoicesDe: filteredChoicesDe, - MaxN: Math.max(Choices.length + 1,ChoicesFr.length + 1, filteredChoicesDe.length + 1), - }); - }}; - - // update the choice at the given index - const updateChoice = (index: number,lang: string) => (e) => { - e.persist(); - switch (lang){ - case 'en' : - setState({...state,Choices: Choices.map((item: string, idx: number) => { - if (idx === index) { - return e.target.value; - } - return item; - })}) - break - case 'fr' : - setState({ - ...state, - ChoicesFr: ChoicesFr.map((item: string, idx: number) => { - if (idx === index) { - return e.target.value; - } - return item; - })}) - break - case 'de' : - setState({ - ...state, - ChoicesDe: ChoicesDe.map((item: string, idx: number) => { - if (idx === index) { - return e.target.value; - } - return item; - }), - }) - break - default: - setState({...state,Choices: Choices.map((item: string, idx: number) => { - if (idx === index) { - return e.target.value; - } - return item; - })}) - } - ; + ...state, + Choices: newChoices.set('fr',newChoices.get('fr').map((item: string, idx: number) => { + if (idx === index) { + return e.target.value; + } + return item; + })), + }); + break; + case 'de': + setState({ + ...state, + Choices: newChoices.set('de',newChoices.get('de').map((item: string, idx: number) => { + if (idx === index) { + return e.target.value; + } + return item; + })), + }); + break; + default: + setState({ + ...state, + Choices: newChoices.set('en',newChoices.get('en').map((item: string, idx: number) => { + if (idx === index) { + return e.target.value; + } + return item; + })), + }); + } + }; - + //console.log('end', [...Choices.entries()]) + console.log(state) return { state, handleChange, addChoice, deleteChoice, updateChoice }; }; -export default useQuestionForm; \ No newline at end of file +export default useQuestionForm; diff --git a/web/frontend/src/schema/configurationValidation.ts b/web/frontend/src/schema/configurationValidation.ts index 10a3741fb..5a9f587e1 100644 --- a/web/frontend/src/schema/configurationValidation.ts +++ b/web/frontend/src/schema/configurationValidation.ts @@ -36,7 +36,7 @@ const selectsSchema = yup.object({ }); } - if (MaxN > Choices.length) { + if (MaxN > Choices.get('en').length) { return this.createError({ path, message: `MaxN should be less or equal to Choices length in selects [objectID: ${ID}]`, @@ -83,13 +83,13 @@ const selectsSchema = yup.object({ const { path, parent } = this; const { MaxN, Choices, ID } = parent; - if (Choices.length < MaxN) { + if (Choices.get('en').length < MaxN) { return this.createError({ path, message: `Choices array length should be at least equal to Max in selects [objectID: ${ID}]`, }); } - if (Choices.includes('')) { + if (Choices.get('en').includes('') || Choices.get('fr').includes('') || Choices.get('de').includes('')) { return this.createError({ path, message: `Choices should not be empty in selects [objectID: ${ID}]`, @@ -181,13 +181,13 @@ const ranksSchema = yup.object({ const { path, parent } = this; const { MinN, MaxN, Choices, ID } = parent; - if (Choices.length !== MaxN || Choices.length !== MinN) { + if (Choices.get('en').length !== MaxN || Choices.get('en').length !== MinN) { return this.createError({ path, message: `Choices array length should be equal to MaxN and MinN in ranks [objectID: ${ID}]`, }); } - if (Choices.includes('')) { + if (Choices.get('en').includes('') || Choices.get('fr').includes('') || Choices.get('de').includes('')) { return this.createError({ path, message: `Choices should not be empty in ranks [objectID: ${ID}]`, @@ -301,7 +301,7 @@ const textsSchema = yup.object({ const { path, parent } = this; const { MaxN, Choices, ID } = parent; - if (Choices.length !== MaxN) { + if (Choices.get('en').length !== MaxN) { return this.createError({ path, message: `Choices array length should be equal to the number of choices [objectID: ${ID}]`, diff --git a/web/frontend/src/types/JSONparser.ts b/web/frontend/src/types/JSONparser.ts index 7c42e0101..e8e5d17ac 100644 --- a/web/frontend/src/types/JSONparser.ts +++ b/web/frontend/src/types/JSONparser.ts @@ -10,8 +10,10 @@ const unmarshalText = (text: any): types.TextQuestion => { }; const unmarshalRank = (rank: any): types.RankQuestion => { + const choice = JSON return { ...rank, + Choices : {'en': rank.Choice.en, 'fr': rank.Choice.fr, 'de': rank.Choice.de}, Type: RANK, }; }; @@ -74,7 +76,7 @@ const unmarshalSubjectAndCreateAnswers = ( elements.set(rank.ID, rank); answerMap.RankAnswers.set( rank.ID, - Array.from(Array(rank.Choices.length).keys()) + Array.from(Array(rank.Choices.get('en').length).keys()) ); answerMap.Errors.set(rank.ID, ''); } @@ -85,7 +87,7 @@ const unmarshalSubjectAndCreateAnswers = ( elements.set(select.ID, select); answerMap.SelectAnswers.set( select.ID, - new Array(select.Choices.length).fill(false) + new Array(select.Choices.get('en').length).fill(false) ); answerMap.Errors.set(select.ID, ''); } @@ -95,7 +97,7 @@ const unmarshalSubjectAndCreateAnswers = ( elements.set(text.ID, text); answerMap.TextAnswers.set( text.ID, - new Array(text.Choices.length).fill('') + new Array(text.Choices.get('en').length).fill('') ); answerMap.Errors.set(text.ID, ''); } @@ -112,7 +114,7 @@ const unmarshalConfig = (json: any): types.Configuration => { MainTitle: json.MainTitle, Scaffold: [], TitleFr: json.TitleFr, - // ScaffoldFr: [], + // ScaffoldFr: [], TitleDe: json.TitleDe, //ScaffoldDe: [], }; @@ -145,13 +147,12 @@ const unmarshalConfigAndCreateAnswers = ( }; const marshalText = (text: types.TextQuestion): any => { - const newText: any = { ...text }; delete newText.Type; return newText; }; -const marshalRank = (rank: types.RankQuestion): any => { +const marshalRank = (rank: types.RankQuestion): any => { const newRank: any = { ...rank }; delete newRank.Type; return newRank; @@ -167,8 +168,7 @@ const marshalSubject = (subject: types.Subject): any => { const newSubject: any = { ...subject }; const { rankQuestion, selectQuestion, textQuestion, subjects } = toArraysOfSubjectElement(subject.Elements); - console.log('toArray', toArraysOfSubjectElement(subject.Elements)) - + console.log('toArray', toArraysOfSubjectElement(subject.Elements)); delete newSubject.Type; delete newSubject.Elements; @@ -178,7 +178,6 @@ const marshalSubject = (subject: types.Subject): any => { newSubject.Subjects = new Array(); rankQuestion.forEach((rank) => newSubject.Ranks.push(marshalRank(rank))); - console.log('rank',rankQuestion) selectQuestion.forEach((select) => newSubject.Selects.push(marshalSelect(select)) ); @@ -189,15 +188,19 @@ const marshalSubject = (subject: types.Subject): any => { }; const marshalConfig = (configuration: types.Configuration): any => { - const title = {en : configuration.MainTitle, fr : configuration.TitleFr, de : configuration.TitleDe}; - console.log('marshall' ,JSON.stringify(title)) - //const scaffold ={en : configuration.Scaffold[0]} - const conf = { MainTitle:JSON.stringify(title), Scaffold: [] }; + const title = { + en: configuration.MainTitle, + fr: configuration.TitleFr, + de: configuration.TitleDe, + }; + console.log('marshall', JSON.stringify(title)); + //const scaffold ={en : configuration.Scaffold[0]} + const conf = { MainTitle: JSON.stringify(title), Scaffold: [] }; for (const subject of configuration.Scaffold) { conf.Scaffold.push(marshalSubject(subject)); } - console.log('maintitle', conf.MainTitle) - console.log('scaffold',conf.Scaffold) + console.log('maintitle', conf.MainTitle); + console.log('scaffold', conf.Scaffold); return conf; }; diff --git a/web/frontend/src/types/configuration.ts b/web/frontend/src/types/configuration.ts index 901b3e39f..65c78cd7d 100644 --- a/web/frontend/src/types/configuration.ts +++ b/web/frontend/src/types/configuration.ts @@ -11,17 +11,17 @@ interface SubjectElement { Title: string; TitleFr: string; TitleDe: string; + Choice: string; } // Rank describes a "rank" question, which requires the user to rank choices. interface RankQuestion extends SubjectElement { MaxN: number; MinN: number; - Choices: string[] - ChoicesFr: string[] - ChoicesDe: string[] - //Map; + Choices: Map; Hint: string; + HintFr: string; + HintDe: string; } // Text describes a "text" question, which allows the user to enter free text. interface TextQuestion extends SubjectElement { @@ -29,11 +29,10 @@ interface TextQuestion extends SubjectElement { MinN: number; MaxLength: number; Regex: string; - Choices: string[] - ChoicesFr: string[] - ChoicesDe: string[] - //Choices: Map; + Choices: Map; Hint: string; + HintFr: string; + HintDe: string; } // Select describes a "select" question, which requires the user to select one @@ -41,12 +40,10 @@ interface TextQuestion extends SubjectElement { interface SelectQuestion extends SubjectElement { MaxN: number; MinN: number; - Choices: string[] - ChoicesFr: string[] - ChoicesDe: string[] - //Choices: Map; + Choices: Map; Hint: string; - + HintFr: string; + HintDe: string; } interface Subject extends SubjectElement { @@ -62,7 +59,6 @@ interface Configuration { Scaffold: Subject[]; TitleFr: string; TitleDe: string; - } // Answers describes the current answers for each type of question diff --git a/web/frontend/src/types/getObjectType.ts b/web/frontend/src/types/getObjectType.ts index 22271608a..9e207d702 100644 --- a/web/frontend/src/types/getObjectType.ts +++ b/web/frontend/src/types/getObjectType.ts @@ -1,3 +1,4 @@ +import { string } from 'prop-types'; import ShortUniqueId from 'short-unique-id'; import * as types from './configuration'; import { ID, RANK, SELECT, SUBJECT, TEXT } from './configuration'; @@ -24,9 +25,10 @@ const newSubject = (): types.Subject => { Order: [], Type: SUBJECT, Elements: new Map(), + Choice: '', }; }; - +const obj = {'en': [''], 'fr': [''], 'de' : ['']} const newRank = (): types.RankQuestion => { return { ID: uid(), @@ -35,11 +37,12 @@ const newRank = (): types.RankQuestion => { TitleDe: '', MaxN: 2, MinN: 2, - Choices: [''], - ChoicesFr: [''], - ChoicesDe: [''], + Choices: new Map(Object.entries(obj)), Type: RANK, Hint: '', + HintFr: '', + HintDe: '', + Choice: '', }; }; @@ -51,12 +54,12 @@ const newSelect = (): types.SelectQuestion => { TitleFr: '', MaxN: 1, MinN: 1, - //Choices: new Map(), - Choices: [''], - ChoicesFr: [''], - ChoicesDe: [''], + Choices: new Map(Object.entries(obj)), Type: SELECT, Hint: '', + HintFr: '', + HintDe: '', + Choice: '', }; }; @@ -70,12 +73,12 @@ const newText = (): types.TextQuestion => { MinN: 0, MaxLength: 50, Regex: '', - //Choices: new Map([['en', ['']],['fr', ['']],['de', ['']]]), - Choices: [''], - ChoicesFr: [''], - ChoicesDe: [''], + Choices: new Map(Object.entries(obj)), Type: TEXT, Hint: '', + HintFr: '', + HintDe: '', + Choice: '', }; }; @@ -111,35 +114,59 @@ const toArraysOfSubjectElement = ( const textQuestion: types.TextQuestion[] = []; const subjects: types.Subject[] = []; let title = ''; + let choice = ''; elements.forEach((element) => { switch (element.Type) { case RANK: - title = JSON.stringify({en : element.Title, fr : element.TitleFr, de : element.TitleDe}); - rankQuestion.push({...element as types.RankQuestion, Title: title}); + console.log('je suis passer par la') + title = JSON.stringify({ + en: element.Title, + fr: element.TitleFr, + de: element.TitleDe, + }); + choice =JSON.stringify({ + en : (element as types.RankQuestion).Choices.get('en'), + fr : (element as types.RankQuestion).Choices.get('fr'), + de : (element as types.RankQuestion).Choices.get('de') + }); + + rankQuestion.push({ ...(element as types.RankQuestion), Title: title,Choice : choice}); break; case SELECT: - title = JSON.stringify({en : element.Title, fr : element.TitleFr, de : element.TitleDe}); - selectQuestion.push({...element as types.SelectQuestion, Title : title}); + title = JSON.stringify({ + en: element.Title, + fr: element.TitleFr, + de: element.TitleDe, + }); + selectQuestion.push({ + ...(element as types.SelectQuestion), + Title: title, + }); break; case TEXT: - title = JSON.stringify({en : element.Title, fr : element.TitleFr, de : element.TitleDe}); - + title = JSON.stringify({ + en: element.Title, + fr: element.TitleFr, + de: element.TitleDe, + }); textQuestion.push({ - ...element as types.TextQuestion, - Title: title, + ...(element as types.TextQuestion), + Title: title, }); - console.log('ok') + console.log('ok'); break; case SUBJECT: - title = JSON.stringify({en : element.Title, fr : element.TitleFr, de : element.TitleDe}); - + title = JSON.stringify({ + en: element.Title, + fr: element.TitleFr, + de: element.TitleDe, + }); subjects.push({ - ...element as types.Subject, - Title: title, - + ...(element as types.Subject), + Title: title, }); - console.log('ok') + console.log('ok'); break; } }); From 3570041fe67a880a60b16809a11c50c259e9e8ca Mon Sep 17 00:00:00 2001 From: Khadija Tagemouati Date: Mon, 19 Dec 2022 10:14:01 +0100 Subject: [PATCH 10/47] Add languages to Hint and try to fix the bug of the map --- kill_testa.sh | 6 - runNode.sh | 138 ------------- setupnNode.sh | 190 ------------------ web/frontend/src/language/en.json | 4 +- .../pages/ballot/components/BallotDisplay.tsx | 26 +-- .../src/pages/ballot/components/Rank.tsx | 26 ++- .../src/pages/ballot/components/Select.tsx | 21 +- .../src/pages/ballot/components/Text.tsx | 21 +- web/frontend/src/pages/form/GroupedResult.tsx | 1 - web/frontend/src/pages/form/Result.tsx | 1 - .../form/components/AddQuestionModal.tsx | 85 ++++---- .../form/components/SubjectComponent.tsx | 4 +- .../form/components/utils/countResult.ts | 2 +- .../form/components/utils/useQuestionForm.ts | 176 ++++++++-------- .../src/schema/configurationValidation.ts | 12 +- web/frontend/src/types/JSONparser.ts | 20 +- web/frontend/src/types/configuration.ts | 9 +- web/frontend/src/types/getObjectType.ts | 50 +++-- 18 files changed, 253 insertions(+), 539 deletions(-) delete mode 100755 kill_testa.sh delete mode 100755 runNode.sh delete mode 100755 setupnNode.sh diff --git a/kill_testa.sh b/kill_testa.sh deleted file mode 100755 index 270f13b7d..000000000 --- a/kill_testa.sh +++ /dev/null @@ -1,6 +0,0 @@ -#! /bin/sh - -# This script kills the tmux session started in start_test.sh and -# removes all the data pertaining to the test. - -rm -rf /tmp/node* && tmux kill-session -t d-voting-test diff --git a/runNode.sh b/runNode.sh deleted file mode 100755 index 2b87a41f8..000000000 --- a/runNode.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/bin/bash - -# This script is creating n dela voting nodes needed to run -# an evoting system. User can pass number of nodes, window attach mode useful for autotest, -# and docker usage. - -set -e - -# by default run on local -DOCKER=false -ATTACH=true - -POSITIONAL_ARGS=() - -while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - echo "This script is creating n dela voting nodes" - echo "Options:" - echo "-h | --help program help (this file)" - echo "-n | --node number of d-voting nodes" - echo "-a | --attach attach tmux window to current shell true/false, by default true" - echo "-d | --docker launch nodes on docker containers true/false, by default false" - exit 0 - ;; - -n|--node) - N_NODE="$2" - shift # past argument - shift # past value - ;; - -a|--attach) - ATTACH="$2" - shift # past argument - shift # past value - ;; - -d|--docker) - DOCKER="$2" - shift # past argument - shift # past value - ;; - -*|--*) - echo "Unknown option $1" - exit 1 - ;; - *) - POSITIONAL_ARGS+=("$1") # save positional arg - shift # past argument - ;; - esac -done - -set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters - -set -o errexit - -command -v tmux >/dev/null 2>&1 || { echo >&2 "tmux is not on your PATH!"; exit 1; } - - -pk=adbacd10fdb9822c71025d6d00092b8a4abb5ebcb673d28d863f7c7c5adaddf3 - - -# Launch session -s="d-voting-test" - -# check if session already exists, if so run the kill_test.sh script -if tmux has-session -t $s 2>/dev/null; then - echo "Session $s already exists, killing it" - ./kill_test.sh -fi - - - -tmux new-session -d -s $s - -# Checks that we can afford to have at least one Byzantine node and keep the -# system working, which is not possible with less than 4 nodes. -if [ $N_NODE -le 3 ]; then - echo "Warning: the number of nodes is less or equal than 3, it will not be resiliant if one node is down" -fi - - -# Clean logs -rm -rf ./log/log -mkdir -p ./log/log - -crypto bls signer new --save private.key --force - -if [ "$DOCKER" == false ]; then - go build -o memcoin ./cli/memcoin -else - # Clean created containers and tmp dir - if [[ $(docker ps -a -q --filter ancestor=node) ]]; then - docker rm -f $(docker ps -a -q --filter ancestor=node) - fi - - rm -rf ./nodedata - mkdir nodedata - - # Create docker network (only run once) - docker network create --driver bridge evoting-net || true - -fi - -from=1 -to=$N_NODE -while [ $from -le $to ] -do - -echo $from -tmux new-window -t $s -window=$from - -if [ "$DOCKER" == false ]; then - tmux send-keys -t $s:$window "PROXY_LOG=info LLVL=info ./memcoin \ - --config /tmp/node$from \ - start \ - --postinstall \ - --promaddr :$((9099 + $from)) \ - --proxyaddr :$((9079 + $from)) \ - --proxykey $pk \ - --listen tcp://0.0.0.0:$((2000 + $from)) \ - --routing tree \ - --public //localhost:$((2000 + $from))| tee ./log/log/node$from.log" C-m -else - docker run -d -it --env LLVL=info --name node$from --network evoting-net -v "$(pwd)"/nodedata:/tmp --publish $(( 9079+$from )):9080 node - tmux send-keys -t $s:$window "docker exec node$from memcoin --config /tmp/node$from start --postinstall \ - --promaddr :9100 --proxyaddr :9080 --proxykey $pk --listen tcp://0.0.0.0:2001 --public //$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' node$from):2001 | tee ./log/log/node$from.log" C-m -fi - -((from++)) -done - -tmux new-window -t $s - - -if [ "$ATTACH" == true ]; then - tmux a -fi diff --git a/setupnNode.sh b/setupnNode.sh deleted file mode 100755 index a492b346b..000000000 --- a/setupnNode.sh +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env bash - -# This script is creating a new chain and setting up the services needed to run -# an evoting system. It ends by starting the http server needed by the frontend -# to communicate with the blockchain. This operation is blocking. It is expected -# that the "memcoin" binary is at the root. You can build it with: -# go build ./cli/memcoin - -# by default run on local -DOCKER=false - -POSITIONAL_ARGS=() - -while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - echo "This script is setting n dela voting nodes and granting access on block chain" - echo "Options:" - echo "-h | --help program help (this file)" - echo "-n | --node number of d-voting nodes" - echo "-d | --docker launch nodes on docker containers true/false, by default false" - exit 0 - ;; - -n|--node) - N_NODE="$2" - shift # past argument - shift # past value - ;; - -d|--docker) - DOCKER="$2" - shift # past argument - shift # past value - ;; - -*|--*) - echo "Unknown option $1" - exit 1 - ;; - *) - POSITIONAL_ARGS+=("$1") # save positional arg - shift # past argument - ;; - esac -done - -set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters - -set -e - -GREEN='\033[0;32m' -NC='\033[0m' # No Color - -if [ "$DOCKER" == false ]; then - echo "${GREEN}[1/4]${NC} connect nodes" - - from=2 - to=$N_NODE - while [ $from -le $to ] - do - ./memcoin --config /tmp/node$from minogrpc join \ - --address //localhost:2001 $(./memcoin --config /tmp/node1 minogrpc token) - - ((from++)) - done - - echo "${GREEN}[2/4]${NC} create a chain" - - ARRAY="" - from=1 - to=$N_NODE - while [ $from -le $to ] - do - ARRAY+="--member " - ARRAY+="$(./memcoin --config /tmp/node$from ordering export) " - - ((from++)) - done - - ./memcoin --config /tmp/node1 ordering setup $ARRAY - - - echo "${GREEN}[3/4]${NC} setup access rights on each node" - - from=1 - - while [ $from -le $to ] - do - ./memcoin --config /tmp/node$from access add \ - --identity $(crypto bls signer read --path private.key --format BASE64_PUBKEY) - - ((from++)) - done - - - echo "${GREEN}[4/4]${NC} grant access on the chain" - - ./memcoin --config /tmp/node1 pool add\ - --key private.key\ - --args go.dedis.ch/dela.ContractArg --args go.dedis.ch/dela.Access\ - --args access:grant_id --args 0300000000000000000000000000000000000000000000000000000000000000\ - --args access:grant_contract --args go.dedis.ch/dela.Evoting\ - --args access:grant_command --args all\ - --args access:identity --args $(crypto bls signer read --path private.key --format BASE64_PUBKEY)\ - --args access:command --args GRANT - - - from=1 - - while [ $from -le $to ] - do - - ./memcoin --config /tmp/node1 pool add\ - --key private.key\ - --args go.dedis.ch/dela.ContractArg --args go.dedis.ch/dela.Access\ - --args access:grant_id --args 0300000000000000000000000000000000000000000000000000000000000000\ - --args access:grant_contract --args go.dedis.ch/dela.Evoting\ - --args access:grant_command --args all\ - --args access:identity --args $(crypto bls signer read --path /tmp/node$from/private.key --format BASE64_PUBKEY)\ - --args access:command --args GRANT - - - ((from++)) - done -else - echo "${GREEN}[1/4]${NC} connect nodes" - conn_token=$(docker exec node1 memcoin --config /tmp/node1 minogrpc token) - vals=($(seq 2 1 $N_NODE)) - - for i in "${vals[@]}" - do - docker exec node$i memcoin --config /tmp/node$i minogrpc join \ - --address //$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' node1):2001 $conn_token - - done - - - echo "${GREEN}[2/4]${NC} create a chain" - vals=($(seq 1 1 $N_NODE)) - ARRAY="" - for i in "${vals[@]}" - do - ARRAY+="--member " - ARRAY+="$(docker exec node$i memcoin --config /tmp/node$i ordering export) " - echo "Node$i addr is:" - echo $(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' node$i) - done - - - docker exec node1 memcoin --config /tmp/node1 ordering setup $ARRAY - - - echo "${GREEN}[3/4]${NC} setup access rights on each node" - access_token=$(docker exec node1 crypto bls signer read --path private.key --format BASE64_PUBKEY) - - for i in "${vals[@]}" - do - docker exec node$i memcoin --config /tmp/node$i access add \ - --identity $access_token - sleep 1 - done - - - - echo "${GREEN}[4/4]${NC} grant access on the chain" - - - docker exec node1 memcoin --config /tmp/node1 pool add\ - --key private.key\ - --args go.dedis.ch/dela.ContractArg --args go.dedis.ch/dela.Access\ - --args access:grant_id --args 0300000000000000000000000000000000000000000000000000000000000000\ - --args access:grant_contract --args go.dedis.ch/dela.Evoting\ - --args access:grant_command --args all\ - --args access:identity --args $access_token\ - --args access:command --args GRANT - - sleep 1 - - for i in "${vals[@]}" - do - access_token_tmp=$(docker exec node$i crypto bls signer read --path /tmp/node$i/private.key --format BASE64_PUBKEY) - docker exec node1 memcoin --config /tmp/node1 pool add\ - --key private.key\ - --args go.dedis.ch/dela.ContractArg --args go.dedis.ch/dela.Access\ - --args access:grant_id --args 0300000000000000000000000000000000000000000000000000000000000000\ - --args access:grant_contract --args go.dedis.ch/dela.Evoting\ - --args access:grant_command --args all\ - --args access:identity --args $access_token_tmp\ - --args access:command --args GRANT - sleep 1 - done -fi \ No newline at end of file diff --git a/web/frontend/src/language/en.json b/web/frontend/src/language/en.json index 2a99c92f2..57fc22035 100644 --- a/web/frontend/src/language/en.json +++ b/web/frontend/src/language/en.json @@ -260,11 +260,11 @@ "statusLoading": "Status loading...", "actionNotAvailable": "Action not available", "uninitialized": "Uninitialized", - "actionTextVoter1": "The form is not open yet, you can come back later to vote once it is open.", "actionTextVoter2": "The results of the form are not available yet.", "resIndiv": "Individual", "resGroup": "Grouped", - "choice": "Choice" + "choice": "Choice", + "logoutWarning": "You are about to log out. Are you sure you want to continue?" } } diff --git a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx index 849f74b3b..c66ab8238 100644 --- a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx +++ b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx @@ -5,7 +5,6 @@ import Rank, { handleOnDragEnd } from './Rank'; import Select from './Select'; import Text from './Text'; import { DragDropContext } from 'react-beautiful-dnd'; -import RemoveElementModal from 'pages/form/components/RemoveElementModal'; type BallotDisplayProps = { configuration: Configuration; @@ -25,32 +24,18 @@ const BallotDisplay: FC = ({ const [titles, setTitles] = useState({}); useEffect(() => { try { - console.log('config', configuration.MainTitle); const ts = JSON.parse(configuration.MainTitle); setTitles(ts); } catch (e) { console.log('error', e); } }, [configuration]); - + const SubjectElementDisplay = (element: types.SubjectElement) => { - const[choices, setChoices]= useState({}); - useEffect(() => { - try { - const choice = JSON.parse(element.Choice) - setChoices(choice); - } catch (e) { - console.log('error', e); - } - }, [element]); return (
- {element.Type === RANK && ( - (element as types.RankQuestion).Choices.set('en',choices.en), - (element as types.RankQuestion).Choices.set('en',choices.fr), - (element as types.RankQuestion).Choices.set('en',choices.de), - - )} + {element.Type === RANK && + ()} {element.Type === SELECT && ( )} - {language=== 'fr' && ( - )} - {language=== 'de' && ( - )} + {language === 'en' && ( + + )} + {language === 'fr' && ( + + )} + {language === 'de' && ( + + )}
{requirementsDisplay()}
diff --git a/web/frontend/src/pages/form/components/Question.tsx b/web/frontend/src/pages/form/components/Question.tsx index 12e5eade4..646d98dd7 100644 --- a/web/frontend/src/pages/form/components/Question.tsx +++ b/web/frontend/src/pages/form/components/Question.tsx @@ -7,7 +7,6 @@ import { RankQuestion, SelectQuestion, TextQuestion } from 'types/configuration' import SubjectDropdown from './SubjectDropdown'; import AddQuestionModal from './AddQuestionModal'; import DisplayTypeIcon from './DisplayTypeIcon'; -import { default as i18n } from 'i18next'; type QuestionProps = { question: RankQuestion | SelectQuestion | TextQuestion; notifyParent(question: RankQuestion | SelectQuestion | TextQuestion): void; From da7521c188b3e7f034059f683ea3e5903fd3db3d Mon Sep 17 00:00:00 2001 From: Khadija Tagemouati Date: Wed, 18 Jan 2023 19:50:45 +0100 Subject: [PATCH 43/47] linting --- web/frontend/src/pages/form/Show.tsx | 4 ++-- web/frontend/src/pages/form/components/AddQuestionModal.tsx | 2 +- .../src/pages/form/components/utils/useQuestionForm.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/frontend/src/pages/form/Show.tsx b/web/frontend/src/pages/form/Show.tsx index 8d4b81bfc..6631e32be 100644 --- a/web/frontend/src/pages/form/Show.tsx +++ b/web/frontend/src/pages/form/Show.tsx @@ -213,8 +213,8 @@ const FormShow: FC = () => { const ts = JSON.parse(configObj.MainTitle); setTitles(ts); } else { - const t = { en: configObj.MainTitle, fr: configObj.TitleFr, de: configObj.TitleDe }; - setTitles(t); + const titles = { en: configObj.MainTitle, fr: configObj.TitleFr, de: configObj.TitleDe }; + setTitles(titles); } } catch (e) { setError(e.error); diff --git a/web/frontend/src/pages/form/components/AddQuestionModal.tsx b/web/frontend/src/pages/form/components/AddQuestionModal.tsx index 3245a464c..ccbe83fd8 100644 --- a/web/frontend/src/pages/form/components/AddQuestionModal.tsx +++ b/web/frontend/src/pages/form/components/AddQuestionModal.tsx @@ -44,7 +44,7 @@ const AddQuestionModal: FC = ({ updateChoice, } = useQuestionForm(question); const [language, setLanguage] = useState('en'); - const { Title, TitleDe, TitleFr, MaxN, MinN, Choices, ChoicesMap, Hint, HintFr, HintDe } = values; + const { Title, TitleDe, TitleFr, MaxN, MinN, ChoicesMap, Hint, HintFr, HintDe } = values; const [errors, setErrors] = useState([]); const handleSave = async () => { try { diff --git a/web/frontend/src/pages/form/components/utils/useQuestionForm.ts b/web/frontend/src/pages/form/components/utils/useQuestionForm.ts index 284fc1345..9e6b74634 100644 --- a/web/frontend/src/pages/form/components/utils/useQuestionForm.ts +++ b/web/frontend/src/pages/form/components/utils/useQuestionForm.ts @@ -142,7 +142,7 @@ const useQuestionForm = (initState: RankQuestion | SelectQuestion | TextQuestion const newChoicesMapDefault = new Map( Object.entries({ ...obj, - ['en']: obj['en'].map((item: string, idx: number) => + ['en']: obj.en.map((item: string, idx: number) => idx === index ? e.target.value : item ), }) From 4d590adc6849b5f804fc6358d4acd286cb7fa2f5 Mon Sep 17 00:00:00 2001 From: Khadija Tagemouati Date: Wed, 18 Jan 2023 19:53:29 +0100 Subject: [PATCH 44/47] linting --- web/frontend/src/pages/form/Show.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/frontend/src/pages/form/Show.tsx b/web/frontend/src/pages/form/Show.tsx index 6631e32be..e713678c4 100644 --- a/web/frontend/src/pages/form/Show.tsx +++ b/web/frontend/src/pages/form/Show.tsx @@ -213,8 +213,8 @@ const FormShow: FC = () => { const ts = JSON.parse(configObj.MainTitle); setTitles(ts); } else { - const titles = { en: configObj.MainTitle, fr: configObj.TitleFr, de: configObj.TitleDe }; - setTitles(titles); + const tis = { en: configObj.MainTitle, fr: configObj.TitleFr, de: configObj.TitleDe }; + setTitles(tis); } } catch (e) { setError(e.error); From 385e70477f0f24a82efec5091f3372e1c7947a4c Mon Sep 17 00:00:00 2001 From: Khadija Tagemouati Date: Wed, 18 Jan 2023 21:00:01 +0100 Subject: [PATCH 45/47] modularize isJson function --- .../src/pages/ballot/components/BallotDisplay.tsx | 9 +-------- web/frontend/src/pages/ballot/components/Rank.tsx | 9 +-------- web/frontend/src/pages/ballot/components/Select.tsx | 9 +-------- web/frontend/src/pages/form/GroupedResult.tsx | 9 +-------- web/frontend/src/pages/form/IndividualResult.tsx | 9 +-------- web/frontend/src/pages/form/Show.tsx | 9 +-------- web/frontend/src/pages/form/components/FormRow.tsx | 9 +-------- web/frontend/src/types/JSONparser.ts | 1 + 8 files changed, 8 insertions(+), 56 deletions(-) diff --git a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx index cbe1b7378..bf62f5044 100644 --- a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx +++ b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx @@ -5,6 +5,7 @@ import Rank, { handleOnDragEnd } from './Rank'; import Select from './Select'; import Text from './Text'; import { DragDropContext } from 'react-beautiful-dnd'; +import { isJson } from 'types/JSONparser'; type BallotDisplayProps = { configuration: Configuration; @@ -21,14 +22,6 @@ const BallotDisplay: FC = ({ userErrors, language, }) => { - const isJson = (str: string) => { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; - }; const [titles, setTitles] = useState({}); useEffect(() => { if (configuration.MainTitle === '') return; diff --git a/web/frontend/src/pages/ballot/components/Rank.tsx b/web/frontend/src/pages/ballot/components/Rank.tsx index 086c80f32..7d2ad9395 100644 --- a/web/frontend/src/pages/ballot/components/Rank.tsx +++ b/web/frontend/src/pages/ballot/components/Rank.tsx @@ -3,6 +3,7 @@ import { Draggable, DropResult, Droppable } from 'react-beautiful-dnd'; import { Answers, ID, RankQuestion } from 'types/configuration'; import { answersFrom } from 'types/getObjectType'; import HintButton from 'components/buttons/HintButton'; +import { isJson } from 'types/JSONparser'; export const handleOnDragEnd = ( result: DropResult, @@ -57,14 +58,6 @@ const Rank: FC = ({ rank, answers, language }) => { ); }; const [titles, setTitles] = useState({}); - const isJson = (str: string) => { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; - }; useEffect(() => { if (isJson(rank.Title)) { const ts = JSON.parse(rank.Title); diff --git a/web/frontend/src/pages/ballot/components/Select.tsx b/web/frontend/src/pages/ballot/components/Select.tsx index bfac5bc5a..f1d3a3c48 100644 --- a/web/frontend/src/pages/ballot/components/Select.tsx +++ b/web/frontend/src/pages/ballot/components/Select.tsx @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'; import { Answers, SelectQuestion } from 'types/configuration'; import { answersFrom } from 'types/getObjectType'; import HintButton from 'components/buttons/HintButton'; +import { isJson } from 'types/JSONparser'; type SelectProps = { select: SelectQuestion; answers: Answers; @@ -54,14 +55,6 @@ const Select: FC = ({ select, answers, setAnswers, language }) => { return
{requirements}
; }; - const isJson = (str: string) => { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; - }; const [titles, setTitles] = useState({}); useEffect(() => { if (isJson(select.Title)) { diff --git a/web/frontend/src/pages/form/GroupedResult.tsx b/web/frontend/src/pages/form/GroupedResult.tsx index c43deaba0..cb3015027 100644 --- a/web/frontend/src/pages/form/GroupedResult.tsx +++ b/web/frontend/src/pages/form/GroupedResult.tsx @@ -29,6 +29,7 @@ import { import { default as i18n } from 'i18next'; import SelectResult from './components/SelectResult'; import TextResult from './components/TextResult'; +import { isJson } from 'types/JSONparser'; type GroupedResultProps = { rankResult: RankResults; @@ -49,14 +50,6 @@ const GroupedResult: FC = ({ rankResult, selectResult, textR [SELECT]: , [TEXT]: , }; - const isJson = (str: string) => { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; - }; const SubjectElementResultDisplay = (element: SubjectElement) => { let titles; diff --git a/web/frontend/src/pages/form/IndividualResult.tsx b/web/frontend/src/pages/form/IndividualResult.tsx index 4a9ec7109..20007eb92 100644 --- a/web/frontend/src/pages/form/IndividualResult.tsx +++ b/web/frontend/src/pages/form/IndividualResult.tsx @@ -31,6 +31,7 @@ import Loading from 'pages/Loading'; import saveAs from 'file-saver'; import { useNavigate } from 'react-router'; import { default as i18n } from 'i18next'; +import { isJson } from 'types/JSONparser'; type IndividualResultProps = { rankResult: RankResults; @@ -66,14 +67,6 @@ const IndividualResult: FC = ({ [SELECT]: , [TEXT]: , }; - const isJson = (str: string) => { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; - }; const SubjectElementResultDisplay = (element: SubjectElement) => { let titles; if (isJson(element.Title)) { diff --git a/web/frontend/src/pages/form/Show.tsx b/web/frontend/src/pages/form/Show.tsx index e713678c4..187f58b0a 100644 --- a/web/frontend/src/pages/form/Show.tsx +++ b/web/frontend/src/pages/form/Show.tsx @@ -15,6 +15,7 @@ import UserIDTable from './components/UserIDTable'; import DKGStatusTable from './components/DKGStatusTable'; import LoadingButton from './components/LoadingButton'; import { default as i18n } from 'i18next'; +import { isJson } from 'types/JSONparser'; const FormShow: FC = () => { const { t } = useTranslation(); @@ -197,14 +198,6 @@ const FormShow: FC = () => { } // eslint-disable-next-line react-hooks/exhaustive-deps }, [error]); - const isJson = (str: string) => { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; - }; const [titles, setTitles] = useState({}); useEffect(() => { try { diff --git a/web/frontend/src/pages/form/components/FormRow.tsx b/web/frontend/src/pages/form/components/FormRow.tsx index 51b880b21..418999e59 100644 --- a/web/frontend/src/pages/form/components/FormRow.tsx +++ b/web/frontend/src/pages/form/components/FormRow.tsx @@ -4,20 +4,13 @@ import { Link } from 'react-router-dom'; import FormStatus from './FormStatus'; import QuickAction from './QuickAction'; import { default as i18n } from 'i18next'; +import { isJson } from 'types/JSONparser'; type FormRowProps = { form: LightFormInfo; }; const FormRow: FC = ({ form }) => { - const isJson = (str: string) => { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; - }; const [titles, setTitles] = useState({}); useEffect(() => { if (form.Title === '') return; diff --git a/web/frontend/src/types/JSONparser.ts b/web/frontend/src/types/JSONparser.ts index 3cda6e6db..7b2514a96 100644 --- a/web/frontend/src/types/JSONparser.ts +++ b/web/frontend/src/types/JSONparser.ts @@ -264,4 +264,5 @@ export { unmarshalConfig, unmarshalConfigAndCreateAnswers, unmarshalSubjectAndCreateAnswers, + isJson }; From 05d188ff380041889354b72e63c67c76e6e3ce10 Mon Sep 17 00:00:00 2001 From: Khadija Tagemouati Date: Wed, 18 Jan 2023 21:04:09 +0100 Subject: [PATCH 46/47] linnting --- web/frontend/src/types/JSONparser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/frontend/src/types/JSONparser.ts b/web/frontend/src/types/JSONparser.ts index 7b2514a96..b40daea98 100644 --- a/web/frontend/src/types/JSONparser.ts +++ b/web/frontend/src/types/JSONparser.ts @@ -264,5 +264,5 @@ export { unmarshalConfig, unmarshalConfigAndCreateAnswers, unmarshalSubjectAndCreateAnswers, - isJson + isJson, }; From a47107bcf7183a6ad8d8bd38b13d3e687f1ff4fc Mon Sep 17 00:00:00 2001 From: Khadija Tagemouati Date: Wed, 18 Jan 2023 23:01:20 +0100 Subject: [PATCH 47/47] modularize --- web/frontend/src/language/LanguageButtons.tsx | 33 +++++++++++++++++++ .../form/components/AddQuestionModal.tsx | 24 +++----------- .../src/pages/form/components/FormForm.tsx | 27 ++++----------- 3 files changed, 45 insertions(+), 39 deletions(-) create mode 100644 web/frontend/src/language/LanguageButtons.tsx diff --git a/web/frontend/src/language/LanguageButtons.tsx b/web/frontend/src/language/LanguageButtons.tsx new file mode 100644 index 000000000..d5c23f5ee --- /dev/null +++ b/web/frontend/src/language/LanguageButtons.tsx @@ -0,0 +1,33 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +type LanguageButtonsProps = { + availableLanguages: string[]; + setLanguage: (lang: string) => void; +}; + +const LanguageButtons: FC = ({ availableLanguages, setLanguage }) => { + const { t } = useTranslation(); + return ( +
+
+ {availableLanguages.map((lang, index) => ( + + ))} +
+
+ ); +}; + +export default LanguageButtons; diff --git a/web/frontend/src/pages/form/components/AddQuestionModal.tsx b/web/frontend/src/pages/form/components/AddQuestionModal.tsx index ccbe83fd8..2886ac3bf 100644 --- a/web/frontend/src/pages/form/components/AddQuestionModal.tsx +++ b/web/frontend/src/pages/form/components/AddQuestionModal.tsx @@ -16,6 +16,7 @@ import useQuestionForm from './utils/useQuestionForm'; import DisplayTypeIcon from './DisplayTypeIcon'; import { availableLanguages } from 'language/Configuration'; +import LanguageButtons from 'language/LanguageButtons'; type AddQuestionModalProps = { question: RankQuestion | SelectQuestion | TextQuestion; @@ -177,25 +178,10 @@ const AddQuestionModal: FC = ({
-
-
- {availableLanguages.map((lang, index) => ( - - ))} -
-
+
{t('mainProperties')}