Skip to content

Commit

Permalink
Merge pull request #280 from bkd-mba-fbi/feature/272-user-settings
Browse files Browse the repository at this point in the history
add user settings module
  • Loading branch information
spahrson authored May 6, 2021
2 parents 986c918 + ca058ef commit 0a1e201
Show file tree
Hide file tree
Showing 27 changed files with 8,540 additions and 8,657 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ application displays a unauthenticated message to the user.

### Important Notes

- When integrated in the Evento Application, the App is wrapped in a
- When integrated into the Evento Application, the App is wrapped in a
`<form>` tag. It is therefore important, that all `<button>`'s are
defined with `type="button"` attribute, otherwise the global form
will get submitted which results in a page reload.
Expand Down
16,520 changes: 7,864 additions & 8,656 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ const routes: Routes = [
loadChildren: () =>
import('./my-profile/my-profile.module').then((m) => m.MyProfileModule),
},
{
path: 'my-settings',
canActivate: [AuthGuard],
loadChildren: () =>
import('./my-settings/my-settings.module').then(
(m) => m.MySettingsModule
),
},
{ path: 'unauthenticated', component: UnauthenticatedComponent },
{
path: '',
Expand Down
1 change: 1 addition & 0 deletions src/app/home.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('HomeComponent', () => {
'/evaluate-absences',
'/my-absences',
'/my-profile',
'/my-settings',
]);
});

Expand Down
1 change: 1 addition & 0 deletions src/app/home.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ export class HomeComponent {
'evaluate-absences',
'my-absences',
'my-profile',
'my-settings',
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<div
class="border-bottom mb-3 pb-3"
*erzLet="{
notificationFormGroup: notificationFormGroup$ | async
} as data"
>
<!-- setting-header -->
<h5 class="m-0 pb-3">
{{ 'my-settings.notifications.title' | translate }}
</h5>

<form
*ngIf="data.notificationFormGroup"
[formGroup]="data.notificationFormGroup"
(ngSubmit)="onSubmit()"
>
<!-- setting-entry -->
<div class="checkbox">
<input
id="my-settings-notifications-gui"
type="checkbox"
class="form-check-input"
formControlName="notificationsGui"
/>
<label class="pl-2" for="my-settings-notifications-gui">
{{ 'my-settings.notifications.gui' | translate }}
</label>
</div>

<!-- setting-entry -->
<div class="checkbox">
<input
id="my-settings-notifications-mail"
type="checkbox"
class="form-check-input"
formControlName="notificationsMail"
/>
<label class="pl-2" for="my-settings-notifications-mail">
{{ 'my-settings.notifications.mail' | translate }}
</label>
</div>

<!-- setting-entry -->
<div class="checkbox">
<input
id="my-settings-notifications-phoneMobile"
type="checkbox"
class="form-check-input"
formControlName="notificationsPhoneMobile"
/>
<label class="pl-2" for="my-settings-notifications-phoneMobile">
{{ 'my-settings.notifications.phoneMobile' | translate }}
</label>
</div>

<div class="d-flex justify-content-end">
<button
type="submit"
class="btn btn-primary ml-2"
[disabled]="saving$ | async"
>
{{ 'my-settings.notifications.save' | translate }}
<div
*ngIf="saving$ | async"
class="spinner-border spinner-border-sm align-middle"
role="status"
>
<span class="sr-only">Loading...</span>
</div>
</button>
</div>
</form>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@import "../../../../bootstrap-variables";

.checkbox input.form-check-input {
// Make the following statements !important since the are
// overwritten in Evento as such
position: static !important;
margin: 0 !important;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { of } from 'rxjs';
import { buildTestModuleMetadata } from 'src/spec-helpers';
import { MySettingsService } from '../../services/my-settings.service';
import { MySettingsNotificationsComponent } from './my-settings-notifications.component';

describe('MySettingsNotificationsComponent', () => {
let fixture: ComponentFixture<MySettingsNotificationsComponent>;
let element: HTMLElement;
let settingsService: jasmine.SpyObj<MySettingsService>;

settingsService = jasmine.createSpyObj('MySettingsService', [
'getCurrentNotificationSettingsPropertyValue',
'updateCurrentNotificationSettingsPropertyValue',
]);
settingsService.getCurrentNotificationSettingsPropertyValue.and.returnValue(
of({ mail: true, gui: true, phoneMobile: false })
);
settingsService.updateCurrentNotificationSettingsPropertyValue.and.returnValue(
of({})
);
(settingsService as any).refetch = of({});

beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule(
buildTestModuleMetadata({
declarations: [MySettingsNotificationsComponent],
providers: [
{
provide: MySettingsService,
useValue: settingsService,
},
],
})
).compileComponents();
})
);

beforeEach(() => {
fixture = TestBed.createComponent(MySettingsNotificationsComponent);
element = fixture.debugElement.nativeElement;
fixture.detectChanges();
});

it('renders form fields with current values', () => {
expect(getInput('notificationsGui').checked).toBe(true);
expect(getInput('notificationsMail').checked).toBe(true);
expect(getInput('notificationsPhoneMobile').checked).toBe(false);
});

