This commit is contained in:
Bastian Wagner
2024-09-08 12:42:08 +02:00
parent e4031c5910
commit d4f896e398
27 changed files with 403 additions and 40 deletions

View File

@@ -1,10 +1,20 @@
import { Body, Controller, Delete, Get, Param, Post, Req, UseGuards } from '@nestjs/common'; import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Req,
UseGuards,
} from '@nestjs/common';
import { UserService } from './user.service'; import { UserService } from './user.service';
import { AuthGuard, Roles, RolesGuard } from 'src/core/secure/guards'; import { AuthGuard, Roles, RolesGuard } from 'src/core/secure/guards';
import { Client } from 'src/model/client.entity'; import { Client } from 'src/model/client.entity';
import { AuthenticatedRequest } from 'src/model'; import { AuthenticatedRequest } from 'src/model';
import { CreateClientDto } from 'src/model/dto/create-client.dto'; import { CreateClientDto } from 'src/model/dto/create-client.dto';
import { ClientService } from 'src/client/client.service'; import { ClientService } from 'src/client/client.service';
import { RedirectUri } from 'src/model/redirect-uri.entity';
@UseGuards(AuthGuard, RolesGuard) @UseGuards(AuthGuard, RolesGuard)
@Controller('app/user') @Controller('app/user')
@@ -42,4 +52,13 @@ export class UserController {
deleteClient(@Req() req: AuthenticatedRequest, @Param('id') id) { deleteClient(@Req() req: AuthenticatedRequest, @Param('id') id) {
return this.clientService.deleteClient(req.user, id); return this.clientService.deleteClient(req.user, id);
} }
@Post('clients/:id/redirect')
saveUris(
@Req() req: AuthenticatedRequest,
@Param('id') id,
@Body() uris: RedirectUri[],
): Promise<RedirectUri[]> {
return this.clientService.saveRedirectUris(req.user, id, uris);
}
} }

View File

@@ -2,12 +2,12 @@ import { Module } from '@nestjs/common';
import { UserController } from './user.controller'; import { UserController } from './user.controller';
import { SecureModule } from 'src/core/secure/secure.module'; import { SecureModule } from 'src/core/secure/secure.module';
import { UserService } from './user.service'; import { UserService } from './user.service';
import { ClientRepository } from 'src/model/client.entity';
import { ClientService } from 'src/client/client.service'; import { ClientService } from 'src/client/client.service';
import { DatabaseModule } from 'src/core/database/database.module';
@Module({ @Module({
controllers: [UserController], controllers: [UserController],
imports: [SecureModule], imports: [SecureModule, DatabaseModule],
providers: [UserService, ClientRepository, ClientService], providers: [UserService, ClientService],
}) })
export class UserModule {} export class UserModule {}

View File

@@ -10,6 +10,7 @@ export class UserService {
return this.clientRepository.find({ return this.clientRepository.find({
where: { admins: { id: user.id } }, where: { admins: { id: user.id } },
relations: ['admins'], relations: ['admins'],
order: { createdAt: 'ASC' },
}); });
} }
} }

View File

@@ -16,6 +16,7 @@ import { LoggerModule } from 'src/core/logger.module';
import { SessionKey, SessionKeyRepository } from 'src/model/session-key.entity'; import { SessionKey, SessionKeyRepository } from 'src/model/session-key.entity';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { Role, RoleRepository } from 'src/model/role.entity'; import { Role, RoleRepository } from 'src/model/role.entity';
import { DatabaseModule } from 'src/core/database/database.module';
@Module({ @Module({
providers: [ providers: [
@@ -47,6 +48,7 @@ import { Role, RoleRepository } from 'src/model/role.entity';
Role, Role,
]), ]),
LoggerModule, LoggerModule,
DatabaseModule,
], ],
}) })
export class AuthModule {} export class AuthModule {}

View File

