first base version

This commit is contained in:
Flo 2024-08-26 12:48:10 +00:00
parent bdb6299692
commit 550f8bb209
32 changed files with 331 additions and 185 deletions

View File

@ -1,5 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { AuthService } from './core/services/auth.service'; import { AuthService } from './core/services/auth.service';
import { AppService } from './core/services/app.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -8,7 +9,8 @@ import { AuthService } from './core/services/auth.service';
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
constructor( constructor(
private authService: AuthService private authService: AuthService,
private appService: AppService,
) { ) {
} }

View File

@ -11,8 +11,8 @@ import { CoreModule } from './core/core.module';
import { AuthGuard } from './core/guards/auth.guard'; import { AuthGuard } from './core/guards/auth.guard';
const routes: Routes = [ const routes: Routes = [
{ path: 'auth', loadChildren: () => import('./core/auth/auth.module').then(m => m.AuthModule) },
{ path: 'home', component: HomeComponent, canActivate: [AuthGuard] }, { path: 'home', component: HomeComponent, canActivate: [AuthGuard] },
{ path: 'auth', loadChildren: () => import('./core/auth/auth.module').then(m => m.AuthModule) },
{ path: '', redirectTo: 'home', pathMatch: 'full' }, { path: '', redirectTo: 'home', pathMatch: 'full' },
]; ];

View File

@ -1,21 +0,0 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AppService {
private username: string|null;
constructor() {
this.username = null;
}
public getUsername(): string|null {
return this.username;
}
public setUsername(name: string|null): void {
this.username = name;
}
}

View File

@ -9,7 +9,7 @@ import { ReactiveFormsModule } from '@angular/forms';
const routes: Routes = [ const routes: Routes = [
{ path: 'login', component: LoginComponent }, { path: 'login', component: LoginComponent },
{ path: 'registration', component: RegistrationComponent }, { path: 'registration', component: RegistrationComponent },
{ path: 'registration/:id', component: ConfirmRegistrationComponent }, { path: 'registration/:registrationId', component: ConfirmRegistrationComponent },
{ path: '', redirectTo: 'login', pathMatch: 'full' }, { path: '', redirectTo: 'login', pathMatch: 'full' },
]; ];

View File

@ -1 +1,23 @@
<p>confirm-registration works!</p> <div class="mb-10">
</div>
<div class="max-w-sm mx-auto mb-10">
<h1 class="font-bold text-center text-skin-primary text-5xl mb-5">
Beekeeper
</h1>
<h1 class="font-bold text-center text-skin-accent text-xl">
Registrierung Abschließen
</h1>
</div>
<form class="max-w-sm mx-auto" [formGroup]="confirmRegistrationForm">
<div class="mb-5">
<label for="password" class="block mb-2 text-sm font-medium text-skin-primary-muted">Passwort</label>
<input formControlName="password" type="password" id="password" class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" required />
</div>
<div class="mb-5">
<label for="passwordConfirmation" class="block mb-2 text-sm font-medium text-skin-primary-muted">Passwort wiederholen</label>
<input formControlName="passwordConfirmation" type="password" id="passwordConfirmation" class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" required />
</div>
<button (click)="confirm()" [disabled]="!confirmRegistrationForm.valid" type="submit" class="w-full 9xl:w-auto font-bold text-skin-secondary bg-skin-accent hover:text-skin-primary rounded-lg text-sm px-5 py-2.5 text-center">Registrieren</button>
</form>

View File

