Merge branch 'main' into import
All checks were successful
CI / Get Changed Files (pull_request) Successful in 10s
CI / Checkstyle Main (pull_request) Has been skipped
CI / Docker backend validation (pull_request) Successful in 15s
CI / eslint (pull_request) Successful in 35s
CI / oxlint (pull_request) Successful in 28s
CI / Docker frontend validation (pull_request) Successful in 48s
CI / prettier (pull_request) Successful in 27s
CI / test-build (pull_request) Successful in 32s

This commit is contained in:
Jan-Marlon Leibl 2025-05-07 14:28:05 +00:00
commit 371de28e07
No known key found for this signature in database
GPG key ID: 944223E4D46B7412
7 changed files with 119 additions and 8 deletions

View file

@ -312,9 +312,11 @@ body {
opacity 0.5s ease-out, opacity 0.5s ease-out,
transform 0.5s ease-out; transform 0.5s ease-out;
transition-delay: 0.5s; transition-delay: 0.5s;
pointer-events: none; /* Prevent clicks when invisible */
} }
.animation-fade.visible { .animation-fade.visible {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
pointer-events: auto; /* Enable clicks when visible */
} }

View file

@ -31,9 +31,15 @@
<div *ngIf="!isOpening && !isOpen" class="mb-6"> <div *ngIf="!isOpening && !isOpen" class="mb-6">
<button <button
(click)="openLootbox()" (click)="openLootbox()"
class="button-primary w-full py-3 text-lg font-semibold rounded" [disabled]="!hasEnoughBalance()"
[ngClass]="{
'button-primary': hasEnoughBalance(),
'bg-gray-500 cursor-not-allowed': !hasEnoughBalance(),
}"
class="w-full py-3 text-lg font-semibold rounded"
> >
Öffnen <span *ngIf="hasEnoughBalance()">Öffnen</span>
<span *ngIf="!hasEnoughBalance()">Nicht genug Guthaben</span>
</button> </button>
</div> </div>
@ -85,12 +91,24 @@
class="flex justify-center gap-4 mt-8 animation-fade" class="flex justify-center gap-4 mt-8 animation-fade"
[class.visible]="animationCompleted" [class.visible]="animationCompleted"
> >
<button (click)="openAgain()" class="button-primary px-6 py-2 font-semibold rounded"> <button
Nochmal Öffnen (click)="openAgain()"
[disabled]="!animationCompleted || !hasEnoughBalance()"
[ngClass]="{
'button-primary': hasEnoughBalance(),
'bg-gray-500 cursor-not-allowed': !hasEnoughBalance(),
}"
class="px-6 py-2 font-semibold rounded"
[attr.aria-hidden]="!animationCompleted"
>
<span *ngIf="hasEnoughBalance()">Nochmal Öffnen</span>
<span *ngIf="!hasEnoughBalance()">Nicht genug Guthaben</span>
</button> </button>
<button <button
(click)="goBack()" (click)="goBack()"
class="bg-deep-blue hover:bg-deep-blue-contrast text-white px-6 py-2 rounded font-semibold transition" class="bg-deep-blue hover:bg-deep-blue-contrast text-white px-6 py-2 rounded font-semibold transition"
[disabled]="!animationCompleted"
[attr.aria-hidden]="!animationCompleted"
> >
Zurück zur Übersicht Zurück zur Übersicht
</button> </button>

View file

