Skip to content

Commit 43e0264

Browse files
committed
Improving add user container appearance and exercise author/admin edit box.
1 parent 5101da7 commit 43e0264

11 files changed

Lines changed: 93 additions & 57 deletions

File tree

src/components/Exercises/EditExerciseUsers/EditExerciseUsers.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,17 @@ const ROLES_FILTER = knownRoles.filter(isSupervisorRole);
1717
const EditExerciseUsers = ({ exercise, instanceId }) => {
1818
return (
1919
<Box
20-
type="warning"
21-
title={<FormattedMessage id="app.editExercise.manageUsers" defaultMessage="Manage related users" />}
22-
noPadding>
20+
type={exercise.permissionHints.changeAuthor || exercise.permissionHints.updateAdmins ? 'warning' : undefined}
21+
title={<FormattedMessage id="app.editExercise.authorAndAdminsTitle" defaultMessage="Author and Administrators" />}
22+
noPadding
23+
unlimitedHeight>
2324
<>
2425
<Table className="border-bottom mb-1">
2526
<tbody>
2627
<tr>
27-
<td className="icon-col">
28-
<AuthorIcon fixedWidth gapLeft={2} />
29-
</td>
28+
<th className="icon-col">
29+
<AuthorIcon fixedWidth gapLeft={2} className="text-success" />
30+
</th>
3031
<th>
3132
<FormattedMessage id="generic.author" defaultMessage="Author" />:
3233
</th>
@@ -35,9 +36,9 @@ const EditExerciseUsers = ({ exercise, instanceId }) => {
3536
</td>
3637
</tr>
3738
<tr>
38-
<td className="icon-col">
39+
<th className="icon-col">
3940
<AdminIcon fixedWidth gapLeft={2} />
40-
</td>
41+
</th>
4142
<th>
4243
<FormattedMessage id="app.exercise.admins" defaultMessage="Administrators" />:
4344
<Explanation id="admins">
@@ -67,7 +68,7 @@ const EditExerciseUsers = ({ exercise, instanceId }) => {
6768
</Table>
6869

6970
{(exercise.permissionHints.changeAuthor || exercise.permissionHints.updateAdmins) && (
70-
<div className="m-3 mt-1">
71+
<div className="m-3">
7172
<AddUserContainer
7273
instanceId={instanceId}
7374
id={`add-exercise-user-${exercise.id}`}

src/components/Groups/AddStudent/AddStudent.js

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
22
import PropTypes from 'prop-types';
33
import ImmutablePropTypes from 'react-immutable-proptypes';
44
import { FormattedMessage } from 'react-intl';
5-
import { Modal } from 'react-bootstrap';
5+
import { Modal, Row, Col } from 'react-bootstrap';
66
import { lruMemoize } from 'reselect';
77

88
import ResourceRenderer from '../../helpers/ResourceRenderer';
@@ -11,7 +11,7 @@ import LeaveJoinGroupButtonContainer from '../../../containers/LeaveJoinGroupBut
1111
import AddUserContainer from '../../../containers/AddUserContainer';
1212
import Button from '../../widgets/TheButton';
1313
import InsetPanel from '../../widgets/InsetPanel';
14-
import Icon, { SuccessIcon } from '../../icons';
14+
import Icon, { InfoIcon, SuccessIcon } from '../../icons';
1515

1616
import { arrayToObject, EMPTY_ARRAY } from '../../../helpers/common.js';
1717
import Callout from '../../widgets/Callout/Callout.js';
@@ -90,21 +90,32 @@ const AddStudent = ({ groups, groupsAccessor, groupId, instanceId, canSearch = f
9090

9191
{inviteUser && (
9292
<>
93-
<hr />
94-
<div className="text-center">
95-
<Button
96-
size="sm"
97-
variant="primary"
98-
onClick={() => {
99-
setUserInvited(false);
100-
setMatchingUsers(EMPTY_ARRAY);
101-
setDialogOpen(true);
102-
}}>
103-
<Icon icon="hand-holding-heart" gapRight={2} />
104-
<FormattedMessage id="app.addStudent.inviteButton" defaultMessage="Invite to Register" />
105-
...
106-
</Button>
107-
</div>
93+
<InsetPanel className="mb-1 mt-3">
94+
<Row>
95+
<Col xs={12} sm>
96+
<small className="text-secondary">
97+
<InfoIcon gapRight={2} />
98+
<FormattedMessage
99+
id="app.addStudent.inviteExplanation"
100+
defaultMessage="If the student you want to add is not registered yet, you can send them an invitation link. It will allow them to register and automatically join this group."
101+
/>
102+
</small>
103+
</Col>
104+
<Col xs={false} sm="auto" className="align-self-center">
105+
<Button
106+
variant="primary"
107+
onClick={() => {
108+
setUserInvited(false);
109+
setMatchingUsers(EMPTY_ARRAY);
110+
setDialogOpen(true);
111+
}}>
112+
<Icon icon="hand-holding-heart" gapRight={2} />
113+
<FormattedMessage id="app.addStudent.inviteButton" defaultMessage="Invite to Register" />
114+
...
115+
</Button>
116+
</Col>
117+
</Row>
118+
</InsetPanel>
108119

109120
<Modal show={dialogOpen} backdrop="static" onHide={() => setDialogOpen(false)} size="xl">
110121
<Modal.Header closeButton>

src/components/Users/UsersListItem/UsersListItem.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const createEmailLink = user => {
1717
const UsersListItem = ({ user, emailColumn = false, createdAtColumn = false, createActions, loggedUserId = '' }) =>
1818
user ? (
1919
<tr>
20-
<td>
20+
<td className="px-1 text-secondary">
2121
{user.privateData && <UserRoleIcon showTooltip tooltipId={`role-${user.id}`} role={user.privateData.role} />}
2222
</td>
2323
<td>
@@ -48,7 +48,7 @@ const UsersListItem = ({ user, emailColumn = false, createdAtColumn = false, cre
4848
</td>
4949
)}
5050

51-
{createActions && <td className="text-end">{createActions(user)}</td>}
51+
{createActions && <td className="text-end pe-1">{createActions(user)}</td>}
5252
</tr>
5353
) : (
5454
<tr>

src/components/helpers/SimpleTextSearch/SimpleTextSearch.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,26 @@ class SimpleTextSearch extends Component {
1717
}
1818

1919
render() {
20-
const { id = 'simpleTextSearch', onSubmit, isLoading, hasFailed } = this.props;
20+
const { id = 'simpleTextSearch', label, onSubmit, isLoading, hasFailed, placeholder = null } = this.props;
2121

2222
return (
2323
<form>
2424
<FormGroup className="mb-3">
2525
<FormLabel htmlFor={id}>
26-
<FormattedMessage id="generic.search" defaultMessage="Search" />:
26+
{label || (
27+
<>
28+
<FormattedMessage id="generic.search" defaultMessage="Search" />:
29+
</>
30+
)}
2731
</FormLabel>
2832
<InputGroup>
29-
<FormControl id={id} type="text" value={this.state.query} onChange={this.queryChangeHandler} />
33+
<FormControl
34+
id={id}
35+
type="text"
36+
value={this.state.query}
37+
onChange={this.queryChangeHandler}
38+
placeholder={placeholder}
39+
/>
3040
<Button
3141
variant="secondary"
3242
type="submit"
@@ -53,6 +63,8 @@ SimpleTextSearch.propTypes = {
5363
onSubmit: PropTypes.func.isRequired,
5464
isLoading: PropTypes.bool,
5565
hasFailed: PropTypes.bool,
66+
placeholder: PropTypes.string,
67+
label: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]),
5668
};
5769

5870
export default SimpleTextSearch;

src/containers/AddUserContainer/AddUserContainer.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import ImmutablePropTypes from 'react-immutable-proptypes';
44
import { connect } from 'react-redux';
55
import { lruMemoize } from 'reselect';
6+
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
67

78
import PaginationContainer from '../PaginationContainer';
89
import SimpleTextSearch from '../../components/helpers/SimpleTextSearch';
@@ -11,7 +12,6 @@ import UsersList from '../../components/Users/UsersList';
1112
import { loggedInUserSelector } from '../../redux/selectors/users.js';
1213
import { getPaginationFilters } from '../../redux/selectors/pagination.js';
1314
import { EMPTY_OBJ } from '../../helpers/common.js';
14-
import { FormattedMessage } from 'react-intl';
1515

1616
const LIMITS = [10];
1717

@@ -26,7 +26,14 @@ const submitHandler = lruMemoize((rolesFilter, setFilters) => search => {
2626
return setFilters(filters);
2727
});
2828

29-
const AddUserContainer = ({ id, filters, createActions, user, rolesFilter = null }) => (
29+
const messages = defineMessages({
30+
emptyQueryPlaceholder: {
31+
id: 'app.addUserContainer.emptyQuery',
32+
defaultMessage: 'Enter a name or its part...',
33+
},
34+
});
35+
36+
const AddUserContainer = ({ id, filters, createActions, user, rolesFilter = null, intl: { formatMessage } }) => (
3037
<ResourceRenderer resource={user}>
3138
{user => (
3239
<PaginationContainer
@@ -35,19 +42,18 @@ const AddUserContainer = ({ id, filters, createActions, user, rolesFilter = null
3542
defaultOrderBy="name"
3643
limits={LIMITS}
3744
hideAllItems={!filters.search}
38-
hideAllMessage={
39-
<div className="text-body-secondary text-center">
40-
<FormattedMessage
41-
id="app.addUserContainer.emptyQuery"
42-
defaultMessage="No results. Enter a search query..."
43-
/>
44-
</div>
45-
}
45+
hideAllMessage={null}
4646
filtersCreator={(filters, setFilters) => (
4747
<SimpleTextSearch
4848
query={filters.search || ''}
4949
isLoading={setFilters === null}
5050
onSubmit={submitHandler(rolesFilter, setFilters)}
51+
label={
52+
<>
53+
<FormattedMessage id="app.addUserContainer.searchUser" defaultMessage="Search user" />:
54+
</>
55+
}
56+
placeholder={formatMessage(messages.emptyQueryPlaceholder)}
5157
/>
5258
)}>
5359
{({ data }) => {
@@ -65,11 +71,12 @@ AddUserContainer.propTypes = {
6571
rolesFilter: PropTypes.array,
6672
user: ImmutablePropTypes.map.isRequired,
6773
filters: PropTypes.object.isRequired,
74+
intl: PropTypes.object,
6875
};
6976

7077
export default connect((state, { id }) => {
7178
return {
7279
user: loggedInUserSelector(state),
7380
filters: getPaginationFilters(id)(state) || EMPTY_OBJ,
7481
};
75-
})(AddUserContainer);
82+
})(injectIntl(AddUserContainer));

src/locales/cs.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@
5050
"app.addStudent.inviteDialog.explain": "Pozvánka bude zaslána uživateli na danou mailovou adresu. Uživatel obdrží odkaz pro registraci lokálním účtem. Detaily uživatelského profilu (jméno a email) vyplňte pečlivě, uživatel nebude mít možnost je změnit. Volitelně můžete také vybrat seznam skupin, do kterých bude uživatel přidán hned po registraci.",
5151
"app.addStudent.inviteDialog.title": "Poslat pozvánku do ReCodExu",
5252
"app.addStudent.inviteDialog.userInvited": "Pozvánka byla odeslána na zvolenou adresu. Uživatel musí pozvánku přijmout, aby dokončil registrační proces.",
53-
"app.addUserContainer.emptyQuery": "Žádné výsledky. Zadejte vyhledávací dotaz...",
53+
"app.addStudent.inviteExplanation": "Pokud student, kterého chcete přidat, ještě není registrován, můžete mu poslat pozvánku. Ta mu umožní se zaregistrovat a automaticky se připojit do této skupiny.",
54+
"app.addUserContainer.emptyQuery": "Zadejte jméno nebo jeho část...",
55+
"app.addUserContainer.searchUser": "Vyhledat uživatele",
5456
"app.allowUserButton.confirmAllow": "Uživatel mohl být zablokován z dobrého důvodu. Opravdu si přejete povolit účet?",
5557
"app.allowUserButton.confirmDisallow": "Pokud zakážete tento uživatelský účet, uživatel nebude moci provést žádnou operaci ani vidět žádná data. Opravdu si přejete účet zakázat?",
5658
"app.apiErrorCodes.400": "Špatný požadavek",
@@ -394,11 +396,11 @@
394396
"app.editExercise.addAdminButton": "Zařadit uživatele mezi administrátory úlohy",
395397
"app.editExercise.archiveExplain": "Archivované úlohy se nezobrazují v běžných výpisech, není možné je měnit a není možné je zadávat do skupin. Archivní status nemá žádný vliv na již existující zadané úlohy. Archivace úloh nemá přímou vazbu na archivaci skupin, ale oba principy spojuje podobný účel.",
396398
"app.editExercise.archiveTitle": "Změnit archivační status",
399+
"app.editExercise.authorAndAdminsTitle": "Autor a administrátoři",
397400
"app.editExercise.deleteExercise": "Smazat úlohu",
398401
"app.editExercise.deleteExerciseWarning": "Smazání úlohy nebude mít žádný vliv na již zadané instance této úlohy ani jejich řešení, zadané úlohy pouze již nebude možné aktualizovat. Smazání ovšem efektivně odebre tuto úlohu ze všech domovských skupin.",
399402
"app.editExercise.editConfig": "Konfigurace úlohy",
400403
"app.editExercise.editTags": "Editace nálepek",
401-
"app.editExercise.manageUsers": "Spravovat přidružené uživatele",
402404
"app.editExercise.removeAdminButton": "Odebrat uživatele ze seznamu administrátorů úlohy",
403405
"app.editExercise.setAuthorButton": "Nahradit stávajícího autora úlohy tímto uživatelem",
404406
"app.editExercise.title": "Upravit základní nastavení úlohy",

src/locales/en.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@
5050
"app.addStudent.inviteDialog.explain": "An invitation will be sent to the user at given email address. The user will receive a link for registration as a local user. User profile details (name and email) must be filled in correctly, since the user will not be able to modify them. Optionally, you may select a list of groups to which the user will be assigned immediately after registration.",
5151
"app.addStudent.inviteDialog.title": "Send invitation to ReCodEx",
5252
"app.addStudent.inviteDialog.userInvited": "An invitation was sent to the specified email address. The user must accept it to complete the registration process.",
53-
"app.addUserContainer.emptyQuery": "No results. Enter a search query...",
53+
"app.addStudent.inviteExplanation": "If the student you want to add is not registered yet, you can send them an invitation link. It will allow them to register and automatically join this group.",
54+
"app.addUserContainer.emptyQuery": "Enter a name or its part...",
55+
"app.addUserContainer.searchUser": "Search user",
5456
"app.allowUserButton.confirmAllow": "The user may have been disabled for a reason. Do you really wish to enable the account?",
5557
"app.allowUserButton.confirmDisallow": "If you disable the account, the user will not be able to perform any operation nor access any data. Do you wish to disable it?",
5658
"app.apiErrorCodes.400": "Bad request",
@@ -394,11 +396,11 @@
394396
"app.editExercise.addAdminButton": "Make the user an exercise admin",
395397
"app.editExercise.archiveExplain": "Archived exercises are not listed by default, cannot be modified, and cannot be assigned. Exercise archive status has no impact on existing assignments. The archiving of exercises is not directly related to group archiving (although they share a similar purpose).",
396398
"app.editExercise.archiveTitle": "Change archived status",
399+
"app.editExercise.authorAndAdminsTitle": "Author and Administrators",
397400
"app.editExercise.deleteExercise": "Delete the exercise",
398401
"app.editExercise.deleteExerciseWarning": "Deletion of an exercise will not affect any existing assignments nor their solutions, except they could not be synchronized anymore. However, the deletion will effectively remove the exercise from all groups of residence.",
399402
"app.editExercise.editConfig": "Exercise Configuration",
400403
"app.editExercise.editTags": "Edit Tags",
401-
"app.editExercise.manageUsers": "Manage related users",
402404
"app.editExercise.removeAdminButton": "Remove the user from exercise admins",
403405
"app.editExercise.setAuthorButton": "Make this user an author of the exercise (replacing current author)",
404406
"app.editExercise.title": "Change Basic Exercise Settings",

src/locales/whitelist_cs.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
"app.homepage.githubLink",
110110
"app.homepage.title",
111111
"app.instancesTable.admin",
112+
"app.markdownTextArea.showAceEditor",
112113
"app.navigation.pipeline",
113114
"app.passwordStrength.ok",
114115
"app.passwordStrength.unknown",
@@ -160,6 +161,5 @@
160161
"app.systemMessagesList.text",
161162
"generic.detail",
162163
"generic.email",
163-
"generic.role",
164-
"app.markdownTextArea.showAceEditor"
165-
]
164+
"generic.role"
165+
]

src/pages/EditExercise/EditExercise.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ class EditExercise extends Component {
157157
<Col lg={6}>
158158
<AttachmentFilesTableContainer exercise={exercise} />
159159

160+
{safeGet(loggedUser, ['privateData', 'instancesIds', 0]) && (
161+
<EditExerciseUsers
162+
exercise={exercise}
163+
instanceId={safeGet(loggedUser, ['privateData', 'instancesIds', 0])}
164+
/>
165+
)}
166+
160167
<ExerciseGroups
161168
showButtons
162169
groupsIds={exercise.groupsIds}
@@ -170,14 +177,6 @@ class EditExercise extends Component {
170177
<Box title={<FormattedMessage id="app.editExercise.editTags" defaultMessage="Edit Tags" />}>
171178
<ExercisesTagsEditContainer exerciseId={exercise.id} />
172179
</Box>
173-
174-
{exercise.permissionHints.changeAuthor &&
175-
safeGet(loggedUser, ['privateData', 'instancesIds', 0]) && (
176-
<EditExerciseUsers
177-
exercise={exercise}
178-
instanceId={safeGet(loggedUser, ['privateData', 'instancesIds', 0])}
179-
/>
180-
)}
181180
</Col>
182181
</Row>
183182
)}

src/pages/GroupInfo/GroupInfo.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ class GroupInfo extends Component {
229229

230230
{isAdminOrSuperadmin && !data.archived && (
231231
<Box
232+
unlimitedHeight
232233
title={
233234
<FormattedMessage id="app.group.adminsView.addSupervisor" defaultMessage="Add Supervisor" />
234235
}>

0 commit comments

Comments
 (0)