@ -1,4 +1,8 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { filter } from 'rxjs';
import { AuthService } from 'src/app/core/services/auth.service';
@Component({ @Component({
selector: 'app-confirm-registration', selector: 'app-confirm-registration',
@ -6,5 +10,32 @@ import { Component } from '@angular/core';
styleUrls: ['./confirm-registration.component.scss'] styleUrls: ['./confirm-registration.component.scss']
}) })
export class ConfirmRegistrationComponent { export class ConfirmRegistrationComponent {
confirmRegistrationForm = new FormGroup({
password: new FormControl('', [Validators.required]),
passwordConfirmation: new FormControl('', [Validators.required]),
});
registrationId: string | undefined;
constructor(
private authService: AuthService,
private activatedRoute: ActivatedRoute
) {
this.activatedRoute.params.subscribe(
(params) => {
this.registrationId = params['registrationId'];
}
)
}
confirm(): void {
if (this.registrationId === undefined)
return;
this.authService.confirmRegistration({
id: this.registrationId!,
password: this.confirmRegistrationForm.value.password!,
passwordConfirmation: this.confirmRegistrationForm.value.passwordConfirmation!
});
}
} }

View File

@ -20,5 +20,5 @@
<input formControlName="password" type="password" id="password" class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" required /> <input formControlName="password" type="password" id="password" class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" required />
<p id="helper-text-explanation" class="mt-2 text-sm text-skin-primary-muted">Neu hier? <a routerLink="/auth/registration" class="font-medium text-skin-accent hover:underline hover:font-bold">Jetzt registrieren!</a></p> <p id="helper-text-explanation" class="mt-2 text-sm text-skin-primary-muted">Neu hier? <a routerLink="/auth/registration" class="font-medium text-skin-accent hover:underline hover:font-bold">Jetzt registrieren!</a></p>
</div> </div>
<button (click)="login()" [disabled]="!loginForm.valid" type="submit" class="w-full 9xl:w-auto font-bold text-skin-primary bg-skin-secondary hover:text-skin-secondary rounded-lg text-sm px-5 py-2.5 text-center">Anmelden</button> <button (click)="login()" [disabled]="!loginForm.valid" type="submit" class="w-full 9xl:w-auto font-bold text-skin-secondary bg-skin-accent hover:text-skin-primary rounded-lg text-sm px-5 py-2.5 text-center">Anmelden</button>
</form> </form>

View File

@ -1 +1,24 @@
<p>registration works!</p> <div class="mb-10">
</div>
<div class="max-w-sm mx-auto mb-10">
<h1 class="font-bold text-center text-skin-primary text-5xl mb-5">
Beekeeper
</h1>
<h1 class="font-bold text-center text-skin-accent text-xl">
Registrierung
</h1>
</div>
<form class="max-w-sm mx-auto" [formGroup]="registrationForm">
<div class="mb-5">
<label for="mail" class="block mb-2 text-sm font-medium text-skin-primary-muted">E-Mail</label>
<input formControlName="mail" type="mail" id="mail" class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" placeholder="your@email.com" required />
</div>
<div class="mb-5">
<label for="username" class="block mb-2 text-sm font-medium text-skin-primary-muted">Benutzername</label>
<input formControlName="username" type="string" id="username" class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" required />
<p id="helper-text-explanation" class="mt-2 text-sm text-skin-primary-muted">Bereits registiert? <a routerLink="/auth/login" class="font-medium text-skin-accent hover:underline hover:font-bold">Jetzt anmelden!</a></p>
</div>
<button (click)="login()" [disabled]="!registrationForm.valid" type="submit" class="w-full 9xl:w-auto font-bold text-skin-secondary bg-skin-accent hover:text-skin-primary rounded-lg text-sm px-5 py-2.5 text-center">Registrieren</button>
</form>

View File

@ -1,4 +1,8 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { filter } from 'rxjs';
import { AuthService } from 'src/app/core/services/auth.service';
@Component({ @Component({
selector: 'app-registration', selector: 'app-registration',
@ -6,5 +10,26 @@ import { Component } from '@angular/core';
styleUrls: ['./registration.component.scss'] styleUrls: ['./registration.component.scss']
}) })
export class RegistrationComponent { export class RegistrationComponent {
registrationForm = new FormGroup({
mail: new FormControl('', [Validators.required]),
username: new FormControl('', [Validators.required]),
});
constructor(
private authService: AuthService,
private router: Router
) {
this.authService.currentState$.pipe(
filter(state => state !== undefined)
).subscribe(state => {
this.router.navigateByUrl('/home');
});
}
login(): void {
this.authService.register({
mail: this.registrationForm.value.mail!,
username: this.registrationForm.value.username!
});
}
} }

