add invites
This commit is contained in:
@@ -23,6 +23,7 @@ import {UserComponent} from './pages/user/user.component'
|
||||
import {JitsiComponent} from './pages/jitsi/jitsi.component'
|
||||
import {AliasesComponent} from './pages/account/aliases/aliases.component';
|
||||
import {DomainsComponent} from './pages/account/domains/domains.component';
|
||||
import {InvitesComponent} from './pages/invites/invites.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', redirectTo: "/services", pathMatch: 'full'},
|
||||
@@ -50,13 +51,14 @@ const routes: Routes = [
|
||||
{path: 'register', component: RegisterComponent, canActivate: [AnonymousGuard]},
|
||||
{path: 'tokens', component: TokensComponent, canActivate: [AuthGuard]},
|
||||
{path: 'jitsi', component: JitsiComponent, canActivate: [AuthenticatedGuard]},
|
||||
{path: 'invites/:quota', component: InvitesComponent, canActivate: [AuthenticatedGuard]},
|
||||
{path: 'unavailable', component: UnavailableComponent},
|
||||
{path: 'p/:username', component: UserComponent, canActivate: [AuthUpdateGuard]},
|
||||
{path: '**', component: NotfoundComponent, pathMatch: 'full', canActivate: [AuthUpdateGuard]},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload', relativeLinkResolution: 'legacy' })],
|
||||
imports: [RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload', relativeLinkResolution: 'legacy'})],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {LoginTotpComponent} from './pages/login-totp/login-totp.component';
|
||||
import {FormLoginComponent} from './pages/form-login/form-login.component';
|
||||
import {FormLoginTotpComponent} from './pages/form-login-totp/form-login-totp.component';
|
||||
import {TokensComponent} from './pages/tokens/tokens.component';
|
||||
import {InvitesComponent} from './pages/invites/invites.component';
|
||||
import {PermissionsComponent} from './ui/permissions/permissions.component';
|
||||
import {ProfileFieldDialog, ProfileFieldsComponent, ProfileFieldBlob} from './ui/profilefields/profilefields.component';
|
||||
import {QuotasComponent} from './ui/quotas/quotas.component';
|
||||
@@ -70,6 +71,7 @@ export class XhrInterceptor implements HttpInterceptor {
|
||||
FormLoginComponent,
|
||||
FormLoginTotpComponent,
|
||||
TokensComponent,
|
||||
InvitesComponent,
|
||||
ServicesComponent,
|
||||
PermissionsComponent,
|
||||
ProfileFieldsComponent, ProfileFieldDialog, ProfileFieldBlob,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<h3>{{'invites' | i18n}}</h3>
|
||||
|
||||
<table mat-table matSort [dataSource]="invites">
|
||||
|
||||
<ng-container matColumnDef="starts">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'invite.starts' | i18n}} </th>
|
||||
<td mat-cell *matCellDef="let invite"> {{ invite.starts | date:datetimeformat}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="expires">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'invite.expires' | i18n}} </th>
|
||||
<td mat-cell *matCellDef="let invite"> {{ invite.expires | date:datetimeformat}} </td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-container matColumnDef="link">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'invite.link' | i18n}} </th>
|
||||
<td mat-cell *matCellDef="let invite"> <a href="{{ invite.codeLink}}" target="_blank" mat-button color="accent">
|
||||
{{
|
||||
invite.code}}</a> </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="note">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'invite.note' | i18n}} </th>
|
||||
<td mat-cell *matCellDef="let invite"> {{ invite.note}} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="inviteColumns"></tr>
|
||||
<tr mat-row *matRowDef="let myRowData; columns: inviteColumns"></tr>
|
||||
</table>
|
||||
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<p>{{'invites.info' | i18n}}</p>
|
||||
<p *ngIf="!inviteQuota">{{'invites.noQuota' | i18n}}</p>
|
||||
<div *ngIf="inviteQuota">
|
||||
<p>{{'invites.left' | i18n:inviteQuota}}</p>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button *ngIf="inviteQuota && !working" mat-raised-button color="primary" (click)="create()">
|
||||
{{'invite.create' | i18n}}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
<div *ngIf="others && others.content">
|
||||
<h4>{{'invites.others' | i18n}}</h4>
|
||||
|
||||
<mat-form-field>
|
||||
<input matInput [formControl]="searchFormControl" placeholder="{{'invites.search' | i18n}}">
|
||||
</mat-form-field>
|
||||
|
||||
<table mat-table matSort [dataSource]="others.content">
|
||||
<ng-container matColumnDef="note">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'invite.note' | i18n}} </th>
|
||||
<td mat-cell *matCellDef="let invite"> {{ invite.note}} </td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="otherColumns"></tr>
|
||||
<tr mat-row *matRowDef="let myRowData; columns: otherColumns"></tr>
|
||||
</table>
|
||||
<mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="others.totalElements" [pageSize]="others.size" (page)="updateOthers($event)" showFirstLastButtons></mat-paginator>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
mat-form-field {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InvitesComponent } from './invites.component';
|
||||
|
||||
describe('InvitesComponent', () => {
|
||||
let component: InvitesComponent;
|
||||
let fixture: ComponentFixture<InvitesComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ InvitesComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InvitesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {PageEvent} from '@angular/material/paginator';
|
||||
|
||||
import {AuthService} from '../../services/auth.service';
|
||||
import {I18nService} from '../../services/i18n.service';
|
||||
import {QuotaService} from '../../services/quota.service';
|
||||
import {InviteService} from '../../services/invites.service';
|
||||
import {FormControl} from '@angular/forms';
|
||||
import {debounceTime} from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-invites',
|
||||
templateUrl: './invites.component.html',
|
||||
styleUrls: ['./invites.component.scss']
|
||||
})
|
||||
export class InvitesComponent implements OnInit {
|
||||
|
||||
quota: string;
|
||||
invites: any[];
|
||||
others: any;
|
||||
inviteQuota: number = 0;
|
||||
success: boolean;
|
||||
working: boolean;
|
||||
datetimeformat: string;
|
||||
pageSizeOptions: number[] = [5, 10, 25, 50];
|
||||
searchFormControl = new FormControl();
|
||||
|
||||
inviteColumns = ["starts", "expires", "link", "note"];
|
||||
otherColumns = ["note"];
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private inviteService: InviteService,
|
||||
private i18n: I18nService,
|
||||
private quotaService: QuotaService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute) {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.datetimeformat = this.i18n.get('format.datetime', []);
|
||||
this.quota = this.route.snapshot.paramMap.get('quota');
|
||||
this.searchFormControl.valueChanges.pipe(debounceTime(500)).subscribe(value => {
|
||||
|
||||
this.inviteService.getOthersPages(this.quota, 0, this.others.size, value).subscribe((data: any) => {
|
||||
this.others = data;
|
||||
}, (error) => {})
|
||||
})
|
||||
|
||||
this.update();
|
||||
|
||||
}
|
||||
|
||||
update(): void {
|
||||
this.inviteQuota = 0;
|
||||
this.quotaService.quotas().subscribe((data: any) => {
|
||||
for(let quota of data) {
|
||||
if(quota.name == "invite_" + this.quota) {
|
||||
this.inviteQuota = quota.value;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.inviteService.get(this.quota).subscribe((data: any) => {
|
||||
this.invites = data;
|
||||
})
|
||||
|
||||
this.inviteService.getOthers(this.quota).subscribe((data: any) => {
|
||||
this.others = data;
|
||||
}, (error) => {})
|
||||
}
|
||||
|
||||
create(): void {
|
||||
this.working = true;
|
||||
|
||||
|
||||
this.inviteService.create(this.quota, {}).subscribe(response => {
|
||||
this.update();
|
||||
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;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
updateOthers(event: PageEvent) {
|
||||
this.inviteService.getOthersPages(this.quota, event.pageIndex, event.pageSize, "").subscribe((data: any) => {
|
||||
this.others = data;
|
||||
}, (error) => {})
|
||||
}
|
||||
}
|
||||
@@ -2,24 +2,34 @@
|
||||
|
||||
<p *ngIf="!services || services.length == 0">{{'services.empty' | i18n}}</p>
|
||||
|
||||
<div fxLayout="row wrap" fxLayoutGap="16px grid">
|
||||
<div fxFlex="33.33%" fxFlex.sm="50%" fxFlex.xs="100%" *ngFor="let service of services">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-icon>{{'services.' + service.name + '.icon' | i18n}}</mat-icon>
|
||||
<mat-card-title>{{'services.' + service.name + '.title' | i18n}}</mat-card-title>
|
||||
<mat-card-subtitle>{{'services.' + service.name + '.subtitle' | i18n}}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>
|
||||
{{ 'services.' + service.name + '.text' | i18n}}
|
||||
</p>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<a href="{{service.url}}" [target]="service.sameSite ? '_self' : '_blank'" mat-raised-button
|
||||
color="accent">{{'services.goto' | i18n}} <mat-icon *ngIf="!service.sameSite" inline="true">
|
||||
open_in_new</mat-icon></a>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table *ngIf="services && services.length > 0" mat-table matSort [dataSource]="services" (matSortChange)="sortData($event)" matSortActive="name" matSortDirection="desc">
|
||||
|
||||
<ng-container matColumnDef="icon">
|
||||
<th mat-header-cell *matHeaderCellDef> </th>
|
||||
<td mat-cell *matCellDef="let service">
|
||||
<mat-icon>{{'services.' + service.name + '.icon' | i18n}}</mat-icon>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="name"> {{'service.name' | i18n}} </th>
|
||||
<td mat-cell *matCellDef="let service" class="text-center">
|
||||
<a href="{{service.url}}" [target]="service.sameSite ? '_self' : '_blank'" mat-button color="accent">
|
||||
<strong>{{'services.' + service.name + '.title' | i18n}}</strong>
|
||||
<mat-icon *ngIf="!service.sameSite" inline="true">
|
||||
open_in_new</mat-icon>
|
||||
</a>
|
||||
<div><small>{{'services.' + service.name + '.subtitle' | i18n}}</small></div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="text">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'service.text' | i18n}} </th>
|
||||
<td mat-cell *matCellDef="let service">{{ 'services.' + service.name + '.text' | i18n}}</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="serviceColumns"></tr>
|
||||
<tr mat-row *matRowDef="let myRowData; columns: serviceColumns"></tr>
|
||||
</table>
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { ServiceService } from '../../services/service.service';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Sort} from '@angular/material/sort';
|
||||
import {ServiceService} from '../../services/service.service';
|
||||
import {I18nService} from '../../services/i18n.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-services',
|
||||
@@ -9,13 +11,34 @@ import { ServiceService } from '../../services/service.service';
|
||||
export class ServicesComponent implements OnInit {
|
||||
|
||||
services = [];
|
||||
serviceColumns = ["icon", "name", "text"];
|
||||
|
||||
constructor(private serviceService: ServiceService) { }
|
||||
constructor(private serviceService: ServiceService, private i18n: I18nService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.serviceService.services().subscribe((data: any) => {
|
||||
this.services = data;
|
||||
this.sortData({"active": "name", "direction": "desc"});
|
||||
})
|
||||
}
|
||||
|
||||
sortData(sort: Sort) {
|
||||
const data = this.services.slice();
|
||||
if(!sort.active || sort.direction === '') {
|
||||
this.services = data;
|
||||
return;
|
||||
}
|
||||
|
||||
this.services = data.sort((a, b) => {
|
||||
const isAsc = sort.direction === 'asc';
|
||||
switch(sort.active) {
|
||||
case 'name': return this.compare(this.i18n.get('services.' + a.name + '.title', []), this.i18n.get('services.' + b.name + '.title', []), isAsc);
|
||||
default: return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
compare(a: number | string | String, b: number | string | String, isAsc: boolean) {
|
||||
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
|
||||
import {environment} from '../../environments/environment';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class InviteService {
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
}
|
||||
|
||||
get(quota: string) {
|
||||
return this.http.get(environment.apiUrl + "/invites" + (quota ? "?quota=" + quota : ""));
|
||||
}
|
||||
|
||||
|
||||
getOthers(quota: string) {
|
||||
return this.http.get(environment.apiUrl + "/invites/" + quota + "/others");
|
||||
}
|
||||
|
||||
getOthersPages(quota: string, page : number, size : number, search : string) {
|
||||
return this.http.get(environment.apiUrl + "/invites/" + quota + "/others?page=" + page + "&size=" + size + "&search=" + search);
|
||||
}
|
||||
|
||||
create(quota: string, invite: any) {
|
||||
return this.http.post(environment.apiUrl + "/invites/" + quota, invite);
|
||||
}
|
||||
|
||||
update(invite: any) {
|
||||
return this.http.post(environment.apiUrl + "/invites", invite);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
<table mat-table matSort [dataSource]="profileFields" (matSortChange)="sortData($event)" matSortActive="index"
|
||||
matSortDirection="asc">
|
||||
<table mat-table matSort [dataSource]="profileFields" (matSortChange)="sortData($event)" matSortActive="index" matSortDirection="asc">
|
||||
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="name"> {{'profileField.name' | i18n}} </th>
|
||||
@@ -15,11 +14,9 @@
|
||||
<span *ngSwitchCase="'DATETIME'">{{profileField.value | date:datetimeformat}}</span>
|
||||
<span *ngSwitchCase="'TIME'">{{profileField.value | date:timeformat}}</span>
|
||||
<a *ngSwitchCase="'URL'" class="accent" href="{{profileField.value}}">{{profileField.value}}</a>
|
||||
<a *ngSwitchCase="'EMAIL'" class="accent"
|
||||
href="mailto:{{profileField.value}}">{{profileField.value}}</a>
|
||||
<a *ngSwitchCase="'EMAIL'" class="accent" href="mailto:{{profileField.value}}">{{profileField.value}}</a>
|
||||
<span *ngSwitchCase="'NUMBER'">{{profileField.value}}</span>
|
||||
<button *ngSwitchCase="'BLOB'" mat-raised-buttonu
|
||||
(click)="openBlob(profileField)">{{'profileField.openBlob' | i18n}}</button>
|
||||
<button *ngSwitchCase="'BLOB'" mat-raised-button (click)="openBlob(profileField)">{{'profileField.openBlob' | i18n}}</button>
|
||||
<mat-slide-toggle *ngSwitchCase="'BOOL'" [checked]="profileField.value == 'true'" disabled>
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user