Merge branch 'main' into feature/authentik
Some checks failed
CI / Get Changed Files (pull_request) Successful in 6s
CI / prettier (pull_request) Failing after 22s
CI / Checkstyle Main (pull_request) Failing after 35s
CI / eslint (pull_request) Failing after 1m41s
CI / test-build (pull_request) Successful in 1m48s

This commit is contained in:
Jan K9f 2025-04-02 16:00:01 +02:00
commit d7fe0e3965
Signed by: jank
GPG key ID: B9F475106B20F144
56 changed files with 2782 additions and 602 deletions

View file

@ -8,7 +8,7 @@ import {
AfterViewInit,
OnDestroy,
} from '@angular/core';
import { ModalAnimationService } from '../../services/modal-animation.service';
import { ModalAnimationService } from '@shared/services/modal-animation.service';
import gsap from 'gsap';
@Component({

View file

@ -0,0 +1,173 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
signal,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { animate, style, transition, trigger } from '@angular/animations';
import { interval, Subscription, takeWhile } from 'rxjs';
@Component({
selector: 'app-debt-dialog',
standalone: true,
imports: [CommonModule],
template: `
<div *ngIf="visible" [@fadeInOut] class="modal-bg" style="z-index: 1000; position: fixed;">
<div class="modal-card" [@cardAnimation]>
<h2 class="modal-heading text-accent-red">WARNUNG!</h2>
<p class="py-2 text-text-secondary mb-4">
Du hast nicht genug Geld für den Double Down. Du bist jetzt im Minus und schuldest uns
{{ amount | currency: 'EUR' }}.
</p>
<p class="py-2 text-accent-red mb-4 font-bold">
Liefer das Geld sofort an den Dead Drop oder es wird unangenehme Konsequenzen geben!
</p>
<div
class="bg-deep-blue-light/50 rounded-lg p-5 mb-6 shadow-inner border border-deep-blue-light/30"
>
<div class="grid grid-cols-2 gap-4">
<div class="text-text-secondary">Schulden:</div>
<div class="font-medium text-right text-accent-red">{{ amount | currency: 'EUR' }}</div>
</div>
</div>
<div class="text-center mb-6">
<div
class="text-8xl font-bold text-accent-red"
[class.animate-pulse]="timeLeft() <= 10"
[class.animate-bounce]="timeLeft() <= 5"
[@countdown]="timeLeft()"
>
{{ timeLeft() }}
</div>
<div class="text-text-secondary mt-2">Sekunden verbleibend</div>
</div>
@if (timeLeft() === 0) {
<div class="text-center mb-6">
<div class="relative">
<div class="absolute inset-0 bg-accent-red/20 blur-xl rounded-full"></div>
<div
class="relative bg-gradient-to-b from-accent-red to-red-900 p-8 rounded-lg border-2 border-accent-red shadow-lg"
>
<div class="flex items-center justify-center gap-4 mb-4">
<svg
class="w-12 h-12 text-accent-red animate-[spin_2s_linear_infinite]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"
/>
</svg>
<span
class="text-4xl font-black tracking-wider text-white animate-[pulse_1s_ease-in-out_infinite]"
>
ZEIT ABGELAUFEN
</span>
<svg
class="w-12 h-12 text-accent-red animate-[spin_2s_linear_infinite]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"
/>
</svg>
</div>
<div
class="text-2xl font-bold text-white/90 tracking-wider animate-[pulse_1s_ease-in-out_infinite]"
>
KONSEQUENZEN FOLGEN
</div>
</div>
</div>
</div>
}
<button type="button" (click)="closeDialog()" class="button-primary w-full py-2">
Verstanden
</button>
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
trigger('fadeInOut', [
transition(':enter', [
style({ opacity: 0 }),
animate('150ms ease-out', style({ opacity: 1 })),
]),
transition(':leave', [animate('150ms ease-in', style({ opacity: 0 }))]),
]),
trigger('cardAnimation', [
transition(':enter', [
style({ opacity: 0, transform: 'scale(0.95)' }),
animate('200ms ease-out', style({ opacity: 1, transform: 'scale(1)' })),
]),
]),
trigger('countdown', [
transition('* => *', [
style({ transform: 'scale(1.2)' }),
animate('100ms ease-out', style({ transform: 'scale(1)' })),
]),
]),
],
})
export class DebtDialogComponent implements OnInit, OnDestroy {
@Input() amount = 0;
@Input() set show(value: boolean) {
this.visible = value;
if (value) {
this.startTimer();
}
}
@Output() dialogClosed = new EventEmitter<void>();
visible = false;
timeLeft = signal(30);
private timerSubscription: Subscription | undefined;
private warningSound = new Audio('assets/sounds/warning.mp3');
ngOnInit() {
if (this.visible) {
this.startTimer();
}
}
ngOnDestroy() {
this.stopTimer();
}
private startTimer() {
this.timeLeft.set(30);
this.timerSubscription = interval(1000)
.pipe(takeWhile(() => this.timeLeft() > 0))
.subscribe(() => {
this.timeLeft.update((value) => value - 1);
if (this.timeLeft() <= 5) {
this.warningSound.play();
}
if (this.timeLeft() === 0) {
setTimeout(() => this.closeDialog(), 5000);
}
});
}
private stopTimer() {
if (this.timerSubscription) {
this.timerSubscription.unsubscribe();
}
}
closeDialog(): void {
this.stopTimer();
this.visible = false;
this.dialogClosed.emit();
}
}

View file

@ -17,8 +17,11 @@
@if (isLoggedIn) {
<div
class="text-white font-bold bg-deep-blue-contrast rounded-full px-4 py-2 text-sm hover:bg-deep-blue-contrast/80 hover:cursor-pointer hover:scale-105 transition-all active:scale-95 select-none duration-300"
routerLink="/home"
>
<span>Balance: {{ balance() | currency: 'EUR' : 'symbol' : '1.2-2' }}</span>
<span [class]="balance() < 0 ? 'text-accent-red' : ''">{{
balance() | currency: 'EUR' : 'symbol' : '1.2-2'
}}</span>
</div>
<button (click)="logout()" class="button-primary px-4 py-1.5">Abmelden</button>
}

View file

@ -1,8 +1,16 @@
import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
inject,
OnInit,
OnDestroy,
signal,
} from '@angular/core';
import { RouterModule } from '@angular/router';
import { UserService } from '../../../service/user.service';
import { CurrencyPipe } from '@angular/common';
import { AuthService } from '../../../service/auth.service';
import { CurrencyPipe } from '@angular/common';
import { UserService } from '@service/user.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-navbar',
@ -11,22 +19,27 @@ import { AuthService } from '../../../service/auth.service';
imports: [RouterModule, CurrencyPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NavbarComponent implements OnInit {
export class NavbarComponent implements OnInit, OnDestroy {
isMenuOpen = false;
private authService: AuthService = inject(AuthService);
isLoggedIn = this.authService.isLoggedIn();
private userService = inject(UserService);
private user = this.userService.getCurrentUser();
private userSubscription: Subscription | undefined;
public balance = signal(0);
ngOnInit() {
this.user.subscribe((user) => {
this.userSubscription = this.userService.currentUser$.subscribe((user) => {
this.balance.set(user?.balance ?? 0);
});
}
ngOnDestroy() {
if (this.userSubscription) {
this.userSubscription.unsubscribe();
}
}
login() {
try {
this.authService.login();