View File

@ -1,5 +1,24 @@
<app-navigation></app-navigation> <app-navigation></app-navigation>
<div> <div class="max-w-screen-xl mx-auto p-4">
<iframe src="/api/health"></iframe> <shared-card icon="/assets/icon.png" header="Test" subHeader="lol noch ein test">
I'm working
</shared-card>
<shared-card icon="/assets/icon.png" header="Test" >
I'm working
</shared-card>
<shared-card>
I'm working
</shared-card>
<div class="mb-5">
<shared-table [items]="colonies" [columns]="columns">
</shared-table>
</div>
<shared-paginator total="20" perPage="5" />
</div> </div>

View File

@ -1,5 +1,11 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { RequestService } from 'src/app/core/services/request.service'; import { RequestService } from 'src/app/core/services/request.service';
import { ColumnDefinition } from 'src/app/shared/components/table/table.component';
interface Colony {
name: string;
}
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
@ -7,6 +13,28 @@ import { RequestService } from 'src/app/core/services/request.service';
styleUrls: ['./home.component.scss'], styleUrls: ['./home.component.scss'],
}) })
export class HomeComponent { export class HomeComponent {
columns: ColumnDefinition[] = [
{
header: 'Name',
columnFunction: (colony:Colony) => colony.name,
routerLink: (colony:Colony) => '#',
},
{
header: 'Name2',
columnFunction: (colony:Colony) => colony.name,
}
];
colonies: Colony[] = [
{
name: 'Die Römer'
},
{
name: 'Die Griechen'
}
];
constructor(public requestService: RequestService) { constructor(public requestService: RequestService) {
} }
} }

View File

