From 40ade2ca06774d0b3a5608aa152b0ada7da2caf0 Mon Sep 17 00:00:00 2001 From: _Bastler <_Bastler@bstly.de> Date: Tue, 30 Mar 2021 15:14:07 +0200 Subject: [PATCH] profile improvements, aliases feature --- src/app/app-routing.module.ts | 4 +- src/app/app.module.ts | 2 + src/app/pages/account/account.component.html | 3 +- .../account/aliases/aliases.component.html | 77 +++++++++++ .../account/aliases/aliases.component.scss | 16 +++ .../account/aliases/aliases.component.spec.ts | 25 ++++ .../account/aliases/aliases.component.ts | 130 ++++++++++++++++++ .../pages/account/info/info.component.html | 2 +- src/app/pages/account/info/info.component.ts | 13 +- .../account/profile/profile.component.html | 2 +- .../account/profile/profile.component.ts | 11 +- .../account/security/security.component.ts | 1 - .../pages/register/register.component.html | 5 +- src/app/pages/register/register.component.ts | 10 +- src/app/pages/register/register.dialog.html | 2 +- .../pages/services/services.component.html | 2 + src/app/pages/user/user.component.html | 10 +- src/app/pages/user/user.component.ts | 5 +- src/app/services/i18n.service.ts | 1 + src/app/services/useralias.service.ts | 30 ++++ .../ui/profilefields/profilefield.dialog.html | 43 +++++- .../profilefields.component.html | 12 +- .../profilefields.component.scss | 17 ++- .../profilefields/profilefields.component.ts | 48 +++++-- src/assets/i18n/de-informal.json | 86 +++++++++--- src/assets/i18n/en.json | 86 +++++++++--- src/styles.scss | 10 ++ 27 files changed, 561 insertions(+), 92 deletions(-) create mode 100644 src/app/pages/account/aliases/aliases.component.html create mode 100644 src/app/pages/account/aliases/aliases.component.scss create mode 100644 src/app/pages/account/aliases/aliases.component.spec.ts create mode 100644 src/app/pages/account/aliases/aliases.component.ts create mode 100644 src/app/services/useralias.service.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 743b3a2..16da065 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -20,6 +20,7 @@ import {SecurityComponent} from './pages/account/security/security.component'; import {UnavailableComponent} from './pages/unavailable/unavailable.component'; import {NotfoundComponent} from './pages/notfound/notfound.component'; import {UserComponent} from './pages/user/user.component' +import {AliasesComponent} from './pages/account/aliases/aliases.component'; const routes: Routes = [ {path: '', redirectTo: "/services", pathMatch: 'full'}, @@ -38,8 +39,9 @@ const routes: Routes = [ {path: 'info', component: InfoComponent, canActivate: [AuthenticatedGuard]}, {path: 'profile', component: ProfileComponent, canActivate: [AuthenticatedGuard]}, + {path: 'security', component: SecurityComponent, canActivate: [AuthenticatedGuard]}, {path: 'voucher', component: VoucherComponent, canActivate: [AuthenticatedGuard]}, - {path: 'security', component: SecurityComponent, canActivate: [AuthenticatedGuard]} + {path: 'aliases', component: AliasesComponent, canActivate: [AuthenticatedGuard]} ] }, {path: 'register', component: RegisterComponent, canActivate: [AnonymousGuard]}, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index fe4156b..05bd79b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -25,6 +25,7 @@ import {SecurityComponent, SecurityTotpDialog} from './pages/account/security/se import {VoucherComponent} from './pages/account/voucher/voucher.component'; import {VoucherDialog} from './pages/account/voucher/voucher.component'; import {InfoComponent} from './pages/account/info/info.component'; +import {AliasesComponent} from './pages/account/aliases/aliases.component'; import {ProfileComponent} from './pages/account/profile/profile.component'; import {PasswordComponent} from './pages/password/password.component'; import {PasswordResetComponent} from './pages/password-reset/password-reset.component'; @@ -75,6 +76,7 @@ export class XhrInterceptor implements HttpInterceptor { VoucherComponent, VoucherDialog, InfoComponent, + AliasesComponent, ProfileComponent, PasswordComponent, PasswordResetComponent, diff --git a/src/app/pages/account/account.component.html b/src/app/pages/account/account.component.html index 449e54f..7537e54 100644 --- a/src/app/pages/account/account.component.html +++ b/src/app/pages/account/account.component.html @@ -4,8 +4,9 @@ \ No newline at end of file diff --git a/src/app/pages/account/aliases/aliases.component.html b/src/app/pages/account/aliases/aliases.component.html new file mode 100644 index 0000000..68a76e0 --- /dev/null +++ b/src/app/pages/account/aliases/aliases.component.html @@ -0,0 +1,77 @@ +

