diff --git a/packages/frontend/.lint-todo b/packages/frontend/.lint-todo
index d72ccbd208..e589a5fbd0 100644
--- a/packages/frontend/.lint-todo
+++ b/packages/frontend/.lint-todo
@@ -1,8 +1,4 @@
add|ember-template-lint|no-at-ember-render-modifiers|2|61|2|61|23cd787c79c34a628dadb6e96dd4004d42eebb79|1731542400000|1762646400000|1793750400000|app/components/new-directory-user.hbs
-add|ember-template-lint|no-at-ember-render-modifiers|4|2|4|2|23cd787c79c34a628dadb6e96dd4004d42eebb79|1731542400000|1762646400000|1793750400000|app/components/user-profile-bio.hbs
-add|ember-template-lint|no-at-ember-render-modifiers|5|2|5|2|f53982efe02d2bef9e7f12b5b862288c594579c2|1731542400000|1762646400000|1793750400000|app/components/user-profile-bio.hbs
-add|ember-template-lint|no-at-ember-render-modifiers|5|2|5|2|23cd787c79c34a628dadb6e96dd4004d42eebb79|1731542400000|1762646400000|1793750400000|app/components/user-profile-roles.hbs
-add|ember-template-lint|no-at-ember-render-modifiers|6|2|6|2|e5120f87b74c5ae8e4c76b9089e0b4a4504c6e3c|1731542400000|1762646400000|1793750400000|app/components/user-profile-roles.hbs
add|ember-template-lint|no-at-ember-render-modifiers|3|2|3|2|1fb0566922ce4f066916e5e2931f650f69d7cfba|1731542400000|1762646400000|1793750400000|app/components/visualizer-program-year-objectives.hbs
add|ember-template-lint|no-at-ember-render-modifiers|4|2|4|2|38e65b45b56fdfd4160d3b0884114b6643e3a036|1731542400000|1762646400000|1793750400000|app/components/visualizer-program-year-objectives.hbs
add|ember-template-lint|no-at-ember-render-modifiers|3|2|3|2|cb6d7acb9879902b89ad1575846d290a564ffbae|1731542400000|1762646400000|1793750400000|app/components/learner-group/instructor-manager.hbs
diff --git a/packages/frontend/app/components/user-profile-bio-details.hbs b/packages/frontend/app/components/user-profile-bio-details.hbs
new file mode 100644
index 0000000000..5ba2fcea34
--- /dev/null
+++ b/packages/frontend/app/components/user-profile-bio-details.hbs
@@ -0,0 +1,135 @@
+{{#let (unique-id) as |templateId|}}
+
+{{/let}}
\ No newline at end of file
diff --git a/packages/frontend/app/components/user-profile-bio-details.js b/packages/frontend/app/components/user-profile-bio-details.js
new file mode 100644
index 0000000000..6590ef33df
--- /dev/null
+++ b/packages/frontend/app/components/user-profile-bio-details.js
@@ -0,0 +1,35 @@
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+// import { action } from '@ember/object';
+// import { service } from '@ember/service';
+// import { TrackedAsyncData } from 'ember-async-data';
+
+export default class UserProfileBioDetailsComponent extends Component {
+ @tracked firstName;
+ @tracked middleName;
+ @tracked lastName;
+ @tracked campusId;
+ @tracked otherId;
+ @tracked email;
+ @tracked displayName;
+ @tracked pronouns;
+ @tracked preferredEmail;
+ @tracked phone;
+ @tracked username;
+ @tracked password;
+
+ constructor() {
+ super(...arguments);
+
+ this.firstName = this.args.user.firstName;
+ this.middleName = this.args.user.middleName;
+ this.lastName = this.args.user.lastName;
+ this.campusId = this.args.user.campusId;
+ this.otherId = this.args.user.otherId;
+ this.email = this.args.user.email;
+ this.displayName = this.args.user.displayName;
+ this.pronouns = this.args.user.pronouns;
+ this.preferredEmail = this.args.user.preferredEmail;
+ this.phone = this.args.user.phone;
+ }
+}
diff --git a/packages/frontend/app/components/user-profile-bio-manager.hbs b/packages/frontend/app/components/user-profile-bio-manager.hbs
new file mode 100644
index 0000000000..c6da654af0
--- /dev/null
+++ b/packages/frontend/app/components/user-profile-bio-manager.hbs
@@ -0,0 +1,289 @@
+{{#let (unique-id) as |templateId|}}
+
+{{/let}}
\ No newline at end of file
diff --git a/packages/frontend/app/components/user-profile-bio-manager.js b/packages/frontend/app/components/user-profile-bio-manager.js
new file mode 100644
index 0000000000..e6a403e80d
--- /dev/null
+++ b/packages/frontend/app/components/user-profile-bio-manager.js
@@ -0,0 +1,36 @@
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+// import { action } from '@ember/object';
+// import { service } from '@ember/service';
+// import { TrackedAsyncData } from 'ember-async-data';
+import { ValidateIf } from 'class-validator';
+import { validatable, IsEmail, NotBlank, Length } from 'ilios-common/decorators/validation';
+
+@validatable
+export default class UserProfileBioManagerComponent extends Component {
+ @tracked @Length(1, 50) @NotBlank() firstName;
+ @tracked @Length(0, 20) middleName;
+ @tracked @Length(1, 50) @NotBlank() lastName;
+ @tracked @Length(0, 16) campusId;
+ @tracked @Length(0, 16) otherId;
+ @tracked @IsEmail() @Length(1, 100) @NotBlank() email;
+ @tracked @Length(0, 200) displayName;
+ @tracked @Length(0, 50) pronouns;
+ @tracked @IsEmail() @Length(0, 100) preferredEmail;
+ @tracked @Length(0, 20) phone;
+ @tracked
+ @Length(1, 100)
+ @NotBlank()
+ username;
+ @tracked
+ @ValidateIf((o) => o.canEditUsernameAndPassword && o.changeUserPassword)
+ @Length(5)
+ @NotBlank()
+ password;
+
+ @tracked showSyncErrorMessage = false;
+ @tracked showUsernameTakenErrorMessage = false;
+ @tracked changeUserPassword = false;
+ @tracked updatedFieldsFromSync = [];
+ @tracked passwordStrengthScore = 0;
+}
diff --git a/packages/frontend/app/components/user-profile-bio.hbs b/packages/frontend/app/components/user-profile-bio.hbs
index 233376b908..dd85934e68 100644
--- a/packages/frontend/app/components/user-profile-bio.hbs
+++ b/packages/frontend/app/components/user-profile-bio.hbs
@@ -1,14 +1,10 @@
{{#let (unique-id) as |templateId|}}
- {{#if this.load.isRunning}}
-
- {{else}}
+ {{#if this.userAuthenticationData.isResolved}}
{{#if @isManaging}}
{{/if}}
- {{#unless @user.authentication.username}}
+ {{#unless this.userAuthentication.username}}
{{t "general.missingRequiredUsername"}}
@@ -350,7 +344,7 @@
{{/if}}
{{else}}
- {{@user.authentication.username}}
+ {{this.userAuthentication.username}}
{{/if}}
@@ -415,7 +409,7 @@
{{/if}}
{{else}}
- {{#if @user.authentication.username}}
+ {{#if this.userAuthentication.username}}
*********
{{/if}}
@@ -423,6 +417,8 @@
{{/if}}
+ {{else}}
+
{{/if}}
{{/let}}
\ No newline at end of file
diff --git a/packages/frontend/app/components/user-profile-bio.js b/packages/frontend/app/components/user-profile-bio.js
index d417d8b7ae..4c981c515c 100644
--- a/packages/frontend/app/components/user-profile-bio.js
+++ b/packages/frontend/app/components/user-profile-bio.js
@@ -44,6 +44,32 @@ export default class UserProfileBioComponent extends Component {
userSearchTypeConfig = new TrackedAsyncData(this.iliosConfig.getUserSearchType());
+ constructor() {
+ super(...arguments);
+
+ this.firstName = this.args.user.firstName;
+ this.middleName = this.args.user.middleName;
+ this.lastName = this.args.user.lastName;
+ this.campusId = this.args.user.campusId;
+ this.otherId = this.args.user.otherId;
+ this.email = this.args.user.email;
+ this.displayName = this.args.user.displayName;
+ this.pronouns = this.args.user.pronouns;
+ this.preferredEmail = this.args.user.preferredEmail;
+ this.phone = this.args.user.phone;
+
+ this.load.perform();
+ }
+
+ @cached
+ get userAuthenticationData() {
+ return new TrackedAsyncData(this.args.user.authentication);
+ }
+
+ get userAuthentication() {
+ return this.userAuthenticationData.isResolved ? this.userAuthenticationData.value : null;
+ }
+
@cached
get hasErrorForPasswordData() {
return new TrackedAsyncData(this.hasErrorFor('password'));
@@ -108,17 +134,6 @@ export default class UserProfileBioComponent extends Component {
@action
cancel() {
- this.firstName = null;
- this.lastName = null;
- this.middleName = null;
- this.campusId = null;
- this.otherId = null;
- this.email = null;
- this.displayName = null;
- this.pronouns = null;
- this.preferredEmail = null;
- this.phone = null;
- this.username = null;
this.password = null;
this.passwordStrengthScore = 0;
this.changeUserPassword = false;
@@ -134,16 +149,6 @@ export default class UserProfileBioComponent extends Component {
}
load = restartableTask(async () => {
- this.firstName = this.args.user.firstName;
- this.middleName = this.args.user.middleName;
- this.lastName = this.args.user.lastName;
- this.campusId = this.args.user.campusId;
- this.otherId = this.args.user.otherId;
- this.email = this.args.user.email;
- this.displayName = this.args.user.displayName;
- this.pronouns = this.args.user.pronouns;
- this.preferredEmail = this.args.user.preferredEmail;
- this.phone = this.args.user.phone;
const auth = await this.args.user.authentication;
if (auth) {
this.username = auth.username;
diff --git a/packages/frontend/app/components/user-profile-roles.hbs b/packages/frontend/app/components/user-profile-roles.hbs
index 3f93d7e974..d385d8250d 100644
--- a/packages/frontend/app/components/user-profile-roles.hbs
+++ b/packages/frontend/app/components/user-profile-roles.hbs
@@ -2,8 +2,6 @@
class="user-profile-roles small-component last
{{if this.hasSavedRecently 'has-saved' 'has-not-saved'}}"
data-test-user-profile-roles
- {{did-insert (perform this.load)}}
- {{did-update (perform this.load) @user.roles}}
...attributes
>
diff --git a/packages/frontend/app/components/user-profile-roles.js b/packages/frontend/app/components/user-profile-roles.js
index 0f36924339..2a2dd1e32b 100644
--- a/packages/frontend/app/components/user-profile-roles.js
+++ b/packages/frontend/app/components/user-profile-roles.js
@@ -1,9 +1,10 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';
-import { dropTask, restartableTask, timeout } from 'ember-concurrency';
-import { tracked } from '@glimmer/tracking';
+import { dropTask, timeout } from 'ember-concurrency';
+import { tracked, cached } from '@glimmer/tracking';
import { action } from '@ember/object';
import { findBy } from 'ilios-common/utils/array-helpers';
+import { TrackedAsyncData } from 'ember-async-data';
export default class UserProfileRolesComponent extends Component {
@service store;
@@ -13,12 +14,17 @@ export default class UserProfileRolesComponent extends Component {
@tracked isFormerStudentFlipped = false;
@tracked isStudentFlipped = false;
@tracked isUserSyncIgnoredFlipped = false;
- @tracked roleTitles = [];
- load = restartableTask(async () => {
- const roles = await this.args.user.roles;
- this.roleTitles = roles.map((role) => role.title.toLowerCase());
- });
+ @cached
+ get roleTitlesData() {
+ return new TrackedAsyncData(this.args.user.roles);
+ }
+
+ get roleTitles() {
+ return this.roleTitlesData.isResolved
+ ? this.roleTitlesData.value.map((role) => role.title.toLowerCase())
+ : [];
+ }
get isStudent() {
const originallyYes = this.roleTitles.includes('student');