@ -1,17 +1,17 @@
<nav class="bg-skin-secondary"> <nav class="bg-skin-accent">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4"> <div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<!-- Title --> <!-- Title -->
<a href="#" class="flex items-center space-x-3"> <a href="#" class="flex items-center space-x-3">
<img src="assets/icon.png" class="h-10" alt="Beekeeper Logo" /> <img src="assets/icon.png" class="h-10" alt="Beekeeper Logo" />
<span class="text-skin-primary self-center text-2xl font-semibold whitespace-nowrap">Beekeeper</span> <span class="text-skin-primary self-center text-4xl font-semibold whitespace-nowrap">Beekeeper</span>
</a> </a>
<div class="flex items-center md:order-2 space-x-3 md:space-x-0"> <div class="flex items-center md:order-2 space-x-3 md:space-x-0">
<!--User Bubble--> <!--User Bubble-->
<button type="button" class="block w-10 h-10 text-sm bg-skin-primary rounded-full md:me-0 focus:ring-4 focus:ring-gray-300" id="user-menu-button" aria-expanded="false" data-dropdown-toggle="user-dropdown" data-dropdown-placement="bottom"> <button type="button" class="block w-10 h-10 text-sm bg-skin-primary rounded-full md:me-0 focus:ring-4 focus:ring-skin-primary" id="user-menu-button" aria-expanded="false" data-dropdown-toggle="user-dropdown" data-dropdown-placement="bottom">
<span class="text-skin-accent rounded-full font-bold p-2">AA</span> <span class="text-skin-accent rounded-full font-bold p-2">AA</span>
<!-- <img class="w-8 h-8 rounded-full" src="/docs/images/people/profile-picture-3.jpg" alt="user photo"> --> <!-- <img class="w-8 h-8 rounded-full" src="/docs/images/people/profile-picture-3.jpg" alt="user photo"> -->
</button> </button>
@ -26,29 +26,32 @@
</div> </div>
<!-- User dropdown --> <!-- User dropdown -->
<div class="z-50 hidden my-4 text-base list-none divide-y divide-gray-100 rounded-lg shadow-sm shadow-skin-primary bg-skin-primary" id="user-dropdown"> <div class="z-50 hidden my-4 text-base list-none divide-y divide-skin-primary rounded-lg shadow-sm shadow-skin-primary bg-skin-secondary" id="user-dropdown">
<div class="px-4 py-3"> <div class="px-4 py-3">
<span class="block text-sm text-skin-accent font-bold">{{state?.username}}</span> <span class="block text-sm text-skin-accent font-bold">{{state?.username}}</span>
<span class="block text-sm text-skin-primary-muted truncate">{{state?.roleIdentifier}}</span> <span class="block text-sm text-skin-primary-muted truncate">{{state?.roleIdentifier}}</span>
</div> </div>
<ul class="p-4" aria-labelledby="user-menu-button"> <ul class="p-4" aria-labelledby="user-menu-button">
<li> <li>
<a href="#" class="block py-2 px-3 rounded text-skin-primary hover:bg-skin-secondary">Einstellungen</a> <a href="#" class="block py-2 px-3 rounded text-skin-primary hover:bg-skin-accent">Einstellungen</a>
</li> </li>
<li> <li>
<button class="w-full block py-2 px-3 rounded text-skin-primary hover:bg-skin-secondary" (click)="logout()">Ausloggen</button> <button class="w-full block py-2 px-3 rounded text-skin-primary hover:bg-skin-accent" (click)="toggleDarkmode()">{{ darkMode ? 'Hell' : 'Dunkel' }}</button>
</li>
<li>
<button class="w-full block py-2 px-3 rounded text-skin-primary hover:bg-skin-accent" (click)="logout()">Ausloggen</button>
</li> </li>
</ul> </ul>
</div> </div>
<!-- Navigatoin --> <!-- Navigatoin -->
<div class="items-center justify-between hidden w-full md:flex md:w-auto md:order-1" id="navbar-user"> <div class="items-center justify-between hidden w-full md:flex md:w-auto md:order-1" id="navbar-user">
<ul class="flex flex-col font-medium p-4 md:p-0 mt-4 rounded-lg bg-skin-primary md:bg-skin-secondary md:space-x-8 md:flex-row md:mt-0 md:border-0 md:bg-skin-fill"> <ul class="flex flex-col text-xl p-4 md:p-0 mt-4 rounded-lg bg-skin-primary md:bg-skin-accent md:space-x-8 md:flex-row md:mt-0 md:border-0">
<li> <li>
<a routerLink="/home" class="block py-2 px-3 rounded text-skin-primary md:p-0" aria-current="page">Home</a> <a routerLink="/home" class="block py-2 px-3 rounded text-skin-primary md:p-0" aria-current="page">Home</a>
</li> </li>
<li> <li>
<a href="#" class="block py-2 px-3 rounded text-skin-primary-muted hover:text-skin-primary hover:bg-skin-secondary md:p-0">About</a> <a href="#" class="block py-2 px-3 rounded text-skin-primary-muted hover:text-skin-primary hover:bg-skin-accent md:p-0">About</a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -4,6 +4,7 @@ import { AuthService } from '../../services/auth.service';
import { UserStateResponse } from '../../models/user-state-request.model'; import { UserStateResponse } from '../../models/user-state-request.model';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { filter, map } from 'rxjs'; import { filter, map } from 'rxjs';
import { AppService } from '../../services/app.service';
@Component({ @Component({
selector: 'app-navigation', selector: 'app-navigation',
@ -13,11 +14,14 @@ import { filter, map } from 'rxjs';
export class NavigationComponent implements OnInit { export class NavigationComponent implements OnInit {
state: UserStateResponse | undefined | null; state: UserStateResponse | undefined | null;
darkMode: boolean;
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private appService: AppService,
private router: Router, private router: Router,
) { ) {
this.darkMode = this.appService.darkMode;
this.state = this.authService.currentState$.value; this.state = this.authService.currentState$.value;
this.authService.currentState$.pipe( this.authService.currentState$.pipe(
filter(state => state === undefined) filter(state => state === undefined)
@ -33,4 +37,9 @@ export class NavigationComponent implements OnInit {
logout(): void { logout(): void {
this.authService.logout(); this.authService.logout();
} }
toggleDarkmode() {
this.appService.toggleDarkMode();
this.darkMode = this.appService.darkMode;
}
} }

View File

@ -5,6 +5,8 @@ import { NavigationComponent } from './components/navigation/navigation.componen
import { AuthGuard } from './guards/auth.guard'; import { AuthGuard } from './guards/auth.guard';
import { AuthService } from './services/auth.service'; import { AuthService } from './services/auth.service';
import { RequestService } from './services/request.service'; import { RequestService } from './services/request.service';
import { AppService } from './services/app.service';
import { SharedModule } from '../shared/shared.module';
@ -12,8 +14,9 @@ import { RequestService } from './services/request.service';
declarations: [HomeComponent, NavigationComponent], declarations: [HomeComponent, NavigationComponent],
exports: [HomeComponent, NavigationComponent], exports: [HomeComponent, NavigationComponent],
imports: [ imports: [
CommonModule CommonModule,
SharedModule
], ],
providers: [AuthGuard, AuthService, RequestService] providers: [AuthGuard, AuthService, RequestService, AppService]
}) })
export class CoreModule { } export class CoreModule { }

View File

@ -0,0 +1,14 @@
export interface ConfirmRegistrationRequest {
id: string,
password: string,
passwordConfirmation: string,
}
export interface ConfirmRegistrationResponse {
id: string,
username: string,
roleIdentifier: string,
createdAt: string,
updatedAt: string,
permissions: string[]
}

View File

@ -0,0 +1,7 @@
export interface RegisterUserRequest {
mail: string,
username: string,
}
export interface RegisterUserResponse {
}

View File

@ -0,0 +1,30 @@
import { Injectable } from "@angular/core";
@Injectable()
export class AppService {
darkMode: boolean;
constructor(
) {
this.darkMode = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
this.applyDarkMode();
}
private applyDarkMode(): void {
if (this.darkMode) {
document.documentElement.classList.add('theme-dark');
} else {
document.documentElement.classList.remove('theme-dark');
}
}
toggleDarkMode() {
this.darkMode = !this.darkMode;
this.applyDarkMode();
}
getDarkMode(): boolean {
return this.darkMode;
}
}

View File

@ -4,6 +4,8 @@ import { UserStateResponse } from "../models/user-state-request.model";
import { BehaviorSubject, Observable } from "rxjs"; import { BehaviorSubject, Observable } from "rxjs";
import { LoginRequest, LoginResponse } from "../models/login-request.model copy"; import { LoginRequest, LoginResponse } from "../models/login-request.model copy";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { RegisterUserRequest, RegisterUserResponse } from "../models/register-user-request.model";
import { ConfirmRegistrationRequest, ConfirmRegistrationResponse } from "../models/confirm-registration-request.model";
@Injectable() @Injectable()
export class AuthService { export class AuthService {
@ -44,6 +46,36 @@ export class AuthService {
return result; return result;
} }
register(body: RegisterUserRequest): RegisterUserResponse|null {
let result = null;
this.requestService.post(
'auth/register-user',
body,
(response: LoginResponse) => {
result = response;
this.readUserState();
}
);
return result;
}
confirmRegistration(body: ConfirmRegistrationRequest): ConfirmRegistrationResponse|null {
let result = null;
this.requestService.post(
'auth/confirm-registration',
body,
(response: LoginResponse) => {
result = response;
this.readUserState();
}
);
return result;
}
logout(): void { logout(): void {
this.requestService.post( this.requestService.post(
'auth/logout-user', 'auth/logout-user',

View File

@ -1,29 +1,32 @@
<div <div
id="card" id="card"
class="grow p-3 my-2 rounded-xl shadow-lg border border-gray-100 dark:bg-zinc-800 dark:border-zinc-900" class="grow p-3 my-2 rounded-xl shadow-sm shadow-skin-primary border border-skin-primary divide-y divide-skin-primary"
> >
<div id="header"> <div *ngIf="icon !== null || header !== null || subHeader !== null" id="header" class="mb-5">
<div class="flex flex-row"> <div class="w-full flex flex-row">
<div *ngIf="icon !== null"> <!--Icon-->
<img class="h-6 w-6 m-2" [src]="icon" /> <div *ngIf="icon !== null" class="basis-auto">
<img class="h-12 w-12 m-2" [src]="icon" />
</div> </div>
<!--Header-->
<div class="basis-full truncate">
<div <div
*ngIf="header !== null" *ngIf="header !== null"
class="my-auto text-xl font-medium text-black dark:text-white truncate" class="text-xl font-medium text-skin-primary mb-2">
>
{{ header }} {{ header }}
</div> </div>
</div>
<div <div
*ngIf="subHeader !== null" *ngIf="subHeader !== null"
class="text-slate-500 dark:text-slate-400 mx-2" class="text-skin-primary-muted pl-2">
>
{{ subHeader }} {{ subHeader }}
</div> </div>
</div> </div>
</div>
</div>
<div id="content" class="mx-5 text-black dark:text-white"> <div id="content" class="text-skin-primary p-5">
<div *ngIf="content !== null" class="my-2 italic">"{{ content }}"</div> <div *ngIf="content !== null" class="italic">"{{ content }}"</div>
<div *ngIf="content === null"> <div *ngIf="content === null">
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>

View File

@ -1,21 +0,0 @@
<div
*ngIf="caption !== null"
class="text-lg font-bold mb-3 text-black dark:text-white"
>
{{ caption }}
</div>
<div class="flex flex-col md:space-x-4">
<div id="form-body" class="flex-1 basis-full">
<ng-content />
</div>
<div class="flex flex-row">
<div class="flex-1"></div>
<button
(click)="submit.emit()"
class="flex-0 p-2 min-w-24 bg-green-700 hover:bg-green-800 dark:bg-green-800 dark:hover:bg-green-900 rounded-lg"
>
<p class="text-lg text-white text-md font-bold">Submit</p>
</button>
</div>
</div>

View File

@ -1,12 +0,0 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'shared-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent {
@Output() submit = new EventEmitter<any>();
@Input() caption: string|null = null;
}

View File

@ -3,7 +3,7 @@
<li> <li>
<button <button
(click)="setActivePage(-1)" (click)="setActivePage(-1)"
class="flex items-center justify-center px-3 h-8 ms-0 leading-tight text-gray-500 dark:text-white bg-white dark:bg-zinc-800 border border-e-0 border-gray-500 dark:border-zinc-800 rounded-s-lg hover:bg-gray-100 hover:text-gray-700" class="flex items-center justify-center px-3 h-8 ms-0 leading-tight text-skin-primary bg-skin-secondary border border-skin-primary rounded-s-lg hover:bg-skin-primary"
> >
<span class="sr-only">Previous</span> <span class="sr-only">Previous</span>
<svg <svg
@ -23,18 +23,19 @@
</svg> </svg>
</button> </button>
</li> </li>
<li *ngFor="let item of items" class="w-full"> <li *ngFor="let item of items" class="w-full">
<div <div
class="flex items-center justify-center h-8 leading-tight border border-gray-500 dark:border-zinc-800" class="flex items-center justify-center h-8 leading-tight border border-skin-primary text-skin-primary"
[ngClass]="{ [ngClass]="{
'bg-green-700 dark:bg-green-800 text-white': item.page === page, 'bg-skin-accent font-bold text-skin-secondary hover:text-skin-primary': item.page === page,
'bg-white dark:bg-zinc-700 text-black dark:text-white': 'bg-skin-secondary':
item.page !== page item.page !== page
}" }"
> >
<div *ngIf="item.page === null" class="px-3">...</div> <div *ngIf="item.page === null" class="px-3">...</div>
<button <button
class="px-3 w-full h-full hover:bg-gray-100 hover:text-gray-700" class="px-3 w-full h-full hover:bg-skin-primary"
*ngIf="item.page !== null" *ngIf="item.page !== null"
(click)="setActivePage(item.page)" (click)="setActivePage(item.page)"
> >
@ -42,10 +43,11 @@
</button> </button>
</div> </div>
</li> </li>
<li> <li>
<button <button
(click)="setActivePage(-2)" (click)="setActivePage(-2)"
class="flex items-center justify-center px-3 h-8 leading-tight text-gray-500 dark:text-white bg-white dark:bg-zinc-800 border border-gray-500 dark:border-zinc-800 rounded-e-lg hover:bg-gray-100 hover:text-gray-700" class="flex items-center justify-center px-3 h-8 leading-tight text-skin-primary bg-skin-secondary border border-skin-primary rounded-e-lg hover:bg-skin-primary"
> >
<span class="sr-only">Next</span> <span class="sr-only">Next</span>
<svg <svg

View File

@ -1,20 +0,0 @@
<div class="text-sm font-medium text-center text-gray-500">
<ul class="flex flex-wrap -mb-px">
<li *ngFor="let tab of tabs; index as currentIndex" class="grow lg:grow-0">
<button
(click)="setActiveTab(tab)"
class="w-full lg:w-auto text-xl font-bold inline-block px-4 pt-4 pb-3 border-b-2 rounded-t-lg hover:text-green-700 hover:border-green-700 hover:no-underline dark:hover:text-green-800 dark:hover:border-green-800"
[ngClass]="{
'text-black border-black dark:text-green-800 dark:border-green-800 hover:text-green-700 dark:hover:border-white dark:hover:text-white':
currentIndex === activeIndex
}"
>
{{ tab.title }}
</button>
</li>
</ul>
</div>
<div class="tab-content">
<ng-content></ng-content>
</div>

View File

@ -1,30 +0,0 @@
import { Component, ContentChildren, EventEmitter, Input, Output, QueryList } from '@angular/core';
import { TabItem } from '../../models/TabItem';
@Component({
selector: 'shared-tab-control',
templateUrl: './tab-control.component.html',
styleUrls: ['./tab-control.component.scss']
})
export class TabControlComponent {
@Input() public tabs: TabItem[] = [];
@Input() public activeTabId: string = '';
@Output() public activeTabIdChange = new EventEmitter<string>();
@ContentChildren('tabContent') tabContents!: QueryList<any>;
public activeIndex = 0;
public ngOnChanges() {
this.setActiveTab(null);
}
public setActiveTab(tab: TabItem|null) {
if (tab !== null) {
this.activeTabId = tab.id;
this.activeTabIdChange.emit(this.activeTabId);
}
this.activeIndex = this.tabs.findIndex((i) => i.id === this.activeTabId);
}
}

View File

@ -1,30 +1,29 @@
<table class="w-full" [ngClass]="[background, foreground, border]"> <table class="w-full text-skin-primary">
<thead class="uppercase border-b text-sm" [ngClass]="[border]">
<thead class="uppercase bg-skin-accent border-b border-skin-primary text-left">
<tr> <tr>
<th <th
*ngFor="let column of columns; index as currentIndex" *ngFor="let column of columns"
[class]="'px-5 py-2 ' + column.headerCss" class="px-5 py-2"
[ngClass]="{
backgroundColumn: currentIndex % 2 === 0,
backgroundColumnAlternate: currentIndex % 2 === 1
}"
> >
<div [innerHtml]="column.header"></div> {{column.header}}
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let item of items" class="border-b" [ngClass]="[border]"> <tr *ngFor="let item of items" class="border-b border-skin-primary">
<td <td
*ngFor="let column of columns; index as currentIndex" *ngFor="let column of columns; index as currentIndex"
[class]="'px-5 py-2 ' + column.columnCss" class="px-5 py-2"
[ngClass]="{ [ngClass]="{
backgroundColumn: currentIndex % 2 === 0, 'bg-skin-primary': currentIndex % 2 === 0,
backgroundColumnAlternate: currentIndex % 2 === 1 'bg-skin-secondary': currentIndex % 2 === 1,
}" }"
> >
<div *ngIf="column.routerLink !== undefined"> <div *ngIf="column.routerLink !== undefined">
<a [routerLink]="column.routerLink(item)"> <a [routerLink]="column.routerLink(item)" class="font-bold hover:text-skin-accent hover:underline">
<div *ngIf="column.columnContent !== undefined"> <div *ngIf="column.columnContent !== undefined">
{{ column.columnContent }} {{ column.columnContent }}
</div> </div>
@ -49,6 +48,8 @@
></div> ></div>
</div> </div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -5,8 +5,6 @@ export interface ColumnDefinition {
columnContent?: string | undefined; columnContent?: string | undefined;
columnFunction?: Function | undefined; columnFunction?: Function | undefined;
routerLink?: Function | undefined; routerLink?: Function | undefined;
headerCss?: string | undefined;
columnCss?: string | undefined;
} }
@Component({ @Component({
@ -15,12 +13,6 @@ export interface ColumnDefinition {
styleUrls: ['./table.component.scss'], styleUrls: ['./table.component.scss'],
}) })
export class TableComponent { export class TableComponent {
@Input() foreground: string = 'text-white';
@Input() background: string = 'bg-zinc-800';
@Input() backgroundColumn: string = 'bg-zinc-800';
@Input() backgroundColumnAlternate: string = 'bg-zinc-700';
@Input() border: string = 'border-zinc-600';
@Input() items: any[] = []; @Input() items: any[] = [];
@Input() columns: ColumnDefinition[] = []; @Input() columns: ColumnDefinition[] = [];

View File

@ -1,26 +1,20 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormComponent } from './components/form/form.component';
import { CardComponent } from './components/card/card.component'; import { CardComponent } from './components/card/card.component';
import { PaginatorComponent } from './components/paginator/paginator.component'; import { PaginatorComponent } from './components/paginator/paginator.component';
import { TabControlComponent } from './components/tab-control/tab-control.component';
import { TableComponent } from './components/table/table.component'; import { TableComponent } from './components/table/table.component';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
@NgModule({ @NgModule({
declarations: [ declarations: [
CardComponent, CardComponent,
TabControlComponent,
PaginatorComponent, PaginatorComponent,
FormComponent,
TableComponent, TableComponent,
], ],
exports: [ exports: [
CardComponent, CardComponent,
TabControlComponent,
PaginatorComponent, PaginatorComponent,
FormComponent,
TableComponent, TableComponent,
FormsModule, FormsModule,

View File

@ -11,10 +11,8 @@
--color-text-accent: 255, 199, 44; --color-text-accent: 255, 199, 44;
--color-text-accent-muted: 255, 199, 44; --color-text-accent-muted: 255, 199, 44;
--color-shadow-primary: 100,100,100; --color-primary: 244, 244, 245;
--color-secondary: 238, 238, 240;
--color-primary: 255, 255, 255;
--color-secondary: 255, 199, 44;
--color-accent: 255, 199, 44; --color-accent: 255, 199, 44;
} }
.theme-dark { .theme-dark {
@ -25,11 +23,8 @@
--color-text-accent: 250, 183, 0; --color-text-accent: 250, 183, 0;
--color-text-accent-muted: 250, 183, 0; --color-text-accent-muted: 250, 183, 0;
--color-shadow-primary: 170,170,170; --color-primary: 24, 24, 27;
--color-secondary: 39, 39, 42;
--color-primary: 10, 10, 10;
--color-secondary: 250, 183, 0;
--color-accent: 250, 183, 0; --color-accent: 250, 183, 0;
} }
} }

View File

@ -34,7 +34,22 @@ module.exports = {
}, },
boxShadowColor: { boxShadowColor: {
skin: { skin: {
primary: withOpacity('--color-shadow-primary'), primary: withOpacity('--color-text-secondary-muted'),
}
},
ringColor: {
skin: {
primary: withOpacity('--color-text-secondary-muted'),
}
},
divideColor: {
skin: {
primary: withOpacity('--color-text-secondary-muted'),
}
},
borderColor: {
skin: {
primary: withOpacity('--color-text-secondary-muted'),
} }
} }
}, },