diff --git a/client/app/api/course/Users.ts b/client/app/api/course/Users.ts index c080279f225..109ffcb206f 100644 --- a/client/app/api/course/Users.ts +++ b/client/app/api/course/Users.ts @@ -1,12 +1,12 @@ import { AxiosResponse } from 'axios'; import { + CourseStaffRole, CourseUserBasicListData, CourseUserBasicMiniEntity, CourseUserData, CourseUserListData, ManageCourseUsersPermissions, ManageCourseUsersSharedData, - StaffRoles, UpdateCourseUserPatchData, } from 'types/course/courseUsers'; import { TimelineData } from 'types/course/referenceTimelines'; @@ -110,13 +110,13 @@ export default class UsersAPI extends BaseCourseAPI { * Upgrade a user to staff. * * @param {CourseUserBasicMiniEntity[]} users - * @param {StaffRoles} role + * @param {CourseStaffRole} role * @return {Promise} list of upgraded users * error response: { errors: [] } - An array of errors will be returned upon validation error. */ upgradeToStaff( users: CourseUserBasicMiniEntity[], - role: StaffRoles, + role: CourseStaffRole, ): Promise { const userIds = users.map((user) => user.id); const params = { diff --git a/client/app/bundles/course/container/Sidebar/CourseUserItem.tsx b/client/app/bundles/course/container/Sidebar/CourseUserItem.tsx index 51f6414860c..1671a18657e 100644 --- a/client/app/bundles/course/container/Sidebar/CourseUserItem.tsx +++ b/client/app/bundles/course/container/Sidebar/CourseUserItem.tsx @@ -4,8 +4,8 @@ import { Avatar, Typography } from '@mui/material'; import { CourseLayoutData } from 'types/course/courses'; import PopupMenu from 'lib/components/core/PopupMenu'; -import { COURSE_USER_ROLES } from 'lib/constants/sharedConstants'; import useTranslation from 'lib/hooks/useTranslation'; +import roleTranslations from 'lib/translations/course/users/roles'; import CourseUserProgress from './CourseUserProgress'; import LevelRing from './LevelRing'; @@ -45,24 +45,30 @@ interface CourseUserNameAndRoleProps { const CourseUserNameAndRole = ( props: CourseUserNameAndRoleProps, -): JSX.Element => ( - <> - - {props.from.courseUserName} - - - - {COURSE_USER_ROLES[props.from.courseUserRole!]} - - -); +): JSX.Element => { + const { t } = useTranslation(); + + return ( + <> + + {props.from.courseUserName} + + + {props.from.courseUserRole && ( + + {t(roleTranslations[props.from.courseUserRole])} + + )} + + ); +}; const SimpleCourseUserItemContent = ( props: CourseUserItemProps, diff --git a/client/app/bundles/course/enrol-requests/components/buttons/PendingEnrolRequestsButtons.tsx b/client/app/bundles/course/enrol-requests/components/buttons/PendingEnrolRequestsButtons.tsx index 9154e9cd815..eeaef281cb3 100644 --- a/client/app/bundles/course/enrol-requests/components/buttons/PendingEnrolRequestsButtons.tsx +++ b/client/app/bundles/course/enrol-requests/components/buttons/PendingEnrolRequestsButtons.tsx @@ -1,17 +1,18 @@ import { FC, memo, useState } from 'react'; -import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import { defineMessages } from 'react-intl'; import equal from 'fast-deep-equal'; import { EnrolRequestRowData } from 'types/course/enrolRequests'; import AcceptButton from 'lib/components/core/buttons/AcceptButton'; import DeleteButton from 'lib/components/core/buttons/DeleteButton'; -import { COURSE_USER_ROLES } from 'lib/constants/sharedConstants'; import { useAppDispatch } from 'lib/hooks/store'; import toast from 'lib/hooks/toast'; +import useTranslation from 'lib/hooks/useTranslation'; +import roleTranslations from 'lib/translations/course/users/roles'; import { approveEnrolRequest, rejectEnrolRequest } from '../../operations'; -interface Props extends WrappedComponentProps { +interface Props { enrolRequest: EnrolRequestRowData; } const styles = { @@ -53,7 +54,8 @@ const translations = defineMessages({ }); const PendingEnrolRequestsButtons: FC = (props) => { - const { intl, enrolRequest } = props; + const { enrolRequest } = props; + const { t } = useTranslation(); const dispatch = useAppDispatch(); const [isApproving, setIsApproving] = useState(false); const [isDeleting, setIsDeleting] = useState(false); @@ -63,7 +65,7 @@ const PendingEnrolRequestsButtons: FC = (props) => { return dispatch(approveEnrolRequest(enrolRequest)) .then(() => { toast.success( - intl.formatMessage(translations.approveSuccess, { + t(translations.approveSuccess, { name: enrolRequest.name, }), ); @@ -73,7 +75,7 @@ const PendingEnrolRequestsButtons: FC = (props) => { ? error.response.data.errors : ''; toast.error( - intl.formatMessage(translations.approveFailure, { + t(translations.approveFailure, { error: errorMessage, }), ); @@ -86,7 +88,7 @@ const PendingEnrolRequestsButtons: FC = (props) => { return dispatch(rejectEnrolRequest(enrolRequest.id)) .then(() => { toast.success( - intl.formatMessage(translations.rejectSuccess, { + t(translations.rejectSuccess, { name: enrolRequest.name, }), ); @@ -96,7 +98,7 @@ const PendingEnrolRequestsButtons: FC = (props) => { ? error.response.data.errors : ''; toast.error( - intl.formatMessage(translations.rejectFailure, { + t(translations.rejectFailure, { error: errorMessage, }), ); @@ -111,12 +113,12 @@ const PendingEnrolRequestsButtons: FC = (props) => { disabled={isApproving || isDeleting} onClick={onApprove} sx={styles.buttonStyle} - tooltip={intl.formatMessage(translations.approveTooltip)} + tooltip={t(translations.approveTooltip)} /> = (props) => { loading={isDeleting} onClick={onDelete} sx={styles.buttonStyle} - tooltip={intl.formatMessage(translations.rejectTooltip)} + tooltip={t(translations.rejectTooltip)} /> ); }; -export default memo( - injectIntl(PendingEnrolRequestsButtons), - (prevProps, nextProps) => { - return equal(prevProps.enrolRequest, nextProps.enrolRequest); - }, -); +export default memo(PendingEnrolRequestsButtons, (prevProps, nextProps) => { + return equal(prevProps.enrolRequest, nextProps.enrolRequest); +}); diff --git a/client/app/bundles/course/enrol-requests/components/tables/EnrolRequestsTable.tsx b/client/app/bundles/course/enrol-requests/components/tables/EnrolRequestsTable.tsx index 28d279b163d..fbad255aeaf 100644 --- a/client/app/bundles/course/enrol-requests/components/tables/EnrolRequestsTable.tsx +++ b/client/app/bundles/course/enrol-requests/components/tables/EnrolRequestsTable.tsx @@ -1,13 +1,9 @@ import { FC, memo, ReactElement } from 'react'; -import { - defineMessages, - FormattedMessage, - injectIntl, - WrappedComponentProps, -} from 'react-intl'; +import { defineMessages } from 'react-intl'; import { Checkbox, MenuItem, TextField, Typography } from '@mui/material'; import equal from 'fast-deep-equal'; import { TableColumns, TableOptions } from 'types/components/DataTable'; +import { COURSE_USER_ROLES } from 'types/course/courseUsers'; import { EnrolRequestMiniEntity, EnrolRequestRowData, @@ -16,13 +12,12 @@ import { import DataTable from 'lib/components/core/layouts/DataTable'; import Note from 'lib/components/core/Note'; import InlineEditTextField from 'lib/components/form/fields/DataTableInlineEditable/TextField'; -import { - COURSE_USER_ROLES, - TIMELINE_ALGORITHMS, -} from 'lib/constants/sharedConstants'; +import { TIMELINE_ALGORITHMS } from 'lib/constants/sharedConstants'; import rebuildObjectFromRow from 'lib/helpers/mui-datatables-helpers'; import { useAppSelector } from 'lib/hooks/store'; +import useTranslation from 'lib/hooks/useTranslation'; import { formatLongDateTime } from 'lib/moment'; +import roleTranslations from 'lib/translations/course/users/roles'; import tableTranslations from 'lib/translations/table'; import { @@ -30,7 +25,7 @@ import { getManageCourseUsersSharedData, } from '../../selectors'; -interface Props extends WrappedComponentProps { +interface Props { title: string; enrolRequests: EnrolRequestMiniEntity[]; pendingEnrolRequests?: boolean; @@ -75,8 +70,9 @@ const EnrolRequestsTable: FC = (props) => { approvedEnrolRequests = false, rejectedEnrolRequests = false, renderRowActionComponent = null, - intl, } = props; + + const { t } = useTranslation(); const permissions = useAppSelector(getManageCourseUserPermissions); const sharedData = useAppSelector(getManageCourseUsersSharedData); const defaultTimelineAlgorithm = sharedData.defaultTimelineAlgorithm; @@ -85,12 +81,9 @@ const EnrolRequestsTable: FC = (props) => { if (enrolRequests && enrolRequests.length === 0) { return ( - } + message={t(translations.noEnrolRequests, { + enrolRequestsType: title.toLowerCase(), + })} /> ); } @@ -98,11 +91,11 @@ const EnrolRequestsTable: FC = (props) => { const requestTypePrefix: string = ((): string => { /* eslint-disable no-else-return */ if (approvedEnrolRequests) { - return intl.formatMessage(translations.approved); + return t(translations.approved); } else if (rejectedEnrolRequests) { - return intl.formatMessage(translations.rejected); + return t(translations.rejected); } else if (pendingEnrolRequests) { - return intl.formatMessage(translations.pending); + return t(translations.pending); } return ''; /* eslint-enable no-else-return */ @@ -135,7 +128,7 @@ const EnrolRequestsTable: FC = (props) => { const basicColumns: TableColumns[] = [ { name: 'id', - label: intl.formatMessage(tableTranslations.id), + label: t(tableTranslations.id), options: { display: false, filter: false, @@ -144,7 +137,7 @@ const EnrolRequestsTable: FC = (props) => { }, { name: 'email', - label: intl.formatMessage(tableTranslations.email), + label: t(tableTranslations.email), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -159,14 +152,14 @@ const EnrolRequestsTable: FC = (props) => { }, { name: 'createdAt', - label: intl.formatMessage(tableTranslations.createdAt), + label: t(tableTranslations.createdAt), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { const enrolRequest = enrolRequests[dataIndex]; return ( - {enrolRequest.createdAt} + {formatLongDateTime(enrolRequest.createdAt)} ); }, @@ -177,7 +170,7 @@ const EnrolRequestsTable: FC = (props) => { const pendingEnrolRequestsColumns: TableColumns[] = [ { name: 'name', - label: intl.formatMessage(tableTranslations.name), + label: t(tableTranslations.name), options: { alignCenter: false, customBodyRender: (value, tableMeta, updateValue): JSX.Element => { @@ -198,7 +191,7 @@ const EnrolRequestsTable: FC = (props) => { ...basicColumns, { name: 'role', - label: intl.formatMessage(tableTranslations.role), + label: t(tableTranslations.role), options: { alignCenter: false, customBodyRender: (value, tableMeta, updateValue): JSX.Element => { @@ -211,12 +204,12 @@ const EnrolRequestsTable: FC = (props) => { value={value || 'student'} variant="standard" > - {Object.keys(COURSE_USER_ROLES).map((option) => ( + {COURSE_USER_ROLES.map((option) => ( - {COURSE_USER_ROLES[option]} + {t(roleTranslations[option])} ))} @@ -226,7 +219,7 @@ const EnrolRequestsTable: FC = (props) => { }, { name: 'phantom', - label: intl.formatMessage(tableTranslations.phantom), + label: t(tableTranslations.phantom), options: { customBodyRender: (value, tableMeta, updateValue): JSX.Element => { const enrolRequest = enrolRequests[tableMeta.rowIndex]; @@ -246,7 +239,7 @@ const EnrolRequestsTable: FC = (props) => { ? [ { name: 'timelineAlgorithm', - label: intl.formatMessage(tableTranslations.timelineAlgorithm), + label: t(tableTranslations.timelineAlgorithm), options: { alignCenter: false, customBodyRender: ( @@ -282,7 +275,7 @@ const EnrolRequestsTable: FC = (props) => { ? [ { name: 'actions', - label: intl.formatMessage(tableTranslations.actions), + label: t(tableTranslations.actions), options: { empty: true, sort: false, @@ -301,7 +294,7 @@ const EnrolRequestsTable: FC = (props) => { const approvedEnrolRequestsColumns: TableColumns[] = [ { name: 'name', - label: intl.formatMessage(tableTranslations.name), + label: t(tableTranslations.name), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -317,14 +310,14 @@ const EnrolRequestsTable: FC = (props) => { ...basicColumns, { name: 'role', - label: intl.formatMessage(tableTranslations.role), + label: t(tableTranslations.role), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { const enrolRequest = enrolRequests[dataIndex]; return ( - {enrolRequest.role ? COURSE_USER_ROLES[enrolRequest.role] : '-'} + {enrolRequest.role ? t(roleTranslations[enrolRequest.role]) : '-'} ); }, @@ -332,7 +325,7 @@ const EnrolRequestsTable: FC = (props) => { }, { name: 'phantom', - label: intl.formatMessage(tableTranslations.phantom), + label: t(tableTranslations.phantom), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -353,7 +346,7 @@ const EnrolRequestsTable: FC = (props) => { }, { name: 'approver', - label: intl.formatMessage(tableTranslations.approver), + label: t(tableTranslations.approver), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -368,7 +361,7 @@ const EnrolRequestsTable: FC = (props) => { }, { name: 'approvedAt', - label: intl.formatMessage(tableTranslations.approvedAt), + label: t(tableTranslations.approvedAt), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -386,7 +379,7 @@ const EnrolRequestsTable: FC = (props) => { const rejectedEnrolRequestsColumns: TableColumns[] = [ { name: 'name', - label: intl.formatMessage(tableTranslations.name), + label: t(tableTranslations.name), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -402,7 +395,7 @@ const EnrolRequestsTable: FC = (props) => { ...basicColumns, { name: 'rejector', - label: intl.formatMessage(tableTranslations.rejector), + label: t(tableTranslations.rejector), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -417,7 +410,7 @@ const EnrolRequestsTable: FC = (props) => { }, { name: 'rejectedAt', - label: intl.formatMessage(tableTranslations.rejectedAt), + label: t(tableTranslations.rejectedAt), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -452,6 +445,6 @@ const EnrolRequestsTable: FC = (props) => { ); }; -export default memo(injectIntl(EnrolRequestsTable), (prevProps, nextProps) => { +export default memo(EnrolRequestsTable, (prevProps, nextProps) => { return equal(prevProps.enrolRequests, nextProps.enrolRequests); }); diff --git a/client/app/bundles/course/group/types.ts b/client/app/bundles/course/group/types.ts index c6ed9c1a965..409f2acf82a 100644 --- a/client/app/bundles/course/group/types.ts +++ b/client/app/bundles/course/group/types.ts @@ -1,9 +1,9 @@ -import { CourseUserRoles, CourseUserShape } from 'types/course/courseUsers'; +import { CourseUserRole, CourseUserShape } from 'types/course/courseUsers'; export interface GroupMember { id: number; name: string; - role: CourseUserRoles; + role: CourseUserRole; isPhantom: boolean; groupRole: 'manager' | 'normal'; } diff --git a/client/app/bundles/course/user-invitations/components/forms/IndividualInvitation.tsx b/client/app/bundles/course/user-invitations/components/forms/IndividualInvitation.tsx index 5dbf8aa1e42..7e7e88c168d 100644 --- a/client/app/bundles/course/user-invitations/components/forms/IndividualInvitation.tsx +++ b/client/app/bundles/course/user-invitations/components/forms/IndividualInvitation.tsx @@ -5,10 +5,13 @@ import { UseFieldArrayAppend, UseFieldArrayRemove, } from 'react-hook-form'; -import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { Close } from '@mui/icons-material'; import { Box, Grid, IconButton, Tooltip } from '@mui/material'; -import { ManageCourseUsersPermissions } from 'types/course/courseUsers'; +import { + COURSE_USER_ROLES, + ManageCourseUsersPermissions, +} from 'types/course/courseUsers'; import { IndividualInvite, IndividualInvites, @@ -17,13 +20,12 @@ import { import FormCheckboxField from 'lib/components/form/fields/CheckboxField'; import FormSelectField from 'lib/components/form/fields/SelectField'; import FormTextField from 'lib/components/form/fields/TextField'; -import { - COURSE_USER_ROLES, - TIMELINE_ALGORITHMS, -} from 'lib/constants/sharedConstants'; +import { TIMELINE_ALGORITHMS } from 'lib/constants/sharedConstants'; +import useTranslation from 'lib/hooks/useTranslation'; +import roleTranslations from 'lib/translations/course/users/roles'; import tableTranslations from 'lib/translations/table'; -interface Props extends WrappedComponentProps { +interface Props { permissions: ManageCourseUsersPermissions; fieldsConfig: { control: Control; @@ -64,13 +66,14 @@ const translations = defineMessages({ }, }); -const userRoleOptions = Object.keys(COURSE_USER_ROLES).map((roleValue) => ({ - label: COURSE_USER_ROLES[roleValue], - value: roleValue, -})); - const IndividualInvitation: FC = (props) => { - const { permissions, fieldsConfig, index, intl } = props; + const { permissions, fieldsConfig, index } = props; + const { t } = useTranslation(); + + const userRoleOptions = COURSE_USER_ROLES.map((roleValue) => ({ + label: t(roleTranslations[roleValue]), + value: roleValue, + })); const renderInvitationBody = ( @@ -82,8 +85,8 @@ const IndividualInvitation: FC = (props) => { field={field} fieldState={fieldState} id={`name-${index}`} - label={intl.formatMessage(tableTranslations.name)} - placeholder={intl.formatMessage(translations.namePlaceholder)} + label={t(tableTranslations.name)} + placeholder={t(translations.namePlaceholder)} sx={styles.textInput} variant="standard" /> @@ -97,8 +100,8 @@ const IndividualInvitation: FC = (props) => { field={field} fieldState={fieldState} id={`email-${index}`} - label={intl.formatMessage(tableTranslations.email)} - placeholder={intl.formatMessage(translations.emailPlaceholder)} + label={t(tableTranslations.email)} + placeholder={t(translations.emailPlaceholder)} sx={styles.textInput} variant="standard" /> @@ -111,7 +114,7 @@ const IndividualInvitation: FC = (props) => { @@ -125,7 +128,7 @@ const IndividualInvitation: FC = (props) => { )} @@ -138,7 +141,7 @@ const IndividualInvitation: FC = (props) => { )} /> @@ -148,7 +151,7 @@ const IndividualInvitation: FC = (props) => { return ( {renderInvitationBody} - + fieldsConfig.remove(index)}> @@ -157,4 +160,4 @@ const IndividualInvitation: FC = (props) => { ); }; -export default injectIntl(IndividualInvitation); +export default IndividualInvitation; diff --git a/client/app/bundles/course/user-invitations/components/tables/InvitationResultInvitationsTable.tsx b/client/app/bundles/course/user-invitations/components/tables/InvitationResultInvitationsTable.tsx index df8e554eec2..2ee9ce1cd37 100644 --- a/client/app/bundles/course/user-invitations/components/tables/InvitationResultInvitationsTable.tsx +++ b/client/app/bundles/course/user-invitations/components/tables/InvitationResultInvitationsTable.tsx @@ -1,24 +1,23 @@ import { FC, memo } from 'react'; -import { injectIntl, WrappedComponentProps } from 'react-intl'; import { Typography } from '@mui/material'; import equal from 'fast-deep-equal'; import { TableColumns, TableOptions } from 'types/components/DataTable'; import { InvitationListData } from 'types/course/userInvitations'; import DataTable from 'lib/components/core/layouts/DataTable'; -import { - COURSE_USER_ROLES, - DEFAULT_TABLE_ROWS_PER_PAGE, -} from 'lib/constants/sharedConstants'; +import { DEFAULT_TABLE_ROWS_PER_PAGE } from 'lib/constants/sharedConstants'; +import useTranslation from 'lib/hooks/useTranslation'; +import roleTranslations from 'lib/translations/course/users/roles'; import tableTranslations from 'lib/translations/table'; -interface Props extends WrappedComponentProps { +interface Props { title: JSX.Element; invitations: InvitationListData[]; } const InvitationResultInvitationsTable: FC = (props) => { - const { title, invitations, intl } = props; + const { title, invitations } = props; + const { t } = useTranslation(); if (invitations && invitations.length === 0) return null; @@ -47,7 +46,7 @@ const InvitationResultInvitationsTable: FC = (props) => { const columns: TableColumns[] = [ { name: 'id', - label: intl.formatMessage(tableTranslations.id), + label: t(tableTranslations.id), options: { display: false, filter: false, @@ -56,7 +55,7 @@ const InvitationResultInvitationsTable: FC = (props) => { }, { name: 'name', - label: intl.formatMessage(tableTranslations.name), + label: t(tableTranslations.name), options: { alignCenter: false, sort: false, @@ -64,7 +63,7 @@ const InvitationResultInvitationsTable: FC = (props) => { }, { name: 'email', - label: intl.formatMessage(tableTranslations.email), + label: t(tableTranslations.email), options: { alignCenter: false, sort: false, @@ -72,7 +71,7 @@ const InvitationResultInvitationsTable: FC = (props) => { }, { name: 'phantom', - label: intl.formatMessage(tableTranslations.phantom), + label: t(tableTranslations.phantom), options: { sort: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -91,7 +90,7 @@ const InvitationResultInvitationsTable: FC = (props) => { }, { name: 'role', - label: intl.formatMessage(tableTranslations.role), + label: t(tableTranslations.role), options: { alignCenter: false, sort: false, @@ -103,7 +102,7 @@ const InvitationResultInvitationsTable: FC = (props) => { className="invitation_result_invitation_role" variant="body2" > - {COURSE_USER_ROLES[invitation.role]} + {t(roleTranslations[invitation.role])} ); }, @@ -111,7 +110,7 @@ const InvitationResultInvitationsTable: FC = (props) => { }, { name: 'sentAt', - label: intl.formatMessage(tableTranslations.invitationSentAt), + label: t(tableTranslations.invitationSentAt), options: { alignCenter: false, sort: false, @@ -132,7 +131,7 @@ const InvitationResultInvitationsTable: FC = (props) => { }; export default memo( - injectIntl(InvitationResultInvitationsTable), + InvitationResultInvitationsTable, (prevProps, nextProps) => { return equal(prevProps.invitations, nextProps.invitations); }, diff --git a/client/app/bundles/course/user-invitations/components/tables/InvitationResultUsersTable.tsx b/client/app/bundles/course/user-invitations/components/tables/InvitationResultUsersTable.tsx index 1f0ff894d04..5164af7ec63 100644 --- a/client/app/bundles/course/user-invitations/components/tables/InvitationResultUsersTable.tsx +++ b/client/app/bundles/course/user-invitations/components/tables/InvitationResultUsersTable.tsx @@ -1,21 +1,22 @@ import { FC, memo } from 'react'; -import { injectIntl, WrappedComponentProps } from 'react-intl'; import { Typography } from '@mui/material'; import equal from 'fast-deep-equal'; import { TableColumns, TableOptions } from 'types/components/DataTable'; import { CourseUserData } from 'types/course/courseUsers'; import DataTable from 'lib/components/core/layouts/DataTable'; -import { COURSE_USER_ROLES } from 'lib/constants/sharedConstants'; +import useTranslation from 'lib/hooks/useTranslation'; +import roleTranslations from 'lib/translations/course/users/roles'; import tableTranslations from 'lib/translations/table'; -interface Props extends WrappedComponentProps { +interface Props { title: JSX.Element; users: CourseUserData[]; } const InvitationResultUsersTable: FC = (props) => { - const { title, users, intl } = props; + const { title, users } = props; + const { t } = useTranslation(); if (users && users.length === 0) return null; @@ -42,7 +43,7 @@ const InvitationResultUsersTable: FC = (props) => { const columns: TableColumns[] = [ { name: 'id', - label: intl.formatMessage(tableTranslations.id), + label: t(tableTranslations.id), options: { display: false, filter: false, @@ -51,7 +52,7 @@ const InvitationResultUsersTable: FC = (props) => { }, { name: 'name', - label: intl.formatMessage(tableTranslations.name), + label: t(tableTranslations.name), options: { alignCenter: false, sort: false, @@ -59,7 +60,7 @@ const InvitationResultUsersTable: FC = (props) => { }, { name: 'email', - label: intl.formatMessage(tableTranslations.email), + label: t(tableTranslations.email), options: { alignCenter: false, sort: false, @@ -67,7 +68,7 @@ const InvitationResultUsersTable: FC = (props) => { }, { name: 'phantom', - label: intl.formatMessage(tableTranslations.phantom), + label: t(tableTranslations.phantom), options: { sort: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -86,7 +87,7 @@ const InvitationResultUsersTable: FC = (props) => { }, { name: 'role', - label: intl.formatMessage(tableTranslations.role), + label: t(tableTranslations.role), options: { alignCenter: false, sort: false, @@ -98,7 +99,7 @@ const InvitationResultUsersTable: FC = (props) => { className="invitation_result_user_role" variant="body2" > - {COURSE_USER_ROLES[user.role]} + {t(roleTranslations[user.role])} ); }, @@ -118,9 +119,6 @@ const InvitationResultUsersTable: FC = (props) => { ); }; -export default memo( - injectIntl(InvitationResultUsersTable), - (prevProps, nextProps) => { - return equal(prevProps.users, nextProps.users); - }, -); +export default memo(InvitationResultUsersTable, (prevProps, nextProps) => { + return equal(prevProps.users, nextProps.users); +}); diff --git a/client/app/bundles/course/user-invitations/components/tables/UserInvitationsTable.tsx b/client/app/bundles/course/user-invitations/components/tables/UserInvitationsTable.tsx index d46b0e0d8ee..2171b44cb1c 100644 --- a/client/app/bundles/course/user-invitations/components/tables/UserInvitationsTable.tsx +++ b/client/app/bundles/course/user-invitations/components/tables/UserInvitationsTable.tsx @@ -1,10 +1,5 @@ import { FC, memo, ReactElement } from 'react'; -import { - defineMessages, - FormattedMessage, - injectIntl, - WrappedComponentProps, -} from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Typography } from '@mui/material'; import equal from 'fast-deep-equal'; import { TableColumns, TableOptions } from 'types/components/DataTable'; @@ -15,19 +10,18 @@ import { import DataTable from 'lib/components/core/layouts/DataTable'; import Note from 'lib/components/core/Note'; -import { - COURSE_USER_ROLES, - TIMELINE_ALGORITHMS, -} from 'lib/constants/sharedConstants'; +import { TIMELINE_ALGORITHMS } from 'lib/constants/sharedConstants'; import rebuildObjectFromRow from 'lib/helpers/mui-datatables-helpers'; import { useAppSelector } from 'lib/hooks/store'; +import useTranslation from 'lib/hooks/useTranslation'; import { formatLongDateTime } from 'lib/moment'; +import roleTranslations from 'lib/translations/course/users/roles'; import tableTranslations from 'lib/translations/table'; import { getManageCourseUserPermissions } from '../../selectors'; import ResendInvitationsButton from '../buttons/ResendAllInvitationsButton'; -interface Props extends WrappedComponentProps { +interface Props { title: string; invitations: InvitationMiniEntity[]; pendingInvitations?: boolean; @@ -57,8 +51,8 @@ const UserInvitationsTable: FC = (props) => { pendingInvitations = false, acceptedInvitations = false, renderRowActionComponent = null, - intl, } = props; + const { t } = useTranslation(); const permissions = useAppSelector(getManageCourseUserPermissions); if (invitations && invitations.length === 0) { @@ -75,8 +69,8 @@ const UserInvitationsTable: FC = (props) => { } const invitationTypePrefix: string = pendingInvitations - ? intl.formatMessage(translations.pending) - : intl.formatMessage(translations.accepted); + ? t(translations.pending) + : t(translations.accepted); const options: TableOptions = { download: false, @@ -104,7 +98,7 @@ const UserInvitationsTable: FC = (props) => { const columns: TableColumns[] = [ { name: 'id', - label: intl.formatMessage(tableTranslations.id), + label: t(tableTranslations.id), options: { display: false, filter: false, @@ -113,7 +107,7 @@ const UserInvitationsTable: FC = (props) => { }, { name: 'name', - label: intl.formatMessage(tableTranslations.name), + label: t(tableTranslations.name), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -128,7 +122,7 @@ const UserInvitationsTable: FC = (props) => { }, { name: 'email', - label: intl.formatMessage(tableTranslations.email), + label: t(tableTranslations.email), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -143,14 +137,14 @@ const UserInvitationsTable: FC = (props) => { }, { name: 'role', - label: intl.formatMessage(tableTranslations.role), + label: t(tableTranslations.role), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { const invitation = invitations[dataIndex]; return ( - {COURSE_USER_ROLES[invitation.role]} + {t(roleTranslations[invitation.role])} ); }, @@ -158,7 +152,7 @@ const UserInvitationsTable: FC = (props) => { }, { name: 'phantom', - label: intl.formatMessage(tableTranslations.phantom), + label: t(tableTranslations.phantom), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -173,7 +167,7 @@ const UserInvitationsTable: FC = (props) => { }, { name: 'invitationKey', - label: intl.formatMessage(tableTranslations.invitationCode), + label: t(tableTranslations.invitationCode), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -191,7 +185,7 @@ const UserInvitationsTable: FC = (props) => { if (pendingInvitations) { columns.push({ name: 'sentAt', - label: intl.formatMessage(tableTranslations.invitationSentAt), + label: t(tableTranslations.invitationSentAt), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -209,7 +203,7 @@ const UserInvitationsTable: FC = (props) => { if (permissions.canManagePersonalTimes) { columns.push({ name: 'timelineAlgorithm', - label: intl.formatMessage(tableTranslations.personalizedTimeline), + label: t(tableTranslations.personalizedTimeline), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -229,7 +223,7 @@ const UserInvitationsTable: FC = (props) => { if (acceptedInvitations) { columns.push({ name: 'confirmedAt', - label: intl.formatMessage(tableTranslations.invitationAcceptedAt), + label: t(tableTranslations.invitationAcceptedAt), options: { alignCenter: false, customBodyRenderLite: (dataIndex): JSX.Element => { @@ -247,7 +241,7 @@ const UserInvitationsTable: FC = (props) => { if (renderRowActionComponent) { columns.push({ name: 'actions', - label: intl.formatMessage(tableTranslations.actions), + label: t(tableTranslations.actions), options: { empty: true, sort: false, @@ -273,9 +267,6 @@ const UserInvitationsTable: FC = (props) => { ); }; -export default memo( - injectIntl(UserInvitationsTable), - (prevProps, nextProps) => { - return equal(prevProps.invitations, nextProps.invitations); - }, -); +export default memo(UserInvitationsTable, (prevProps, nextProps) => { + return equal(prevProps.invitations, nextProps.invitations); +}); diff --git a/client/app/bundles/course/users/components/buttons/UserManagementButtons.tsx b/client/app/bundles/course/users/components/buttons/UserManagementButtons.tsx index 1c367ffd674..9fb8664df35 100644 --- a/client/app/bundles/course/users/components/buttons/UserManagementButtons.tsx +++ b/client/app/bundles/course/users/components/buttons/UserManagementButtons.tsx @@ -1,16 +1,17 @@ import { FC, memo, useState } from 'react'; -import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import { defineMessages } from 'react-intl'; import equal from 'fast-deep-equal'; import { CourseUserMiniEntity } from 'types/course/courseUsers'; import DeleteButton from 'lib/components/core/buttons/DeleteButton'; -import { COURSE_USER_ROLES } from 'lib/constants/sharedConstants'; import { useAppDispatch } from 'lib/hooks/store'; import toast from 'lib/hooks/toast'; +import useTranslation from 'lib/hooks/useTranslation'; +import roleTranslations from 'lib/translations/course/users/roles'; import { deleteUser } from '../../operations'; -interface Props extends WrappedComponentProps { +interface Props { user: CourseUserMiniEntity; disabled?: boolean; } @@ -37,12 +38,13 @@ const translations = defineMessages({ }); const UserManagementButtons: FC = (props) => { - const { intl, user } = props; + const { user } = props; + const { t } = useTranslation(); const dispatch = useAppDispatch(); const [isDeleting, setIsDeleting] = useState(false); const userTranslationDict = { - role: COURSE_USER_ROLES[user.role], + role: t(roleTranslations[user.role]), name: user.name, email: user.email, }; @@ -51,20 +53,13 @@ const UserManagementButtons: FC = (props) => { setIsDeleting(true); return dispatch(deleteUser(user.id)) .then(() => { - toast.success( - intl.formatMessage( - translations.deletionScheduled, - userTranslationDict, - ), - ); + toast.success(t(translations.deletionScheduled, userTranslationDict)); }) .finally(() => { setIsDeleting(false); }) .catch((error) => { - toast.error( - intl.formatMessage(translations.deletionFailure, userTranslationDict), - ); + toast.error(t(translations.deletionFailure, userTranslationDict)); throw error; }) .finally(() => setIsDeleting(false)); @@ -74,8 +69,8 @@ const UserManagementButtons: FC = (props) => {
= (props) => { ); }; -export default memo(injectIntl(UserManagementButtons), equal); +export default memo(UserManagementButtons, equal); diff --git a/client/app/bundles/course/users/components/misc/UpgradeToStaff.tsx b/client/app/bundles/course/users/components/misc/UpgradeToStaff.tsx index c769460892a..5b926ebb4d6 100644 --- a/client/app/bundles/course/users/components/misc/UpgradeToStaff.tsx +++ b/client/app/bundles/course/users/components/misc/UpgradeToStaff.tsx @@ -1,5 +1,5 @@ import { FC, useState } from 'react'; -import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import { defineMessages } from 'react-intl'; import CheckBoxIcon from '@mui/icons-material/CheckBox'; import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; import { LoadingButton } from '@mui/lab'; @@ -13,13 +13,16 @@ import { Typography, } from '@mui/material'; import { + COURSE_STAFF_ROLES, + CourseStaffRole, CourseUserBasicMiniEntity, - StaffRoles, } from 'types/course/courseUsers'; -import { STAFF_ROLES } from 'lib/constants/sharedConstants'; import { useAppDispatch, useAppSelector } from 'lib/hooks/store'; import toast from 'lib/hooks/toast'; +import useTranslation from 'lib/hooks/useTranslation'; +import roleTranslations from 'lib/translations/course/users/roles'; +import tableTranslations from 'lib/translations/table'; import { upgradeToStaff } from '../../operations'; import { getStudentOptionMiniEntities } from '../../selectors'; @@ -27,8 +30,6 @@ import { getStudentOptionMiniEntities } from '../../selectors'; const icon = ; const checkedIcon = ; -type Props = WrappedComponentProps; - const translations = defineMessages({ upgradeSuccess: { id: 'course.users.UpgradeToStaff.upgradeSuccess', @@ -43,27 +44,21 @@ const translations = defineMessages({ id: 'course.users.UpgradeToStaff.upgradeHeader', defaultMessage: 'Upgrade Student', }, - nameLabel: { - id: 'course.users.UpgradeToStaff.nameLabel', - defaultMessage: 'Name', - }, upgradeButton: { id: 'course.users.UpgradeToStaff.upgradeButton', defaultMessage: 'Upgrade to staff', }, }); -const UpgradeToStaff: FC = (props) => { - const { intl } = props; +const UpgradeToStaff: FC = () => { + const { t } = useTranslation(); const students = useAppSelector(getStudentOptionMiniEntities); const [isLoading, setIsLoading] = useState(false); const [selectedStudents, setSelectedStudents] = useState< CourseUserBasicMiniEntity[] >([]); - const [role, setRole] = useState( - Object.keys(STAFF_ROLES)[0] as StaffRoles, // object.keys returns string[]; we know it is a StaffRoles - ); + const [role, setRole] = useState('teaching_assistant'); const dispatch = useAppDispatch(); const onSubmit = (): Promise => { @@ -71,11 +66,10 @@ const UpgradeToStaff: FC = (props) => { setSelectedStudents([]); return dispatch(upgradeToStaff(selectedStudents, role)) .then(() => { - const roleLabel = STAFF_ROLES[role]; toast.success( - intl.formatMessage(translations.upgradeSuccess, { + t(translations.upgradeSuccess, { count: selectedStudents.length, - role: roleLabel, + role: t(roleTranslations[role]), }), ); }) @@ -84,7 +78,7 @@ const UpgradeToStaff: FC = (props) => { ? error.response.data.errors : ''; toast.error( - intl.formatMessage(translations.upgradeFailure, { + t(translations.upgradeFailure, { error: errorMessage, }), ); @@ -105,7 +99,7 @@ const UpgradeToStaff: FC = (props) => { return (
- {intl.formatMessage(translations.upgradeHeader)} + {t(translations.upgradeHeader)} = (props) => { renderInput={(params): JSX.Element => ( )} @@ -138,17 +132,19 @@ const UpgradeToStaff: FC = (props) => { /> - {/* eslint-disable-next-line @typescript-eslint/no-shadow */} - {Object.keys(STAFF_ROLES).map((role) => ( - - {STAFF_ROLES[role]} + {COURSE_STAFF_ROLES.map((roleValue) => ( + + {t(roleTranslations[roleValue])} ))} @@ -159,11 +155,11 @@ const UpgradeToStaff: FC = (props) => { style={{ marginTop: '4px' }} variant="contained" > - {intl.formatMessage(translations.upgradeButton)} + {t(translations.upgradeButton)}
); }; -export default injectIntl(UpgradeToStaff); +export default UpgradeToStaff; diff --git a/client/app/bundles/course/users/components/misc/UserProfileCard.tsx b/client/app/bundles/course/users/components/misc/UserProfileCard.tsx index 874cc8709b2..36f1731b0f5 100644 --- a/client/app/bundles/course/users/components/misc/UserProfileCard.tsx +++ b/client/app/bundles/course/users/components/misc/UserProfileCard.tsx @@ -1,16 +1,17 @@ import { FC, MouseEvent } from 'react'; -import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { scroller } from 'react-scroll'; import { Avatar, Card, CardContent, Grid, Typography } from '@mui/material'; import { CourseUserEntity } from 'types/course/courseUsers'; import Link from 'lib/components/core/Link'; -import { COURSE_USER_ROLES } from 'lib/constants/sharedConstants'; +import useTranslation from 'lib/hooks/useTranslation'; +import roleTranslations from 'lib/translations/course/users/roles'; import UserProfileCardStats from './UserProfileCardStats'; import styles from './UserProfileCard.scss'; -interface Props extends WrappedComponentProps { +interface Props { user: CourseUserEntity; } @@ -29,7 +30,9 @@ const translations = defineMessages({ }, }); -const UserProfileCard: FC = ({ user, intl }) => { +const UserProfileCard: FC = ({ user }) => { + const { t } = useTranslation(); + const handleScrollToAchievements = (e: MouseEvent): void => { e.preventDefault(); scroller.scrollTo('user-profile-achievements', { @@ -51,7 +54,7 @@ const UserProfileCard: FC = ({ user, intl }) => { {user.level >= 0 && ( )} @@ -59,7 +62,7 @@ const UserProfileCard: FC = ({ user, intl }) => { @@ -71,7 +74,7 @@ const UserProfileCard: FC = ({ user, intl }) => { > @@ -111,7 +114,7 @@ const UserProfileCard: FC = ({ user, intl }) => { > {user.name} - {COURSE_USER_ROLES[user.role]} + {t(roleTranslations[user.role])} {user.email} {renderUserStats()} @@ -122,4 +125,4 @@ const UserProfileCard: FC = ({ user, intl }) => { ); }; -export default injectIntl(UserProfileCard); +export default UserProfileCard; diff --git a/client/app/bundles/course/users/components/tables/ManageUsersTable/RoleMenu.tsx b/client/app/bundles/course/users/components/tables/ManageUsersTable/RoleMenu.tsx index 9809579837d..0462848dc04 100644 --- a/client/app/bundles/course/users/components/tables/ManageUsersTable/RoleMenu.tsx +++ b/client/app/bundles/course/users/components/tables/ManageUsersTable/RoleMenu.tsx @@ -2,15 +2,16 @@ import { memo } from 'react'; import { MenuItem, TextField } from '@mui/material'; import equal from 'fast-deep-equal'; import { + COURSE_USER_ROLES, CourseUserMiniEntity, - CourseUserRoles, + CourseUserRole, } from 'types/course/courseUsers'; import { updateUser } from 'bundles/course/users/operations'; -import { COURSE_USER_ROLES } from 'lib/constants/sharedConstants'; import { useAppDispatch } from 'lib/hooks/store'; import toast from 'lib/hooks/toast'; import useTranslation from 'lib/hooks/useTranslation'; +import roleTranslations from 'lib/translations/course/users/roles'; import translations from './translations'; @@ -18,12 +19,6 @@ interface RoleMenuProps { for: CourseUserMiniEntity; } -const roles = Object.keys(COURSE_USER_ROLES).map((option) => ( - - {COURSE_USER_ROLES[option]} - -)); - const RoleMenu = (props: RoleMenuProps): JSX.Element => { const { for: user } = props; @@ -31,13 +26,19 @@ const RoleMenu = (props: RoleMenuProps): JSX.Element => { const { t } = useTranslation(); - const handleRoleUpdate = (role: CourseUserRoles): void => { + const roles = COURSE_USER_ROLES.map((option) => ( + + {t(roleTranslations[option])} + + )); + + const handleRoleUpdate = (role: CourseUserRole): void => { dispatch(updateUser(user.id, { role })) .then(() => { toast.success( t(translations.changeRoleSuccess, { name: user.name, - role: COURSE_USER_ROLES[role], + role: t(roleTranslations[role]), }), ); }) @@ -45,7 +46,7 @@ const RoleMenu = (props: RoleMenuProps): JSX.Element => { toast.error( t(translations.changeRoleFailure, { name: user.name, - role: COURSE_USER_ROLES[role], + role: t(roleTranslations[role]), error: error.response?.data?.errors ?? '', }), ); @@ -57,9 +58,7 @@ const RoleMenu = (props: RoleMenuProps): JSX.Element => { key={user.id} className="course_user_role" InputProps={{ disableUnderline: true }} - onChange={(e): void => - handleRoleUpdate(e.target.value as CourseUserRoles) - } + onChange={(e): void => handleRoleUpdate(e.target.value as CourseUserRole)} select value={user.role} variant="standard" diff --git a/client/app/bundles/course/users/components/tables/ManageUsersTable/index.tsx b/client/app/bundles/course/users/components/tables/ManageUsersTable/index.tsx index 5b35f998c61..9bc17811721 100644 --- a/client/app/bundles/course/users/components/tables/ManageUsersTable/index.tsx +++ b/client/app/bundles/course/users/components/tables/ManageUsersTable/index.tsx @@ -1,18 +1,13 @@ import { ReactElement, useMemo } from 'react'; import { MenuItem, Typography } from '@mui/material'; -import { - CourseUserMiniEntity, - CourseUserRoles, -} from 'types/course/courseUsers'; +import { CourseUserMiniEntity, CourseUserRole } from 'types/course/courseUsers'; import Note from 'lib/components/core/Note'; import Table, { ColumnTemplate } from 'lib/components/table'; -import { - COURSE_USER_ROLES, - DEFAULT_TABLE_ROWS_PER_PAGE, -} from 'lib/constants/sharedConstants'; +import { DEFAULT_TABLE_ROWS_PER_PAGE } from 'lib/constants/sharedConstants'; import { useAppSelector } from 'lib/hooks/store'; import useTranslation from 'lib/hooks/useTranslation'; +import roleTranslations from 'lib/translations/course/users/roles'; import tableTranslations from 'lib/translations/table'; import { getManageCourseUserPermissions } from '../../../selectors'; @@ -159,7 +154,7 @@ const ManageUsersTable = (props: ManageUsersTableProps): JSX.Element => { cell: (user) => , unless: !manageStaff || !permissions?.canManageCourseUsers, csvDownloadable: true, - csvValue: (value: CourseUserRoles) => COURSE_USER_ROLES[value], + csvValue: (value: CourseUserRole) => t(roleTranslations[value]), }, { id: 'actions', diff --git a/client/app/bundles/course/users/operations.ts b/client/app/bundles/course/users/operations.ts index ef588bc326f..65ddaf45b26 100644 --- a/client/app/bundles/course/users/operations.ts +++ b/client/app/bundles/course/users/operations.ts @@ -1,11 +1,11 @@ import { Operation } from 'store'; import { + CourseStaffRole, CourseUserBasicListData, CourseUserBasicMiniEntity, CourseUserEntity, CourseUserMiniEntity, LearningRateRecordsEntity, - StaffRoles, UpdateCourseUserPatchData, } from 'types/course/courseUsers'; import { @@ -144,7 +144,7 @@ export function updateUser( export function upgradeToStaff( users: CourseUserBasicMiniEntity[], - role: StaffRoles, + role: CourseStaffRole, ): Operation { return async (dispatch) => CourseAPI.users.upgradeToStaff(users, role).then((response) => { diff --git a/client/app/bundles/course/users/pages/UserStatistics/index.tsx b/client/app/bundles/course/users/pages/UserStatistics/index.tsx index 97dd398d902..624083af4be 100644 --- a/client/app/bundles/course/users/pages/UserStatistics/index.tsx +++ b/client/app/bundles/course/users/pages/UserStatistics/index.tsx @@ -1,10 +1,10 @@ import { FC } from 'react'; -import { CourseUserRoles } from 'types/course/courseUsers'; +import { CourseUserRole } from 'types/course/courseUsers'; import LearningRateRecords from './LearningRateRecords'; interface UserStatisticsProps { - userRole: CourseUserRoles; + userRole: CourseUserRole; } const UserStatistics: FC = (props) => { diff --git a/client/app/bundles/course/users/selectors.ts b/client/app/bundles/course/users/selectors.ts index 2518072b1a1..c76d066e7b3 100644 --- a/client/app/bundles/course/users/selectors.ts +++ b/client/app/bundles/course/users/selectors.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { AppState } from 'store'; +import { COURSE_STAFF_ROLES } from 'types/course/courseUsers'; import { SelectionKey } from 'types/store'; import { selectEntity, @@ -7,8 +8,6 @@ import { selectMiniEntity, } from 'utilities/store'; -import { STAFF_ROLES } from 'lib/constants/sharedConstants'; - function getLocalState(state: AppState) { return state.users; } @@ -32,7 +31,10 @@ export function getAllStaffMiniEntities(state: AppState) { return selectMiniEntities( getLocalState(state).users, getLocalState(state).users.ids, - ).filter((entity) => Object.keys(STAFF_ROLES).indexOf(entity.role) > -1); + ).filter( + (entity) => + COURSE_STAFF_ROLES.findIndex((role) => role === entity.role) >= 0, + ); } export function getAllUserOptionMiniEntities(state: AppState) { diff --git a/client/app/bundles/users/components/tables/CoursesTable.tsx b/client/app/bundles/users/components/tables/CoursesTable.tsx index f4c3e24ca9e..05c158dc67b 100644 --- a/client/app/bundles/users/components/tables/CoursesTable.tsx +++ b/client/app/bundles/users/components/tables/CoursesTable.tsx @@ -1,5 +1,4 @@ import { FC } from 'react'; -import { injectIntl, WrappedComponentProps } from 'react-intl'; import { Box, Table, @@ -12,35 +11,33 @@ import { import { UserCourseMiniEntity } from 'types/users'; import Link from 'lib/components/core/Link'; -import { COURSE_USER_ROLES } from 'lib/constants/sharedConstants'; +import useTranslation from 'lib/hooks/useTranslation'; import { formatLongDateTime } from 'lib/moment'; +import roleTranslations from 'lib/translations/course/users/roles'; import tableTranslations from 'lib/translations/table'; -interface Props extends WrappedComponentProps { +interface Props { title: string; courses: UserCourseMiniEntity[]; } -const CoursesTable: FC = ({ title, courses, intl }: Props) => { +const CoursesTable: FC = ({ title, courses }: Props) => { + const { t } = useTranslation(); return ( {title} - - {intl.formatMessage(tableTranslations.enrolledAt)} - - - {intl.formatMessage(tableTranslations.course)} - - {intl.formatMessage(tableTranslations.name)} - {intl.formatMessage(tableTranslations.role)} + {t(tableTranslations.enrolledAt)} + {t(tableTranslations.course)} + {t(tableTranslations.name)} + {t(tableTranslations.role)} - {intl.formatMessage(tableTranslations.level)} + {t(tableTranslations.level)} - {intl.formatMessage(tableTranslations.achievements)} + {t(tableTranslations.achievements)} @@ -71,7 +68,7 @@ const CoursesTable: FC = ({ title, courses, intl }: Props) => { - {COURSE_USER_ROLES[course.courseUserRole]} + {t(roleTranslations[course.courseUserRole])} {course.courseUserLevel} @@ -87,4 +84,4 @@ const CoursesTable: FC = ({ title, courses, intl }: Props) => { ); }; -export default injectIntl(CoursesTable); +export default CoursesTable; diff --git a/client/app/lib/constants/sharedConstants.ts b/client/app/lib/constants/sharedConstants.ts index 8491ea699da..00ae7ed3de7 100644 --- a/client/app/lib/constants/sharedConstants.ts +++ b/client/app/lib/constants/sharedConstants.ts @@ -1,4 +1,3 @@ -import type { CourseUserRoles, StaffRoles } from 'types/course/courseUsers'; import { InstanceUserRoles, RoleRequestRoles, @@ -39,17 +38,6 @@ export const ROLE_REQUEST_ROLES: Record = { administrator: 'Administrator', }; -export const COURSE_USER_ROLES: Record = { - student: 'Student', - teaching_assistant: 'Teaching Assistant', - manager: 'Manager', - owner: 'Owner', - observer: 'Observer', -}; - -export const { student, ...staffRoles } = COURSE_USER_ROLES; -export const STAFF_ROLES: Record = staffRoles; - export const AVAILABLE_LOCALES: { [key in Locale]: string } = { en: 'English', zh: '中文', @@ -94,8 +82,6 @@ export default { USER_ROLES, INSTANCE_USER_ROLES, ROLE_REQUEST_ROLES, - COURSE_USER_ROLES, - STAFF_ROLES, AVAILABLE_LOCALES, }; diff --git a/client/app/lib/translations/course/users/roles.ts b/client/app/lib/translations/course/users/roles.ts new file mode 100644 index 00000000000..a035dcf15be --- /dev/null +++ b/client/app/lib/translations/course/users/roles.ts @@ -0,0 +1,26 @@ +import { defineMessages } from 'react-intl'; + +const translations = defineMessages({ + student: { + id: 'lib.translations.course.users.roles.student', + defaultMessage: 'Student', + }, + teaching_assistant: { + id: 'lib.translations.course.users.roles.teachingAssistant', + defaultMessage: 'Teaching Assistant', + }, + manager: { + id: 'lib.translations.course.users.roles.manager', + defaultMessage: 'Manager', + }, + owner: { + id: 'lib.translations.course.users.roles.owner', + defaultMessage: 'Owner', + }, + observer: { + id: 'lib.translations.course.users.roles.observer', + defaultMessage: 'Observer', + }, +}); + +export default translations; diff --git a/client/app/types/course/courseUsers.ts b/client/app/types/course/courseUsers.ts index 0f964e933fe..2655cedd3e0 100644 --- a/client/app/types/course/courseUsers.ts +++ b/client/app/types/course/courseUsers.ts @@ -18,19 +18,22 @@ export type ManageCourseUsersPermissions = Permissions< | 'canRegisterWithCode' >; -export type CourseUserRoles = - | 'student' - | 'teaching_assistant' - | 'manager' - | 'owner' - | 'observer'; +export const COURSE_STAFF_ROLES = [ + 'teaching_assistant', + 'manager', + 'owner', + 'observer', +] as const; -export type StaffRoles = Exclude; +export const COURSE_USER_ROLES = ['student', ...COURSE_STAFF_ROLES] as const; + +export type CourseUserRole = (typeof COURSE_USER_ROLES)[number]; +export type CourseStaffRole = (typeof COURSE_STAFF_ROLES)[number]; export interface CourseUserShape { id: number; name: string; - role: CourseUserRoles; + role: CourseUserRole; isPhantom: boolean; } @@ -39,12 +42,12 @@ export interface CourseUserBasicListData { name: string; userUrl?: string; imageUrl?: string; - role?: CourseUserRoles; + role?: CourseUserRole; } export interface CourseUserListData extends CourseUserBasicListData { email: string; - role: CourseUserRoles; + role: CourseUserRole; phantom?: boolean; timelineAlgorithm?: TimelineAlgorithm; } @@ -98,7 +101,7 @@ export interface CourseUserFormData { name: string; phantom: boolean; timelineAlgorithm?: TimelineAlgorithm; - role?: CourseUserRoles; + role?: CourseUserRole; } /** @@ -110,7 +113,7 @@ export interface UpdateCourseUserPatchData { phantom?: boolean; timeline_algorithm?: TimelineAlgorithm; reference_timeline_id?: number | null; - role?: CourseUserRoles; + role?: CourseUserRole; }; } diff --git a/client/app/types/course/courses.ts b/client/app/types/course/courses.ts index 083add24cc3..fc0407a2e1f 100644 --- a/client/app/types/course/courses.ts +++ b/client/app/types/course/courses.ts @@ -5,7 +5,7 @@ import { CourseComponentIconName } from 'lib/constants/icons'; import { AchievementBadgeData } from './assessment/assessments'; import { TodoData } from './lesson-plan/todos'; import { AnnouncementData, AnnouncementEntity } from './announcements'; -import { CourseUserListData, CourseUserRoles } from './courseUsers'; +import { CourseUserListData, CourseUserRole } from './courseUsers'; import { NotificationData } from './notifications'; export type CoursePermissions = Permissions<'canCreate' | 'isCurrentUser'>; @@ -100,7 +100,7 @@ export interface CourseLayoutData { userName: string; courseLogoUrl?: string; courseUserName?: string; - courseUserRole?: CourseUserRoles; + courseUserRole?: CourseUserRole; userAvatarUrl?: string; homeRedirectsToLearn?: boolean; sidebar?: SidebarItemData[]; diff --git a/client/app/types/course/enrolRequests.ts b/client/app/types/course/enrolRequests.ts index 4fd4aa3707b..6ebedbcc9a4 100644 --- a/client/app/types/course/enrolRequests.ts +++ b/client/app/types/course/enrolRequests.ts @@ -1,4 +1,4 @@ -import { CourseUserRoles } from './courseUsers'; +import { CourseUserRole } from './courseUsers'; import { TimelineAlgorithm } from './personalTimes'; export interface EnrolRequestMiniEntity { @@ -8,7 +8,7 @@ export interface EnrolRequestMiniEntity { status: string; phantom: boolean; timelineAlgorithm?: TimelineAlgorithm; - role?: CourseUserRoles; + role?: CourseUserRole; createdAt: string; confirmedBy: string | null; confirmedAt: string | null; @@ -20,7 +20,7 @@ export interface EnrolRequestListData { email: string; status: string; phantom: boolean; - role?: CourseUserRoles; + role?: CourseUserRole; timelineAlgorithm?: TimelineAlgorithm; createdAt: string; confirmedBy: string | null; @@ -33,7 +33,7 @@ export interface EnrolRequestListData { course_user: { name: string; phantom: boolean; - role?: CourseUserRoles; + role?: CourseUserRole; timeline_algorithm?: TimelineAlgorithm; }; } diff --git a/client/app/types/users.ts b/client/app/types/users.ts index b8bfc65b96c..6caf4e1b5bb 100644 --- a/client/app/types/users.ts +++ b/client/app/types/users.ts @@ -1,4 +1,4 @@ -import { CourseUserRoles } from './course/courseUsers'; +import { CourseUserRole } from './course/courseUsers'; export type UserRoles = 'normal' | 'administrator'; @@ -52,7 +52,7 @@ export interface UserCourseListData { userCount: number; courseUserId: number; courseUserName: string; - courseUserRole: CourseUserRoles; + courseUserRole: CourseUserRole; courseUserAchievement: number; courseUserLevel: number; type: string; @@ -65,7 +65,7 @@ export interface UserCourseMiniEntity { userCount: number; courseUserId: number; courseUserName: string; - courseUserRole: CourseUserRoles; + courseUserRole: CourseUserRole; courseUserAchievement: number; courseUserLevel: number; type: string; diff --git a/client/locales/en.json b/client/locales/en.json index f561a6b9f08..4678e037958 100644 --- a/client/locales/en.json +++ b/client/locales/en.json @@ -7137,9 +7137,6 @@ "course.users.SelectCourseUser.placeholder": { "defaultMessage": "No course user selected" }, - "course.users.UpgradeToStaff.nameLabel": { - "defaultMessage": "Name" - }, "course.users.UpgradeToStaff.upgradeButton": { "defaultMessage": "Upgrade to staff" }, @@ -7773,6 +7770,21 @@ "lib.translations.course.users.manageUsersHeader": { "defaultMessage": "Manage Users" }, + "lib.translations.course.users.roles.student": { + "defaultMessage": "Student" + }, + "lib.translations.course.users.roles.teachingAssistant": { + "defaultMessage": "Teaching Assistant" + }, + "lib.translations.course.users.roles.observer": { + "defaultMessage": "Observer" + }, + "lib.translations.course.users.roles.manager": { + "defaultMessage": "Manager" + }, + "lib.translations.course.users.roles.owner": { + "defaultMessage": "Owner" + }, "lib.translations.experimental": { "defaultMessage": "Experimental" }, diff --git a/client/locales/ko.json b/client/locales/ko.json index 18566a0af62..5e73a3047ce 100644 --- a/client/locales/ko.json +++ b/client/locales/ko.json @@ -7124,9 +7124,6 @@ "course.users.SelectCourseUser.placeholder": { "defaultMessage": "선택된 과정 사용자가 없습니다." }, - "course.users.UpgradeToStaff.nameLabel": { - "defaultMessage": "이름" - }, "course.users.UpgradeToStaff.upgradeButton": { "defaultMessage": "직원으로 승격" }, @@ -7760,6 +7757,21 @@ "lib.translations.course.users.manageUsersHeader": { "defaultMessage": "사용자 관리" }, + "lib.translations.course.users.roles.student": { + "defaultMessage": "학생" + }, + "lib.translations.course.users.roles.teachingAssistant": { + "defaultMessage": "조교" + }, + "lib.translations.course.users.roles.observer": { + "defaultMessage": "관찰자" + }, + "lib.translations.course.users.roles.manager": { + "defaultMessage": "관리자" + }, + "lib.translations.course.users.roles.owner": { + "defaultMessage": "소유자" + }, "lib.translations.experimental": { "defaultMessage": "실험적" }, diff --git a/client/locales/zh.json b/client/locales/zh.json index ad6ff630255..ab005dbc724 100644 --- a/client/locales/zh.json +++ b/client/locales/zh.json @@ -7118,9 +7118,6 @@ "course.users.SelectCourseUser.placeholder": { "defaultMessage": "未选择课程用户" }, - "course.users.UpgradeToStaff.nameLabel": { - "defaultMessage": "姓名" - }, "course.users.UpgradeToStaff.upgradeButton": { "defaultMessage": "升级为助教" }, @@ -7754,6 +7751,21 @@ "lib.translations.course.users.manageUsersHeader": { "defaultMessage": "管理用户" }, + "lib.translations.course.users.roles.student": { + "defaultMessage": "学生" + }, + "lib.translations.course.users.roles.teachingAssistant": { + "defaultMessage": "助教" + }, + "lib.translations.course.users.roles.observer": { + "defaultMessage": "观察者" + }, + "lib.translations.course.users.roles.manager": { + "defaultMessage": "管理员" + }, + "lib.translations.course.users.roles.owner": { + "defaultMessage": "拥有者" + }, "lib.translations.experimental": { "defaultMessage": "实验性的" },