From 389018af85360f72b1392720d8d7945ed8b80ecf Mon Sep 17 00:00:00 2001 From: Phan Huy Tran Date: Wed, 23 Apr 2025 11:09:14 +0200 Subject: [PATCH 01/36] feat: add create and delete routes for lootboxes, remove initial creation of lootboxes --- .../de/szut/casino/CasinoApplication.java | 61 ------------------- .../casino/lootboxes/CreateLootBoxDto.java | 30 +++++++++ .../casino/lootboxes/CreateRewardDto.java | 26 ++++++++ .../casino/lootboxes/LootBoxController.java | 39 ++++++++++-- .../szut/casino/lootboxes/LootBoxEntity.java | 8 ++- .../szut/casino/lootboxes/RewardEntity.java | 8 +++ 6 files changed, 106 insertions(+), 66 deletions(-) create mode 100644 backend/src/main/java/de/szut/casino/lootboxes/CreateLootBoxDto.java create mode 100644 backend/src/main/java/de/szut/casino/lootboxes/CreateRewardDto.java diff --git a/backend/src/main/java/de/szut/casino/CasinoApplication.java b/backend/src/main/java/de/szut/casino/CasinoApplication.java index 1d7e861..68e2ebe 100644 --- a/backend/src/main/java/de/szut/casino/CasinoApplication.java +++ b/backend/src/main/java/de/szut/casino/CasinoApplication.java @@ -26,65 +26,4 @@ public class CasinoApplication { public static RestTemplate restTemplate() { return new RestTemplate(); } - - @Bean - public CommandLineRunner initData(LootBoxRepository lootBoxRepository, RewardRepository rewardRepository) { - return _ -> { - if (lootBoxRepository.count() == 0) { - LootBoxEntity basicLootBox = new LootBoxEntity(); - basicLootBox.setName("Basic LootBox"); - basicLootBox.setPrice(new BigDecimal("2")); - basicLootBox.setRewards(new ArrayList<>()); // Initialize the list - - LootBoxEntity premiumLootBox = new LootBoxEntity(); - premiumLootBox.setName("Premium LootBox"); - premiumLootBox.setPrice(new BigDecimal("5")); - premiumLootBox.setRewards(new ArrayList<>()); // Initialize the list - - lootBoxRepository.saveAll(Arrays.asList(basicLootBox, premiumLootBox)); - - RewardEntity commonReward = new RewardEntity(); - commonReward.setValue(new BigDecimal("0.50")); - commonReward.setProbability(new BigDecimal("0.7")); - - RewardEntity rareReward = new RewardEntity(); - rareReward.setValue(new BigDecimal("2.00")); - rareReward.setProbability(new BigDecimal("0.25")); - - RewardEntity epicReward = new RewardEntity(); - epicReward.setValue(new BigDecimal("5.00")); - epicReward.setProbability(new BigDecimal("0.5")); - - RewardEntity premiumCommon = new RewardEntity(); - premiumCommon.setValue(new BigDecimal("2.00")); - premiumCommon.setProbability(new BigDecimal("0.6")); - - RewardEntity premiumRare = new RewardEntity(); - premiumRare.setValue(new BigDecimal("5.00")); - premiumRare.setProbability(new BigDecimal("0.3")); - - RewardEntity legendaryReward = new RewardEntity(); - legendaryReward.setValue(new BigDecimal("15.00")); - legendaryReward.setProbability(new BigDecimal("0.10")); - - rewardRepository.saveAll(Arrays.asList( - commonReward, rareReward, epicReward, - premiumCommon, premiumRare, legendaryReward - )); - - basicLootBox.getRewards().add(commonReward); - basicLootBox.getRewards().add(premiumRare); - - premiumLootBox.getRewards().add(premiumCommon); - premiumLootBox.getRewards().add(premiumRare); - premiumLootBox.getRewards().add(legendaryReward); - - lootBoxRepository.saveAll(Arrays.asList(basicLootBox, premiumLootBox)); - - System.out.println("Initial LootBoxes and rewards created successfully"); - } else { - System.out.println("LootBoxes already exist, skipping initialization"); - } - }; - } } diff --git a/backend/src/main/java/de/szut/casino/lootboxes/CreateLootBoxDto.java b/backend/src/main/java/de/szut/casino/lootboxes/CreateLootBoxDto.java new file mode 100644 index 0000000..10e1b4e --- /dev/null +++ b/backend/src/main/java/de/szut/casino/lootboxes/CreateLootBoxDto.java @@ -0,0 +1,30 @@ +package de.szut.casino.lootboxes; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class CreateLootBoxDto { + @NotEmpty(message = "Loot box name cannot be empty") + @Size(min = 3, max = 50, message = "Loot box name must be between 3 and 50 characters") + private String name; + + @NotNull(message = "Price cannot be null") + @DecimalMin(value = "0.01", message = "Price must be greater than 0") + private BigDecimal price; + + private List rewards = new ArrayList<>(); +} diff --git a/backend/src/main/java/de/szut/casino/lootboxes/CreateRewardDto.java b/backend/src/main/java/de/szut/casino/lootboxes/CreateRewardDto.java new file mode 100644 index 0000000..0dbe819 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/lootboxes/CreateRewardDto.java @@ -0,0 +1,26 @@ +package de.szut.casino.lootboxes; + +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class CreateRewardDto { + @NotNull(message = "Reward value cannot be null") + @DecimalMin(value = "0.00", message = "Reward value must be positive") + private BigDecimal value; + + @NotNull(message = "Probability cannot be null") + @DecimalMin(value = "0.0", message = "Probability must be at least 0.0") + @DecimalMax(value = "1.0", message = "Probability must be at most 1.0") + private BigDecimal probability; +} diff --git a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java index 51a17e1..2d4309d 100644 --- a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java +++ b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java @@ -3,13 +3,11 @@ package de.szut.casino.lootboxes; import de.szut.casino.user.UserEntity; import de.szut.casino.user.UserRepository; import de.szut.casino.user.UserService; +import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; @RestController public class LootBoxController { @@ -55,4 +53,37 @@ public class LootBoxController { return ResponseEntity.ok(reward); } + + @PostMapping("/lootboxes") + public ResponseEntity createLootbox(@RequestBody @Valid CreateLootBoxDto createLootBoxDto) { + List rewardEntities = new ArrayList<>(); + + for (CreateRewardDto createRewardDto : createLootBoxDto.getRewards()) { + rewardEntities.add(new RewardEntity(createRewardDto.getValue(), createRewardDto.getProbability())); + } + + LootBoxEntity lootBoxEntity = new LootBoxEntity( + createLootBoxDto.getName(), + createLootBoxDto.getPrice(), + rewardEntities + ); + + this.lootBoxRepository.save(lootBoxEntity); + + return ResponseEntity.ok(lootBoxEntity); + } + + @DeleteMapping("/lootboxes/{id}") + public ResponseEntity deleteLootbox(@PathVariable Long id) { + Optional optionalLootBox = lootBoxRepository.findById(id); + if (optionalLootBox.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + LootBoxEntity lootBox = optionalLootBox.get(); + lootBoxRepository.delete(lootBox); + + return ResponseEntity.ok(Collections.singletonMap("message", "successfully deleted lootbox")); + } + } diff --git a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxEntity.java b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxEntity.java index 49eb532..eb5b93a 100644 --- a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxEntity.java +++ b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxEntity.java @@ -18,9 +18,15 @@ import java.util.List; @Entity @Getter @Setter -@AllArgsConstructor @NoArgsConstructor public class LootBoxEntity { + + public LootBoxEntity(String name, BigDecimal price, List rewards) { + this.name = name; + this.price = price; + this.rewards = rewards; + } + @Id @GeneratedValue private Long id; diff --git a/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java b/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java index ce0c155..c69390c 100644 --- a/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java +++ b/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import java.math.BigDecimal; @@ -13,7 +14,14 @@ import java.util.List; @Getter @Setter @Entity +@NoArgsConstructor public class RewardEntity { + + public RewardEntity(BigDecimal value, BigDecimal probability) { + this.value = value; + this.probability = probability; + } + @Id @GeneratedValue private Long id; From d6077645d98e5a3204515ec9895d94990dfa8f1c Mon Sep 17 00:00:00 2001 From: csimonis Date: Wed, 23 Apr 2025 10:29:51 +0200 Subject: [PATCH 02/36] feat(transaction): add transaction retrieval and DTO mapping --- .../casino/deposit/TransactionEntity.java | 4 ++ .../casino/deposit/TransactionRepository.java | 7 +++ .../de/szut/casino/user/UserController.java | 7 +-- .../transaction/GetTransactionService.java | 45 +++++++++++++++++++ .../transaction/TransactionController.java | 27 +++++++++++ .../transaction/dto/GetTransactionDto.java | 16 +++++++ .../src/app/feature/home/home.component.html | 4 +- .../src/app/feature/home/home.component.ts | 10 +++-- .../transaction-history.component.css | 0 .../transaction-history.component.html | 1 + .../transaction-history.component.ts | 17 +++++++ frontend/src/app/model/Transaction.ts | 2 +- .../src/app/service/transaction.service.ts | 19 ++++++++ 13 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 backend/src/main/java/de/szut/casino/user/transaction/GetTransactionService.java create mode 100644 backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java create mode 100644 backend/src/main/java/de/szut/casino/user/transaction/dto/GetTransactionDto.java create mode 100644 frontend/src/app/feature/transaction-history/transaction-history.component.css create mode 100644 frontend/src/app/feature/transaction-history/transaction-history.component.html create mode 100644 frontend/src/app/feature/transaction-history/transaction-history.component.ts create mode 100644 frontend/src/app/service/transaction.service.ts diff --git a/backend/src/main/java/de/szut/casino/deposit/TransactionEntity.java b/backend/src/main/java/de/szut/casino/deposit/TransactionEntity.java index e3fc412..c98d1cd 100644 --- a/backend/src/main/java/de/szut/casino/deposit/TransactionEntity.java +++ b/backend/src/main/java/de/szut/casino/deposit/TransactionEntity.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.Setter; import java.math.BigDecimal; +import java.util.Date; @Setter @Getter @@ -26,4 +27,7 @@ public class TransactionEntity { @Enumerated(EnumType.STRING) private TransactionStatus status = TransactionStatus.PROCESSING; + + @Column(name = "created_at") + private Date createdAt = new Date(); } diff --git a/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java b/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java index 94b9b6b..d3b2862 100644 --- a/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java +++ b/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java @@ -5,10 +5,17 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Optional; @Service public interface TransactionRepository extends JpaRepository { @Query("SELECT t FROM TransactionEntity t WHERE t.sessionId = ?1") Optional findOneBySessionID(String sessionId); + + @Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1") + List findAllByUserId(UserEntity id); + + @Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1 ORDER BY t.id DESC LIMIT ?2") + List findByUserIdWithLimit(UserEntity userEntity, Integer limit); } diff --git a/backend/src/main/java/de/szut/casino/user/UserController.java b/backend/src/main/java/de/szut/casino/user/UserController.java index 5daf7ef..031d56a 100644 --- a/backend/src/main/java/de/szut/casino/user/UserController.java +++ b/backend/src/main/java/de/szut/casino/user/UserController.java @@ -4,12 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RestController; - +import org.springframework.web.bind.annotation.*; import de.szut.casino.user.dto.CreateUserDto; import de.szut.casino.user.dto.GetUserDto; import jakarta.validation.Valid; diff --git a/backend/src/main/java/de/szut/casino/user/transaction/GetTransactionService.java b/backend/src/main/java/de/szut/casino/user/transaction/GetTransactionService.java new file mode 100644 index 0000000..5b8cce4 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/user/transaction/GetTransactionService.java @@ -0,0 +1,45 @@ +package de.szut.casino.user.transaction; + +import de.szut.casino.deposit.TransactionEntity; +import de.szut.casino.deposit.TransactionRepository; +import de.szut.casino.user.UserEntity; +import de.szut.casino.user.UserService; +import de.szut.casino.user.transaction.dto.GetTransactionDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class GetTransactionService { + + @Autowired + private UserService userService; + + @Autowired + private TransactionRepository transactionRepository; + + public List getUserTransactions(String authToken, Integer limit) { + Optional user = this.userService.getCurrentUser(authToken); + if (user.isPresent()) { + if (limit != null && limit > 0) { + return this.transactionRepository.findByUserIdWithLimit(user.get(), limit); + } else { + return this.transactionRepository.findAllByUserId(user.get()); + } + } + + return List.of(); + } + + public List mapTransactionsToDtos(List transactions) { + return transactions.stream() + .map(transaction -> new GetTransactionDto( + transaction.getAmount(), + transaction.getStatus(), + transaction.getCreatedAt()) + ).toList(); + } +} + diff --git a/backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java b/backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java new file mode 100644 index 0000000..c813753 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java @@ -0,0 +1,27 @@ +package de.szut.casino.user.transaction; + +import de.szut.casino.deposit.TransactionEntity; +import de.szut.casino.user.transaction.dto.GetTransactionDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +public class TransactionController { + + @Autowired + private GetTransactionService transactionService; + + @GetMapping("/user/transactions") + public ResponseEntity> getUserTransactions(@RequestHeader("Authorization") String authToken, @RequestParam(value = "limit", required = false) Integer limit) { + List transactionEntities = this.transactionService.getUserTransactions(authToken, limit); + + return ResponseEntity.ok(this.transactionService.mapTransactionsToDtos(transactionEntities)); + } +} + diff --git a/backend/src/main/java/de/szut/casino/user/transaction/dto/GetTransactionDto.java b/backend/src/main/java/de/szut/casino/user/transaction/dto/GetTransactionDto.java new file mode 100644 index 0000000..f37fbf4 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/user/transaction/dto/GetTransactionDto.java @@ -0,0 +1,16 @@ +package de.szut.casino.user.transaction.dto; + +import de.szut.casino.deposit.TransactionStatus; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +import java.util.Date; + +@AllArgsConstructor +@NoArgsConstructor +public class GetTransactionDto { + public double amount = 0; + public TransactionStatus status = TransactionStatus.PROCESSING; + public Date createdAt = new Date(); +} + diff --git a/frontend/src/app/feature/home/home.component.html b/frontend/src/app/feature/home/home.component.html index b1bfd9b..4a723a8 100644 --- a/frontend/src/app/feature/home/home.component.html +++ b/frontend/src/app/feature/home/home.component.html @@ -100,11 +100,11 @@

{{ transaction.type }}

-

{{ transaction.date }}

+

{{ transaction.createdAt | date }}

{{ transaction.amount | currency: 'EUR' }} diff --git a/frontend/src/app/feature/home/home.component.ts b/frontend/src/app/feature/home/home.component.ts index eb12454..440e22d 100644 --- a/frontend/src/app/feature/home/home.component.ts +++ b/frontend/src/app/feature/home/home.component.ts @@ -1,16 +1,18 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { CurrencyPipe, NgFor } from '@angular/common'; +import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; +import { AsyncPipe, CurrencyPipe, DatePipe, NgFor } from '@angular/common'; import { DepositComponent } from '../deposit/deposit.component'; import { ActivatedRoute, Router } from '@angular/router'; import { ConfirmationComponent } from '@shared/components/confirmation/confirmation.component'; import { Transaction } from 'app/model/Transaction'; import { NavbarComponent } from '@shared/components/navbar/navbar.component'; import { Game } from 'app/model/Game'; +import { Observable, of } from 'rxjs'; +import { TransactionService } from '@service/transaction.service'; @Component({ selector: 'app-homepage', standalone: true, - imports: [NavbarComponent, CurrencyPipe, NgFor, DepositComponent, ConfirmationComponent], + imports: [NavbarComponent, CurrencyPipe, NgFor, DepositComponent, ConfirmationComponent, AsyncPipe, DatePipe], templateUrl: './home.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -74,7 +76,7 @@ export default class HomeComponent implements OnInit { allGames: Game[] = [...this.featuredGames]; - recentTransactions: Transaction[] = []; + recentTransactions: Observable = inject(TransactionService).getUsersTransactions(5); openDepositModal() { this.isDepositModalOpen = true; diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.css b/frontend/src/app/feature/transaction-history/transaction-history.component.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.html b/frontend/src/app/feature/transaction-history/transaction-history.component.html new file mode 100644 index 0000000..441ab03 --- /dev/null +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.html @@ -0,0 +1 @@ +

transaction-history works!

diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.ts b/frontend/src/app/feature/transaction-history/transaction-history.component.ts new file mode 100644 index 0000000..7eed581 --- /dev/null +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.ts @@ -0,0 +1,17 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { TransactionService } from '@service/transaction.service'; +import { Observable } from 'rxjs'; + +@Component({ + standalone: true, + selector: 'app-transaction-history', + imports: [], + templateUrl: './transaction-history.component.html', + styleUrl: './transaction-history.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TransactionHistoryComponent { + private transactionService: TransactionService = inject(TransactionService); + + transactions$: Observable<> = this.transactionService.getUsersTransactions(); +} diff --git a/frontend/src/app/model/Transaction.ts b/frontend/src/app/model/Transaction.ts index 21277b4..0b3b482 100644 --- a/frontend/src/app/model/Transaction.ts +++ b/frontend/src/app/model/Transaction.ts @@ -2,5 +2,5 @@ export interface Transaction { id: string; type: string; amount: number; - date: string; + createdAt: string; } diff --git a/frontend/src/app/service/transaction.service.ts b/frontend/src/app/service/transaction.service.ts new file mode 100644 index 0000000..a060d77 --- /dev/null +++ b/frontend/src/app/service/transaction.service.ts @@ -0,0 +1,19 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +@Injectable({ + providedIn: 'root', +}) +export class TransactionService { + private http: HttpClient = inject(HttpClient); + + public getUsersTransactions(limit: number|null = null) { + const baseUrl = '/backend/user/transactions'; + + if (limit !== null) { + return this.http.get(`${baseUrl}?limit=${limit}`); + } + + return this.http.get(`${baseUrl}`); + } +} From 9817fb95db48a78d3e3442e9f67b47ece2e5c9e1 Mon Sep 17 00:00:00 2001 From: csimonis Date: Wed, 23 Apr 2025 11:02:49 +0200 Subject: [PATCH 03/36] feat(transaction-history): add transaction history feature with modal --- .../casino/deposit/TransactionRepository.java | 2 +- .../src/app/feature/home/home.component.html | 7 +++--- .../src/app/feature/home/home.component.ts | 23 +++++++++++++++--- .../transaction-history.component.html | 11 ++++++++- .../transaction-history.component.ts | 24 ++++++++++++++++--- frontend/src/app/model/Transaction.ts | 2 +- 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java b/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java index d3b2862..6d092f9 100644 --- a/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java +++ b/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java @@ -16,6 +16,6 @@ public interface TransactionRepository extends JpaRepository findAllByUserId(UserEntity id); - @Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1 ORDER BY t.id DESC LIMIT ?2") + @Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1 ORDER BY t.createdAt DESC LIMIT ?2") List findByUserIdWithLimit(UserEntity userEntity, Integer limit); } diff --git a/frontend/src/app/feature/home/home.component.html b/frontend/src/app/feature/home/home.component.html index 4a723a8..c6014cf 100644 --- a/frontend/src/app/feature/home/home.component.html +++ b/frontend/src/app/feature/home/home.component.html @@ -81,9 +81,10 @@ [isOpen]="isDepositModalOpen" (closeModalEmitter)="closeDepositModal()" > - + @@ -103,8 +104,8 @@ *ngFor="let transaction of recentTransactions|async" >
-

{{ transaction.type }}

-

{{ transaction.createdAt | date }}

+

{{ transaction.status }}

+

{{ transaction.createdAt | date : 'd.m.Y H:m'}}

{{ transaction.amount | currency: 'EUR' }} diff --git a/frontend/src/app/feature/home/home.component.ts b/frontend/src/app/feature/home/home.component.ts index 440e22d..af4c712 100644 --- a/frontend/src/app/feature/home/home.component.ts +++ b/frontend/src/app/feature/home/home.component.ts @@ -8,22 +8,26 @@ import { NavbarComponent } from '@shared/components/navbar/navbar.component'; import { Game } from 'app/model/Game'; import { Observable, of } from 'rxjs'; import { TransactionService } from '@service/transaction.service'; +import format from 'ajv/dist/vocabularies/format'; +import { TransactionHistoryComponent } from '../transaction-history/transaction-history.component'; @Component({ selector: 'app-homepage', standalone: true, - imports: [NavbarComponent, CurrencyPipe, NgFor, DepositComponent, ConfirmationComponent, AsyncPipe, DatePipe], + imports: [NavbarComponent, CurrencyPipe, NgFor, DepositComponent, ConfirmationComponent, AsyncPipe, DatePipe, TransactionHistoryComponent], templateUrl: './home.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export default class HomeComponent implements OnInit { isDepositModalOpen = false; isDepositSuccessful = false; + isTransactionModalOpen = false; constructor( public route: ActivatedRoute, - public router: Router - ) {} + public router: Router, + ) { + } ngOnInit() { this.isDepositSuccessful = this.route.snapshot.queryParams['success'] == 'true'; @@ -81,6 +85,7 @@ export default class HomeComponent implements OnInit { openDepositModal() { this.isDepositModalOpen = true; } + closeDepositModal() { this.isDepositModalOpen = false; } @@ -88,11 +93,23 @@ export default class HomeComponent implements OnInit { openDepositConfirmationModal() { this.isDepositSuccessful = true; } + + openTransactionModal() + { + this.isTransactionModalOpen = true; + } + closeDepositConfirmationModal() { this.isDepositSuccessful = false; } + closeTransactionModal() { + this.isTransactionModalOpen = false; + } + navigateToGame(route: string) { this.router.navigate([route]); } + + protected readonly format = format; } diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.html b/frontend/src/app/feature/transaction-history/transaction-history.component.html index 441ab03..8455ed0 100644 --- a/frontend/src/app/feature/transaction-history/transaction-history.component.html +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.html @@ -1 +1,10 @@ -

transaction-history works!

+ diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.ts b/frontend/src/app/feature/transaction-history/transaction-history.component.ts index 7eed581..b24ef40 100644 --- a/frontend/src/app/feature/transaction-history/transaction-history.component.ts +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.ts @@ -1,17 +1,35 @@ -import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, inject, Input, Output } from '@angular/core'; import { TransactionService } from '@service/transaction.service'; import { Observable } from 'rxjs'; +import { Transaction } from '../../model/Transaction'; +import { AsyncPipe, CurrencyPipe, DatePipe, NgForOf, NgIf } from '@angular/common'; +import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component'; @Component({ standalone: true, selector: 'app-transaction-history', - imports: [], + imports: [ + NgForOf, + AsyncPipe, + CurrencyPipe, + DatePipe, + AnimatedNumberComponent, + NgIf, + ], templateUrl: './transaction-history.component.html', styleUrl: './transaction-history.component.css', changeDetection: ChangeDetectionStrategy.OnPush, }) export class TransactionHistoryComponent { + @Input() + isOpen: boolean = false; private transactionService: TransactionService = inject(TransactionService); + @Output() + closeEventEmitter = new EventEmitter(); + transactions$: Observable = this.transactionService.getUsersTransactions(); - transactions$: Observable<> = this.transactionService.getUsersTransactions(); + closeDialog() { + this.isOpen = false; + this.closeEventEmitter.emit(); + } } diff --git a/frontend/src/app/model/Transaction.ts b/frontend/src/app/model/Transaction.ts index 0b3b482..2a57559 100644 --- a/frontend/src/app/model/Transaction.ts +++ b/frontend/src/app/model/Transaction.ts @@ -1,6 +1,6 @@ export interface Transaction { id: string; - type: string; + status: string; amount: number; createdAt: string; } From 03d67ef362d75056482e88c97fd8c2e86d428e68 Mon Sep 17 00:00:00 2001 From: csimonis Date: Wed, 23 Apr 2025 11:37:11 +0200 Subject: [PATCH 04/36] feat: add pagination support for user transactions retrieval --- .../casino/deposit/TransactionRepository.java | 4 +- .../transaction/GetTransactionService.java | 4 +- .../transaction/TransactionController.java | 8 +++- .../transaction-history.component.html | 39 ++++++++++++++-- .../transaction-history.component.ts | 44 +++++++++++++++++-- .../src/app/service/transaction.service.ts | 10 +++-- 6 files changed, 92 insertions(+), 17 deletions(-) diff --git a/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java b/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java index 6d092f9..c5a98c6 100644 --- a/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java +++ b/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java @@ -16,6 +16,6 @@ public interface TransactionRepository extends JpaRepository findAllByUserId(UserEntity id); - @Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1 ORDER BY t.createdAt DESC LIMIT ?2") - List findByUserIdWithLimit(UserEntity userEntity, Integer limit); + @Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1 ORDER BY t.createdAt DESC LIMIT ?2 OFFSET ?3") + List findByUserIdWithLimit(UserEntity userEntity, Integer limit, Integer offset); } diff --git a/backend/src/main/java/de/szut/casino/user/transaction/GetTransactionService.java b/backend/src/main/java/de/szut/casino/user/transaction/GetTransactionService.java index 5b8cce4..6a83aa7 100644 --- a/backend/src/main/java/de/szut/casino/user/transaction/GetTransactionService.java +++ b/backend/src/main/java/de/szut/casino/user/transaction/GetTransactionService.java @@ -20,11 +20,11 @@ public class GetTransactionService { @Autowired private TransactionRepository transactionRepository; - public List getUserTransactions(String authToken, Integer limit) { + public List getUserTransactions(String authToken, Integer limit, Integer offset) { Optional user = this.userService.getCurrentUser(authToken); if (user.isPresent()) { if (limit != null && limit > 0) { - return this.transactionRepository.findByUserIdWithLimit(user.get(), limit); + return this.transactionRepository.findByUserIdWithLimit(user.get(), limit, offset); } else { return this.transactionRepository.findAllByUserId(user.get()); } diff --git a/backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java b/backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java index c813753..3beb492 100644 --- a/backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java +++ b/backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java @@ -18,8 +18,12 @@ public class TransactionController { private GetTransactionService transactionService; @GetMapping("/user/transactions") - public ResponseEntity> getUserTransactions(@RequestHeader("Authorization") String authToken, @RequestParam(value = "limit", required = false) Integer limit) { - List transactionEntities = this.transactionService.getUserTransactions(authToken, limit); + public ResponseEntity> getUserTransactions( + @RequestHeader("Authorization") String authToken, + @RequestParam(value = "limit", required = false) Integer limit, + @RequestParam(value = "offset", required = false) Integer offset + ) { + List transactionEntities = this.transactionService.getUserTransactions(authToken, limit, offset); return ResponseEntity.ok(this.transactionService.mapTransactionsToDtos(transactionEntities)); } diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.html b/frontend/src/app/feature/transaction-history/transaction-history.component.html index 8455ed0..78ad158 100644 --- a/frontend/src/app/feature/transaction-history/transaction-history.component.html +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.html @@ -1,10 +1,41 @@ diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.ts b/frontend/src/app/feature/transaction-history/transaction-history.component.ts index b24ef40..2b658fe 100644 --- a/frontend/src/app/feature/transaction-history/transaction-history.component.ts +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.ts @@ -1,10 +1,21 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, inject, Input, Output } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + inject, + Input, + Output, + signal, + WritableSignal, +} from '@angular/core'; import { TransactionService } from '@service/transaction.service'; import { Observable } from 'rxjs'; import { Transaction } from '../../model/Transaction'; import { AsyncPipe, CurrencyPipe, DatePipe, NgForOf, NgIf } from '@angular/common'; import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component'; +const PER_PAGE = 5 + @Component({ standalone: true, selector: 'app-transaction-history', @@ -18,18 +29,43 @@ import { AnimatedNumberComponent } from '@blackjack/components/animated-number/a ], templateUrl: './transaction-history.component.html', styleUrl: './transaction-history.component.css', - changeDetection: ChangeDetectionStrategy.OnPush, + changeDetection: ChangeDetectionStrategy.OnPush }) export class TransactionHistoryComponent { @Input() isOpen: boolean = false; - private transactionService: TransactionService = inject(TransactionService); + loading: WritableSignal = signal(true); + skeletonItems = Array(PER_PAGE).fill({}); @Output() closeEventEmitter = new EventEmitter(); - transactions$: Observable = this.transactionService.getUsersTransactions(); + + private offset: number = 0; + + private transactionService: TransactionService = inject(TransactionService); + transactions$: Observable = this.loadTransactions(); closeDialog() { this.isOpen = false; this.closeEventEmitter.emit(); } + + forward() { + this.offset++; + this.transactions$ = this.loadTransactions(); + } + + back() { + this.offset--; + this.transactions$ = this.loadTransactions(); + } + + loadTransactions() { + this.loading.set(true); + + const transactions$ = this.transactionService.getUsersTransactions(PER_PAGE, this.offset * PER_PAGE); + + transactions$.subscribe({complete: () => this.loading.set(false)}) + + return transactions$; + } } diff --git a/frontend/src/app/service/transaction.service.ts b/frontend/src/app/service/transaction.service.ts index a060d77..7fcb659 100644 --- a/frontend/src/app/service/transaction.service.ts +++ b/frontend/src/app/service/transaction.service.ts @@ -7,11 +7,15 @@ import { HttpClient } from '@angular/common/http'; export class TransactionService { private http: HttpClient = inject(HttpClient); - public getUsersTransactions(limit: number|null = null) { - const baseUrl = '/backend/user/transactions'; + public getUsersTransactions(limit: number|null = null, offset: number|null = null) { + let baseUrl = new URL(`${window.location.origin}/backend/user/transactions`); if (limit !== null) { - return this.http.get(`${baseUrl}?limit=${limit}`); + baseUrl.searchParams.append('limit', limit.toString()); + } + + if (offset !== null) { + baseUrl.searchParams.append('offset', offset.toString()); } return this.http.get(`${baseUrl}`); From 157e774e8602d8e24b3ec002ca6bfa9ef6eabf03 Mon Sep 17 00:00:00 2001 From: csimonis Date: Wed, 23 Apr 2025 11:54:30 +0200 Subject: [PATCH 05/36] feat(transaction-history): add disabled state styling for buttons --- .../transaction-history.component.css | 9 +++++ .../transaction-history.component.html | 40 +++++++++---------- .../transaction-history.component.ts | 23 ++--------- 3 files changed, 30 insertions(+), 42 deletions(-) diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.css b/frontend/src/app/feature/transaction-history/transaction-history.component.css index e69de29..c3fdef2 100644 --- a/frontend/src/app/feature/transaction-history/transaction-history.component.css +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.css @@ -0,0 +1,9 @@ +button[disabled] { + cursor: not-allowed; + background-color: #ccc; + box-shadow: none; +} + +button[disabled]:hover { + background-color: #ccc; +} diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.html b/frontend/src/app/feature/transaction-history/transaction-history.component.html index 78ad158..5d26590 100644 --- a/frontend/src/app/feature/transaction-history/transaction-history.component.html +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.html @@ -1,36 +1,32 @@