@@ -1,12 +1,12 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { NestjsFormDataModule } from 'nestjs-form-data'; import { NestjsFormDataModule } from 'nestjs-form-data';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LoggerModule } from 'src/core/logger.module'; import { LoggerModule } from 'src/core/logger.module';
import { ClientController } from './client.controller'; import { ClientController } from './client.controller';
import { DatabaseModule } from 'src/core/database/database.module';
@Module({ @Module({
providers: [], providers: [],
controllers: [ClientController], controllers: [ClientController],
imports: [NestjsFormDataModule, TypeOrmModule.forFeature([]), LoggerModule], imports: [NestjsFormDataModule, DatabaseModule, LoggerModule],
}) })
export class ClientModule {} export class ClientModule {}

View File

@@ -1,12 +1,15 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { Client, ClientRepository } from 'src/model/client.entity'; import { Client, ClientRepository } from 'src/model/client.entity';
import { RedirectUri } from 'src/model/redirect-uri.entity'; import { RedirectRepository, RedirectUri } from 'src/model/redirect-uri.entity';
import { User } from 'src/model/user.entity'; import { User } from 'src/model/user.entity';
@Injectable() @Injectable()
export class ClientService { export class ClientService {
constructor(private clientRepo: ClientRepository) {} constructor(
private clientRepo: ClientRepository,
private uriRepo: RedirectRepository,
) {}
async createClient( async createClient(
user: User, user: User,
@@ -71,9 +74,36 @@ export class ClientService {
}); });
if (client) { if (client) {
await this.uriRepo.remove(client.redirectUris);
return this.clientRepo.remove(client); return this.clientRepo.remove(client);
} }
throw new HttpException('client not found', HttpStatus.BAD_REQUEST); throw new HttpException('client not found', HttpStatus.BAD_REQUEST);
} }
async saveRedirectUris(
user: User,
clientId: string,
uris: RedirectUri[],
): Promise<RedirectUri[]> {
const client = await this.clientRepo.findOne({
where: { id: clientId, admins: { id: user.id } },
});
if (!client) {
throw new HttpException('client not found', HttpStatus.NOT_FOUND);
}
console.log(uris);
uris.map((u) => {
u.client = client;
});
const redirects = await this.uriRepo.save(uris);
client.redirectUris = redirects;
await this.clientRepo.save(client);
return this.uriRepo.find({
where: { client: { id: clientId } },
order: { createdAt: 'ASC' },
});
}
} }

View File