@ -4,6 +4,8 @@ import { ActivatedRoute, Router } from '@angular/router';
import { LootboxService } from '../services/lootbox.service'; import { LootboxService } from '../services/lootbox.service';
import { LootBox, Reward } from 'app/model/LootBox'; import { LootBox, Reward } from 'app/model/LootBox';
import { NavbarComponent } from '@shared/components/navbar/navbar.component'; import { NavbarComponent } from '@shared/components/navbar/navbar.component';
import { UserService } from '@service/user.service';
import { User } from 'app/model/User';
@Component({ @Component({
selector: 'app-lootbox-opening', selector: 'app-lootbox-opening',
@ -21,14 +23,20 @@ export default class LootboxOpeningComponent {
wonReward: Reward | null = null; wonReward: Reward | null = null;
prizeList: Reward[] = []; prizeList: Reward[] = [];
animationCompleted = false; animationCompleted = false;
currentUser: User | null = null;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private lootboxService: LootboxService, private lootboxService: LootboxService,
private userService: UserService,
private cdr: ChangeDetectorRef private cdr: ChangeDetectorRef
) { ) {
this.loadLootbox(); this.loadLootbox();
this.userService.currentUser$.subscribe((user) => {
this.currentUser = user;
this.cdr.detectChanges();
});
} }
private loadLootbox(): void { private loadLootbox(): void {
@ -57,8 +65,24 @@ export default class LootboxOpeningComponent {
openLootbox(): void { openLootbox(): void {
if (!this.lootbox || this.isOpening) return; if (!this.lootbox || this.isOpening) return;
// Check if user has enough balance
if (!this.hasEnoughBalance()) {
this.error = 'Nicht genug Guthaben, um diese Lootbox zu öffnen.';
window.scrollTo(0, 0);
this.cdr.detectChanges();
setTimeout(() => {
this.error = '';
this.cdr.detectChanges();
}, 5000);
return;
}
this.resetState(true); this.resetState(true);
if (this.lootbox.price) {
this.userService.updateLocalBalance(-this.lootbox.price);
}
setTimeout(() => { setTimeout(() => {
this.lootboxService.purchaseLootBox(this.lootbox!.id).subscribe({ this.lootboxService.purchaseLootBox(this.lootbox!.id).subscribe({
next: this.handleRewardSuccess.bind(this), next: this.handleRewardSuccess.bind(this),
@ -72,6 +96,7 @@ export default class LootboxOpeningComponent {
this.generateCasePrizes(reward); this.generateCasePrizes(reward);
this.isOpening = false; this.isOpening = false;
this.isOpen = true; this.isOpen = true;
this.cdr.detectChanges(); this.cdr.detectChanges();
} }
@ -80,7 +105,12 @@ export default class LootboxOpeningComponent {
const rewards = this.lootbox.rewards; const rewards = this.lootbox.rewards;
const fallback = rewards[Math.floor(Math.random() * rewards.length)]; const fallback = rewards[Math.floor(Math.random() * rewards.length)];
this.handleRewardSuccess(fallback);
this.wonReward = fallback;
this.generateCasePrizes(fallback);
this.isOpening = false;
this.isOpen = true;
this.cdr.detectChanges();
} }
private resetState(isOpening = false): void { private resetState(isOpening = false): void {
@ -112,6 +142,12 @@ export default class LootboxOpeningComponent {
setTimeout(() => { setTimeout(() => {
this.animationCompleted = true; this.animationCompleted = true;
if (this.wonReward) {
this.userService.updateLocalBalance(this.wonReward.value);
}
this.userService.refreshCurrentUser();
this.cdr.detectChanges(); this.cdr.detectChanges();
}, 10000); }, 10000);
} }
@ -168,4 +204,9 @@ export default class LootboxOpeningComponent {
if (!this.wonReward || !this.lootbox) return ''; if (!this.wonReward || !this.lootbox) return '';
return this.wonReward.value > (this.lootbox.price || 0) ? 'text-emerald' : 'text-accent-red'; return this.wonReward.value > (this.lootbox.price || 0) ? 'text-emerald' : 'text-accent-red';
} }
hasEnoughBalance(): boolean {
if (!this.currentUser || !this.lootbox) return false;
return this.currentUser.balance >= this.lootbox.price;
}
} }

View file

@ -44,9 +44,15 @@
<div class="mt-4"> <div class="mt-4">
<button <button
(click)="openLootbox(lootbox.id)" (click)="openLootbox(lootbox.id)"
class="button-primary w-full py-2 rounded font-semibold" [disabled]="!hasEnoughBalance(lootbox.price)"
[ngClass]="{
'button-primary': hasEnoughBalance(lootbox.price),
'bg-gray-500 cursor-not-allowed': !hasEnoughBalance(lootbox.price),
}"
class="w-full py-2 rounded font-semibold"
> >
Öffnen <span *ngIf="hasEnoughBalance(lootbox.price)">Öffnen</span>
<span *ngIf="!hasEnoughBalance(lootbox.price)">Nicht genug Guthaben</span>
</button> </button>
</div> </div>
</div> </div>

View file

@ -5,6 +5,8 @@ import { LootboxService } from '../services/lootbox.service';
import { LootBox } from 'app/model/LootBox'; import { LootBox } from 'app/model/LootBox';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { timeout } from 'rxjs'; import { timeout } from 'rxjs';
import { UserService } from '@service/user.service';
import { User } from 'app/model/User';
@Component({ @Component({
selector: 'app-lootbox-selection', selector: 'app-lootbox-selection',
@ -17,6 +19,7 @@ export default class LootboxSelectionComponent implements OnInit {
lootboxes: LootBox[] = []; lootboxes: LootBox[] = [];
isLoading = true; isLoading = true;
error = ''; error = '';
currentUser: User | null = null;
// Fallback data in case the API call fails // Fallback data in case the API call fails
fallbackLootboxes: LootBox[] = [ fallbackLootboxes: LootBox[] = [
@ -86,11 +89,16 @@ export default class LootboxSelectionComponent implements OnInit {
constructor( constructor(
private lootboxService: LootboxService, private lootboxService: LootboxService,
private router: Router, private router: Router,
private cdr: ChangeDetectorRef private cdr: ChangeDetectorRef,
private userService: UserService
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
this.loadLootboxes(); this.loadLootboxes();
this.userService.currentUser$.subscribe((user) => {
this.currentUser = user;
this.cdr.detectChanges();
});
} }
loadLootboxes(): void { loadLootboxes(): void {
@ -120,6 +128,24 @@ export default class LootboxSelectionComponent implements OnInit {
} }
openLootbox(lootboxId: number): void { openLootbox(lootboxId: number): void {
const lootbox = this.lootboxes.find((box) => box.id === lootboxId);
if (!lootbox) {
return;
}
if (!this.currentUser || this.currentUser.balance < lootbox.price) {
this.error = 'Nicht genug Guthaben, um diese Lootbox zu öffnen.';
// Scroll to top to see the error message
window.scrollTo(0, 0);
this.cdr.detectChanges();
setTimeout(() => {
this.error = '';
this.cdr.detectChanges();
}, 5000);
return;
}
this.router.navigate(['/game/lootboxes/open', lootboxId]); this.router.navigate(['/game/lootboxes/open', lootboxId]);
} }
@ -136,4 +162,8 @@ export default class LootboxSelectionComponent implements OnInit {
formatProbability(probability: number): string { formatProbability(probability: number): string {
return (probability * 100).toFixed(0) + '%'; return (probability * 100).toFixed(0) + '%';
} }
hasEnoughBalance(price: number): boolean {
return !!this.currentUser && this.currentUser.balance >= price;
}
} }

View file

@ -33,6 +33,17 @@ export class UserService {
this.getCurrentUser().subscribe(); this.getCurrentUser().subscribe();
} }
public updateLocalBalance(amount: number): void {
const currentUser = this.currentUserSubject.getValue();
if (currentUser) {
const updatedUser = {
...currentUser,
balance: currentUser.balance + amount,
};
this.currentUserSubject.next(updatedUser);
}
}
public createUser(id: string, username: string): Observable<User> { public createUser(id: string, username: string): Observable<User> {
return this.http return this.http
.post<User>('/backend/user', { .post<User>('/backend/user', {

View file

@ -31,7 +31,10 @@ export class NavbarComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.userSubscription = this.userService.currentUser$.subscribe((user) => { this.userSubscription = this.userService.currentUser$.subscribe((user) => {
this.balance.set(user?.balance ?? 0); this.balance.set(user?.balance ?? 0);
console.log('Updated navbar balance:', user?.balance);
}); });
this.userService.refreshCurrentUser();
} }
ngOnDestroy() { ngOnDestroy() {