diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/TestRunnerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/TestRunnerTest.java
index 04a4a42f..c71c0bb5 100644
--- a/backend/app/src/test/java/com/ugent/pidgeon/util/TestRunnerTest.java
+++ b/backend/app/src/test/java/com/ugent/pidgeon/util/TestRunnerTest.java
@@ -119,7 +119,6 @@ public void testRunDockerTest() throws IOException {
verify(dockerModel, times(1)).addZipInputFiles(file);
verify(dockerModel, times(1)).cleanUp();
- verify(dockerModel, times(1)).addUtilFiles(extraFilesPathResolved);
assertEquals(1, filehandlerCalled.get());
/* artifacts are empty */
@@ -128,7 +127,6 @@ public void testRunDockerTest() throws IOException {
assertEquals(dockerTemplateTestOutput, result);
verify(dockerModel, times(2)).addZipInputFiles(file);
verify(dockerModel, times(2)).cleanUp();
- verify(dockerModel, times(2)).addUtilFiles(extraFilesPathResolved);
assertEquals(1, filehandlerCalled.get());
/* aritifacts are null */
@@ -137,7 +135,6 @@ public void testRunDockerTest() throws IOException {
assertEquals(dockerTemplateTestOutput, result);
verify(dockerModel, times(3)).addZipInputFiles(file);
verify(dockerModel, times(3)).cleanUp();
- verify(dockerModel, times(3)).addUtilFiles(extraFilesPathResolved);
assertEquals(1, filehandlerCalled.get());
/* No template */
@@ -147,7 +144,6 @@ public void testRunDockerTest() throws IOException {
assertEquals(dockerTestOutput, result);
verify(dockerModel, times(4)).addZipInputFiles(file);
verify(dockerModel, times(4)).cleanUp();
- verify(dockerModel, times(4)).addUtilFiles(extraFilesPathResolved);
/* Error gets thrown */
when(dockerModel.runSubmission(testEntity.getDockerTestScript())).thenThrow(new RuntimeException("Error"));
diff --git a/frontend/public/docker_langauges/bash.svg b/frontend/public/docker_langauges/bash.svg
new file mode 100644
index 00000000..890b5d92
--- /dev/null
+++ b/frontend/public/docker_langauges/bash.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/docker_langauges/custom.svg b/frontend/public/docker_langauges/custom.svg
new file mode 100644
index 00000000..c3416414
--- /dev/null
+++ b/frontend/public/docker_langauges/custom.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/docker_langauges/haskell.svg b/frontend/public/docker_langauges/haskell.svg
new file mode 100644
index 00000000..0c627ebd
--- /dev/null
+++ b/frontend/public/docker_langauges/haskell.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/docker_langauges/node-js.svg b/frontend/public/docker_langauges/node-js.svg
new file mode 100644
index 00000000..3e77c253
--- /dev/null
+++ b/frontend/public/docker_langauges/node-js.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/docker_langauges/python.svg b/frontend/public/docker_langauges/python.svg
new file mode 100644
index 00000000..bf2a1601
--- /dev/null
+++ b/frontend/public/docker_langauges/python.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/components/forms/projectFormTabs/DockerFormTab.tsx b/frontend/src/components/forms/projectFormTabs/DockerFormTab.tsx
index edd80af6..f09d02a6 100644
--- a/frontend/src/components/forms/projectFormTabs/DockerFormTab.tsx
+++ b/frontend/src/components/forms/projectFormTabs/DockerFormTab.tsx
@@ -1,18 +1,62 @@
-import { UploadOutlined } from "@ant-design/icons"
-import {Button, Form, Input, Switch, Upload} from "antd"
+import { CodepenCircleFilled, InboxOutlined, UploadOutlined } from "@ant-design/icons"
+import { Button, Dropdown, Form, Input, Menu, Select, SelectProps, Switch, Upload } from "antd"
+import { TextAreaProps } from "antd/es/input"
import { FormInstance } from "antd/lib"
-import {FC, useEffect} from "react"
+import React, { FC, useEffect, useLayoutEffect, useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
import useAppApi from "../../../hooks/useAppApi"
import MarkdownTooltip from "../../common/MarkdownTooltip"
import MarkdownTextfield from "../../input/MarkdownTextfield"
+import TextArea from "antd/es/input/TextArea"
+
+import BashIcon from "../../../../public/docker_langauges/bash.svg"
+import PythonIcon from "../../../../public/docker_langauges/python.svg"
+import NodeIcon from "../../../../public/docker_langauges/node-js.svg"
+import HaskellIcon from "../../../../public/docker_langauges/haskell.svg"
+import Custom from "../../../../public/docker_langauges/custom.svg"
+
+
+type DockerLanguage = "bash" | "python" | "node" | "haskell" | "custom"
+const languageOptions: Record = {
+ bash: "fedora",
+ python: "python",
+ node: "node",
+ haskell: "haskell",
+ custom: ""
+}
+
+const imageToLanguage: Record = {
+ fedora: "bash",
+ python: "python",
+ node: "node",
+ haskell: "haskell",
+}
+
+
+const languagesSelectorItems:SelectProps["options"] = [
+ {
+ label: <>Bash>,
+ value: "bash",
+ },{
+ label: <>Python>,
+ value: "python",
+ }, {
+ label: <>NodeJS>,
+ value: "node",
+ }, {
+ label: <>Haskell>,
+ value: "haskell",
+ }, {
+ label: <>Custom>,
+ value: "custom",
+ }
+]
const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => {
const { t } = useTranslation()
- const {message} = useAppApi()
-
+ const { message } = useAppApi()
const dockerImage = Form.useWatch("dockerImage", form)
const dockerTemplate = Form.useWatch("dockerTemplate", form)
const dockerMode = Form.useWatch("dockerMode", form)
@@ -28,6 +72,8 @@ const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => {
form.validateFields(["dockerScript", "dockerTemplate"])
}, [dockerDisabled])
+
+ const dockerImageSelect= useMemo(()=> imageToLanguage[dockerImage] || "custom",[dockerImage])
function isValidTemplate(template: string): string {
if (template.length === 0) {
@@ -56,7 +102,7 @@ const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => {
const isDescription = line.length >= 13 && line.substring(0, 13).toLowerCase() === ">description="
// option lines
if (line.toLowerCase() !== ">required" && line.toLowerCase() !== ">optional" && !isDescription) {
- return t("project.tests.dockerTemplateValidation.inValidOptions", { line:lineNumber.toString() })
+ return t("project.tests.dockerTemplateValidation.inValidOptions", { line: lineNumber.toString() })
}
} else {
isConfigurationLine = false
@@ -69,31 +115,22 @@ const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => {
return ""
}
-
-
const normFile = (e: any) => {
if (Array.isArray(e)) {
- return e;
+ return e
}
- return e?.fileList;
- };
+ return e?.fileList
+ }
- let switchClassName = 'template-switch'
+ let switchClassName = "template-switch"
let scriptPlaceholder
-
+
if (withTemplate) {
- switchClassName += ' template-switch-active'
- scriptPlaceholder = "bash /shared/input/helloworld.sh > \"/shared/output/helloWorldTest\"\n"+
- "bash /shared/input/helloug.sh > \"/shared/output/helloUGent\"\n"
+ switchClassName += " template-switch-active"
+ scriptPlaceholder = 'bash /shared/input/helloworld.sh > "/shared/output/helloWorldTest"\n' + 'bash /shared/input/helloug.sh > "/shared/output/helloUGent"\n'
} else {
- switchClassName += ' template-switch-inactive'
- scriptPlaceholder = "output=$(bash /shared/input/helloworld.sh)\n"+
- "if [[ \"$output\" == \"Hello World\" ]]; then \n"+
- " echo 'Test one is successful\n"+
- " echo 'PUSH ALLOWED' > /shared/output/testOutput\n"+
- "else\n"+
- " echo 'Test one failed: script failed to print \"Hello World\"'\n"+
- "fi"
+ switchClassName += " template-switch-inactive"
+ scriptPlaceholder = "output=$(bash /shared/input/helloworld.sh)\n" + 'if [[ "$output" == "Hello World" ]]; then \n' + " echo 'Test one is successful\n" + " echo 'PUSH ALLOWED' > /shared/output/testOutput\n" + "else\n" + " echo 'Test one failed: script failed to print \"Hello World\"'\n" + "fi"
}
@@ -110,11 +147,16 @@ const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => {
name="dockerImage"
>
form.setFieldValue("dockerImage", languageOptions[val])}
+ options={languagesSelectorItems}
+ />}
placeholder={t("project.tests.dockerImagePlaceholder")}
/>
-
<>
= ({ form }) => {
}
name="dockerScript"
>
-
@@ -224,4 +266,4 @@ const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => {
)
}
-export default DockerFormTab
+export default DockerFormTab
\ No newline at end of file
diff --git a/frontend/src/components/forms/projectFormTabs/GeneralFormTab.tsx b/frontend/src/components/forms/projectFormTabs/GeneralFormTab.tsx
index 37d1357d..df5ca7c5 100644
--- a/frontend/src/components/forms/projectFormTabs/GeneralFormTab.tsx
+++ b/frontend/src/components/forms/projectFormTabs/GeneralFormTab.tsx
@@ -2,106 +2,88 @@ import { DatePicker, Form, FormInstance, Input, Switch, Typography } from "antd"
import { useTranslation } from "react-i18next"
import { FC } from "react"
import MarkdownEditor from "../../input/MarkdownEditor"
-import dayjs from 'dayjs';
+import dayjs from "dayjs"
const GeneralFormTab: FC<{ form: FormInstance }> = ({ form }) => {
- const { t } = useTranslation()
- const description = Form.useWatch("description", form)
- const visible = Form.useWatch("visible", form)
+ const { t } = useTranslation()
+ const description = Form.useWatch("description", form)
+ const visible = Form.useWatch("visible", form)
- return (
- <>
-
-
-
+ return (
+ <>
+
+
+
-
- {t("project.change.description")}
-
-
+ {t("project.change.description")}
+
-
-
-
+
+
+
- {!visible && (
-
- current && current.isBefore(dayjs().startOf('day'))}
- />
-
- )}
+ {!visible && (
+
+ current && current.isBefore(dayjs().startOf("day"))}
+ />
+
+ )}
-
-
-
+
+
+
-
- {
- const hours = [];
- for (let i = 0; i < dayjs().hour(); i++) {
- hours.push(i);
- }
- return hours;
- },
- disabledMinutes: (selectedHour) => {
- const minutes = [];
- if (selectedHour === dayjs().hour()) {
- for (let i = 0; i < dayjs().minute(); i++) {
- minutes.push(i);
- }
- }
- return minutes;
- },
- disabledSeconds: (selectedHour, selectedMinute) => {
- const seconds = [];
- if (selectedHour === dayjs().hour() && selectedMinute === dayjs().minute()) {
- for (let i = 0; i < dayjs().second(); i++) {
- seconds.push(i);
- }
- }
- return seconds;
- },
- }}
- format="YYYY-MM-DD HH:mm:ss"
- disabledDate={(current) => current && current.isBefore(dayjs().startOf('day'))}
-
- />
-
- >
- )
+
+ {
+ const hours = []
+ for (let i = 0; i < dayjs().hour(); i++) {
+ hours.push(i)
+ }
+ return hours
+ },
+ }}
+ format="YYYY-MM-DD HH:mm:ss"
+ disabledDate={(current) => current && current.isBefore(dayjs().startOf("day"))}
+ />
+
+ >
+ )
}
export default GeneralFormTab
diff --git a/frontend/src/i18n/en/translation.json b/frontend/src/i18n/en/translation.json
index 2104c4a9..292d63ff 100644
--- a/frontend/src/i18n/en/translation.json
+++ b/frontend/src/i18n/en/translation.json
@@ -86,8 +86,8 @@
"search": "Search",
"emailError": "Please enter a valid email",
"emailTooShort": "Email must be at least 3 characters long",
- "nameError": "Name must be at least 3 characters long",
- "surnameError": "Surname must be at least 3 characters long",
+ "nameError": "Name or surname must be at least 3 characters long",
+ "surnameError": "Name or surname must be at least 3 characters long",
"searchTutorial": "Enter a name, surname, or email to find users.",
"searchTooShort": "The search must be at least 3 characters long",
"noUsersFound": "No users found",
@@ -213,7 +213,7 @@
"simpleMode": "Without template",
"templateMode": "With template",
"fileStructureTooltip": "This templates specifies the file structure a submission has to follow.\nIt uses the following syntax:\n* Folders end on `'/'`\n* Use indents to specify files inside a folder\n* Regex can be used\n\t* `'.'` is still a normal `'.'`\n\t* `'\\.'` can be used as regex `'.'`\n* `'-'` at the start of a line specifies a file/folder that is not allowed",
- "dockerImageTooltip": "Specify a valid Docker-container from [Docker Hub](https://hub.docker.com/) on which the test script will be run.",
+ "dockerImageTooltip": "Specify a valid Docker container from [Docker Hub](https://hub.docker.com/) on which the test script will be run. You can also choose a language with a preselected container.",
"dockerScriptTooltip": "Bash-script that is executed.\n* The files of the student's submission can be found in `'/shared/input'`\n* Extra files uploaded below can be found in `'/shared/extra'`\n\n More information about the required output depends on the mode and can be found below.",
"dockerTemplateTooltip": "To specify specific tests, you need to provide a template. First, enter the test name with '@{test}'. Below this, you can use '>' to provide options such as ('>required', '>optional', '>description'). Everything under these options until the next test or the end of the file is the expected output.",
"dockerTestDirTooltip": "Upload additional files needed for the Docker test. These files are available in the folder `'/shared/extra'`.\n\nOnce uploaded u can click the filename to download them again. Uploading a new file will replace the old one.",
diff --git a/frontend/src/i18n/nl/translation.json b/frontend/src/i18n/nl/translation.json
index e30e5e17..b5c963aa 100644
--- a/frontend/src/i18n/nl/translation.json
+++ b/frontend/src/i18n/nl/translation.json
@@ -87,8 +87,8 @@
"search": "Zoeken",
"emailError": "Vul een geldig email adres in",
"emailTooShort": "Email moet minstens 3 karakters lang zijn",
- "nameError": "Naam moet minstens 3 karakters lang zijn",
- "surnameError": "Achternaam moet minstens 3 karakters lang zijn",
+ "nameError": "Naam of achternaam moet minstens 3 karakters lang zijn",
+ "surnameError": "Naam of achternaam moet minstens 3 karakters lang zijn",
"searchTooShort": "Zoekopdracht moet minstens 3 karakters lang zijn",
"searchTutorial": "Vul een email adres, naam of achternaam in om gebruikers op te zoeken.",
"noUsersFound": "Geen gebruikers gevonden",
@@ -215,7 +215,7 @@
"templateMode": "Met sjabloon",
"fileStructurePreview": "Voorbeeld van bestandsstructuur",
"fileStructureTooltip": "Dit sjabloon specificeert de bestandsstructuur die een indiening moet volgen.\nHet gebruikt de volgende syntax:\n* Mappen eindigen op `'/'`\n* Gebruik inspringing om bestanden binnen een map aan te geven\n* Regex kan worden gebruikt\n\t* `'.'` is nog steeds een normale `'.'`\n\t* `'\\.'` kan worden gebruikt als regex `'.'`\n* `'-'` aan het begin van een regel geeft aan dat een bestand/map niet is toegestaan",
- "dockerImageTooltip": "Specificeer een geldige Docker-container van [Docker Hub](https://hub.docker.com/) waarop het testscript zal worden uitgevoerd.",
+ "dockerImageTooltip": "Specificeer een geldige Docker-container van [Docker Hub](https://hub.docker.com/) waarop het testscript zal worden uitgevoerd. Je kan ook kiezen voor een voorgeconfigureerde programmeertaal met bijhorende container.",
"dockerScriptTooltip": "Bash-script dat wordt uitgevoerd.\n* De bestanden van de student zijn indieningen zijn te vinden in `'/shared/input'`\n* Extra bestanden die hieronder zijn geüpload, zijn te vinden in `'/shared/extra'`\n\nMeer informatie over de vereiste uitvoer is afhankelijk van de modus en is hieronder te vinden.",
"dockerTemplateTooltip": "Om specifieke tests te definiëren, moet je een sjabloon invoeren. Geef eerst de naam van de test in met '@{test}'. Hieronder kun je met een '>' opties geven zoals ('>required', '>optional', '>description'). Alles onder de opties tot de volgende test of het einde van het bestand is de verwachte output.",
"dockerTestDirTooltip": "Upload extra bestanden die nodig zijn voor de dockertest. Deze bestanden zijn beschikbaar in de map `'/shared/extra'`.\n\nAls je een file geüpload hebt kan je deze downloaden door op de bestandsnaam te klikken. Als je een nieuw bestand uploadt zal het oude bestand overschreven worden.",
@@ -250,7 +250,7 @@
"getStarted": "Aan de slag",
"docs": "Documentatie"
},
-
+
"submission": {
"submission": "Indiening",
"submittedFiles": "Ingediende bestanden:",
diff --git a/frontend/src/pages/editProject/EditProject.tsx b/frontend/src/pages/editProject/EditProject.tsx
index cfc75422..7098c253 100644
--- a/frontend/src/pages/editProject/EditProject.tsx
+++ b/frontend/src/pages/editProject/EditProject.tsx
@@ -43,10 +43,9 @@ const EditProject: React.FC = () => {
dockerImage: null,
dockerMode: false
}
+
if (response.success) {
const tests = response.response.data
- console.log(tests)
-
if (tests.extraFilesName) {
const downloadLink = AppRoutes.DOWNLOAD_PROJECT_TESTS.replace(":projectId", projectId).replace(":courseId", courseId!)
@@ -75,7 +74,6 @@ const EditProject: React.FC = () => {
}
form.setFieldsValue(formVals)
-
setInitialDockerValues(formVals)
}
@@ -88,6 +86,9 @@ const EditProject: React.FC = () => {
const handleCreation = async () => {
const values: ProjectFormData & DockerFormData = form.getFieldsValue()
+
+ console.log(values)
+
if (values.visible) {
values.visibleAfter = null
}
diff --git a/frontend/src/pages/editRole/EditRole.tsx b/frontend/src/pages/editRole/EditRole.tsx
index 948b189f..ce669205 100644
--- a/frontend/src/pages/editRole/EditRole.tsx
+++ b/frontend/src/pages/editRole/EditRole.tsx
@@ -1,5 +1,5 @@
import { useContext, useEffect, useState } from "react"
-import { Form, Input, Spin, Select, Typography } from "antd"
+import { Form, Input, Spin, Select, Typography, Space } from "antd"
import UserList from "./components/UserList"
import { ApiRoutes, GET_Responses, UserRole } from "../../@types/requests.d"
import apiCall from "../../util/apiFetch"
@@ -10,18 +10,22 @@ import { UserContext } from "../../providers/UserProvider"
import useUser from "../../hooks/useUser"
export type UsersType = GET_Responses[ApiRoutes.USERS]
-type SearchType = "name" | "surname" | "email"
+type SearchType = "name" | "email"
const ProfileContent = () => {
const [users, setUsers] = useState(null)
const myself = useUser()
const [loading, setLoading] = useState(false)
const [form] = Form.useForm()
- const searchValue = Form.useWatch("search", form)
+ const firstSearchValue = Form.useWatch("first", form)
+ const secondSearchValue = Form.useWatch("second", form)
+ const searchValue = `${firstSearchValue || ''} ${secondSearchValue || ''}`.trim();
const [debouncedSearchValue] = useDebounceValue(searchValue, 250)
const [searchType, setSearchType] = useState("name")
const { t } = useTranslation()
+ const emailRegex = /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/;
+
useEffect(() => {
onSearch()
}, [debouncedSearchValue])
@@ -43,14 +47,56 @@ const ProfileContent = () => {
})
}
+ const [isError, setIsError] = useState(false)
+
+ const checkValidate = () => {
+ if (searchType === "email") {
+ if (!emailRegex.test(form.getFieldValue("first"))) {
+ return false
+ } else {
+ return true
+ }
+ } else {
+ const firstValue = form.getFieldValue("first")
+ const secondValue = form.getFieldValue("second")
+ const firstValueLength = firstValue ? firstValue.length : 0
+ const secondValueLength = secondValue ? secondValue.length : 0
+ if (firstValueLength < 3 && secondValueLength < 3) {
+ console.log("error")
+ return false
+ } else {
+ console.log("no error")
+ return true
+ }
+ }
+ }
+
+ const validate = () => {
+ if (!checkValidate()) {
+ setIsError(true)
+ } else {
+ setIsError(false)
+ }
+ }
+
const onSearch = async () => {
- const value = form.getFieldValue("search")
- if (!value || value.length < 3) return
+ //validation
+ if (!checkValidate()) {
+ return
+ }
+
+ const firstValue = form.getFieldValue("first")
setLoading(true)
const params = new URLSearchParams()
- params.append(searchType, form.getFieldValue("search"))
+ if (searchType === "email") {
+ params.append(searchType, form.getFieldValue("first"))
+ } else {
+ const secondValue = form.getFieldValue("second")
+ if (firstValue) params.append("name", firstValue)
+ if (secondValue) params.append("surname", secondValue)
+ }
+ console.log(params)
apiCall.get((ApiRoutes.USERS + "?" + params.toString()) as ApiRoutes.USERS).then((res) => {
-
setUsers(res.data)
setLoading(false)
})
@@ -62,49 +108,63 @@ const ProfileContent = () => {
form={form}
name="search"
onFinish={onSearch}
+ onChange={validate}
+ validateTrigger={[]}
>
-
+
+
+
+
+ {isError && {searchType === "email" ? t("editRole.emailError") : t("editRole.nameError")}}
+
+
{users !== null ? (
<>
{loading ? (
diff --git a/frontend/src/pages/editRole/components/UserList.tsx b/frontend/src/pages/editRole/components/UserList.tsx
index ed4b77bf..c62e5c55 100644
--- a/frontend/src/pages/editRole/components/UserList.tsx
+++ b/frontend/src/pages/editRole/components/UserList.tsx
@@ -6,6 +6,7 @@ import { useState } from "react"
import { UsersType } from "../EditRole"
import { GET_Responses, ApiRoutes } from "../../../@types/requests.d"
import { User } from "../../../providers/UserProvider"
+import useUser from "../../../hooks/useUser"
//this is ugly, but if I put this in GET_responses, it will be confused with the User type (and there's no GET request with this as a response).
//this is also the only place this is used, so I think it's fine.
@@ -16,9 +17,10 @@ const UserList: React.FC<{ users: UsersType; updateRole: (user: UsersListItem, r
const [visible, setVisible] = useState(false)
const [selectedUser, setSelectedUser] = useState(null)
const [selectedRole, setSelectedRole] = useState(null)
+ const { user } = useUser()
- const handleMenuClick = (user: UsersListItem, role: UserRole) => {
- setSelectedUser(user)
+ const handleMenuClick = (listuser: UsersListItem, role: UserRole) => {
+ setSelectedUser(listuser)
setSelectedRole(role)
setVisible(true)
}
@@ -44,12 +46,13 @@ const UserList: React.FC<{ users: UsersType; updateRole: (user: UsersListItem, r
return a.email.localeCompare(b.email);
});
- const renderUserItem = (user: UsersListItem) => (
+ const renderUserItem = (listuser: UsersListItem) => (
-
+
handleMenuClick(user, e.key as UserRole),
+ selectedKeys: [listuser.role],
+ onClick: (e) => handleMenuClick(listuser, e.key as UserRole),
}}
>
e.preventDefault()}>
- {t("editRole." + user.role)}
+ {t("editRole." + listuser.role)}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 8d61f9c2..9c426dcb 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -181,6 +181,13 @@ html {
}
+.select-icon {
+ width: 1em;
+ margin-right: 0.5rem;
+ position: relative;
+ top: 2px;
+}
+
/* *************************** Landing page *************************** */
.landing-page * {