@@ -5,7 +5,7 @@ import {
AuthorizationCodeRepository, AuthorizationCodeRepository,
} from 'src/model/auth-code.entity'; } from 'src/model/auth-code.entity';
import { Client, ClientRepository } from 'src/model/client.entity'; import { Client, ClientRepository } from 'src/model/client.entity';
import { RedirectUri } from 'src/model/redirect-uri.entity'; import { RedirectRepository, RedirectUri } from 'src/model/redirect-uri.entity';
import { SessionKey, SessionKeyRepository } from 'src/model/session-key.entity'; import { SessionKey, SessionKeyRepository } from 'src/model/session-key.entity';
import { User, UserRepository } from 'src/model/user.entity'; import { User, UserRepository } from 'src/model/user.entity';
@@ -15,6 +15,7 @@ const REPOSITORIES = [
ClientRepository, ClientRepository,
AuthorizationCodeRepository, AuthorizationCodeRepository,
SessionKeyRepository, SessionKeyRepository,
RedirectRepository,
]; ];
@Module({ @Module({

View File

@@ -9,6 +9,7 @@ import {
OneToMany, OneToMany,
ManyToMany, ManyToMany,
JoinTable, JoinTable,
CreateDateColumn,
} from 'typeorm'; } from 'typeorm';
import { RedirectUri } from './redirect-uri.entity'; import { RedirectUri } from './redirect-uri.entity';
import { AuthorizationCode } from './auth-code.entity'; import { AuthorizationCode } from './auth-code.entity';
@@ -19,6 +20,9 @@ export class Client {
@PrimaryGeneratedColumn('uuid') @PrimaryGeneratedColumn('uuid')
id: string; id: string;
@CreateDateColumn({ name: 'created_at' })
createdAt?: Date;
@Column() @Column()
clientName: string; clientName: string;
@@ -30,7 +34,6 @@ export class Client {
description: string; description: string;
@OneToMany(() => RedirectUri, (uri) => uri.client, { @OneToMany(() => RedirectUri, (uri) => uri.client, {
cascade: true,
eager: true, eager: true,
}) })
redirectUris: RedirectUri[]; redirectUris: RedirectUri[];

View File

@@ -1,14 +1,33 @@
import { Entity, PrimaryGeneratedColumn, ManyToOne, Column } from 'typeorm'; import {
Entity,
PrimaryGeneratedColumn,
ManyToOne,
Column,
DataSource,
Repository,
CreateDateColumn,
} from 'typeorm';
import { Client } from './client.entity'; import { Client } from './client.entity';
import { Injectable } from '@nestjs/common';
@Entity() @Entity()
export class RedirectUri { export class RedirectUri {
@PrimaryGeneratedColumn('uuid') @PrimaryGeneratedColumn('uuid')
id: string; id: string;
@CreateDateColumn({ name: 'created_at' })
createdAt?: Date;
@ManyToOne(() => Client, (client) => client.redirectUris) @ManyToOne(() => Client, (client) => client.redirectUris)
client: Client; client: Client;
@Column() @Column()
uri: string; uri: string;
} }
@Injectable()
export class RedirectRepository extends Repository<RedirectUri> {
constructor(dataSource: DataSource) {
super(RedirectUri, dataSource.createEntityManager());
}
}

View File

@@ -1,9 +1,18 @@
<div class="header">{{ client.clientName }}</div> <div class="header flex-row">
<span>{{ client.clientName }}</span>
<div class="flex-row">
<div class="flex-row" style=" gap: 0;"><mat-icon>shield_person</mat-icon><div style="line-height: 8px;">{{ client.admins.length }}</div></div>
<div class="flex-row" style=" gap: 0;"><mat-icon>link</mat-icon><div style="line-height: 8px;">{{ client.redirectUris.length }}</div></div>
</div>
</div>
<div class="body"> <div class="body">
{{ client.description }} {{ client.description }}
</div> </div>
<div class="footer"> <div class="flex-row footer">
<button mat-button >Admins</button> <div>{{ client.createdAt | date }}</div>
<button mat-button >Redirect URIs</button> <div class="flex-row">
<button mat-button (click)="showAdmins()" >Admins</button>
<button mat-button (click)="showUris()" >Redirect URIs</button>
<button mat-flat-button color="warn" (click)="deleteClient()">Löschen</button> <button mat-flat-button color="warn" (click)="deleteClient()">Löschen</button>
</div> </div>
</div>

View File

@@ -6,12 +6,13 @@
} }
.footer { .footer {
display: flex; margin-top: 12px;
flex-direction: row;
justify-content: flex-end;
gap: 8px;
} }
.header { .header {
font-weight: bold; font-weight: bold;
} }
.body {
margin-top: 12px;
}

View File

@@ -1,18 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CardComponent } from './card.component'; import { ClientCardComponent } from './card.component';
describe('CardComponent', () => { describe('CardComponent', () => {
let component: CardComponent; let component: ClientCardComponent;
let fixture: ComponentFixture<CardComponent>; let fixture: ComponentFixture<ClientCardComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [CardComponent] imports: [ClientCardComponent]
}) })
.compileComponents(); .compileComponents();
fixture = TestBed.createComponent(CardComponent); fixture = TestBed.createComponent(ClientCardComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -3,25 +3,47 @@ import { MatButtonModule } from '@angular/material/button';
import { Client } from '../../../model/client.interface'; import { Client } from '../../../model/client.interface';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { HotToastService } from '@ngxpert/hot-toast'; import { HotToastService } from '@ngxpert/hot-toast';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { ClientAdminsComponent } from '../client-admins/client-admins.component';
import { ClientRedirectUrisComponent } from '../client-redirect-uris/client-redirect-uris.component';
import { MatIconModule } from '@angular/material/icon';
import { CommonModule } from '@angular/common';
@Component({ @Component({
selector: 'app-card', selector: 'app-card',
standalone: true, standalone: true,
imports: [MatButtonModule], imports: [MatButtonModule, MatDialogModule, MatIconModule, CommonModule],
templateUrl: './card.component.html', templateUrl: './card.component.html',
styleUrl: './card.component.scss' styleUrl: './card.component.scss'
}) })
export class CardComponent { export class ClientCardComponent {
@Input() client: Client; @Input() client: Client;
@Output() onDelete = new EventEmitter<Client>(); @Output() onDelete = new EventEmitter<Client>();
private http = inject(HttpClient); private http = inject(HttpClient);
private toast = inject(HotToastService); private toast = inject(HotToastService);
private dialog = inject(MatDialog);
deleteClient() { deleteClient() {
this.onDelete.emit(this.client); this.onDelete.emit(this.client);
} }
showAdmins() {
const dialog = this.dialog.open(ClientAdminsComponent, {
autoFocus: false
});
dialog.componentInstance.client = this.client;
dialog.componentInstance.isDialog = true;
}
showUris() {
const dialog = this.dialog.open(ClientRedirectUrisComponent, {
autoFocus: false
});
dialog.componentInstance.client = this.client;
dialog.componentInstance.isDialog = true;
}
} }

View File

@@ -0,0 +1,17 @@
<h3 matDialogTitle>Client Admins</h3>
<div mat-dialog-content class="content">
@for (admin of client.admins; track $index) {
<div class="admin__item flex-row">
<span class="admin__name">{{ admin.firstName }} {{ admin.lastName }}</span>
@if($index!= 0) {
<button mat-raised-button color="warn" class="remove_btn" ><mat-icon>delete</mat-icon></button>
}
</div>
}
</div>
@if (isDialog) {
<div mat-dialog-actions>
<button mat-stroked-button color="warn" mat-dialog-close >close</button>
</div>
}

View File

@@ -0,0 +1,21 @@
.admin__item {
box-sizing: border-box;
padding: 6px;
}
.content{
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 4px 0;
}
mat-icon {
margin: 0 !important;
padding: 0;
}
.remove_btn {
min-width: 0;
padding: 0 14px;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ClientAdminsComponent } from './client-admins.component';
describe('ClientAdminsComponent', () => {
let component: ClientAdminsComponent;
let fixture: ComponentFixture<ClientAdminsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ClientAdminsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ClientAdminsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,36 @@
import { AfterViewInit, Component, Input } from '@angular/core';
import { Client } from '../../../model/client.interface';
import { MatIconModule } from '@angular/material/icon';
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-client-admins',
standalone: true,
imports: [MatIconModule, MatDialogModule, MatButtonModule],
templateUrl: './client-admins.component.html',
styleUrl: './client-admins.component.scss'
})
export class ClientAdminsComponent {
private _client: Client;
isDialog = false;
constructor(private dialogRef: MatDialogRef<ClientAdminsComponent>) {
}
@Input()
set client(client: Client) {
if (!client) { throw new Error('Client not set')}
this._client = client;
}
get client(): Client {
return this._client;
}
}

View File

@@ -0,0 +1,28 @@
<h3 matDialogTitle>Client Redirect-URIs</h3>
<div mat-dialog-content class="content">
@for (uri of client.redirectUris; track $index) {
<div class="admin__item flex-row">
<span class="admin__name">{{ uri.uri }}</span>
@if(client.redirectUris.length > 1) {
<button mat-raised-button color="warn" class="remove_btn" (click)="removeUri(uri)" ><mat-icon>delete</mat-icon></button>
}
</div>
}
<div class="add_entry flex-row">
<input type="text" id="addURIInput" placeholder="URL" >
<button (click)="addUri()" mat-button>Neu</button>
</div>
</div>
@if (isDialog) {
<div mat-dialog-actions>
<button mat-stroked-button color="warn" mat-dialog-close >close</button>
</div>
}
@if (isLoading) {
<div class="global-spinner" >
<mat-spinner></mat-spinner>
</div>
}

View File

@@ -0,0 +1,21 @@
.admin__item {
box-sizing: border-box;
padding: 6px;
}
.content{
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 4px 0;
}
mat-icon {
margin: 0 !important;
padding: 0;
}
.remove_btn {
min-width: 0;
padding: 0 14px;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ClientRedirectUrisComponent } from './client-redirect-uris.component';
describe('ClientRedirectUrisComponent', () => {
let component: ClientRedirectUrisComponent;
let fixture: ComponentFixture<ClientRedirectUrisComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ClientRedirectUrisComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ClientRedirectUrisComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,63 @@
import { Component, inject, Input } from '@angular/core';
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { Client } from '../../../model/client.interface';
import { ClientAdminsComponent } from '../client-admins/client-admins.component';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { HttpClient } from '@angular/common/http';
import { RedirectUri } from '../../../model/redirect-uri.interface';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@Component({
selector: 'app-client-redirect-uris',
standalone: true,
imports: [MatIconModule, MatDialogModule, MatButtonModule, MatProgressSpinnerModule],
templateUrl: './client-redirect-uris.component.html',
styleUrl: './client-redirect-uris.component.scss'
})
export class ClientRedirectUrisComponent {
private _client: Client;
private http: HttpClient = inject(HttpClient);
isDialog = false;
isLoading = false;
constructor(private dialogRef: MatDialogRef<ClientAdminsComponent>) {
}
@Input()
set client(client: Client) {
if (!client) { throw new Error('Client not set')}
this._client = client;
}
get client(): Client {
return this._client;
}
addUri() {
const val: HTMLInputElement = document.getElementById('addURIInput') as any;
console.log(val.value)
this.client.redirectUris.push({
uri: val.value
});
val.value = null;
this.saveClient();
};
removeUri(u: RedirectUri) {
this.client.redirectUris = this.client.redirectUris.filter(r => r != u);
this.saveClient()
}
saveClient() {
this.isLoading = true;
this.http.post<RedirectUri[]>(`api/app/user/clients/${this.client.id}/redirect`, this.client.redirectUris).subscribe(res => {
this.client.redirectUris = res;
this.isLoading = false;
})
}
}

View File

@@ -40,13 +40,12 @@
</mat-step> </mat-step>
<mat-step [editable]="true"> <mat-step [editable]="true">
<ng-template matStepLabel>Admins und Redirect URLs</ng-template> <ng-template matStepLabel>Admins und Redirect URLs</ng-template>
<div style="flex: 1 1 auto">space</div> <div class="admin_container">
<div> {{ client?.clientName }}</div> @if (client && client.admins) {
<app-client-admins [client]="client" ></app-client-admins>
}
</div>
</mat-step> </mat-step>
</mat-stepper> </mat-stepper>
</mat-dialog-content> </mat-dialog-content>

View File

@@ -11,11 +11,13 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import {MatStepper, MatStepperModule} from '@angular/material/stepper'; import {MatStepper, MatStepperModule} from '@angular/material/stepper';
import { HotToastService } from '@ngxpert/hot-toast'; import { HotToastService } from '@ngxpert/hot-toast';
import { Client } from '../../../model/client.interface'; import { Client } from '../../../model/client.interface';
import { User } from '../../../model/user.interface';
import { ClientAdminsComponent } from '../client-admins/client-admins.component';
@Component({ @Component({
selector: 'app-create-client', selector: 'app-create-client',
standalone: true, standalone: true,
imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatIconModule, MatDialogModule, MatButtonModule, MatProgressSpinnerModule, CommonModule, MatStepperModule], imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatIconModule, MatDialogModule, MatButtonModule, MatProgressSpinnerModule, CommonModule, MatStepperModule, ClientAdminsComponent],
templateUrl: './create-client.component.html', templateUrl: './create-client.component.html',
styleUrl: './create-client.component.scss' styleUrl: './create-client.component.scss'
}) })
@@ -36,6 +38,7 @@ export class CreateClientComponent {
client: Client; client: Client;
create() { create() {
if (this.createClient.invalid) { return; } if (this.createClient.invalid) { return; }
@@ -52,6 +55,7 @@ export class CreateClientComponent {
) )
.subscribe({ .subscribe({
next: data => { next: data => {
console.log(this.stepper)
this.client = data; this.client = data;
this.createClient.enable(); this.createClient.enable();
this.stepper.next(); this.stepper.next();

View File

@@ -22,8 +22,11 @@
<div class="card-container"> <div class="card-container">
<div class="card-container__list"> <div class="card-container__list">
@for (client of clients; track $index) { <ng-container #listSection>
</ng-container>
<!-- @for (client of clients; track $index) {
<app-card [client]="client" (onDelete)="openDeleteDialog($event)" ></app-card> <app-card [client]="client" (onDelete)="openDeleteDialog($event)" ></app-card>
} } -->
</div> </div>
</div> </div>

View File

@@ -1,8 +1,8 @@
import { Component, inject, OnInit } from '@angular/core'; import { Component, inject, Injector, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { UserService } from '../auth/user.service'; import { UserService } from '../auth/user.service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { CardComponent } from './components/card/card.component'; import { ClientCardComponent } from './components/card/card.component';
import { Client } from '../model/client.interface'; import { Client } from '../model/client.interface';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@@ -10,11 +10,12 @@ import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { CreateClientComponent } from './components/create-client/create-client.component'; import { CreateClientComponent } from './components/create-client/create-client.component';
import { CreateHotToastRef, HotToastService } from '@ngxpert/hot-toast'; import { CreateHotToastRef, HotToastService } from '@ngxpert/hot-toast';
import {MatBottomSheet, MatBottomSheetModule, MatBottomSheetRef} from '@angular/material/bottom-sheet'; import {MatBottomSheet, MatBottomSheetModule, MatBottomSheetRef} from '@angular/material/bottom-sheet';
import { ClientAdminsComponent } from './components/client-admins/client-admins.component';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
standalone: true, standalone: true,
imports: [CardComponent, MatButtonModule, MatIconModule, MatDialogModule, MatBottomSheetModule], imports: [ClientCardComponent, MatButtonModule, MatIconModule, MatDialogModule, MatBottomSheetModule],
templateUrl: './dashboard.component.html', templateUrl: './dashboard.component.html',
styleUrl: './dashboard.component.scss' styleUrl: './dashboard.component.scss'
}) })
@@ -26,7 +27,9 @@ export class DashboardComponent implements OnInit {
private dialog: MatDialog = inject(MatDialog); private dialog: MatDialog = inject(MatDialog);
private toast: HotToastService = inject(HotToastService); private toast: HotToastService = inject(HotToastService);
private bottomSheet = inject(MatBottomSheet); private bottomSheet = inject(MatBottomSheet);
private injector: Injector = inject(Injector);
@ViewChild('listSection', { read: ViewContainerRef, static: true }) clientList!: ViewContainerRef;
clients: Client[] = []; clients: Client[] = [];
@@ -42,6 +45,18 @@ export class DashboardComponent implements OnInit {
load() { load() {
this.http.get<Client[]>('api/app/user/clients').subscribe(res => { this.http.get<Client[]>('api/app/user/clients').subscribe(res => {
this.clients = res; this.clients = res;
this.createList();
})
}
createList() {
this.clientList.clear();
this.clients.forEach(c => {
const ref = this.clientList.createComponent(ClientCardComponent);
ref.instance.client = c;
ref.instance.onDelete.subscribe(() => {
this.openDeleteDialog(c)
})
}) })
} }
@@ -103,6 +118,7 @@ export class DashboardComponent implements OnInit {
} }
} }

View File

@@ -2,9 +2,10 @@ import { RedirectUri } from "./redirect-uri.interface";
import { User } from "./user.interface"; import { User } from "./user.interface";
export interface Client { export interface Client {
admins: User;
clientName: string; clientName: string;
id: string; id: string;
description: string; description: string;
redirectUris: RedirectUri[]; redirectUris: RedirectUri[];
admins: User[];
createdAt?: string;
} }

View File

@@ -21,6 +21,7 @@ html, body {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
justify-content: space-between;
} }
.create-client__dialog{ .create-client__dialog{