diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 34cf6e7386..02b18aca49 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -287,6 +287,7 @@ "searchPlaceholder": "Search...", "searchTitle": "Find Users", "emailLabel": "Email", + "emailMessage": "Message (optional)", "toastSuccess": "User added to the project.", "toastFail": "Failed to add user to the project." }, diff --git a/src/components/Dialogs/UploadImage.tsx b/src/components/Dialogs/UploadImage.tsx index d12cf914b9..a624bdbb0c 100644 --- a/src/components/Dialogs/UploadImage.tsx +++ b/src/components/Dialogs/UploadImage.tsx @@ -1,4 +1,4 @@ -import { Grid, Typography } from "@mui/material"; +import { Grid2, Typography } from "@mui/material"; import { FormEvent, ReactElement, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -53,25 +53,23 @@ export default function ImageUpload(props: ImageUploadProps): ReactElement { {t("createProject.fileSelected", { val: filename })} )} - - - updateFile(file)} - accept="image/*" - > - {t("buttons.browse")} - - - - - {t("buttons.save")} - - - + + + updateFile(file)} + accept="image/*" + > + {t("buttons.browse")} + + + + {t("buttons.save")} + + ); } diff --git a/src/components/Dialogs/ViewImageDialog.tsx b/src/components/Dialogs/ViewImageDialog.tsx index 88e23b474d..98280e9a69 100644 --- a/src/components/Dialogs/ViewImageDialog.tsx +++ b/src/components/Dialogs/ViewImageDialog.tsx @@ -1,4 +1,4 @@ -import { Dialog, DialogContent, DialogTitle, Grid, Icon } from "@mui/material"; +import { Dialog, DialogContent, DialogTitle, Grid2, Icon } from "@mui/material"; import { ReactElement } from "react"; import { CloseButton, DeleteButtonWithDialog } from "components/Buttons"; @@ -39,16 +39,14 @@ export default function ViewImageDialog( - - - - - + + + ); diff --git a/src/components/ProjectSettings/BaseSettings.tsx b/src/components/ProjectSettings/BaseSettings.tsx index eba43e01d3..10e46e58c2 100644 --- a/src/components/ProjectSettings/BaseSettings.tsx +++ b/src/components/ProjectSettings/BaseSettings.tsx @@ -3,7 +3,8 @@ import { Accordion, AccordionDetails, AccordionSummary, - Grid, + Box, + Stack, Typography, } from "@mui/material"; import { type ReactElement, type ReactNode } from "react"; @@ -15,6 +16,8 @@ interface BaseSettingsProps { icon: ReactNode; /** If maxWidth not specified, defaults to 700px. */ maxWidth?: string | number; + /** If minWidth not specified, defaults to Accordion's default. */ + minWidth?: string | number; /** Setting title (goes second in `AccordionSummary` after icon). */ title: ReactNode; } @@ -29,17 +32,14 @@ export default function BaseSettings(props: BaseSettingsProps): ReactElement { background: (theme) => theme.palette.background.default, border: (theme) => `1px solid ${theme.palette.divider}`, maxWidth: props.maxWidth || "700px", + minWidth: props.minWidth, }} > }> - - - {props.icon} - - - {props.title} - - + + {props.icon} + {props.title} + {props.body} diff --git a/src/components/ProjectSettings/ProjectAutocomplete.tsx b/src/components/ProjectSettings/ProjectAutocomplete.tsx index 5890af880f..0bf3cdd4c7 100644 --- a/src/components/ProjectSettings/ProjectAutocomplete.tsx +++ b/src/components/ProjectSettings/ProjectAutocomplete.tsx @@ -1,5 +1,5 @@ import { HelpOutline } from "@mui/icons-material"; -import { Grid, MenuItem, Select, Tooltip } from "@mui/material"; +import { MenuItem, Select, Stack, Tooltip } from "@mui/material"; import { type ReactElement } from "react"; import { useTranslation } from "react-i18next"; @@ -18,31 +18,28 @@ export default function ProjectAutocomplete( }; return ( - - - - - - - - - - + + + + + + + ); } diff --git a/src/components/ProjectSettings/ProjectDomains.tsx b/src/components/ProjectSettings/ProjectDomains.tsx index ead883e90c..3cc85e8468 100644 --- a/src/components/ProjectSettings/ProjectDomains.tsx +++ b/src/components/ProjectSettings/ProjectDomains.tsx @@ -9,7 +9,7 @@ import { Dialog, DialogContent, DialogTitle, - Grid, + Grid2, IconButton, Stack, Typography, @@ -313,24 +313,29 @@ export function AddDomainDialog(props: AddDomainDialogProps): ReactElement { return ( - - {t("projectSettings.domains.add")} - + + {t("projectSettings.domains.add")} + +
submit()} + size="small" > - t.palette.success.main }} /> + + cancel()} + size="small" > - t.palette.error.main }} /> + - - +
+
+ diff --git a/src/components/ProjectSettings/ProjectImport.tsx b/src/components/ProjectSettings/ProjectImport.tsx index a5144bbc0d..e6309038ab 100644 --- a/src/components/ProjectSettings/ProjectImport.tsx +++ b/src/components/ProjectSettings/ProjectImport.tsx @@ -1,4 +1,4 @@ -import { Grid, Typography } from "@mui/material"; +import { Grid2, Typography } from "@mui/material"; import { type ReactElement, useEffect, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import { toast } from "react-toastify"; @@ -75,8 +75,9 @@ export default function ProjectImport( }; return ( - - + + {/* Upload/LIFT instructions */} + {t("projectSettings.import.body")}{" "} @@ -87,48 +88,43 @@ export default function ProjectImport( FillerTextC - + - - {/* Choose file button */} - - {t("projectSettings.import.chooseFile")} - - + {/* Choose file button */} + + {t("projectSettings.import.chooseFile")} + - - {/* Upload button */} - - {t("buttons.upload")} - - + {/* Upload button */} + + {t("buttons.upload")} + - - {/* Displays the name of the selected file */} - {liftFile && ( - - {t("createProject.fileSelected", { val: liftFile.name })} - - )} - + {/* Name of the selected file */} + {liftFile && ( + + {t("createProject.fileSelected", { val: liftFile.name })} + + )} + {/* Dialog if LIFT contents don't match vernacular language */} {liftLangs && ( )} - + ); } diff --git a/src/components/ProjectSettings/ProjectLanguages.tsx b/src/components/ProjectSettings/ProjectLanguages.tsx index e73b576c9a..871c334634 100644 --- a/src/components/ProjectSettings/ProjectLanguages.tsx +++ b/src/components/ProjectSettings/ProjectLanguages.tsx @@ -9,11 +9,12 @@ import { } from "@mui/icons-material"; import { Button, - Grid, + Grid2, IconButton, MenuItem, Select, SelectChangeEvent, + Stack, Typography, } from "@mui/material"; import { LanguagePicker, languagePickerStrings_en } from "mui-language-picker"; @@ -192,50 +193,46 @@ export default function ProjectLanguages( ); const addAnalysisLangPicker = (): ReactElement => ( - - - - setNewLang((prev: WritingSystem) => ({ ...prev, bcp47 })) - } - name={newLang.name} - setName={(name: string) => - setNewLang((prev: WritingSystem) => ({ ...prev, name })) - } - font={newLang.font} - setFont={(font: string) => - setNewLang((prev: WritingSystem) => ({ ...prev, font })) - } - setDir={(rtl: boolean) => - setNewLang((prev: WritingSystem) => ({ - ...prev, - rtl: rtl || undefined, - })) - } - t={languagePickerStrings_en} - /> - {" "} - - addAnalysisWritingSystem()} - id={ProjectLanguagesId.ButtonAddAnalysisLangConfirm} - size="large" - > - - - {" "} - - resetState()} - id={ProjectLanguagesId.ButtonAddAnalysisLangClear} - size="large" - > - - - - + + + setNewLang((prev: WritingSystem) => ({ ...prev, bcp47 })) + } + name={newLang.name} + setName={(name: string) => + setNewLang((prev: WritingSystem) => ({ ...prev, name })) + } + font={newLang.font} + setFont={(font: string) => + setNewLang((prev: WritingSystem) => ({ ...prev, font })) + } + setDir={(rtl: boolean) => + setNewLang((prev: WritingSystem) => ({ + ...prev, + rtl: rtl || undefined, + })) + } + t={languagePickerStrings_en} + /> + + addAnalysisWritingSystem()} + id={ProjectLanguagesId.ButtonAddAnalysisLangConfirm} + size="large" + > + + + + resetState()} + id={ProjectLanguagesId.ButtonAddAnalysisLangClear} + size="large" + > + + + ); const vernacularLanguageDisplay = (): ReactElement => ( @@ -258,32 +255,28 @@ export default function ProjectLanguages( ); const vernacularLanguageEditor = (): ReactElement => ( - - - setNewVernName(e.target.value)} - onBlur={() => { - setChangeVernName(false); - setNewVernName(props.project.vernacularWritingSystem.name); - }} - autoFocus - /> - - - - - + + setNewVernName(e.target.value)} + onBlur={() => { + setChangeVernName(false); + setNewVernName(props.project.vernacularWritingSystem.name); + }} + autoFocus + /> + + + ); return ( @@ -328,30 +321,22 @@ function ImmutableWritingSystem( ): ReactElement { const { t } = useTranslation(); + const number = props.index === undefined ? "" : `${props.index + 1}. `; + const wsText: string[] = []; + if (props.ws.name) { + wsText.push(`${t("projectSettings.language.name")}: ${props.ws.name}`); + } + wsText.push(`${t("projectSettings.language.bcp47")}: ${props.ws.bcp47}`); + if (props.ws.font) { + wsText.push(`${t("projectSettings.language.font")}: ${props.ws.font}`); + } + const text = number + wsText.join(", "); + return ( - - {props.index !== undefined && ( - - {`${props.index + 1}. `} - - )} - - {!!props.ws.name && ( - - {`${t("projectSettings.language.name")}: ${props.ws.name}, `} - - )} - - {`${t("projectSettings.language.bcp47")}: ${props.ws.bcp47}`} - - {!!props.ws.font && ( - - {`, ${t("projectSettings.language.font")}: ${props.ws.font}`} - - )} - - {props.buttons} - + + {text} + {props.buttons} + ); } diff --git a/src/components/ProjectSettings/ProjectName.tsx b/src/components/ProjectSettings/ProjectName.tsx index 8db9c8e742..5dbb15b998 100644 --- a/src/components/ProjectSettings/ProjectName.tsx +++ b/src/components/ProjectSettings/ProjectName.tsx @@ -1,4 +1,4 @@ -import { Button, Grid } from "@mui/material"; +import { Button, Stack } from "@mui/material"; import { type ReactElement, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "react-toastify"; @@ -24,27 +24,23 @@ export default function ProjectName(props: ProjectSettingProps): ReactElement { }; return ( - - - setProjName(e.target.value)} - onBlur={() => setProjName(props.project.name)} - /> - - - - - + + setProjName(e.target.value)} + onBlur={() => setProjName(props.project.name)} + /> + + ); } diff --git a/src/components/ProjectSettings/ProjectProtectedOverride.tsx b/src/components/ProjectSettings/ProjectProtectedOverride.tsx index 89b03f82b9..297b84d982 100644 --- a/src/components/ProjectSettings/ProjectProtectedOverride.tsx +++ b/src/components/ProjectSettings/ProjectProtectedOverride.tsx @@ -1,5 +1,5 @@ import { HelpOutline } from "@mui/icons-material"; -import { Grid, MenuItem, Select, Tooltip } from "@mui/material"; +import { MenuItem, Select, Stack, Tooltip } from "@mui/material"; import { type ReactElement } from "react"; import { useTranslation } from "react-i18next"; @@ -18,31 +18,28 @@ export default function ProjectProtectedOverride( }; return ( - - - - - - - - - - + + + + + + + ); } diff --git a/src/components/ProjectSettings/ProjectSchedule/CalendarView.tsx b/src/components/ProjectSettings/ProjectSchedule/CalendarView.tsx index ed4aa71930..bd8690af96 100644 --- a/src/components/ProjectSettings/ProjectSchedule/CalendarView.tsx +++ b/src/components/ProjectSettings/ProjectSchedule/CalendarView.tsx @@ -1,4 +1,4 @@ -import { Icon } from "@mui/material"; +import { Grid2, Icon } from "@mui/material"; import { DateCalendar } from "@mui/x-date-pickers"; import dayjs, { Dayjs } from "dayjs"; import { ReactElement } from "react"; @@ -40,5 +40,9 @@ export default function CalendarView(props: CalendarViewProps): ReactElement { return Array.from(new Set(months)).sort().map(dayjs); } - return <>{handleCalendarView(getScheduledMonths(props.projectSchedule))}; + return ( + + {handleCalendarView(getScheduledMonths(props.projectSchedule))} + + ); } diff --git a/src/components/ProjectSettings/ProjectSchedule/DateScheduleEdit.tsx b/src/components/ProjectSettings/ProjectSchedule/DateScheduleEdit.tsx index 1ea9e8cc65..52ace6c37f 100644 --- a/src/components/ProjectSettings/ProjectSchedule/DateScheduleEdit.tsx +++ b/src/components/ProjectSettings/ProjectSchedule/DateScheduleEdit.tsx @@ -1,4 +1,4 @@ -import { Button, Grid } from "@mui/material"; +import { Button, Stack } from "@mui/material"; import { DateCalendar } from "@mui/x-date-pickers"; import dayjs, { Dayjs } from "dayjs"; import { ReactElement, useState } from "react"; @@ -57,7 +57,7 @@ export default function DateScheduleEdit( } return ( - <> + - - - - - - - {t("buttons.confirm")} - - - - + + + + + + {t("buttons.confirm")} + + + ); } diff --git a/src/components/ProjectSettings/ProjectSchedule/DateSelector.tsx b/src/components/ProjectSettings/ProjectSchedule/DateSelector.tsx index 212c4ad9cb..a230c2a12b 100644 --- a/src/components/ProjectSettings/ProjectSchedule/DateSelector.tsx +++ b/src/components/ProjectSettings/ProjectSchedule/DateSelector.tsx @@ -1,4 +1,4 @@ -import { Button, Grid } from "@mui/material"; +import { Button, Stack } from "@mui/material"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { Dayjs } from "dayjs"; import { enqueueSnackbar } from "notistack"; @@ -60,40 +60,40 @@ export default function DateSelector(props: DateSelectorProps): ReactElement { } return ( - <> - setStartDate(newValue)} - /> -    - setEndDate(newValue)} - /> - - - - - - handleSubmit(), - }} - > - {t("buttons.confirm")} - - - - + + + setStartDate(newValue)} + /> + + setEndDate(newValue)} + /> + + + + + + handleSubmit(), + }} + > + {t("buttons.confirm")} + + + ); } diff --git a/src/components/ProjectSettings/ProjectSchedule/index.tsx b/src/components/ProjectSettings/ProjectSchedule/index.tsx index aa83497be6..069dd4aac1 100644 --- a/src/components/ProjectSettings/ProjectSchedule/index.tsx +++ b/src/components/ProjectSettings/ProjectSchedule/index.tsx @@ -1,5 +1,5 @@ import { CalendarMonth, DateRange, EventRepeat } from "@mui/icons-material"; -import { Button, Grid, Typography } from "@mui/material"; +import { Button, Grid2, Stack, Typography } from "@mui/material"; import { type ReactElement, useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import Modal from "react-modal"; @@ -57,22 +57,9 @@ export default function ProjectSchedule( return ( <> - + {!props.readOnly && ( - + } textId="projectSettings.schedule.setDays" @@ -91,19 +78,11 @@ export default function ProjectSchedule( onClick={() => setShowRemove(true)} buttonId={"Project-Schedule-removeDays"} /> - + )} - - - - + + + setShowRemove(false)} > - {t("projectSettings.schedule.removeAll")} + + {t("projectSettings.schedule.removeAll")} - - + - - + - - + + ); diff --git a/src/components/ProjectSettings/index.tsx b/src/components/ProjectSettings/index.tsx index 92d36342f9..2653dc2195 100644 --- a/src/components/ProjectSettings/index.tsx +++ b/src/components/ProjectSettings/index.tsx @@ -305,6 +305,7 @@ export default function ProjectSettingsComponent(): ReactElement { /> } maxWidth="1050px" // Comfortably fits three months + minWidth="300px" // Ensures space for one month /> diff --git a/src/components/ProjectUsers/AddProjectUsers.tsx b/src/components/ProjectUsers/AddProjectUsers.tsx index 0e1c757950..f43305e3f8 100644 --- a/src/components/ProjectUsers/AddProjectUsers.tsx +++ b/src/components/ProjectUsers/AddProjectUsers.tsx @@ -1,4 +1,4 @@ -import { Button, Grid, Typography } from "@mui/material"; +import { Button, Stack, Typography } from "@mui/material"; import { ReactElement, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import Modal from "react-modal"; @@ -60,21 +60,17 @@ export default function AddProjectUsers( return ( <> - + - - {RuntimeConfig.getInstance().emailServicesEnabled() && ( - - + {RuntimeConfig.getInstance().emailServicesEnabled() && ( + <> {t("projectSettings.invite.or")} - - - - - )} + + )} + {RuntimeConfig.getInstance().emailServicesEnabled() && ( - - + + + {/* Title */} + {t("projectSettings.invite.inviteByEmailLabel")} + + {/* Email address input */} setEmail(e.target.value)} - variant="outlined" - style={{ width: "100%" }} - margin="normal" - autoFocus - inputProps={{ maxLength: 100 }} + required + slotProps={{ htmlInput: { maxLength: 320 } }} /> + + {/* Email message input */} setMessage(e.target.value)} - variant="outlined" - style={{ width: "100%" }} - margin="normal" + slotProps={{ htmlInput: { maxLength: 10000 } }} /> - - - onSubmit(), - variant: "contained", - color: "primary", - }} - > - {t("buttons.invite")} - - - - - + + {/* Submit button */} + + onSubmit(), + variant: "contained", + }} + > + {t("buttons.invite")} + + + + ); } diff --git a/src/components/ProjectUsers/UserList.tsx b/src/components/ProjectUsers/UserList.tsx index 732981b5d6..1dc2c05752 100644 --- a/src/components/ProjectUsers/UserList.tsx +++ b/src/components/ProjectUsers/UserList.tsx @@ -2,7 +2,6 @@ import { Done } from "@mui/icons-material"; import { Avatar, Button, - Grid, List, ListItem, ListItemIcon, @@ -114,7 +113,7 @@ export default function UserList(props: UserListProps): ReactElement { }; return ( - +
{t("projectSettings.invite.searchTitle")} updateUsers(e.target.value)} @@ -125,6 +124,6 @@ export default function UserList(props: UserListProps): ReactElement { {filteredInProj.map(inProjListItem)} {filteredNotInProj.map(notInProjListItem)} - +
); } diff --git a/src/components/SiteSettings/Banners.tsx b/src/components/SiteSettings/Banners.tsx index 4b8090d228..6fa2591e01 100644 --- a/src/components/SiteSettings/Banners.tsx +++ b/src/components/SiteSettings/Banners.tsx @@ -1,19 +1,18 @@ -import { Button, Grid } from "@mui/material"; +import { Button, Stack } from "@mui/material"; import { ChangeEvent, ReactElement, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { BannerType } from "api/models"; import { getBannerText, updateBanner } from "backend"; -import theme from "types/theme"; import { NormalizedTextField } from "utilities/fontComponents"; export default function Banners(): ReactElement { return ( - <> + {[BannerType.Announcement, BannerType.Login].map((type) => ( ))} - + ); } @@ -57,26 +56,19 @@ function Banner(props: BannerProps): ReactElement { }; return ( - - - - + + - + - - - - + + ); } diff --git a/src/components/SiteSettings/ProjectManagement/ProjectUsersButtonWithConfirmation.tsx b/src/components/SiteSettings/ProjectManagement/ProjectUsersButtonWithConfirmation.tsx index 509e47e5ed..2de2991843 100644 --- a/src/components/SiteSettings/ProjectManagement/ProjectUsersButtonWithConfirmation.tsx +++ b/src/components/SiteSettings/ProjectManagement/ProjectUsersButtonWithConfirmation.tsx @@ -39,7 +39,7 @@ export default function ProjectUsersButtonWithConfirmation( {t("siteSettings.projectRoles")} setOpen(false)} open={open}> - + {t("siteSettings.projectRoles")} setOpen(false)} /> diff --git a/src/components/SiteSettings/UserManagement/ConfirmDeletion.tsx b/src/components/SiteSettings/UserManagement/ConfirmDeletion.tsx index 69d2a58040..639e9657b4 100644 --- a/src/components/SiteSettings/UserManagement/ConfirmDeletion.tsx +++ b/src/components/SiteSettings/UserManagement/ConfirmDeletion.tsx @@ -1,11 +1,4 @@ -import { - Button, - Grid, - Typography, - CardContent, - Card, - CardActions, -} from "@mui/material"; +import { Box, Button, Stack, Typography } from "@mui/material"; import { Fragment, ReactElement } from "react"; import { useTranslation } from "react-i18next"; @@ -26,63 +19,44 @@ export default function ConfirmDeletion( return ; } return ( - <> - - - - - {props.user.username} + + + + {props.user.username} + + + + {t("siteSettings.deleteUser.confirm")} + + + + + + - - - - -
- - - - + + + + ); } diff --git a/src/components/SiteSettings/UserManagement/UserList.tsx b/src/components/SiteSettings/UserManagement/UserList.tsx index 300c42da7c..c70658516f 100644 --- a/src/components/SiteSettings/UserManagement/UserList.tsx +++ b/src/components/SiteSettings/UserManagement/UserList.tsx @@ -2,13 +2,13 @@ import { DeleteForever, VpnKey } from "@mui/icons-material"; import { Avatar, Button, - Grid, List, ListItem, ListItemAvatar, ListItemIcon, ListItemText, SelectChangeEvent, + Stack, Typography, } from "@mui/material"; import { ReactElement, useCallback, useEffect, useState } from "react"; @@ -101,9 +101,9 @@ export default function UserList(props: UserListProps): ReactElement { }; return ( - + <> {t("projectSettings.invite.searchTitle")} - + setFilterInput(e.target.value)} placeholder={t("projectSettings.invite.searchPlaceholder")} @@ -116,8 +116,8 @@ export default function UserList(props: UserListProps): ReactElement { } onReverseClick={() => setReverseSorting(!reverseSorting)} /> - + {sortedUsers.map(userListItem)} - + ); } diff --git a/src/components/SiteSettings/UserManagement/index.tsx b/src/components/SiteSettings/UserManagement/index.tsx index bfff55a4c7..0a5a12dec2 100644 --- a/src/components/SiteSettings/UserManagement/index.tsx +++ b/src/components/SiteSettings/UserManagement/index.tsx @@ -1,4 +1,3 @@ -import { Grid } from "@mui/material"; import { ReactElement, useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import Modal from "react-modal"; @@ -67,9 +66,7 @@ export default function UserManagement(): ReactElement { return ( <> - - - + - - {t("siteSettings.projectList")} - + } textId="siteSettings.projectList" /> } value={SiteSettingsTab.Projects} /> @@ -51,10 +47,7 @@ export default function SiteSettings(): ReactElement { data-testid={SiteSettingsTab.Users} id={SiteSettingsTab.Users.toString()} label={ - - - {t("siteSettings.userList")} - + } textId="siteSettings.userList" /> } value={SiteSettingsTab.Users} /> @@ -62,10 +55,10 @@ export default function SiteSettings(): ReactElement { data-testid={SiteSettingsTab.Banners} id={SiteSettingsTab.Banners.toString()} label={ - - - {t("siteSettings.banners.title")} - + } + textId="siteSettings.banners.title" + /> } value={SiteSettingsTab.Banners} /> @@ -84,6 +77,21 @@ export default function SiteSettings(): ReactElement { ); } +interface TabLabelProps { + icon: ReactElement; + textId: string; +} + +function TabLabel(props: TabLabelProps): ReactElement { + const { t } = useTranslation(); + return ( + + {props.icon} + {t(props.textId)} + + ); +} + interface TabPanelProps { children?: ReactNode; index: SiteSettingsTab;