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..5a16f0d 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,20 @@ 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.createdAt DESC LIMIT ?2 OFFSET ?3") + List 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); } 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..831c438 --- /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 de.szut.casino.user.transaction.dto.UserTransactionsDto; +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 UserTransactionsDto getUserTransactionsDto(String authToken, Integer limit, Integer offset) { + Optional user = this.userService.getCurrentUser(authToken); + if (user.isPresent()) { + List transactionEntities = this.transactionRepository.findByUserIdWithLimit(user.get(), limit, offset); + Boolean hasMore = this.transactionRepository.hasMore(user.get(), limit, offset); + + return new UserTransactionsDto(mapTransactionsToDtos(transactionEntities), hasMore); + } + + return new UserTransactionsDto(List.of(), false); + } + + 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..61805b0 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java @@ -0,0 +1,32 @@ +package de.szut.casino.user.transaction; + +import de.szut.casino.deposit.TransactionEntity; +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.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, + @RequestParam(value = "offset", required = false) Integer offset + ) { + UserTransactionsDto transactionEntities = this.transactionService.getUserTransactionsDto(authToken, limit, offset); + + return ResponseEntity.ok(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/backend/src/main/java/de/szut/casino/user/transaction/dto/UserTransactionsDto.java b/backend/src/main/java/de/szut/casino/user/transaction/dto/UserTransactionsDto.java new file mode 100644 index 0000000..54e116b --- /dev/null +++ b/backend/src/main/java/de/szut/casino/user/transaction/dto/UserTransactionsDto.java @@ -0,0 +1,12 @@ +package de.szut.casino.user.transaction.dto; + +import lombok.AllArgsConstructor; + +import java.util.List; + +@AllArgsConstructor +public class UserTransactionsDto { + public List transactions; + public Boolean hasMore; +} + diff --git a/frontend/src/app/feature/home/home.component.html b/frontend/src/app/feature/home/home.component.html index b1bfd9b..e03bed8 100644 --- a/frontend/src/app/feature/home/home.component.html +++ b/frontend/src/app/feature/home/home.component.html @@ -81,9 +81,16 @@ [isOpen]="isDepositModalOpen" (closeModalEmitter)="closeDepositModal()" > - + @@ -100,11 +107,13 @@
-

{{ transaction.type }}

-

{{ transaction.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 eb12454..be84450 100644 --- a/frontend/src/app/feature/home/home.component.ts +++ b/frontend/src/app/feature/home/home.component.ts @@ -1,22 +1,36 @@ -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 } from 'rxjs'; +import { TransactionService } from '@service/transaction.service'; +import format from 'ajv/dist/vocabularies/format'; +import { TransactionHistoryComponent } from '../transaction-history/transaction-history.component'; +import { TransactionData } from '../../model/TransactionData'; @Component({ selector: 'app-homepage', standalone: true, - imports: [NavbarComponent, CurrencyPipe, NgFor, DepositComponent, ConfirmationComponent], + 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, @@ -74,11 +88,13 @@ export default class HomeComponent implements OnInit { allGames: Game[] = [...this.featuredGames]; - recentTransactions: Transaction[] = []; + recentTransactionData: Observable = + inject(TransactionService).getUsersTransactions(5); openDepositModal() { this.isDepositModalOpen = true; } + closeDepositModal() { this.isDepositModalOpen = false; } @@ -86,11 +102,22 @@ 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.css b/frontend/src/app/feature/transaction-history/transaction-history.component.css new file mode 100644 index 0000000..c68e294 --- /dev/null +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.css @@ -0,0 +1,8 @@ +button[disabled] { + cursor: not-allowed; + background-color: #077b58; + box-shadow: none; +} +button[disabled]:hover { + background-color: #077b58; +} 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..974964c --- /dev/null +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.html @@ -0,0 +1,60 @@ + 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..0bef06d --- /dev/null +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.ts @@ -0,0 +1,54 @@ +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + inject, + Input, + Output, +} from '@angular/core'; +import { TransactionService } from '@service/transaction.service'; +import { Observable } from 'rxjs'; +import { AsyncPipe, CurrencyPipe, DatePipe, NgForOf, NgIf } from '@angular/common'; +import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component'; +import { TransactionData } from '../../model/TransactionData'; + +const PER_PAGE = 5; + +@Component({ + standalone: true, + selector: 'app-transaction-history', + 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 = false; + @Output() + closeEventEmitter = new EventEmitter(); + + protected offset = 0; + + private transactionService: TransactionService = inject(TransactionService); + transactionData$: Observable = this.loadTransactions(); + + closeDialog() { + this.isOpen = false; + this.closeEventEmitter.emit(); + } + + forward() { + this.offset++; + this.transactionData$ = this.loadTransactions(); + } + + back() { + this.offset--; + this.transactionData$ = this.loadTransactions(); + } + + loadTransactions() { + return this.transactionService.getUsersTransactions(PER_PAGE, this.offset * PER_PAGE); + } +} diff --git a/frontend/src/app/model/Transaction.ts b/frontend/src/app/model/Transaction.ts index 21277b4..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; - date: string; + createdAt: string; } diff --git a/frontend/src/app/model/TransactionData.ts b/frontend/src/app/model/TransactionData.ts new file mode 100644 index 0000000..2da39bb --- /dev/null +++ b/frontend/src/app/model/TransactionData.ts @@ -0,0 +1,6 @@ +import { Transaction } from './Transaction'; + +export interface TransactionData { + transactions: Transaction[]; + hasMore: boolean; +} diff --git a/frontend/src/app/service/transaction.service.ts b/frontend/src/app/service/transaction.service.ts new file mode 100644 index 0000000..55aba0f --- /dev/null +++ b/frontend/src/app/service/transaction.service.ts @@ -0,0 +1,24 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { TransactionData } from '../model/TransactionData'; + +@Injectable({ + providedIn: 'root', +}) +export class TransactionService { + private http: HttpClient = inject(HttpClient); + + public getUsersTransactions(limit: number | null = null, offset: number | null = null) { + const baseUrl = new URL(`${window.location.origin}/backend/user/transactions`); + + if (limit !== null) { + baseUrl.searchParams.append('limit', limit.toString()); + } + + if (offset !== null) { + baseUrl.searchParams.append('offset', offset.toString()); + } + + return this.http.get(`${baseUrl}`); + } +}