|
9 | 9 | <form |
10 | 10 | v-if="!loading" |
11 | 11 | class="form" |
| 12 | + data-testid="form" |
12 | 13 | @submit.prevent="submitForm" |
13 | 14 | > |
14 | | - <h1> |
15 | | - {{ $tr('editUserDetailsHeader') }} |
16 | | - </h1> |
17 | | - |
18 | 15 | <section> |
| 16 | + <h1> |
| 17 | + {{ $tr('editUserDetailsHeader') }} |
| 18 | + </h1> |
| 19 | + |
19 | 20 | <FullNameTextbox |
20 | 21 | ref="fullNameTextbox" |
21 | 22 | :autofocus="true" |
|
35 | 36 | :errors.sync="caughtErrors" |
36 | 37 | /> |
37 | 38 |
|
| 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 | + |
38 | 50 | <template v-if="editingSuperAdmin"> |
39 | 51 | <h2 class="header user-type"> |
40 | 52 | {{ coreString('userTypeLabel') }} |
|
56 | 68 | <template v-else> |
57 | 69 | <KSelect |
58 | 70 | v-model="typeSelected" |
59 | | - class="select" |
| 71 | + :class="{ select: true, 'learner-role-disabled': disableLearnerRoleOption }" |
60 | 72 | :disabled="formDisabled" |
61 | 73 | :label="coreString('userTypeLabel')" |
62 | 74 | :options="userTypeOptions" |
| 75 | + data-testid="user-type" |
63 | 76 | /> |
| 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> |
64 | 96 |
|
65 | 97 | <fieldset |
66 | 98 | v-if="coachIsSelected" |
|
128 | 160 | </KButtonGroup> |
129 | 161 | </div> |
130 | 162 | </form> |
| 163 | + <LearnerLimitReachedModal |
| 164 | + v-if="isLearnerLimitModalOpen" |
| 165 | + data-testid="learner-limit-modal" |
| 166 | + @close="isLearnerLimitModalOpen = false" |
| 167 | + /> |
131 | 168 | </KPageContainer> |
132 | 169 | </ImmersivePage> |
133 | 170 |
|
|
156 | 193 | import useSnackbar from 'kolibri/composables/useSnackbar'; |
157 | 194 | import { handleApiError } from 'kolibri/utils/appError'; |
158 | 195 | import useFacility from 'kolibri-common/composables/useFacility'; |
| 196 | + import { picturePasswordStrings } from 'kolibri-common/strings/picturePasswords'; |
| 197 | + import UserPicturePassword from 'kolibri-common/components/UserPicturePassword.vue'; |
159 | 198 | import IdentifierTextbox from './users/sidePanels/UserCreate/IdentifierTextbox.vue'; |
| 199 | + import LearnerLimitReachedModal from './LearnerLimitReachedModal.vue'; |
160 | 200 |
|
161 | 201 | export default { |
162 | 202 | name: 'UserEditPage', |
|
174 | 214 | UsernameTextbox, |
175 | 215 | UserTypeDisplay, |
176 | 216 | ExtraDemographics, |
| 217 | + LearnerLimitReachedModal, |
| 218 | + UserPicturePassword, |
177 | 219 | }, |
178 | 220 | mixins: [commonCoreStrings], |
179 | 221 | setup() { |
180 | 222 | const { createSnackbar } = useSnackbar(); |
181 | 223 | const { currentUserId, logout } = useUser(); |
182 | | - const { updateFacilityConfig, facilityConfig } = useFacility(); |
| 224 | + const { updateFacilityConfig, selectedFacility, facilityConfig } = useFacility(); |
| 225 | + const { picturePassword$, learnerCreationDisabled$ } = picturePasswordStrings; |
| 226 | +
|
183 | 227 | return { |
| 228 | + // state |
| 229 | + currentUserId, |
| 230 | + facilityConfig, |
| 231 | + selectedFacility, |
| 232 | + // actions |
184 | 233 | logout, |
185 | 234 | createSnackbar, |
186 | | - currentUserId, |
187 | 235 | updateFacilityConfig, |
188 | | - facilityConfig, |
189 | 236 | handleApiError, |
| 237 | + // strings |
| 238 | + learnerCreationDisabled$, |
| 239 | + picturePassword$, |
190 | 240 | }; |
191 | 241 | }, |
192 | 242 | data() { |
|
207 | 257 | userCopy: {}, |
208 | 258 | caughtErrors: [], |
209 | 259 | status: '', |
| 260 | + userPicturePassword: null, |
| 261 | + isLearnerLimitModalOpen: false, |
210 | 262 | }; |
211 | 263 | }, |
212 | 264 | computed: { |
|
228 | 280 | { |
229 | 281 | label: this.coreString('learnerLabel'), |
230 | 282 | value: UserKinds.LEARNER, |
| 283 | + disabled: this.disableLearnerRoleOption, |
231 | 284 | }, |
232 | 285 | { |
233 | 286 | label: this.coreString('coachLabel'), |
|
256 | 309 |
|
257 | 310 | return ''; |
258 | 311 | }, |
| 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 | + }, |
259 | 329 | newUserKind() { |
260 | 330 | const { value } = this.typeSelected; |
261 | 331 | if (value === UserKinds.COACH && this.classCoachIsSelected) { |
|
278 | 348 | this.gender = user.gender; |
279 | 349 | this.birthYear = user.birth_year; |
280 | 350 | this.extraDemographics = user.extra_demographics; |
| 351 | + this.userPicturePassword = user.picture_password; |
281 | 352 | this.setKind(user); |
282 | 353 | this.makeCopyOfUser(user); |
283 | 354 | }); |
|
358 | 429 | submitForm() { |
359 | 430 | this.formSubmitted = true; |
360 | 431 |
|
| 432 | + if (this.disableLearnerRoleOption && this.newUserKind === UserKinds.LEARNER) { |
| 433 | + this.isLearnerLimitModalOpen = true; |
| 434 | + return; |
| 435 | + } |
| 436 | +
|
361 | 437 | if (!this.formIsValid) { |
362 | 438 | return this.focusOnInvalidField(); |
363 | 439 | } |
|
434 | 510 |
|
435 | 511 | .coach-selector { |
436 | 512 | padding: 0; |
437 | | - margin: 0; |
| 513 | + margin: 0 0 10px; |
438 | 514 | border: 0; |
439 | 515 | } |
440 | 516 |
|
|
463 | 539 | } |
464 | 540 | } |
465 | 541 |
|
| 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 | +
|
466 | 571 | .narrow-container { |
467 | 572 | max-width: 500px; |
468 | 573 | margin: auto; |
|
0 commit comments