[ACS-9980] Add the option to provide custom user profile sections (#4859)

* [ACS-9980] Add the option to provide custom user profile sections

* [ACS-9980] Cr fixes

* [ACS-9980] Formatting fix
This commit is contained in:
MichalKinas
2025-10-23 13:49:02 +02:00
committed by GitHub
parent 2d8354b970
commit 24b2b23afd
12 changed files with 323 additions and 161 deletions
+6 -1
View File
@@ -463,7 +463,12 @@
"COLLAPSE_NAVIGATION": "Collapse navigation menu",
"OPTIONS_SETTINGS": "Options and settings",
"MY_PROFILE": "My profile",
"EXPAND_NAVIGATION": "Expand navigation menu"
"EXPAND_NAVIGATION": "Expand navigation menu",
"EXPAND_SECTION": "Expand section",
"COLLAPSE_SECTION": "Collapse section",
"EDIT": "Edit",
"CANCEL": "Cancel",
"SAVE": "Save"
},
"FOLDER_INFO": {
"ICON": "Folder Icon",
@@ -166,7 +166,8 @@ import { SaveSearchSidenavComponent } from './components/search/search-save/side
'app.canShowLogout': rules.canShowLogout,
'app.isContentServiceEnabled': rules.isContentServiceEnabled,
'app.areTagsEnabled': rules.areTagsEnabled,
'app.areCategoriesEnabled': rules.areCategoriesEnabled
'app.areCategoriesEnabled': rules.areCategoriesEnabled,
'app.isSSOEnabled': rules.isSSOEnabled
}
})
]
@@ -1,10 +1,15 @@
<div class="app-profile-container">
<div class="app-profile-row">
<div class="app-profile-title">
<button *ngIf="(appNavNarMode$ | async) === 'collapsed'" mat-icon-button (click)="toggleClick()"
title="{{'APP.TOOLTIPS.EXPAND_NAVIGATION' | translate}}">
<mat-icon>menu</mat-icon>
</button>
@if ((appNavNarMode$ | async) === 'collapsed') {
<button
mat-icon-button
(click)="toggleNavigationMenu()"
[attr.title]="'APP.TOOLTIPS.EXPAND_NAVIGATION' | translate"
[attr.aria-label]="'APP.TOOLTIPS.EXPAND_NAVIGATION' | translate">
<mat-icon>menu</mat-icon>
</button>
}
<button
class="app-profile-icon"
mat-icon-button
@@ -16,26 +21,49 @@
<h3 class="app-profile">{{'APP.EDIT_PROFILE.MY_PROFILE' | translate}}</h3>
</div>
</div>
<div class="app-profile-general-row" [formGroup]="profileForm">
<div class="app-profile-general-row" [formGroup]="profileForm" id="general-dropdown">
<div class="app-profile-general">
<div class="app-profile-general-section">
<mat-icon class="app-profile-general-icon" (click)="toggleGeneralDropdown()" id="toggle-general-dropdown">
{{ generalSectionDropdown ? 'expand_more' : 'chevron_right'}}</mat-icon>
<button
mat-icon-button
(click)="toggleGeneralDropdown()"
[attr.title]="`APP.TOOLTIPS.${generalSectionDropdown ? 'COLLAPSE' : 'EXPAND'}_SECTION` | translate"
[attr.aria-label]="`APP.TOOLTIPS.${generalSectionDropdown ? 'COLLAPSE' : 'EXPAND'}_SECTION` | translate">
<mat-icon>{{ generalSectionDropdown ? 'expand_more' : 'chevron_right'}}</mat-icon>
</button>
<h4 class="app-general-title">{{'APP.EDIT_PROFILE.GENERAL' | translate}}</h4>
</div>
<div class="app-general-edit-btn" *ngIf="generalSectionButtonsToggle">
<button mat-raised-button (click)="toggleGeneralButtons()" id="general-edit-button"
class="app-general-edit">{{'APP.EDIT_PROFILE.EDIT' | translate}}</button>
</div>
<div class="app-general-edit-btn" *ngIf="!generalSectionButtonsToggle">
<button mat-raised-button class="app-general-cancel-btn" id="general-cancel-button"
(click)="toggleGeneralButtons()">{{'APP.EDIT_PROFILE.CANCEL' | translate}}</button>
<button mat-raised-button class="app-general-save-btn" id="general-save-button" [disabled]="profileForm.invalid"
(click)="onSaveGeneralData(profileForm)">{{'APP.EDIT_PROFILE.SAVE' | translate}}</button>
</div>
@if (generalSectionButtonsToggle) {
<button
mat-icon-button
(click)="toggleGeneralButtons()"
class="app-general-edit"
[attr.title]="'APP.TOOLTIPS.EDIT' | translate"
[attr.aria-label]="'APP.TOOLTIPS.EDIT' | translate">
<mat-icon>edit</mat-icon>
</button>
} @else {
<button
mat-icon-button
class="app-general-cancel-btn"
[attr.title]="'APP.TOOLTIPS.CANCEL' | translate"
[attr.aria-label]="'APP.TOOLTIPS.CANCEL' | translate"
(click)="toggleGeneralButtons()">
<mat-icon>close</mat-icon>
</button>
<button
mat-icon-button
color="primary"
[attr.title]="'APP.TOOLTIPS.SAVE' | translate"
[attr.aria-label]="'APP.TOOLTIPS.SAVE' | translate"
[disabled]="profileForm.invalid"
(click)="onSaveGeneralData(profileForm)">
<mat-icon>check</mat-icon>
</button>
}
</div>
<mat-divider class="app-divider" *ngIf="generalSectionDropdown" />
<div *ngIf="generalSectionDropdown">
@if (generalSectionDropdown) {
<mat-divider class="app-divider" />
<div class="app-general-dropdown">
<div class="app-general-dropdown-details">
<h4 class="app-profile-general-dropdown-heading">{{'APP.EDIT_PROFILE.FIRST_NAME' | translate}}</h4>
@@ -49,106 +77,176 @@
<mat-divider class="app-general-dropdown-divider" />
<div class="app-general-dropdown-details">
<h4 class="app-profile-general-dropdown-heading">{{'APP.EDIT_PROFILE.JOB_TITLE' | translate}}</h4>
<p class="app-profile-general-dropdown-details" *ngIf="generalSectionButtonsToggle">{{personDetails?.jobTitle}}</p>
<input type="text" value="" formControlName="jobTitle" *ngIf="!generalSectionButtonsToggle"
class="app-profile-general-dropdown-input-details app-selected">
@if (generalSectionButtonsToggle) {
<p class="app-profile-general-dropdown-details">{{personDetails?.jobTitle}}</p>
} @else {
<input type="text" value="" formControlName="jobTitle" class="app-profile-general-dropdown-input-details app-selected">
}
</div>
<mat-divider class="app-general-dropdown-divider" />
<div class="app-general-dropdown-details">
<h4 class="app-profile-general-dropdown-heading">{{'APP.EDIT_PROFILE.LOCATION' | translate}}</h4>
<p class="app-profile-general-dropdown-details" *ngIf="generalSectionButtonsToggle">{{personDetails?.location}}</p>
<input type="text" value="" formControlName="location" *ngIf="!generalSectionButtonsToggle"
class="app-profile-general-dropdown-input-details app-selected">
@if (generalSectionButtonsToggle) {
<p class="app-profile-general-dropdown-details">{{personDetails?.location}}</p>
} @else {
<input type="text" value="" formControlName="location" class="app-profile-general-dropdown-input-details app-selected">
}
</div>
<mat-divider class="app-general-dropdown-divider" />
<div class="app-general-dropdown-details">
<h4 class="app-profile-general-dropdown-heading">{{'APP.EDIT_PROFILE.TELEPHONE' | translate}}</h4>
<p class="app-profile-general-dropdown-details" *ngIf="generalSectionButtonsToggle">{{personDetails?.telephone}}</p>
<input type="tel" name="Telephone" value="" formControlName="telephone" *ngIf="!generalSectionButtonsToggle"
class="app-profile-general-dropdown-input-details app-selected" aria-describedby="app-view-profile-phone-error-message">
<mat-error class="app-error-message" *ngIf="profileForm.get('telephone').invalid" role="alert" id="app-view-profile-phone-error-message">
{{ 'APP.EDIT_PROFILE.INVALID_INPUT' | translate }}
</mat-error>
@if (generalSectionButtonsToggle) {
<p class="app-profile-general-dropdown-details">{{personDetails?.telephone}}</p>
} @else {
<input
type="tel"
name="Telephone"
value=""
formControlName="telephone"
class="app-profile-general-dropdown-input-details app-selected"
aria-describedby="app-view-profile-phone-error-message">
}
@if (profileForm.controls['telephone'].errors) {
<mat-error class="app-error-message" role="alert" id="app-view-profile-phone-error-message">
{{ 'APP.EDIT_PROFILE.INVALID_INPUT' | translate }}
</mat-error>
}
</div>
<mat-divider class="app-general-dropdown-divider" />
<div class="app-general-dropdown-details">
<h4 class="app-profile-general-dropdown-heading">{{'APP.EDIT_PROFILE.MOBILE' | translate}}</h4>
<p class="app-profile-general-dropdown-details" *ngIf="generalSectionButtonsToggle">{{personDetails?.mobile}}</p>
<input type="tel" value="" formControlName="mobile" *ngIf="!generalSectionButtonsToggle"
class="app-profile-general-dropdown-input-details app-selected" aria-describedby="app-view-profile-mobile-error-message">
<mat-error class="app-error-message" *ngIf="profileForm.get('mobile').invalid" role="alert" id="app-view-profile-mobile-error-message">
{{ 'APP.EDIT_PROFILE.INVALID_INPUT' | translate }}
</mat-error>
@if (generalSectionButtonsToggle) {
<p class="app-profile-general-dropdown-details">{{personDetails?.mobile}}</p>
} @else {
<input
type="tel"
value=""
formControlName="mobile"
class="app-profile-general-dropdown-input-details app-selected"
aria-describedby="app-view-profile-mobile-error-message">
}
@if (profileForm.controls['mobile'].errors) {
<mat-error class="app-error-message" role="alert" id="app-view-profile-mobile-error-message">
{{ 'APP.EDIT_PROFILE.INVALID_INPUT' | translate }}
</mat-error>
}
</div>
</div>
</div>
}
</div>
<div class="app-profile-contact-row" [formGroup]="profileForm">
<div class="app-profile-contact-row" [formGroup]="profileForm" id="contact-dropdown">
<div class="app-profile-general profile-general-bottom-radius">
<div class="app-profile-general-section">
<mat-icon class="app-profile-general-icon" (click)="toggleContactDropdown()" id="toggle-contact-dropdown">
{{ contactSectionDropdown ? 'expand_more' : 'chevron_right'}}</mat-icon>
<button
mat-icon-button
(click)="toggleContactDropdown()"
[attr.title]="`APP.TOOLTIPS.${contactSectionDropdown ? 'COLLAPSE' : 'EXPAND'}_SECTION` | translate"
[attr.aria-label]="`APP.TOOLTIPS.${contactSectionDropdown ? 'COLLAPSE' : 'EXPAND'}_SECTION` | translate">
<mat-icon>{{ contactSectionDropdown ? 'expand_more' : 'chevron_right'}}</mat-icon>
</button>
<h4 class="app-general-title">{{'APP.EDIT_PROFILE.COMPANY_DETAILS' | translate}}</h4>
</div>
<div class="app-general-edit-btn" *ngIf="contactSectionButtonsToggle">
<button mat-raised-button class="app-general-edit" id="contact-edit-button"
(click)="toggleContactButtons()">{{'APP.EDIT_PROFILE.EDIT' | translate}}</button>
</div>
<div class="app-general-edit-btn" *ngIf="!contactSectionButtonsToggle">
<button mat-raised-button class="app-general-cancel-btn" id="contact-cancel-button"
(click)="toggleContactButtons()">{{'APP.EDIT_PROFILE.CANCEL' | translate}}</button>
<button mat-raised-button class="app-general-save-btn" id="contact-save-button" [disabled]="profileForm.invalid"
(click)="onSaveCompanyData(profileForm)">{{'APP.EDIT_PROFILE.SAVE' | translate}}</button>
</div>
@if (contactSectionButtonsToggle) {
<button
mat-icon-button
(click)="toggleContactButtons()"
class="app-general-edit"
[attr.title]="'APP.TOOLTIPS.EDIT' | translate"
[attr.aria-label]="'APP.TOOLTIPS.EDIT' | translate">
<mat-icon>edit</mat-icon>
</button>
} @else {
<button
mat-icon-button
class="app-general-cancel-btn"
[attr.title]="'APP.TOOLTIPS.CANCEL' | translate"
[attr.aria-label]="'APP.TOOLTIPS.CANCEL' | translate"
(click)="toggleContactButtons()">
<mat-icon>close</mat-icon>
</button>
<button
mat-icon-button
color="primary"
[attr.title]="'APP.TOOLTIPS.SAVE' | translate"
[attr.aria-label]="'APP.TOOLTIPS.SAVE' | translate"
[disabled]="profileForm.invalid"
(click)="onSaveCompanyData(profileForm)">
<mat-icon>check</mat-icon>
</button>
}
</div>
<mat-divider class="app-divider" *ngIf="contactSectionDropdown" />
<div *ngIf="contactSectionDropdown">
@if (contactSectionDropdown) {
<mat-divider class="app-divider" />
<div class="app-general-dropdown">
<div class="app-general-dropdown-details">
<h4 class="app-profile-general-dropdown-heading">{{'APP.EDIT_PROFILE.NAME' | translate}}</h4>
<p class="app-profile-general-dropdown-details" *ngIf="contactSectionButtonsToggle">{{personDetails?.company?.organization}}</p>
<input type="text" value="" *ngIf="!contactSectionButtonsToggle" formControlName="companyName"
class="app-profile-general-dropdown-input-details app-selected">
@if (contactSectionButtonsToggle) {
<p class="app-profile-general-dropdown-details">{{personDetails?.company?.organization}}</p>
} @else {
<input type="text" value="" formControlName="companyName" class="app-profile-general-dropdown-input-details app-selected">
}
</div>
<mat-divider class="app-general-dropdown-divider" />
<div class="app-general-dropdown-details">
<h4 class="app-profile-general-dropdown-heading">{{'APP.EDIT_PROFILE.ADDRESS' | translate}}</h4>
<p class="app-profile-general-dropdown-details" *ngIf="contactSectionButtonsToggle">{{personDetails?.company?.address1}}</p>
<input type="text" value="" *ngIf="!contactSectionButtonsToggle" formControlName="companyAddress"
class="app-profile-general-dropdown-input-details app-selected">
@if (contactSectionButtonsToggle) {
<p class="app-profile-general-dropdown-details">{{personDetails?.company?.address1}}</p>
} @else {
<input type="text" value="" formControlName="companyAddress" class="app-profile-general-dropdown-input-details app-selected">
}
</div>
<mat-divider class="app-general-dropdown-divider" />
<div class="app-general-dropdown-details">
<h4 class="app-profile-general-dropdown-heading">{{'APP.EDIT_PROFILE.POSTCODE' | translate}}</h4>
<p class="app-profile-general-dropdown-details" *ngIf="contactSectionButtonsToggle">{{personDetails?.company?.postcode}}</p>
<input type="text" value="" *ngIf="!contactSectionButtonsToggle" formControlName="companyPostCode"
class="app-profile-general-dropdown-input-details app-selected">
@if (contactSectionButtonsToggle) {
<p class="app-profile-general-dropdown-details">{{personDetails?.company?.postcode}}</p>
} @else {
<input type="text" value="" formControlName="companyPostCode" class="app-profile-general-dropdown-input-details app-selected">
}
</div>
<mat-divider class="app-general-dropdown-divider" />
<div class="app-general-dropdown-details">
<h4 class="app-profile-general-dropdown-heading">{{'APP.EDIT_PROFILE.TELEPHONE' | translate}}</h4>
<p class="app-profile-general-dropdown-details" *ngIf="contactSectionButtonsToggle">{{personDetails?.company?.telephone}}</p>
<input type="tel" value="" *ngIf="!contactSectionButtonsToggle" formControlName="companyTelephone"
class="app-profile-general-dropdown-input-details app-selected" aria-describedby="app-view-profile-company-phone-error-message">
<mat-error class="app-error-message" *ngIf="profileForm.get('companyTelephone').invalid" role="alert"
id="app-view-profile-company-phone-error-message">
{{ 'APP.EDIT_PROFILE.INVALID_INPUT' | translate }}
</mat-error>
@if (contactSectionButtonsToggle) {
<p class="app-profile-general-dropdown-details">{{personDetails?.company?.telephone}}</p>
} @else {
<input
type="tel"
value=""
formControlName="companyTelephone"
class="app-profile-general-dropdown-input-details app-selected"
aria-describedby="app-view-profile-company-phone-error-message">
}
@if (profileForm.controls['companyTelephone'].errors) {
<mat-error class="app-error-message" role="alert" id="app-view-profile-company-phone-error-message">
{{ 'APP.EDIT_PROFILE.INVALID_INPUT' | translate }}
</mat-error>
}
</div>
<mat-divider class="app-general-dropdown-divider" />
<div class="app-general-dropdown-details">
<h4 class="app-profile-general-dropdown-heading">{{'APP.EDIT_PROFILE.EMAIL' | translate}}</h4>
<p class="app-profile-general-dropdown-details" *ngIf="contactSectionButtonsToggle">{{personDetails?.company?.email}}</p>
<input type="text" value="" *ngIf="!contactSectionButtonsToggle" formControlName="companyEmail"
class="app-profile-general-dropdown-input-details app-selected" aria-describedby="app-view-profile-company-email-error-message">
<mat-error class="app-error-message" *ngIf="profileForm.get('companyEmail').invalid" role="alert"
id="app-view-profile-company-email-error-message">
{{ 'APP.EDIT_PROFILE.INVALID_INPUT' | translate }}
</mat-error>
@if (contactSectionButtonsToggle) {
<p class="app-profile-general-dropdown-details">{{personDetails?.company?.email}}</p>
} @else {
<input
type="text"
value=""
formControlName="companyEmail"
class="app-profile-general-dropdown-input-details app-selected"
aria-describedby="app-view-profile-company-email-error-message">
}
@if (profileForm.controls['companyEmail'].errors) {
<mat-error class="app-error-message" role="alert" id="app-view-profile-company-email-error-message">
{{ 'APP.EDIT_PROFILE.INVALID_INPUT' | translate }}
</mat-error>
}
</div>
</div>
</div>
}
</div>
@for (section of sections; track $index) {
<adf-dynamic-component [id]="section.component"/>
}
</div>
@@ -43,6 +43,7 @@ app-view-profile {
}
.app-profile-general-icon {
padding: 0 0.5rem 0 1rem;
cursor: pointer;
}
@@ -52,7 +53,16 @@ app-view-profile {
.app-profile-general {
display: flex;
padding-left: 1rem;
align-items: center;
width: 100%;
.app-general-edit {
color: var(--adf-theme-foreground-text-color-054);
}
.app-general-cancel-btn {
color: var(--adf-metadata-action-button-clear-color);
}
}
.app-profile-general-bottom-radius {
@@ -62,48 +72,12 @@ app-view-profile {
.app-profile-general-section {
display: flex;
width: 60%;
padding-top: 1rem;
cursor: pointer;
}
.app-general-title {
margin-left: 0.6rem;
margin-top: 4px;
letter-spacing: 0.5px;
}
.app-general-edit-btn {
width: 60%;
text-align: end;
.app-general-edit {
background: var(--theme-grey-text-background-color);
height: 30px;
margin: 1rem;
}
.app-general-cancel-btn {
height: 30px;
margin: 1rem;
margin-left: 0.5rem;
background-color: var(--theme-grey-text-background-color);
padding-top: 0.25px;
}
.app-general-save-btn {
width: 75px;
height: 30px;
margin: 1rem;
margin-left: 0.5rem;
color: var(--theme-white-background);
background-color: var(--theme-blue-button-color);
font-weight: 600;
}
align-items: center;
width: 100%;
}
.app-divider {
border-top-width: 2px;
border-top-width: 1px;
border-top-color: var(--theme-grey-background-color);
padding-left: -1px;
padding-right: 1px;
@@ -116,10 +90,6 @@ app-view-profile {
box-shadow: 0 0 2px var(--theme-blue-button-color);
}
.app-general-edit:hover {
background: var(--theme-grey-hover-background-color);
}
.app-general-dropdown-divider {
border-top-color: var(--theme-grey-divider-color);
}
@@ -183,7 +153,7 @@ app-view-profile {
}
.app-profile-contact-row {
margin: 2rem 0 5rem 2rem;
margin: 2rem 0 0 2rem;
width: 70%;
border: 1px solid var(--theme-grey-background-color);
border-radius: 1rem;
@@ -25,13 +25,13 @@
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { ViewProfileComponent } from './view-profile.component';
import { AppTestingModule } from '../../testing/app-testing.module';
import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { BehaviorSubject, Subject } from 'rxjs';
import { AppService } from '@alfresco/aca-shared';
import { BehaviorSubject, of, Subject } from 'rxjs';
import { AppExtensionService, AppService } from '@alfresco/aca-shared';
import { UnitTestingUtils } from '@alfresco/adf-core';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { HarnessLoader } from '@angular/cdk/testing';
import { DynamicExtensionComponent } from '@alfresco/adf-extensions';
describe('ViewProfileComponent', () => {
let fixture: ComponentFixture<ViewProfileComponent>;
@@ -39,6 +39,7 @@ describe('ViewProfileComponent', () => {
let router: Router;
let unitTestingUtils: UnitTestingUtils;
let loader: HarnessLoader;
let appExtensionService: AppExtensionService;
const appServiceMock = {
toggleAppNavBar$: new Subject(),
@@ -61,6 +62,7 @@ describe('ViewProfileComponent', () => {
router = TestBed.inject(Router);
loader = TestbedHarnessEnvironment.loader(fixture);
unitTestingUtils = new UnitTestingUtils(fixture.debugElement, loader);
appExtensionService = TestBed.inject(AppExtensionService);
router.initialNavigation();
});
@@ -71,7 +73,7 @@ describe('ViewProfileComponent', () => {
it('should toggle the appService toggleAppNavBar$ Subject', () => {
spyOn(appServiceMock.toggleAppNavBar$, 'next');
component.toggleClick();
component.toggleNavigationMenu();
expect(appServiceMock.toggleAppNavBar$.next).toHaveBeenCalled();
});
@@ -156,8 +158,7 @@ describe('ViewProfileComponent', () => {
component.generalSectionDropdown = false;
fixture.detectChanges();
const generalToggleIcon = fixture.debugElement.query(By.css('#toggle-general-dropdown'));
generalToggleIcon.triggerEventHandler('click', null);
unitTestingUtils.clickByCSS('#general-dropdown button');
expect(component.toggleGeneralDropdown).toHaveBeenCalled();
expect(component.generalSectionButtonsToggle).toBe(true);
@@ -168,8 +169,7 @@ describe('ViewProfileComponent', () => {
component.contactSectionDropdown = false;
fixture.detectChanges();
const contactToggleIcon = fixture.debugElement.query(By.css('#toggle-contact-dropdown'));
contactToggleIcon.triggerEventHandler('click', null);
unitTestingUtils.clickByCSS('#contact-dropdown button');
expect(component.toggleContactDropdown).toHaveBeenCalled();
expect(component.contactSectionButtonsToggle).toBe(true);
@@ -179,12 +179,10 @@ describe('ViewProfileComponent', () => {
spyOn(component, 'toggleGeneralButtons').and.callThrough();
fixture.detectChanges();
const generalEditButton = fixture.debugElement.query(By.css('#general-edit-button'));
generalEditButton.triggerEventHandler('click', null);
unitTestingUtils.clickByCSS('#general-dropdown .app-general-edit');
fixture.detectChanges();
const generalCancelButton = fixture.debugElement.query(By.css('#general-cancel-button'));
generalCancelButton.triggerEventHandler('click', null);
unitTestingUtils.clickByCSS('#general-dropdown .app-general-cancel-btn');
expect(component.toggleGeneralButtons).toHaveBeenCalledTimes(2);
});
@@ -193,12 +191,10 @@ describe('ViewProfileComponent', () => {
spyOn(component, 'toggleContactButtons').and.callThrough();
fixture.detectChanges();
const contactEditButton = fixture.debugElement.query(By.css('#contact-edit-button'));
contactEditButton.triggerEventHandler('click', null);
unitTestingUtils.clickByCSS('#contact-dropdown .app-general-edit');
fixture.detectChanges();
const contactCancelButton = fixture.debugElement.query(By.css('#contact-cancel-button'));
contactCancelButton.triggerEventHandler('click', null);
unitTestingUtils.clickByCSS('#contact-dropdown .app-general-cancel-btn');
expect(component.toggleContactButtons).toHaveBeenCalledTimes(2);
});
@@ -209,4 +205,12 @@ describe('ViewProfileComponent', () => {
expect(router.navigate).toHaveBeenCalledWith(['/personal-files'], { replaceUrl: true });
});
it('should render additional user profile sections if provided', () => {
spyOn(appExtensionService, 'getUserProfileSections').and.returnValue(of([{ id: 'test-section' }]));
fixture.detectChanges();
expect(component.sections.length).toBe(1);
expect(unitTestingUtils.getByDirective(DynamicExtensionComponent)).toBeDefined();
});
});
@@ -27,8 +27,8 @@ import { PeopleApi, Person } from '@alfresco/js-api';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Observable, throwError } from 'rxjs';
import { AppService } from '@alfresco/aca-shared';
import { Observable, take, throwError } from 'rxjs';
import { AppExtensionService, AppService, UserProfileSection } from '@alfresco/aca-shared';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
@@ -36,9 +36,19 @@ import { TranslatePipe } from '@ngx-translate/core';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { DynamicExtensionComponent } from '@alfresco/adf-extensions';
@Component({
imports: [CommonModule, TranslatePipe, ReactiveFormsModule, MatButtonModule, MatIconModule, MatDividerModule, MatFormFieldModule],
imports: [
CommonModule,
TranslatePipe,
ReactiveFormsModule,
MatButtonModule,
MatIconModule,
MatDividerModule,
MatFormFieldModule,
DynamicExtensionComponent
],
selector: 'app-view-profile',
templateUrl: './view-profile.component.html',
styleUrls: ['./view-profile.component.scss'],
@@ -60,13 +70,15 @@ export class ViewProfileComponent implements OnInit {
contactSectionDropdown = false;
contactSectionButtonsToggle = true;
appNavNarMode$: Observable<'collapsed' | 'expanded'>;
sections: UserProfileSection[] = [];
constructor(
private router: Router,
apiService: AlfrescoApiService,
private appService: AppService
private readonly apiService: AlfrescoApiService,
private readonly appService: AppService,
private readonly extensionService: AppExtensionService
) {
this.peopleApi = new PeopleApi(apiService.getInstance());
this.peopleApi = new PeopleApi(this.apiService.getInstance());
this.appNavNarMode$ = appService.appNavNarMode$.pipe(takeUntilDestroyed());
}
@@ -81,9 +93,14 @@ export class ViewProfileComponent implements OnInit {
.catch((error) => {
throwError(error);
});
this.extensionService
.getUserProfileSections()
.pipe(take(1))
.subscribe((sections) => (this.sections = sections));
}
toggleClick() {
toggleNavigationMenu() {
this.appService.toggleAppNavBar$.next();
}
@@ -112,10 +129,6 @@ export class ViewProfileComponent implements OnInit {
toggleGeneralDropdown() {
this.generalSectionDropdown = !this.generalSectionDropdown;
if (!this.generalSectionDropdown) {
this.generalSectionButtonsToggle = true;
}
}
toggleGeneralButtons() {
@@ -143,10 +156,6 @@ export class ViewProfileComponent implements OnInit {
toggleLoginDropdown() {
this.loginSectionDropdown = !this.loginSectionDropdown;
if (!this.loginSectionDropdown) {
this.loginSectionButtonsToggle = true;
}
}
toggleLoginButtons() {
@@ -161,10 +170,6 @@ export class ViewProfileComponent implements OnInit {
toggleContactDropdown() {
this.contactSectionDropdown = !this.contactSectionDropdown;
if (!this.contactSectionDropdown) {
this.contactSectionButtonsToggle = true;
}
}
toggleContactButtons() {
@@ -1196,6 +1196,18 @@ describe('app.evaluators', () => {
expect(app.isSmartFolder(context)).toBeTrue();
});
});
describe('isSSOEnabled', () => {
it('should return true if sso is enabled', () => {
context.appConfig = { get: () => 'OAUTH' } as any;
expect(app.isSSOEnabled(context)).toBe(true);
});
it('should return false if sso is not enabled', () => {
context.appConfig = { get: () => 'basic' } as any;
expect(app.isSSOEnabled(context)).toBe(false);
});
});
});
describe('Versions compatibility', () => {
@@ -560,3 +560,5 @@ export const canDisplayKnowledgeRetrievalButton = (context: AcaRuleContext): boo
navigation.isRecentFiles(context) ||
navigation.isFavorites(context) ||
((navigation.isSearchResults(context) || navigation.isLibraryContent(context)) && !navigation.isLibraries(context)));
export const isSSOEnabled = (context: AcaRuleContext): boolean => context.appConfig.get('authType') === 'OAUTH';
@@ -34,3 +34,7 @@ export interface Badge extends Partial<Pick<ContentActionRef, 'component' | 'act
icon: string;
tooltip: string;
}
export interface UserProfileSection extends Partial<Pick<ContentActionRef, 'component' | 'rules'>> {
id: string;
}
@@ -1682,6 +1682,41 @@ describe('AppExtensionService', () => {
});
});
it('should get custom user profile sections from config', (done) => {
extensions.setEvaluators({
'action.enabled': () => true
});
applyConfig({
...defaultConfigMock,
features: {
userProfileSections: [
{
id: 'section1-id',
component: 'testComponent1',
rules: {
visible: 'action.enabled'
}
},
{
id: 'section2-id',
component: 'testComponent2',
rules: {
visible: 'action.enabled'
}
}
]
}
});
service.getUserProfileSections().subscribe((sections) => {
expect(sections.length).toBe(2);
expect(sections[0].id).toEqual('section1-id');
expect(sections[1].id).toEqual('section2-id');
done();
});
});
it('should update sidebar actions correctly', () => {
spyOn(loader, 'getContentActions').and.callThrough();
service.updateSidebarActions();
@@ -53,7 +53,7 @@ import { AppConfigService, AuthenticationService, LogService } from '@alfresco/a
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { NodeEntry, RepositoryInfo } from '@alfresco/js-api';
import { ViewerRules } from '../models/viewer.rules';
import { Badge } from '../models/types';
import { Badge, UserProfileSection } from '../models/types';
import { NodePermissionService } from '../services/node-permission.service';
import { map } from 'rxjs/operators';
import { SearchCategory } from '@alfresco/adf-content-services';
@@ -92,6 +92,7 @@ export class AppExtensionService implements RuleContext {
private _filesDocumentListPreset = new BehaviorSubject<Array<DocumentListPresetRef>>([]);
private _customMetadataPanels = new BehaviorSubject<Array<ContentActionRef>>([]);
private _bulkActions = new BehaviorSubject<Array<ContentActionRef>>([]);
private readonly _userProfileSections = new BehaviorSubject<Array<UserProfileSection>>([]);
documentListPresets: {
libraries: Array<DocumentListPresetRef>;
@@ -169,6 +170,7 @@ export class AppExtensionService implements RuleContext {
this._openWithActions.next(this.loader.getContentActions(config, 'features.viewer.openWith'));
this._createActions.next(this.loader.getElements<ContentActionRef>(config, 'features.create'));
this._badges.next(this.loader.getElements<Badge>(config, 'features.badges'));
this._userProfileSections.next(this.loader.getElements<UserProfileSection>(config, 'features.userProfileSections'));
this._filesDocumentListPreset.next(this.getDocumentListPreset(config, 'files'));
this._customMetadataPanels.next(this.loader.getElements<ContentActionRef>(config, 'features.customMetadataPanels'));
this._bulkActions.next(this.loader.getElements<ContentActionRef>(config, 'features.bulk-actions'));
@@ -384,6 +386,10 @@ export class AppExtensionService implements RuleContext {
return this._badges.pipe(map((badges) => badges.filter((badge) => this.evaluateRule(badge.rules.visible, node))));
}
getUserProfileSections(): Observable<Array<UserProfileSection>> {
return this._userProfileSections.pipe(map((sections) => sections.filter((section) => this.evaluateRule(section.rules.visible))));
}
getCustomMetadataPanels(node: NodeEntry): Observable<Array<ContentActionRef>> {
return this._customMetadataPanels.pipe(map((panels) => panels.filter((panel) => this.evaluateRule(panel.rules.visible, node))));
}