feat: add transaction history to homepage (CAS-56) #137

Merged
csimonis merged 8 commits from feature/transaction-history into main 2025-04-23 10:38:31 +00:00
11 changed files with 47 additions and 24 deletions
Showing only changes of commit 35184807c0 - Show all commits

View file

@ -18,4 +18,7 @@ public interface TransactionRepository extends JpaRepository<TransactionEntity,
@Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1 ORDER BY t.createdAt DESC LIMIT ?2 OFFSET ?3") @Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1 ORDER BY t.createdAt DESC LIMIT ?2 OFFSET ?3")
List<TransactionEntity> findByUserIdWithLimit(UserEntity userEntity, Integer limit, Integer offset); List<TransactionEntity> findByUserIdWithLimit(UserEntity userEntity, Integer limit, Integer offset);
@Query("SELECT COUNT(t) > ?2 + ?3 FROM TransactionEntity t WHERE t.user = ?1")
Boolean hasMore(UserEntity userEntity, Integer limit, Integer offset);
} }

View file

@ -5,6 +5,7 @@ import de.szut.casino.deposit.TransactionRepository;
import de.szut.casino.user.UserEntity; import de.szut.casino.user.UserEntity;
import de.szut.casino.user.UserService; import de.szut.casino.user.UserService;
import de.szut.casino.user.transaction.dto.GetTransactionDto; import de.szut.casino.user.transaction.dto.GetTransactionDto;
import de.szut.casino.user.transaction.dto.UserTransactionsDto;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -20,17 +21,16 @@ public class GetTransactionService {
@Autowired @Autowired
private TransactionRepository transactionRepository; private TransactionRepository transactionRepository;
public List<TransactionEntity> getUserTransactions(String authToken, Integer limit, Integer offset) { public UserTransactionsDto getUserTransactionsDto(String authToken, Integer limit, Integer offset) {
Optional<UserEntity> user = this.userService.getCurrentUser(authToken); Optional<UserEntity> user = this.userService.getCurrentUser(authToken);
if (user.isPresent()) { if (user.isPresent()) {
if (limit != null && limit > 0) { List<TransactionEntity> transactionEntities = this.transactionRepository.findByUserIdWithLimit(user.get(), limit, offset);
return this.transactionRepository.findByUserIdWithLimit(user.get(), limit, offset); Boolean hasMore = this.transactionRepository.hasMore(user.get(), limit, offset);
} else {
return this.transactionRepository.findAllByUserId(user.get()); return new UserTransactionsDto(mapTransactionsToDtos(transactionEntities), hasMore);
}
} }
return List.of(); return new UserTransactionsDto(List.of(), false);
} }
public List<GetTransactionDto> mapTransactionsToDtos(List<TransactionEntity> transactions) { public List<GetTransactionDto> mapTransactionsToDtos(List<TransactionEntity> transactions) {

View file

@ -2,6 +2,7 @@ package de.szut.casino.user.transaction;
import de.szut.casino.deposit.TransactionEntity; import de.szut.casino.deposit.TransactionEntity;
import de.szut.casino.user.transaction.dto.GetTransactionDto; import de.szut.casino.user.transaction.dto.GetTransactionDto;
import de.szut.casino.user.transaction.dto.UserTransactionsDto;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -18,14 +19,14 @@ public class TransactionController {
private GetTransactionService transactionService; private GetTransactionService transactionService;
@GetMapping("/user/transactions") @GetMapping("/user/transactions")
public ResponseEntity<List<GetTransactionDto>> getUserTransactions( public ResponseEntity<UserTransactionsDto> getUserTransactions(
@RequestHeader("Authorization") String authToken, @RequestHeader("Authorization") String authToken,
@RequestParam(value = "limit", required = false) Integer limit, @RequestParam(value = "limit", required = false) Integer limit,
@RequestParam(value = "offset", required = false) Integer offset @RequestParam(value = "offset", required = false) Integer offset
) { ) {
List<TransactionEntity> transactionEntities = this.transactionService.getUserTransactions(authToken, limit, offset); UserTransactionsDto transactionEntities = this.transactionService.getUserTransactionsDto(authToken, limit, offset);
return ResponseEntity.ok(this.transactionService.mapTransactionsToDtos(transactionEntities)); return ResponseEntity.ok(transactionEntities);
} }
} }

View file

@ -0,0 +1,12 @@
package de.szut.casino.user.transaction.dto;
import lombok.AllArgsConstructor;
import java.util.List;
@AllArgsConstructor
public class UserTransactionsDto {
public List<GetTransactionDto> transactions;
public Boolean hasMore;
}

View file

@ -101,7 +101,7 @@
<div class="space-y-3"> <div class="space-y-3">
<div <div
class="flex justify-between items-center" class="flex justify-between items-center"
*ngFor="let transaction of recentTransactions|async" *ngFor="let transaction of (recentTransactionData | async)?.transactions"
> >
<div> <div>
<p class="text-sm font-medium">{{ transaction.status }}</p> <p class="text-sm font-medium">{{ transaction.status }}</p>

View file

@ -3,13 +3,13 @@ import { AsyncPipe, CurrencyPipe, DatePipe, NgFor } from '@angular/common';
import { DepositComponent } from '../deposit/deposit.component'; import { DepositComponent } from '../deposit/deposit.component';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { ConfirmationComponent } from '@shared/components/confirmation/confirmation.component'; import { ConfirmationComponent } from '@shared/components/confirmation/confirmation.component';
import { Transaction } from 'app/model/Transaction';
import { NavbarComponent } from '@shared/components/navbar/navbar.component'; import { NavbarComponent } from '@shared/components/navbar/navbar.component';
import { Game } from 'app/model/Game'; import { Game } from 'app/model/Game';
import { Observable, of } from 'rxjs'; import { Observable } from 'rxjs';
import { TransactionService } from '@service/transaction.service'; import { TransactionService } from '@service/transaction.service';
import format from 'ajv/dist/vocabularies/format'; import format from 'ajv/dist/vocabularies/format';
import { TransactionHistoryComponent } from '../transaction-history/transaction-history.component'; import { TransactionHistoryComponent } from '../transaction-history/transaction-history.component';
import { TransactionData } from '../../model/TransactionData';
@Component({ @Component({
selector: 'app-homepage', selector: 'app-homepage',
@ -80,7 +80,7 @@ export default class HomeComponent implements OnInit {
allGames: Game[] = [...this.featuredGames]; allGames: Game[] = [...this.featuredGames];
recentTransactions: Observable<Transaction[]> = inject(TransactionService).getUsersTransactions(5); recentTransactionData: Observable<TransactionData> = inject(TransactionService).getUsersTransactions(5);
openDepositModal() { openDepositModal() {
this.isDepositModalOpen = true; this.isDepositModalOpen = true;

View file

@ -1,9 +1,8 @@
button[disabled] { button[disabled] {
cursor: not-allowed; cursor: not-allowed;
background-color: #ccc; background-color: #077b58;
box-shadow: none; box-shadow: none;
} }
button[disabled]:hover { button[disabled]:hover {
background-color: #ccc; background-color: #077b58;
} }

View file

@ -8,7 +8,7 @@
</button> </button>
<h2 class="modal-heading">Transaktionen</h2> <h2 class="modal-heading">Transaktionen</h2>
<p class="py-2 text-text-secondary mb-4">Hier siehst du alle vergangenen Einzahlungen</p> <p class="py-2 text-text-secondary mb-4">Hier siehst du alle vergangenen Einzahlungen</p>
@for (transaction of transactions$ | async; track null) { @for (transaction of (transactionData$ | async)?.transactions; track null) {
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<div> <div>
<p class="text-sm font-medium">{{ transaction.status }}</p> <p class="text-sm font-medium">{{ transaction.status }}</p>
@ -25,11 +25,11 @@
</div> </div>
</div> </div>
} }
<div class="inline inline-flex w-full"> <div class="inline inline-flex w-full gap-2">
<button type="button" (click)="back()" class="button-primary w-full py-2" [disabled]="offset <= 0"> <button type="button" (click)="back()" class="button-primary w-full py-2" [disabled]="offset <= 0">
< <
</button> </button>
<button type="button" (click)="forward()" class="button-primary w-full py-2"> <button type="button" (click)="forward()" class="button-primary w-full py-2" [disabled]="!(transactionData$|async)?.hasMore">
> >
</button> </button>
</div> </div>

View file

@ -4,6 +4,7 @@ import { Observable } from 'rxjs';
import { Transaction } from '../../model/Transaction'; import { Transaction } from '../../model/Transaction';
import { AsyncPipe, CurrencyPipe, DatePipe, NgForOf, NgIf } from '@angular/common'; import { AsyncPipe, CurrencyPipe, DatePipe, NgForOf, NgIf } from '@angular/common';
import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component'; import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component';
import { TransactionData } from '../../model/TransactionData';
const PER_PAGE = 5 const PER_PAGE = 5
@ -31,7 +32,7 @@ export class TransactionHistoryComponent {
protected offset: number = 0; protected offset: number = 0;
private transactionService: TransactionService = inject(TransactionService); private transactionService: TransactionService = inject(TransactionService);
transactions$: Observable<Transaction[]> = this.loadTransactions(); transactionData$: Observable<TransactionData> = this.loadTransactions();
closeDialog() { closeDialog() {
this.isOpen = false; this.isOpen = false;
@ -40,12 +41,12 @@ export class TransactionHistoryComponent {
forward() { forward() {
this.offset++; this.offset++;
this.transactions$ = this.loadTransactions(); this.transactionData$ = this.loadTransactions();
} }
back() { back() {
this.offset--; this.offset--;
this.transactions$ = this.loadTransactions(); this.transactionData$ = this.loadTransactions();
} }
loadTransactions() { loadTransactions() {

View file

@ -0,0 +1,6 @@
import { Transaction } from './Transaction';
export interface TransactionData {
transactions: Transaction[];
hasMore: boolean;
}

View file

@ -1,5 +1,6 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { TransactionData } from '../model/TransactionData';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -18,6 +19,6 @@ export class TransactionService {
baseUrl.searchParams.append('offset', offset.toString()); baseUrl.searchParams.append('offset', offset.toString());
} }
return this.http.get<any[]>(`${baseUrl}`); return this.http.get<TransactionData>(`${baseUrl}`);
} }
} }