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
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:
commit
371de28e07
7 changed files with 119 additions and 8 deletions
|
@ -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 */
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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', {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Reference in a new issue