it('updates settings on submit and reloads', () => {
changeValue('notificationsGui', false);
changeValue('notificationsMail', false);
changeValue('notificationsPhoneMobile', true);
clickSubmitButton();

expect(
settingsService.updateCurrentNotificationSettingsPropertyValue
).toHaveBeenCalled();
expect(
settingsService.getCurrentNotificationSettingsPropertyValue
).toHaveBeenCalled();
});

function getInput(name: string): HTMLInputElement {
const field = element.querySelector(`input[formControlName="${name}"]`);
expect(field).not.toBeNull();
return field as HTMLInputElement;
}

function changeValue(name: string, value: any): void {
const input = getInput(name);
input.value = value;
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
}

function clickSubmitButton(): void {
const button = element.querySelector(
'.btn-primary'
) as Option<HTMLButtonElement>;
if (button) {
button.click();
fixture.detectChanges();
}
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { BehaviorSubject } from 'rxjs';
import { NotificationSettingPropertyValueType } from 'src/app/shared/models/user-setting.model';
import { MySettingsService } from '../../services/my-settings.service';
import { shareReplay, map, take, finalize, switchMap } from 'rxjs/operators';
import { ToastrService } from 'ngx-toastr';
import { TranslateService } from '@ngx-translate/core';

@Component({
selector: 'erz-my-settings-notifications',
templateUrl: './my-settings-notifications.component.html',
styleUrls: ['./my-settings-notifications.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MySettingsNotificationsComponent {
notificationSettings$ = this.settingsService.refetch.pipe(
switchMap(() =>
this.settingsService.getCurrentNotificationSettingsPropertyValue()
)
);
notificationFormGroup$ = this.notificationSettings$.pipe(
map(this.createNotificationFormGroup.bind(this)),
shareReplay(1)
);

private saving$ = new BehaviorSubject(false);
private submitted$ = new BehaviorSubject(false);

constructor(
private settingsService: MySettingsService,
private formBuilder: FormBuilder,
private toastr: ToastrService,
private translate: TranslateService
) {}

private createNotificationFormGroup(
notifications: NotificationSettingPropertyValueType
): FormGroup {
return this.formBuilder.group({
notificationsGui: [notifications.gui],
notificationsMail: [notifications.mail],
notificationsPhoneMobile: [notifications.phoneMobile],
});
}

private save(gui: boolean, mail: boolean, phoneMobile: boolean): void {
this.saving$.next(true);
this.settingsService
.updateCurrentNotificationSettingsPropertyValue(gui, mail, phoneMobile)
.pipe(finalize(() => this.saving$.next(false)))
.subscribe(this.onSaveSuccess.bind(this));
}

private onSaveSuccess(): void {
this.toastr.success(
this.translate.instant('my-settings.notifications.save-success')
);
}

onSubmit(): void {
this.submitted$.next(true);
this.notificationFormGroup$.pipe(take(1)).subscribe((formGroup) => {
if (formGroup.valid) {
const {
notificationsGui,
notificationsMail,
notificationsPhoneMobile,
} = formGroup.value;
this.save(
notificationsGui,
notificationsMail,
notificationsPhoneMobile
);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="erz-container erz-container-limited erz-container-padding-y">
<erz-my-settings-notifications></erz-my-settings-notifications>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { MySettingsShowComponent } from './my-settings-show.component';

describe('MySettingsShowComponent', () => {
let component: MySettingsShowComponent;
let fixture: ComponentFixture<MySettingsShowComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [MySettingsShowComponent],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(MySettingsShowComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'erz-my-settings-show',
templateUrl: './my-settings-show.component.html',
styleUrls: ['./my-settings-show.component.scss'],
})
export class MySettingsShowComponent implements OnInit {
ngOnInit(): void {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<router-outlet></router-outlet>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';

import { MySettingsComponent } from './my-settings.component';
import { buildTestModuleMetadata } from '../../../../spec-helpers';

describe('MySettingsComponent', () => {
let component: MySettingsComponent;
let fixture: ComponentFixture<MySettingsComponent>;

beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule(
buildTestModuleMetadata({
declarations: [MySettingsComponent],
})
).compileComponents();
})
);

beforeEach(() => {
fixture = TestBed.createComponent(MySettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { MySettingsService } from '../../services/my-settings.service';

@Component({
selector: 'erz-my-settings',
templateUrl: './my-settings.component.html',
styleUrls: ['./my-settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [MySettingsService],
})
export class MySettingsComponent implements OnInit {
constructor() {}

ngOnInit(): void {}
}
18 changes: 18 additions & 0 deletions src/app/my-settings/my-settings-routing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { MySettingsShowComponent } from './components/my-settings-show/my-settings-show.component';
import { MySettingsComponent } from './components/my-settings/my-settings.component';

const routes: Routes = [
{
path: '',
component: MySettingsComponent,
children: [{ path: '', component: MySettingsShowComponent }],
},
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class MySettingsRoutingModule {}
Loading

0 comments on commit 0a1e201

Please sign in to comment.