From a6aaaf6850246ced0ca92e4441418d7510954146 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Sat, 17 Mar 2018 23:07:20 +0100 Subject: [PATCH 1/3] Improve selecting location - lower initial zoom when no location was set - remove location button - don't allow saving the null island - minor fixes and refactors --- .../select-location.component.html | 8 +++++- .../select-location.component.scss | 5 ++++ .../select-location.component.spec.ts | 6 ++++- .../select-location.component.ts | 25 +++++++++++++----- .../user-edit-location.component.html | 2 +- .../user-edit-location.component.spec.ts | 26 ++++++++++++++----- .../user-edit-location.component.ts | 10 ++++--- 7 files changed, 64 insertions(+), 18 deletions(-) diff --git a/src/app/shared/select-location/select-location.component.html b/src/app/shared/select-location/select-location.component.html index 4c2c6af..0d7a698 100644 --- a/src/app/shared/select-location/select-location.component.html +++ b/src/app/shared/select-location/select-location.component.html @@ -1,5 +1,11 @@ + (click)="updateLocation()">Save Location + +no location saved
diff --git a/src/app/shared/select-location/select-location.component.scss b/src/app/shared/select-location/select-location.component.scss index bfd801f..74a8908 100644 --- a/src/app/shared/select-location/select-location.component.scss +++ b/src/app/shared/select-location/select-location.component.scss @@ -18,3 +18,8 @@ background-repeat: no-repeat; z-index: 1000; } + +.no-location-info { + font-style: italic; + color: grey; +} diff --git a/src/app/shared/select-location/select-location.component.spec.ts b/src/app/shared/select-location/select-location.component.spec.ts index 04d083b..abab9e4 100644 --- a/src/app/shared/select-location/select-location.component.spec.ts +++ b/src/app/shared/select-location/select-location.component.spec.ts @@ -1,6 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SelectLocationComponent } from './select-location.component'; +import { NotificationsService } from 'app/notifications/notifications.service'; describe('SelectLocationComponent', () => { let component: SelectLocationComponent; @@ -8,7 +9,10 @@ describe('SelectLocationComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ SelectLocationComponent ] + declarations: [ SelectLocationComponent ], + providers: [ + NotificationsService + ] }) .compileComponents(); })); diff --git a/src/app/shared/select-location/select-location.component.ts b/src/app/shared/select-location/select-location.component.ts index 610d61f..f5302ba 100644 --- a/src/app/shared/select-location/select-location.component.ts +++ b/src/app/shared/select-location/select-location.component.ts @@ -1,10 +1,10 @@ import { Component, OnInit, Input, Output, ViewChild, ElementRef, EventEmitter } from '@angular/core'; - import { inRange } from 'lodash'; - import { LatLng, Map, TileLayer } from 'leaflet'; import 'leaflet'; +import { NotificationsService } from 'app/notifications/notifications.service'; + @Component({ selector: 'app-select-location', templateUrl: './select-location.component.html', @@ -23,16 +23,18 @@ export class SelectLocationComponent implements OnInit { @Input() disabled = false; // tslint:disable-next-line:no-output-on-prefix - @Output() public onSubmit = new EventEmitter<[number, number]>(); + @Output() public submit = new EventEmitter<[number, number]>(); - constructor() { } + constructor(private notify: NotificationsService) { } ngOnInit() { const location = (this.location) ? this.location : [0, 0]; + const zoom = (this.location) ? 8 : 1; + this.map = new Map(this.locationContainer.nativeElement, { center: new LatLng(location[0], location[1]), - zoom: 8, + zoom, scrollWheelZoom: 'center', // zoom to the center point layers: [ new TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { @@ -69,7 +71,18 @@ export class SelectLocationComponent implements OnInit { const { lat, lng } = this.map.getCenter(); const location: [number, number] = [lat, lng]; - this.onSubmit.emit(location); + // prevent saving the null island + if (location[0] === 0 && location[1] === 0) { + this.notify.error('Please choose a non-default location.'); + + return; + } + + this.submit.emit(location); + } + + public removeLocation() { + this.submit.emit(null); } } diff --git a/src/app/user-edit/user-edit-location/user-edit-location.component.html b/src/app/user-edit/user-edit-location/user-edit-location.component.html index 869978c..e3bc4cb 100644 --- a/src/app/user-edit/user-edit-location/user-edit-location.component.html +++ b/src/app/user-edit/user-edit-location/user-edit-location.component.html @@ -1,2 +1,2 @@ - + diff --git a/src/app/user-edit/user-edit-location/user-edit-location.component.spec.ts b/src/app/user-edit/user-edit-location/user-edit-location.component.spec.ts index 7c461a7..2ead182 100644 --- a/src/app/user-edit/user-edit-location/user-edit-location.component.spec.ts +++ b/src/app/user-edit/user-edit-location/user-edit-location.component.spec.ts @@ -5,9 +5,9 @@ import { By } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs/Observable'; -import { ModelService } from '../../model.service'; -import { NotificationsService } from '../../notifications/notifications.service'; -import { User } from '../../shared/types'; +import { ModelService } from 'app/model.service'; +import { NotificationsService } from 'app/notifications/notifications.service'; +import { User } from 'app/shared/types'; import { UserEditLocationComponent } from './user-edit-location.component'; @@ -16,7 +16,7 @@ class SelectLocationStubComponent { @Input() location; @Input() disabled; // tslint:disable-next-line:no-output-on-prefix - @Output() onSubmit = new EventEmitter<[number, number]>(); + @Output() submit = new EventEmitter<[number, number]>(); } class ActivatedRouteStub { @@ -70,13 +70,27 @@ describe('UserEditLocationComponent', () => { // emit the location-update event const selectLocation = fixture.debugElement.query(By.css('app-select-location')); - selectLocation.componentInstance.onSubmit.emit([7, 9]); + selectLocation.componentInstance.submit.emit([7, 9]); // wait for model.updateUser to resolve tick(); expect(spyNotificationsInfo.calls.count()).toEqual(1); - expect(spyNotificationsInfo.calls.first().args[0]).toEqual('Your location was updated.'); + expect(spyNotificationsInfo.calls.first().args[0]).toEqual('Your location was saved.'); + })); + + it('should notify about success after a successful removal', fakeAsync(() => { + + // emit the location-update event + const selectLocation = fixture.debugElement.query(By.css('app-select-location')); + selectLocation.componentInstance.submit.emit(null); + + // wait for model.updateUser to resolve + tick(); + + expect(spyNotificationsInfo.calls.count()).toEqual(1); + + expect(spyNotificationsInfo.calls.first().args[0]).toEqual('Your location was removed.'); })); }); diff --git a/src/app/user-edit/user-edit-location/user-edit-location.component.ts b/src/app/user-edit/user-edit-location/user-edit-location.component.ts index e93253c..44346a5 100644 --- a/src/app/user-edit/user-edit-location/user-edit-location.component.ts +++ b/src/app/user-edit/user-edit-location/user-edit-location.component.ts @@ -25,7 +25,10 @@ export class UserEditLocationComponent implements OnInit { }); } - async updateLocation(location: [number, number]) { + /** + * Save or remove the location. + */ + async updateLocation(location: [number, number]|null) { this.isSelectLocationDisabled = true; const updatedUser = await this.model.updateUser(this.user.username, { location }); @@ -34,7 +37,8 @@ export class UserEditLocationComponent implements OnInit { this.isSelectLocationDisabled = false; - this.notify.info('Your location was updated.'); + // notify about success + const savedOrRemoved = (location) ? 'saved' : 'removed'; + this.notify.info(`Your location was ${savedOrRemoved}.`); } - } From bc10d27d499c7e39429839f3acc19f662f1d5cfe Mon Sep 17 00:00:00 2001 From: mrkvon Date: Sun, 18 Mar 2018 01:51:19 +0100 Subject: [PATCH 2/3] improve contacts - add Contacts link to user menu - show reference - add meaning of contact colors to contact trust title - refactor --- src/app/header/header.component.html | 1 + .../user-contact/user-contact.component.html | 35 ++++++++++++++++--- .../user-contact/user-contact.component.scss | 31 +++++++++++++++- .../user-contact.component.spec.ts | 18 ++++++---- .../user-contact/user-contact.component.ts | 31 ++++++++++++++++ 5 files changed, 104 insertions(+), 12 deletions(-) diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index 2dec90a..7f11977 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -78,6 +78,7 @@ Profile Edit Profile + Contacts Verify Email Account Logout diff --git a/src/app/user/contacts/user-contact/user-contact.component.html b/src/app/user/contacts/user-contact/user-contact.component.html index 5802e8e..2d101a2 100644 --- a/src/app/user/contacts/user-contact/user-contact.component.html +++ b/src/app/user/contacts/user-contact/user-contact.component.html @@ -1,12 +1,39 @@
-
{{contact.from.givenName}} {{contact.from.familyName}}
-
+ + +
+ {{contact.from.username}} - + + + + subject + +
+
+
+ reference:
- + + +
pending
diff --git a/src/app/user/contacts/user-contact/user-contact.component.scss b/src/app/user/contacts/user-contact/user-contact.component.scss index 50c37c0..ca1a15c 100644 --- a/src/app/user/contacts/user-contact/user-contact.component.scss +++ b/src/app/user/contacts/user-contact/user-contact.component.scss @@ -7,6 +7,8 @@ $trust-diameter: 1em; $trust-color-1: green; $trust-color-2: yellow; +$content-padding: ($contact-width - $avatar-size)/2; + .user-contact-wrapper { width: $contact-width; border: 3px solid $darker; @@ -18,12 +20,13 @@ $trust-color-2: yellow; width: $avatar-size; height: $avatar-size; display: block; - margin: ($contact-width - $avatar-size)/2 auto; + margin: $content-padding auto; } .contact-username-trust { text-align: center; display: block; + padding-bottom: $content-padding; } .contact-trust-1 { @@ -42,6 +45,10 @@ $trust-color-2: yellow; color: $trust-color-1; } +.contact-trust { + cursor: default; +} + .contact-username { color: gray; } @@ -49,3 +56,25 @@ $trust-color-2: yellow; .contact-actions { text-align: center; } + +.contact-reference-toggle { + cursor: pointer; + + mat-icon { + height: 1em; + width: 1em; + font-size: 1em; + vertical-align: middle; + } +} + +.contact-reference { + font-size: small; + text-align: justify; + padding: 0 $content-padding $content-padding; + + .reference-label { + color: grey; + font-style: italic; + } +} diff --git a/src/app/user/contacts/user-contact/user-contact.component.spec.ts b/src/app/user/contacts/user-contact/user-contact.component.spec.ts index 06611d9..1d1f6b5 100644 --- a/src/app/user/contacts/user-contact/user-contact.component.spec.ts +++ b/src/app/user/contacts/user-contact/user-contact.component.spec.ts @@ -1,11 +1,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - +import { RouterTestingModule } from '@angular/router/testing'; import { Input, Component } from '@angular/core'; import { UserContactComponent } from './user-contact.component'; - -import { Contact } from '../../../shared/types'; -import { RouterLinkStubDirective } from '../../../../testing/router-stubs'; +import { Contact } from 'app/shared/types'; +import { EditorOutputComponent } from 'app/shared/editor-output/editor-output.component'; +import { MaterialModule } from 'app/material.module'; @Component({ selector: 'app-avatar', template: '' }) class AvatarStubComponent { @@ -19,10 +19,14 @@ describe('UserContactComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ - UserContactComponent, - RouterLinkStubDirective, - AvatarStubComponent + AvatarStubComponent, + EditorOutputComponent, + UserContactComponent ], + imports: [ + MaterialModule, + RouterTestingModule + ] }) .compileComponents(); })); diff --git a/src/app/user/contacts/user-contact/user-contact.component.ts b/src/app/user/contacts/user-contact/user-contact.component.ts index eb7a46f..752afa0 100644 --- a/src/app/user/contacts/user-contact/user-contact.component.ts +++ b/src/app/user/contacts/user-contact/user-contact.component.ts @@ -15,9 +15,40 @@ export class UserContactComponent implements OnInit { @Input() public contact: Contact; + public referenceVisibility = false; + + // TODO this should be dried + public trustLevels = [ + { + value: 1, + label: 'some trust: not met in reality' + }, + { + value: 2, + label: 'trust: acquaintance, friend, ...' + }, + { + value: 4, + label: 'high trust: good friend, collaborator, ...' + }, + { + value: 8, + label: 'full trust: close friend, family, long term collaborator, ...' + } + ]; + constructor() { } ngOnInit() { } + get trustLevelDescription(): string { + const level = this.trustLevels.find(lvl => lvl.value === this.contact.trust); + return level.label; + } + + public toggleReferenceVisibility() { + this.referenceVisibility = !this.referenceVisibility; + } + } From ea721b5d19e6e6004d80f40b1b9fb215872fb410 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Sun, 18 Mar 2018 02:35:04 +0100 Subject: [PATCH 3/3] show proper error message when email validation fails --- src/app/model.service.ts | 7 +++++- .../verify-email-code.component.html | 5 ++++ .../verify-email-code.component.spec.ts | 24 +++++++++++++------ .../verify-email-code.component.ts | 4 ++-- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/app/model.service.ts b/src/app/model.service.ts index 209e025..b947af1 100644 --- a/src/app/model.service.ts +++ b/src/app/model.service.ts @@ -106,7 +106,12 @@ export class ModelService { const { email, token } = response.meta; return { email, token }; } catch (e) { - throw { status: e.status, message: 'todo error' }; + throw { status: e.status, message: getErrorMessage(e) }; + } + + function getErrorMessage(e): string { + const error = e.error.errors[0]; + return (error.title === 'invalid request') ? error.detail : error.title; } } diff --git a/src/app/verify-email-code/verify-email-code.component.html b/src/app/verify-email-code/verify-email-code.component.html index e69de29..bd6bfef 100644 --- a/src/app/verify-email-code/verify-email-code.component.html +++ b/src/app/verify-email-code/verify-email-code.component.html @@ -0,0 +1,5 @@ +
+ verifying... +
+ + diff --git a/src/app/verify-email-code/verify-email-code.component.spec.ts b/src/app/verify-email-code/verify-email-code.component.spec.ts index 5309782..2ae7111 100644 --- a/src/app/verify-email-code/verify-email-code.component.spec.ts +++ b/src/app/verify-email-code/verify-email-code.component.spec.ts @@ -3,10 +3,11 @@ import { ActivatedRoute, Router } from '@angular/router'; import { VerifyEmailCodeComponent } from './verify-email-code.component'; -import { AuthService } from '../auth.service'; -import { HeaderControlService } from '../header-control.service'; -import { ModelService } from '../model.service'; -import { NotificationsService } from '../notifications/notifications.service'; +import { AuthService } from 'app/auth.service'; +import { FofComponent } from 'app/fof/fof.component'; +import { HeaderControlService } from 'app/header-control.service'; +import { ModelService } from 'app/model.service'; +import { NotificationsService } from 'app/notifications/notifications.service'; class ActivatedRouteStub { snapshot = { @@ -52,7 +53,10 @@ describe('VerifyEmailCodeComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ VerifyEmailCodeComponent ], + declarations: [ + FofComponent, + VerifyEmailCodeComponent + ], providers: [ { provide: AuthService, useValue: authStubService }, { provide: ActivatedRoute, useClass: ActivatedRouteStub }, @@ -92,7 +96,10 @@ describe('VerifyEmailCodeComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ VerifyEmailCodeComponent ], + declarations: [ + FofComponent, + VerifyEmailCodeComponent + ], providers: [ { provide: AuthService, useClass: AuthStubService }, { provide: ActivatedRoute, useClass: ActivatedRouteStub }, @@ -143,7 +150,10 @@ describe('VerifyEmailCodeComponent', () => { describe('failed verification', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ VerifyEmailCodeComponent ], + declarations: [ + FofComponent, + VerifyEmailCodeComponent + ], providers: [ { provide: AuthService, useClass: AuthStubService }, { provide: ActivatedRoute, useClass: ActivatedRouteStub }, diff --git a/src/app/verify-email-code/verify-email-code.component.ts b/src/app/verify-email-code/verify-email-code.component.ts index c576543..4d6fd33 100644 --- a/src/app/verify-email-code/verify-email-code.component.ts +++ b/src/app/verify-email-code/verify-email-code.component.ts @@ -16,6 +16,7 @@ export class VerifyEmailCodeComponent implements OnInit, OnDestroy { public verifying = true; public failed = false; + public errorMessage = ''; // this is used to launch the after-success part of the page and hide the form public verificationSuccess: boolean; @@ -68,13 +69,12 @@ export class VerifyEmailCodeComponent implements OnInit, OnDestroy { // on success // inform this.notify.info(`your email ${email} was successfully verified`); - // TODO login, when we get jwt token this.auth.login(token); // redirect to /home await this.router.navigate(['/']); } catch (e) { this.failed = true; - // TODO better error reporting + this.errorMessage = `verification error: ${e.message}`; this.notify.error(`there was an error: ${e.message}`); } finally { this.verifying = false;