Skip to content

Commit 6457a41

Browse files
authored
Update user edit page and user table for picture login (#14573)
* Update utility to use constants * Add ambiguous key for iconName * Update user edit page to show picture password and disable learner role * Disable reset password when piclo enabled * Create basic test for UserEditPage changes
1 parent 1993eba commit 6457a41

9 files changed

Lines changed: 591 additions & 27 deletions

File tree

kolibri/plugins/facility/frontend/views/UserEditPage.vue

Lines changed: 114 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
<form
1010
v-if="!loading"
1111
class="form"
12+
data-testid="form"
1213
@submit.prevent="submitForm"
1314
>
14-
<h1>
15-
{{ $tr('editUserDetailsHeader') }}
16-
</h1>
17-
1815
<section>
16+
<h1>
17+
{{ $tr('editUserDetailsHeader') }}
18+
</h1>
19+
1920
<FullNameTextbox
2021
ref="fullNameTextbox"
2122
:autofocus="true"
@@ -35,6 +36,17 @@
3536
:errors.sync="caughtErrors"
3637
/>
3738

39+
<section
40+
v-if="showPicturePasswordSection"
41+
class="picture-password-section"
42+
data-testid="picture-password-section"
43+
>
44+
<h3>
45+
{{ picturePassword$() }}
46+
</h3>
47+
<UserPicturePassword :picturePassword="userPicturePassword" />
48+
</section>
49+
3850
<template v-if="editingSuperAdmin">
3951
<h2 class="header user-type">
4052
{{ coreString('userTypeLabel') }}
@@ -56,11 +68,31 @@
5668
<template v-else>
5769
<KSelect
5870
v-model="typeSelected"
59-
class="select"
71+
:class="{ select: true, 'learner-role-disabled': disableLearnerRoleOption }"
6072
:disabled="formDisabled"
6173
:label="coreString('userTypeLabel')"
6274
:options="userTypeOptions"
75+
data-testid="user-type"
6376
/>
77+
<div
78+
v-if="disableLearnerRoleOption"
79+
class="learner-limit-message"
80+
data-testid="learner-limit-message"
81+
>
82+
<KIcon
83+
class="icon"
84+
icon="warningIncomplete"
85+
/>
86+
<p :style="{ color: $themeTokens.annotation }">
87+
{{ learnerCreationDisabled$() }}
88+
<KButton
89+
appearance="basic-link"
90+
:text="coreString('learnMoreAction')"
91+
data-testid="learner-limit-modal-trigger"
92+
@click="isLearnerLimitModalOpen = true"
93+
/>
94+
</p>
95+
</div>
6496

6597
<fieldset
6698
v-if="coachIsSelected"
@@ -128,6 +160,11 @@
128160
</KButtonGroup>
129161
</div>
130162
</form>
163+
<LearnerLimitReachedModal
164+
v-if="isLearnerLimitModalOpen"
165+
data-testid="learner-limit-modal"
166+
@close="isLearnerLimitModalOpen = false"
167+
/>
131168
</KPageContainer>
132169
</ImmersivePage>
133170

@@ -156,7 +193,10 @@
156193
import useSnackbar from 'kolibri/composables/useSnackbar';
157194
import { handleApiError } from 'kolibri/utils/appError';
158195
import useFacility from 'kolibri-common/composables/useFacility';
196+
import { picturePasswordStrings } from 'kolibri-common/strings/picturePasswords';
197+
import UserPicturePassword from 'kolibri-common/components/UserPicturePassword.vue';
159198
import IdentifierTextbox from './users/sidePanels/UserCreate/IdentifierTextbox.vue';
199+
import LearnerLimitReachedModal from './LearnerLimitReachedModal.vue';
160200
161201
export default {
162202
name: 'UserEditPage',
@@ -174,19 +214,29 @@
174214
UsernameTextbox,
175215
UserTypeDisplay,
176216
ExtraDemographics,
217+
LearnerLimitReachedModal,
218+
UserPicturePassword,
177219
},
178220
mixins: [commonCoreStrings],
179221
setup() {
180222
const { createSnackbar } = useSnackbar();
181223
const { currentUserId, logout } = useUser();
182-
const { updateFacilityConfig, facilityConfig } = useFacility();
224+
const { updateFacilityConfig, selectedFacility, facilityConfig } = useFacility();
225+
const { picturePassword$, learnerCreationDisabled$ } = picturePasswordStrings;
226+
183227
return {
228+
// state
229+
currentUserId,
230+
facilityConfig,
231+
selectedFacility,
232+
// actions
184233
logout,
185234
createSnackbar,
186-
currentUserId,
187235
updateFacilityConfig,
188-
facilityConfig,
189236
handleApiError,
237+
// strings
238+
learnerCreationDisabled$,
239+
picturePassword$,
190240
};
191241
},
192242
data() {
@@ -207,6 +257,8 @@
207257
userCopy: {},
208258
caughtErrors: [],
209259
status: '',
260+
userPicturePassword: null,
261+
isLearnerLimitModalOpen: false,
210262
};
211263
},
212264
computed: {
@@ -228,6 +280,7 @@
228280
{
229281
label: this.coreString('learnerLabel'),
230282
value: UserKinds.LEARNER,
283+
disabled: this.disableLearnerRoleOption,
231284
},
232285
{
233286
label: this.coreString('coachLabel'),
@@ -256,6 +309,23 @@
256309
257310
return '';
258311
},
312+
picturePasswordEnabled() {
313+
return Boolean(this.facilityConfig.picture_password_settings);
314+
},
315+
disableLearnerRoleOption() {
316+
return (
317+
this.picturePasswordEnabled &&
318+
this.selectedFacility.picture_passwords_exhausted &&
319+
this.kind !== UserKinds.LEARNER
320+
);
321+
},
322+
showPicturePasswordSection() {
323+
return (
324+
this.picturePasswordEnabled &&
325+
this.kind === UserKinds.LEARNER &&
326+
Boolean(this.userPicturePassword)
327+
);
328+
},
259329
newUserKind() {
260330
const { value } = this.typeSelected;
261331
if (value === UserKinds.COACH && this.classCoachIsSelected) {
@@ -278,6 +348,7 @@
278348
this.gender = user.gender;
279349
this.birthYear = user.birth_year;
280350
this.extraDemographics = user.extra_demographics;
351+
this.userPicturePassword = user.picture_password;
281352
this.setKind(user);
282353
this.makeCopyOfUser(user);
283354
});
@@ -358,6 +429,11 @@
358429
submitForm() {
359430
this.formSubmitted = true;
360431
432+
if (this.disableLearnerRoleOption && this.newUserKind === UserKinds.LEARNER) {
433+
this.isLearnerLimitModalOpen = true;
434+
return;
435+
}
436+
361437
if (!this.formIsValid) {
362438
return this.focusOnInvalidField();
363439
}
@@ -434,7 +510,7 @@
434510
435511
.coach-selector {
436512
padding: 0;
437-
margin: 0;
513+
margin: 0 0 10px;
438514
border: 0;
439515
}
440516
@@ -463,6 +539,35 @@
463539
}
464540
}
465541
542+
.learner-role-disabled {
543+
margin-bottom: 4px;
544+
}
545+
546+
.learner-limit-message {
547+
display: flex;
548+
align-items: baseline;
549+
margin-bottom: 12px;
550+
551+
.icon {
552+
flex-shrink: 0;
553+
margin-top: 2px;
554+
font-size: 14px;
555+
}
556+
557+
p {
558+
margin: 0 0 0 4px;
559+
font-size: 14px;
560+
}
561+
}
562+
563+
.picture-password-section {
564+
margin: 12px 0 24px;
565+
566+
h3 {
567+
margin-bottom: 8px;
568+
}
569+
}
570+
466571
.narrow-container {
467572
max-width: 500px;
468573
margin: auto;

0 commit comments

Comments
 (0)