{{'user.aliases' | i18n}}

+ + + + + + + + + + + + + + + + + + + + +
{{'user.aliases.alias' | i18n}} {{ alias.alias}} {{'visibility' | i18n}} + + + {{'visibility.' + visibility + '.icon' | i18n}} {{'visibility.' + + visibility | i18n}} + + + + {{'visibility.' + alias.visibility + '.icon' | i18n}} {{'visibility.' + + alias.visibility | i18n}} + + + {{'user.aliases.delete' | i18n}} + + delete + +
+ +
+ + +

{{'user.aliases.info' | i18n}}

+

{{'user.aliases.noQuota' | i18n}}

+
+

{{'user.aliases.left' | i18n:aliasCreation}}

+ + + + {{'user.aliases.error' | i18n}} + + + + + + {{'visibility.' + visibility + '.icon' | i18n}} {{'visibility.' + + visibility | i18n}} + + + + {{'visibility.' + alias.visibility + '.icon' | i18n}} {{'visibility.' + + alias.visibility | i18n}} + + + +
+
+ + + +
+ +
\ No newline at end of file diff --git a/src/app/pages/account/aliases/aliases.component.scss b/src/app/pages/account/aliases/aliases.component.scss new file mode 100644 index 0000000..fe8d2a1 --- /dev/null +++ b/src/app/pages/account/aliases/aliases.component.scss @@ -0,0 +1,16 @@ +mat-form-field { + display: block; +} + +.mat-header-cell, +.mat-cell { + &.text-right { + text-align: right; + } +} + +.align-right{ + display: flex; + padding: 21px 0; + justify-content: flex-end; +} \ No newline at end of file diff --git a/src/app/pages/account/aliases/aliases.component.spec.ts b/src/app/pages/account/aliases/aliases.component.spec.ts new file mode 100644 index 0000000..ee004f2 --- /dev/null +++ b/src/app/pages/account/aliases/aliases.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AliasesComponent } from './aliases.component'; + +describe('AliasesComponent', () => { + let component: AliasesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AliasesComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AliasesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/account/aliases/aliases.component.ts b/src/app/pages/account/aliases/aliases.component.ts new file mode 100644 index 0000000..698b7a9 --- /dev/null +++ b/src/app/pages/account/aliases/aliases.component.ts @@ -0,0 +1,130 @@ +import {Component, OnInit, ViewChild} from '@angular/core'; +import {QuotaService} from '../../../services/quota.service'; +import {I18nService} from '../../../services/i18n.service'; +import {Sort} from '@angular/material/sort'; +import {UserAliasService} from '../../../services/useralias.service'; +import {FormBuilder, FormGroup, Validators, NgForm} from '@angular/forms'; +import {MatDialog} from '@angular/material/dialog'; +import {ConfirmDialog} from '../../../ui/confirm/confirm.component'; + +@Component({ + selector: 'app-account-aliases', + templateUrl: './aliases.component.html', + styleUrls: ['./aliases.component.scss'] +}) +export class AliasesComponent implements OnInit { + + form: FormGroup; + @ViewChild('formDirective') private formDirective: NgForm; + aliasCreation: number = 0; + aliases: any[] = []; + alias: any = { + visibility: "PROTECTED" + }; + success: boolean; + working: boolean; + + aliasesColumns = ["alias", "visibility", "delete"]; + visibilities = ["PRIVATE", "PROTECTED", "PUBLIC"]; + + constructor( + private quotaService: QuotaService, + private formBuilder: FormBuilder, + private userAliasService: UserAliasService, + private i18n : I18nService, + public dialog: MatDialog) {} + + ngOnInit(): void { + + this.form = this.formBuilder.group({ + alias: ['', Validators.required], + visibility: ['', Validators.required], + }); + + this.update(); + } + + create(): void { + this.working = true; + this.userAliasService.create(this.alias).subscribe(response => { + this.update(); + this.formDirective.resetForm(); + this.alias = { + visibility: "PROTECTED" + }; + this.working = false; + }, (error) => { + this.working = false; + if(error.status == 409) { + let errors = {}; + for(let code of error.error) { + errors[code.field] = errors[code.field] || {}; + errors[code.field][code.code] = true; + } + + for(let code in errors) { + this.form.get(code).setErrors(errors[code]); + } + } + }) + } + + updateAlias(alias) { + this.userAliasService.update(alias).subscribe((response: any) => { + alias = response; + }) + } + + update() { + this.aliasCreation = 0; + this.quotaService.quotas().subscribe((data: any) => { + for(let quota of data) { + if(quota.name == "alias_creation") { + this.aliasCreation = quota.value; + } + } + }) + + this.userAliasService.get().subscribe((data: any) => { + this.aliases = data; + }) + } + + confirmDelete(alias) { + const dialogRef = this.dialog.open(ConfirmDialog, { + data: { + 'label': 'user.aliases.confirmDelete', + 'args': [alias.alias] + } + }) + + dialogRef.afterClosed().subscribe(result => { + if(result) { + this.userAliasService.delete(alias.id).subscribe((result: any) => { + this.update(); + }) + } + }); + } + + sortData(sort: Sort) { + const data = this.aliases.slice(); + if(!sort.active || sort.direction === '') { + this.aliases = data; + return; + } + + this.aliases = data.sort((a, b) => { + const isAsc = sort.direction === 'asc'; + switch(sort.active) { + case 'alias': return this.compare(a.alias, b.alias, isAsc); + case 'visibility': return this.compare(this.i18n.get('visibility.' + a.visibility, []), this.i18n.get('visibility.' + b.visibility, []), isAsc); + default: return 0; + } + }); + } + + compare(a: number | string | String, b: number | string | String, isAsc: boolean) { + return (a < b ? -1 : 1) * (isAsc ? 1 : -1); + } +} diff --git a/src/app/pages/account/info/info.component.html b/src/app/pages/account/info/info.component.html index 63afe2e..bbb9ed5 100644 --- a/src/app/pages/account/info/info.component.html +++ b/src/app/pages/account/info/info.component.html @@ -3,4 +3,4 @@

{{'quotas' | i18n}}

{{'profile' | i18n}}

- \ No newline at end of file + \ No newline at end of file diff --git a/src/app/pages/account/info/info.component.ts b/src/app/pages/account/info/info.component.ts index 2b7eb80..4c684fc 100644 --- a/src/app/pages/account/info/info.component.ts +++ b/src/app/pages/account/info/info.component.ts @@ -1,7 +1,6 @@ -import { Component, OnInit } from '@angular/core'; -import { PermissionService } from './../../../services/permission.service'; -import { QuotaService } from './../../../services/quota.service'; -import { ProfileService } from './../../../services/profile.service'; +import {Component, OnInit} from '@angular/core'; +import {PermissionService} from './../../../services/permission.service'; +import {QuotaService} from './../../../services/quota.service'; @Component({ selector: 'app-account-info', @@ -12,9 +11,8 @@ export class InfoComponent implements OnInit { permissions = []; quotas = []; - profileFields = []; - constructor(private permissionService: PermissionService, private quotaService: QuotaService, private profileService : ProfileService) { } + constructor(private permissionService: PermissionService, private quotaService: QuotaService) {} ngOnInit(): void { this.permissionService.permissions().subscribe((data: any) => { @@ -25,9 +23,6 @@ export class InfoComponent implements OnInit { this.quotas = data; }) - this.profileService.getAll().subscribe((data: any) => { - this.profileFields = data; - }) } } diff --git a/src/app/pages/account/profile/profile.component.html b/src/app/pages/account/profile/profile.component.html index b189c6e..4c98616 100644 --- a/src/app/pages/account/profile/profile.component.html +++ b/src/app/pages/account/profile/profile.component.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/app/pages/account/profile/profile.component.ts b/src/app/pages/account/profile/profile.component.ts index e51e96f..11a30af 100644 --- a/src/app/pages/account/profile/profile.component.ts +++ b/src/app/pages/account/profile/profile.component.ts @@ -1,6 +1,4 @@ import {Component, OnInit} from '@angular/core'; -import {ProfileService} from '../../../services/profile.service'; -import {I18nService} from '../../../services/i18n.service'; @Component({ selector: 'app-account-profile', @@ -9,16 +7,11 @@ import {I18nService} from '../../../services/i18n.service'; }) export class ProfileComponent implements OnInit { - profileFields = []; - types = ["TEXT", "NUMBER", "DATE", "URL", "EMAIL", "BOOL"]; - visibilities = ["PRIVATE", "PROTECTED", "PUBLIC"]; - constructor(private profileService: ProfileService, private i18n: I18nService) {} + constructor() {} ngOnInit(): void { - this.profileService.getAll().subscribe((data: any) => { - this.profileFields = data; - }) + } } diff --git a/src/app/pages/account/security/security.component.ts b/src/app/pages/account/security/security.component.ts index 5e47aa4..05ad842 100644 --- a/src/app/pages/account/security/security.component.ts +++ b/src/app/pages/account/security/security.component.ts @@ -25,7 +25,6 @@ export class SecurityComponent implements OnInit { passwordForm: FormGroup; statusForm: FormGroup; @ViewChild('passwordFormDirective') private passwordFormDirective: NgForm; - @ViewChild('statusFormDirective') private statusFormDirective: NgForm; constructor( private formBuilder: FormBuilder, diff --git a/src/app/pages/register/register.component.html b/src/app/pages/register/register.component.html index b799915..077d15e 100644 --- a/src/app/pages/register/register.component.html +++ b/src/app/pages/register/register.component.html @@ -34,9 +34,10 @@ - {{'email.primary' | i18n}} info + {{'email.primary' | i18n}} + info {{'pgp.privateKey.copyKey' | i18n}} - diff --git a/src/app/pages/services/services.component.html b/src/app/pages/services/services.component.html index 6991e00..7f45993 100644 --- a/src/app/pages/services/services.component.html +++ b/src/app/pages/services/services.component.html @@ -1,5 +1,7 @@

{{'services' | i18n}}

+

{{'services.empty' | i18n}}

+
diff --git a/src/app/pages/user/user.component.html b/src/app/pages/user/user.component.html index 404c03f..cd7ed7d 100644 --- a/src/app/pages/user/user.component.html +++ b/src/app/pages/user/user.component.html @@ -2,8 +2,14 @@
-

{{username}}

- +

{{model.username}}

+ +
+

{{'user.aliases' | i18n}}

+ + {{alias}} + +
diff --git a/src/app/pages/user/user.component.ts b/src/app/pages/user/user.component.ts index efc7a3d..3b672df 100644 --- a/src/app/pages/user/user.component.ts +++ b/src/app/pages/user/user.component.ts @@ -11,20 +11,19 @@ import {ProfileService} from '../../services/profile.service'; export class UserComponent implements OnInit { username; - profileFields = []; + model: any; error = false; success = false; constructor( private profileService: ProfileService, - private router: Router, private route: ActivatedRoute) {} async ngOnInit() { this.username = this.route.snapshot.paramMap.get('username'); this.profileService.getAllForUser(this.username).subscribe((data: any) => { - this.profileFields = data; this.success = true; + this.model = data; }, error => { this.error = error; }) diff --git a/src/app/services/i18n.service.ts b/src/app/services/i18n.service.ts index c1cec7d..6eb6191 100644 --- a/src/app/services/i18n.service.ts +++ b/src/app/services/i18n.service.ts @@ -67,6 +67,7 @@ export class I18nService { } getInternal(key, args: string[], from): string { + key += ''; if(!from) { return key; } else if(from[key]) { diff --git a/src/app/services/useralias.service.ts b/src/app/services/useralias.service.ts new file mode 100644 index 0000000..3773251 --- /dev/null +++ b/src/app/services/useralias.service.ts @@ -0,0 +1,30 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; + +import {environment} from '../../environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class UserAliasService { + + constructor(private http: HttpClient) { + } + + get() { + return this.http.get(environment.apiUrl + "/users/aliases"); + } + + create(alias) { + return this.http.post(environment.apiUrl + "/users/aliases", alias); + } + + update(alias) { + return this.http.patch(environment.apiUrl + "/users/aliases", alias); + } + + delete(id) { + return this.http.delete(environment.apiUrl + "/users/aliases/" + id); + } + +} \ No newline at end of file diff --git a/src/app/ui/profilefields/profilefield.dialog.html b/src/app/ui/profilefields/profilefield.dialog.html index 4b8b389..0b2b6db 100644 --- a/src/app/ui/profilefields/profilefield.dialog.html +++ b/src/app/ui/profilefields/profilefield.dialog.html @@ -4,35 +4,52 @@ + + {{'profileField.error.name' | i18n}} + - {{'profileField.type.' + type | i18n}} + + {{'profileField.error.type' | i18n}} +
+ + {{'profileField.error.TEXT' | i18n}} + + + {{'profileField.error.DATE' | i18n}} + + + {{'profileField.error.URL' | i18n}} + + + {{'profileField.error.EMAIL' | i18n}} + @@ -41,31 +58,49 @@ + + {{'profileField.error.NUMBER' | i18n}} + + + {{'profileField.error.BLOB' | i18n}} +
+ placeholder="{{'visibility' | i18n}}"> - {{'profileField.visibility.' + visibility | i18n}} + {{'visibility.' + visibility + '.icon' | i18n}} {{'visibility.' + + visibility | i18n}} + + + {{'visibility.' + profileField.visibility + '.icon' | i18n}} {{'visibility.' + + profileField.visibility | i18n}} + + + {{'profileField.error.visibility' | i18n}} + + + {{'profileField.error.index' | i18n}} + - \ No newline at end of file diff --git a/src/app/ui/profilefields/profilefields.component.html b/src/app/ui/profilefields/profilefields.component.html index 4d5b6b2..d4ec736 100644 --- a/src/app/ui/profilefields/profilefields.component.html +++ b/src/app/ui/profilefields/profilefields.component.html @@ -24,14 +24,14 @@ - {{'profileField.visibility' | i18n}} - {{'profileField.visibility.' + profileField.visibility | i18n}} + {{'visibility' | i18n}} + {{'visibility.' + profileField.visibility + '.icon' | i18n}} {{'visibility.' + profileField.visibility | i18n}} - {{'profileField.edit' | i18n}} - + {{'profileField.edit' | i18n}} + edit @@ -39,8 +39,8 @@ - {{'profileField.delete' | i18n}} - + {{'profileField.delete' | i18n}} + delete diff --git a/src/app/ui/profilefields/profilefields.component.scss b/src/app/ui/profilefields/profilefields.component.scss index ccd3994..d7b0876 100644 --- a/src/app/ui/profilefields/profilefields.component.scss +++ b/src/app/ui/profilefields/profilefields.component.scss @@ -1,3 +1,16 @@ table { - width: 100%; -} \ No newline at end of file + width: 100%; +} + +.mat-header-cell, +.mat-cell { + &.text-right { + text-align: right; + } +} + +.align-right { + display: flex; + padding: 21px 0; + justify-content: flex-end; +} diff --git a/src/app/ui/profilefields/profilefields.component.ts b/src/app/ui/profilefields/profilefields.component.ts index 2a3b25b..b8e3d57 100644 --- a/src/app/ui/profilefields/profilefields.component.ts +++ b/src/app/ui/profilefields/profilefields.component.ts @@ -2,9 +2,9 @@ import {Component, OnInit, Inject, Input} from '@angular/core'; import {Sort} from '@angular/material/sort'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog'; +import {ConfirmDialog} from '../confirm/confirm.component'; import {I18nService} from '../../services/i18n.service'; import {ProfileService} from '../../services/profile.service'; -import {ConfirmDialog} from '../confirm/confirm.component'; @Component({ selector: 'app-profilefields', @@ -13,9 +13,10 @@ import {ConfirmDialog} from '../confirm/confirm.component'; }) export class ProfileFieldsComponent implements OnInit { - @Input() profileFields: Array; + @Input() username; @Input() edit; profileFieldColumns = ["name", "value"]; + profileFields: Array = []; constructor(private i18n: I18nService, private profileService: ProfileService, public dialog: MatDialog) {} @@ -25,6 +26,20 @@ export class ProfileFieldsComponent implements OnInit { this.profileFieldColumns.push("edit"); this.profileFieldColumns.push("delete"); } + this.update(); + } + + + update() { + if(this.username) { + this.profileService.getAllForUser(this.username).subscribe((data: any) => { + this.profileFields = data.profileFields; + }) + } else { + this.profileService.getAll().subscribe((data: any) => { + this.profileFields = data; + }) + } } sortData(sort: Sort) { @@ -40,6 +55,7 @@ export class ProfileFieldsComponent implements OnInit { case 'name': return this.compare(this.i18n.get('profileField.name.' + a.name, []), this.i18n.get('profileField.name.' + b.name, []), isAsc); case 'value': return this.compare(a.value, b.value, isAsc); case 'index': return this.compare(a.index, b.index, isAsc); + case 'visibility': return this.compare(this.i18n.get('visibility.' + a.visibility, []), this.i18n.get('visibility.' + b.visibility, []), isAsc); default: return 0; } }); @@ -85,8 +101,7 @@ export class ProfileFieldsComponent implements OnInit { dialogRef.afterClosed().subscribe(result => { if(result) { this.profileService.delete(profileField.name).subscribe((result: any) => { - this.profileFields.splice(this.profileFields.indexOf(profileField), 1); - this.profileFields = [...this.profileFields]; + this.update(); }) } }); @@ -100,10 +115,7 @@ export class ProfileFieldsComponent implements OnInit { dialogRef.afterClosed().subscribe(result => { if(result) { - this.profileService.createOrUpdate(result).subscribe((result: any) => { - this.profileFields.push(result); - this.profileFields = [...this.profileFields]; - }); + this.update(); } }); @@ -132,6 +144,7 @@ export class ProfileFieldDialog { visibilities = ["PRIVATE", "PROTECTED", "PUBLIC"]; constructor( + private profileService: ProfileService, private formBuilder: FormBuilder, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) { @@ -157,6 +170,25 @@ export class ProfileFieldDialog { } } + + save(profileField) { + this.profileService.createOrUpdate(profileField).subscribe((result: any) => { + this.dialogRef.close(profileField); + }, (error) => { + if(error.status == 409) { + let errors = {}; + for(let code of error.error) { + errors[code.field] = errors[code.field] || {}; + errors[code.field][code.code] = true; + } + + for(let code in errors) { + this.form.get(code).setErrors(errors[code]); + } + } + }); + } + } diff --git a/src/assets/i18n/de-informal.json b/src/assets/i18n/de-informal.json index a162f86..d66526e 100644 --- a/src/assets/i18n/de-informal.json +++ b/src/assets/i18n/de-informal.json @@ -7,7 +7,10 @@ "email": { ".": "E-Mail Adresse", "invalid": "ungültige E-Mail Adresse", - "primary": "primäre E-Mail Adresse" + "primary": { + ".": "primäre E-Mail Adresse", + "hint": "Eine primäre E-Mail Adresse dient dazu eine andere Kontaktmöglichkeit als deine we.bstly Adresse anzugeben." + } }, "greet": "Hallo {0}", "help": "Hilfe", @@ -91,12 +94,38 @@ "create": "Neues Profilfeld hinzufügen", "delete": "Löschen", "edit": "Bearbeiten", + "error": { + "BLOB": { + ".": "Kein gültiger Binärblob" + }, + "BOOL": { + ".": "Kein gültiger Boolean" + }, + "DATE": { + ".": "Kein gültiges Datum" + }, + "EMAIL": { + ".": "Keine gültige E-Mail Adresse" + }, + "name": "Name zu kurz oder ungültig", + "NUMBER": { + ".": "Keine gültige Nummer" + }, + "TEXT": { + ".": "Textfeld zu lang" + }, + "type": "Ungültiger Typ für dieses Profilfeld", + "URL": { + ".": "Keine gültige URL" + }, + "visibility": "Keine gültige Sichtbarkeit für Profilfeld" + }, "index": "Index", "name": { ".": "Key", - "darkTheme" : "Dunkles Thema", + "darkTheme": "Dunkles Thema", "email": "E-Mail Adresse", - "locale" : "Sprache", + "locale": "Sprache", "primaryEmail": "E-Mail Adresse primär verwenden", "prtyMap": "Partey Karte", "publicKey": "Öffentlicher PGP Schlüssel" @@ -126,19 +155,7 @@ ".": "URL" } }, - "value": "Wert", - "visibility": { - ".": "Sichtbarkeit", - "PRIVATE": { - ".": "Privat" - }, - "PROTECTED": { - ".": "Geschützt" - }, - "PUBLIC": { - ".": "Öffentlich" - } - } + "value": "Wert" }, "quotas": { ".": "Quotas", @@ -182,7 +199,7 @@ }, "status": { ".": "Status", - "change" : "Status aktualisieren", + "change": "Status aktualisieren", "hint": "Dein User Status gibt an, wie mit deinen Account Daten umgegangen wird wenn deine Berechtigungen ausgelaufen sind.", "NORMAL": { ".": "Normal", @@ -196,7 +213,7 @@ ".": "Schlafen", "hint": "Dein Account sowie alle gespeicherten Daten werden nicht(!) gelöscht. Du kannst deinen Account also jederzeit wieder reaktivieren." }, - "success" : "Status erfolgreich geändert" + "success": "Status erfolgreich geändert" } }, "service-unavailable": { @@ -207,6 +224,13 @@ }, "services": { ".": "Dienste", + "alias_creation": { + "icon": "alternate_email", + "subtitle": "Anlegen von alternativen Namen", + "text": "Du kannst zusätzlich zu deinem Usernamen noch alternative Namen anlegen.", + "title": "Alternative Namen" + }, + "empty": "Du hast aktuell keine Berechtigungen zur Nutzung von Diensten.", "gitea": { "icon": "code", "subtitle": "Git-Repositories", @@ -284,6 +308,17 @@ }, "user": { ".": "User", + "aliases": { + ".": "Alternative Namen", + "alias": "Alternativer Name", + "confirmDelete": "Möchtest du wirklich den alternativen Namen '{0}' löschen?", + "create": "Alternativen Namen anlegen", + "delete": "Löschen", + "error": "Dieser alternative Name ist leider nicht zulässig.", + "info": "Du kannst hier alternative Namen erstellen. Die Anzahl wird über eine Quota begrenzt.", + "left": "Du kannst noch {0} alternative(n) Namen anlegen.", + "noQuota": "Deine Quota für alternative Namen ist leider aufgebraucht." + }, "unavailable": { ".": "Zugriff verweigert", "text": "Dieser Benutzer existiert nicht oder du hast keine Berechtigungen, um auf das Profil zuzugreifen." @@ -294,6 +329,21 @@ "error": "Bitte wähle einen anderen Usernamen aus.", "missing": "Bitte gebe einen Usernamen an." }, + "visibility": { + ".": "Sichtbarkeit", + "PRIVATE": { + ".": "Privat", + "icon" : "lock" + }, + "PROTECTED": { + ".": "Geschützt", + "icon" : "shield" + }, + "PUBLIC": { + ".": "Öffentlich", + "icon" : "public" + } + }, "voucher": { ".": "Gutscheincode", "code": "Code", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 8395f44..bcc4310 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -7,7 +7,10 @@ "email": { ".": "Email address", "invalid": "invalid email address", - "primary": "primary email address" + "primary": { + ".": "primary email address", + "hint": "A primary email address is used for contact you instead of you we.bstly address." + } }, "greet": "Hello {0}", "help": "Help", @@ -91,12 +94,38 @@ "create": "Add new profile field", "delete": "Delete", "edit": "Edit", + "error": { + "BLOB": { + ".": "Invalid binary blob" + }, + "BOOL": { + ".": "Invalid boolean" + }, + "DATE": { + ".": "Invalid date" + }, + "EMAIL": { + ".": "Invalid email address" + }, + "name": "name invalid or too short", + "NUMBER": { + ".": "Invalid numeric" + }, + "TEXT": { + ".": "Text too long" + }, + "type": "Invalid type for this profile field", + "URL": { + ".": "Invalid URL" + }, + "visibility": "Invalid visibility for profile field" + }, "index": "Index", "name": { ".": "Key", - "darkTheme" : "Dark Theme", + "darkTheme": "Dark Theme", "email": "Email address", - "locale" : "Locale", + "locale": "Locale", "primaryEmail": "Use email address primary", "prtyMap": "Partey map", "publicKey": "Public PGP key" @@ -126,19 +155,7 @@ ".": "URL" } }, - "value": "Value", - "visibility": { - ".": "Visibility", - "PRIVATE": { - ".": "Private" - }, - "PROTECTED": { - ".": "Protected" - }, - "PUBLIC": { - ".": "Public" - } - } + "value": "Value" }, "quotas": { ".": "Quotas", @@ -182,7 +199,7 @@ }, "status": { ".": "Status", - "change" : "Change status", + "change": "Change status", "hint": "You Account Status controls the handling of your account data after all permissions expired.", "NORMAL": { ".": "Normal", @@ -196,7 +213,7 @@ ".": "Sleep", "hint": "Your account and all your data will not(!) be deleted. So you have reactivate your account anytime." }, - "success" : "Status successfully changed" + "success": "Status successfully changed" } }, "service-unavailable": { @@ -207,6 +224,13 @@ }, "services": { ".": "Services", + "alias_creation": { + "icon": "alternate_email", + "subtitle": "Creation of Aliases", + "text": "You can add additional aliases besides your username.", + "title": "Aliases" + }, + "empty" : "You have insufficient permissions to use any services.", "gitea": { "icon": "code", "subtitle": "Git-Repositories", @@ -284,6 +308,17 @@ }, "user": { ".": "User", + "aliases": { + ".": "Aliases", + "alias": "Alias", + "confirmDelete": "Are you sure you want to delete your alias '{0}'?", + "create": "Add Alias", + "delete": "Delete", + "error": "This alias is invalid.", + "info": "You can add Aliases here. The number is limited due to a quota.", + "left": "You have {0} Alias(es) left.", + "noQuota": "Your quota for Aliases is depleted." + }, "unavailable": { ".": "Access denied", "text": "The provided user does not exists or you have insufficient privileges to access profile." @@ -294,6 +329,21 @@ "error": "Please choose a different username.", "missing": "Please enter a valid username." }, + "visibility": { + ".": "Visibility", + "PRIVATE": { + ".": "Private", + "icon" : "lock" + }, + "PROTECTED": { + ".": "Protected", + "icon" : "shield" + }, + "PUBLIC": { + ".": "Public", + "icon" : "public" + } + }, "voucher": { ".": "Voucher", "code": "Code", diff --git a/src/styles.scss b/src/styles.scss index 0ec1c2a..226218d 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -194,6 +194,16 @@ mat-sidenav-container { color: $warn; } +.align-right{ + display: flex; + padding: 21px 0; + justify-content: flex-end; +} + +.mat-tooltip-trigger { + cursor: pointer; +} + mat-card.warn, mat-card.accent {