Compare commits

..

No commits in common. "64cd36536e270e18652deba1a023a155f1dec7e8" and "be9dcc41a83a4ace68aa7eeacd21adcb302209e5" have entirely different histories.

20 changed files with 282 additions and 624 deletions

View file

@ -1,28 +0,0 @@
name: Build docs
on:
push:
branches: [main]
jobs:
build-docs:
runs-on: ubuntu-latest
container:
image: git.kjan.de/actions/runner-latex:latest
env:
# Edit here with the names of your latex file and directory (can use ".")
DIR: docs
FILE: projektdokumentation.tex
steps:
- name: Checkout
uses: actions/checkout@v4
- name: LaTeX compile
working-directory: ${{ env.DIR }}
run: latexmk -pdf -xelatex ${{ env.FILE }}
- name: Upload artifacts
uses: https://git.kjan.de/actions/upload-artifact@v4
with:
name: Doku
path: docs/projektdokumentation.pdf

View file

@ -1,137 +1,59 @@
# Casino Gaming Platform - Backend API # Starter für das LF08 Projekt
A Spring Boot backend application providing REST APIs for a casino gaming platform with multiple games, user management, authentication, and payment processing. ## Requirements
* Docker https://docs.docker.com/get-docker/
* Docker compose (bei Windows und Mac schon in Docker enthalten) https://docs.docker.com/compose/install/
## Features ## Endpunkt
```
### Games http://localhost:8080
- **Blackjack** - Classic card game with deck management ```
- **Coinflip** - Simple heads/tails betting game ## Swagger
- **Dice** - Dice rolling game ```
- **Slots** - Slot machine with symbols and payouts http://localhost:8080/swagger
- **Lootboxes** - Reward system with configurable prizes
### User Management
- User registration and authentication
- OAuth2 integration (Google, GitHub)
- Email verification and password reset
- Balance management and transaction history
### Payment System
- Deposit functionality with webhook support
- Transaction tracking and status management
- Balance updates and fund validation
## Tech Stack
- **Java 17** with Spring Boot
- **Spring Security** with JWT authentication
- **Spring Data JPA** with PostgreSQL
- **OAuth2** for social login
- **Email service** for notifications
- **OpenAPI/Swagger** for API documentation
## Build & Run
### Prerequisites
- Java 17+
- Gradle
- Docker & Docker Compose (for PostgreSQL)
### Build Commands
```bash
# Build the application
./gradlew build
# Clean build
./gradlew clean build
# Run the application
./gradlew bootRun
# Generate JAR file
./gradlew bootJar
``` ```
### Testing
# Postgres
### Terminal öffnen
für alles gilt, im Terminal im Ordner docker/local sein
```bash ```bash
# Run all tests
./gradlew test
# Run specific test class
./gradlew test --tests "FullyQualifiedClassName"
# Run checkstyle
./gradlew checkstyleMain checkstyleTest
```
## API Endpoints
The application runs on `http://localhost:8080`
### API Documentation
- **Swagger UI**: `http://localhost:8080/swagger-ui.html`
- **OpenAPI Spec**: `http://localhost:8080/v3/api-docs`
### Main Endpoints
- `/api/auth/**` - Authentication and user management
- `/api/games/blackjack/**` - Blackjack game operations
- `/api/games/coinflip/**` - Coinflip game operations
- `/api/games/dice/**` - Dice game operations
- `/api/games/slots/**` - Slot machine operations
- `/api/lootboxes/**` - Lootbox management
- `/api/deposits/**` - Payment and deposit handling
- `/api/users/**` - User profile management
- `/api/health` - Health check endpoint
## Database Setup
### PostgreSQL with Docker
```bash
# Start PostgreSQL container
cd docker/local cd docker/local
```
### Postgres starten
```bash
docker compose up docker compose up
```
Achtung: Der Docker-Container läuft dauerhaft! Wenn er nicht mehr benötigt wird, sollten Sie ihn stoppen.
# Stop PostgreSQL container ### Postgres stoppen
```bash
docker compose down docker compose down
```
# Reset database (if needed) ### Postgres Datenbank wipen, z.B. bei Problemen
```bash
docker compose down docker compose down
docker volume rm local_lf8_starter_postgres_data docker volume rm local_lf8_starter_postgres_data
docker compose up docker compose up
``` ```
### Database Configuration ### Intellij-Ansicht für Postgres Datenbank einrichten
Database connection settings are configured in `src/main/resources/application.properties` ```bash
1. Lasse den Docker-Container mit der PostgreSQL-Datenbank laufen
2. im Ordner resources die Datei application.properties öffnen und die URL der Datenbank kopieren
3. rechts im Fenster den Reiter Database öffnen
4. In der Database-Symbolleiste auf das Datenbanksymbol mit dem Schlüssel klicken
5. auf das Pluszeichen klicken
6. Datasource from URL auswählen
7. URL der DB einfügen und PostgreSQL-Treiber auswählen, mit OK bestätigen
8. Username lf8_starter und Passwort secret eintragen (siehe application.properties), mit Apply bestätigen
9. im Reiter Schemas alle Häkchen entfernen und lediglich vor lf8_starter_db und public Häkchen setzen
10. mit Apply und ok bestätigen
```
# Keycloak
### IntelliJ Database Setup ### Keycloak Token
1. Start the PostgreSQL Docker container 1. Auf der Projektebene [GetBearerToken.http](../GetBearerToken.http) öffnen.
2. Open `application.properties` and copy the database URL 2. Neben der Request auf den grünen Pfeil drücken
3. In IntelliJ, open the Database tab (right panel) 3. Aus dem Reponse das access_token kopieren
4. Click the database icon with key in the toolbar
5. Click the plus (+) icon
6. Select "Datasource from URL"
7. Paste the database URL and select PostgreSQL driver
8. Enter credentials (username: `lf8_starter`, password: `secret`)
9. In Schemas tab, uncheck all except `lf8_starter_db` and `public`
10. Apply and confirm
## Authentication
The application supports multiple authentication methods:
- JWT-based authentication
- OAuth2 (Google, GitHub)
- Email/password with verification
### Getting Bearer Token
For API testing, use the provided HTTP client file:
1. Open `GetBearerToken.http` at project root
2. Execute the request
3. Copy the `access_token` from the response
4. Use in Authorization header: `Bearer <token>`
## Configuration
Key configuration files:
- `application.properties` - Main application configuration
- `SecurityConfig.java` - Security and CORS settings
- `OpenAPIConfiguration.java` - API documentation setup

View file

@ -39,7 +39,7 @@ repositories {
} }
dependencies { dependencies {
implementation("com.stripe:stripe-java:29.2.0") implementation("com.stripe:stripe-java:29.1.0")
implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
compileOnly("org.projectlombok:lombok") compileOnly("org.projectlombok:lombok")
@ -47,13 +47,13 @@ dependencies {
testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.5.0") implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.4.5")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.5.0") implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.4.5")
runtimeOnly("org.postgresql:postgresql") runtimeOnly("org.postgresql:postgresql")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8")
implementation("io.jsonwebtoken:jjwt-api:0.12.6") implementation("io.jsonwebtoken:jjwt-api:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6") runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
implementation("org.springframework.boot:spring-boot-starter-mail") implementation("org.springframework.boot:spring-boot-starter-mail")
} }

View file

@ -91,7 +91,7 @@ public class JwtUtils {
} }
private Claims extractAllClaims(String token) { private Claims extractAllClaims(String token) {
return Jwts.parser() return Jwts.parserBuilder()
.setSigningKey(getSigningKey()) .setSigningKey(getSigningKey())
.build() .build()
.parseClaimsJws(token) .parseClaimsJws(token)

View file

@ -1,106 +1,18 @@
# Casino Gaming Platform - Frontend # Casino Gaming Platform - Frontend
A modern Angular 20 casino gaming platform featuring multiple games including Blackjack, Coinflip, Dice, Slots, and Lootboxes. Built with Angular 20, TailwindCSS 4, and powered by Bun for fast development. This is the frontend application for the Casino Gaming Platform. It's built with Angular 18 and TailwindCSS, providing a responsive and modern UI for the casino gaming experience.
## 🎮 Features ## Development
- **Multiple Games**: Blackjack, Coinflip, Dice, Slots, Lootboxes ### Commands
- **User Authentication**: OAuth2, email verification, password recovery
- **Real-time Gaming**: Interactive game mechanics with animations
- **Payment Integration**: Stripe integration for deposits
- **Responsive Design**: Mobile-first design with TailwindCSS
- **Audio Experience**: Game sounds and audio feedback
- **Transaction History**: Complete betting and transaction tracking
## 🚀 Getting Started - **Build**: `bun run build` or `bunx @angular/cli build`
- **Start Dev Server**: `bun run start` or `bunx @angular/cli serve --proxy-config src/proxy.conf.json`
### Prerequisites - **Format Code**: `bun run format` or `prettier --write "src/**/*.{ts,html,css,scss}"`
- **Lint**: `bun run lint` or `ng lint`
- [Bun](https://bun.sh/) (recommended) or Node.js 18+ - **Test**: `bun run test` or `bunx @angular/cli test`
- Angular CLI 20+
### Installation
```bash
# Install dependencies
bun install
# Start development server
bun run start
```
The app will be available at `http://localhost:4200`
## 📋 Commands
### Development
- **Start Dev Server**: `bun run start` - Starts dev server with proxy configuration
- **Build**: `bun run build` - Production build
- **Watch Build**: `bun run watch` - Development build with file watching
### Code Quality
- **Format**: `bun run format` - Format code with Prettier
- **Format Check**: `bun run format:check` - Check code formatting
- **Lint**: `bun run lint` - Run ESLint
- **OxLint**: `bun run oxlint` - Run OxLint with strict warnings
### Testing
- **Test All**: `bun run test` - Run all tests with Karma/Jasmine
- **Test Single File**: `bunx @angular/cli test --include=path/to/test.spec.ts` - **Test Single File**: `bunx @angular/cli test --include=path/to/test.spec.ts`
## 🛠️ Technology Stack
### Core
- **Angular 20**: Latest Angular framework with standalone components
- **TypeScript 5.8**: Strongly typed JavaScript
- **RxJS 7.8**: Reactive programming for HTTP and state management
### Styling & UI
- **TailwindCSS 4**: Utility-first CSS framework
- **PostCSS**: CSS processing and optimization
- **FontAwesome**: Icon library with Angular integration
### Animation & Interaction
- **GSAP**: High-performance animations
- **CountUp.js**: Number animation effects
- **Custom Audio Service**: Game sound effects and feedback
### Development Tools
- **Bun**: Fast JavaScript runtime and package manager
- **ESLint + Angular ESLint**: Code linting with Angular-specific rules
- **OxLint**: Fast Rust-based linter
- **Prettier**: Code formatting
- **Karma + Jasmine**: Testing framework
### Payment & APIs
- **Stripe**: Payment processing integration
- **Custom HTTP Interceptors**: API communication and error handling
## 🏗️ Architecture
### Project Structure
```
src/
├── app/
│ ├── feature/ # Feature modules
│ │ ├── auth/ # Authentication (login, register, OAuth2)
│ │ ├── game/ # Game modules (blackjack, coinflip, dice, slots)
│ │ ├── lootboxes/ # Lootbox system
│ │ └── deposit/ # Payment and deposits
│ ├── model/ # Data models and interfaces
│ ├── service/ # Core services (auth, user, transaction)
│ └── shared/ # Shared components, directives, services
├── environments/ # Environment configurations
└── public/ # Static assets (images, sounds)
```
### Key Components
- **Game Components**: Modular game implementations with services
- **Shared Components**: Reusable UI components (navbar, footer, modals)
- **Services**: Business logic and API communication
- **Guards**: Route protection and authentication
- **Interceptors**: HTTP request/response handling
## Style Guide ## Style Guide
### Color Palette ### Color Palette

View file

@ -21,8 +21,7 @@
{ {
"glob": "**/*", "glob": "**/*",
"input": "public" "input": "public"
}, }
"src/assets"
], ],
"styles": [ "styles": [
"src/styles.css" "src/styles.css"

View file

@ -62,13 +62,15 @@ export const routes: Routes = [
loadComponent: () => loadComponent: () =>
import('./feature/lootboxes/lootbox-selection/lootbox-selection.component'), import('./feature/lootboxes/lootbox-selection/lootbox-selection.component'),
canActivate: [authGuard], canActivate: [authGuard],
}, children: [
{ {
path: 'lootboxes/open/:id', path: 'open/:id',
loadComponent: () => loadComponent: () =>
import('./feature/lootboxes/lootbox-opening/lootbox-opening.component'), import('./feature/lootboxes/lootbox-opening/lootbox-opening.component'),
canActivate: [authGuard], canActivate: [authGuard],
}, },
],
},
{ {
path: 'dice', path: 'dice',
loadComponent: () => import('./feature/game/dice/dice.component'), loadComponent: () => import('./feature/game/dice/dice.component'),

View file

@ -61,9 +61,6 @@ export class AnimatedNumberComponent implements OnChanges, AfterViewInit {
this.countUp = new CountUp(this.numberElement.nativeElement, this.value, { this.countUp = new CountUp(this.numberElement.nativeElement, this.value, {
startVal: this.previousValue, startVal: this.previousValue,
duration: this.duration, duration: this.duration,
decimalPlaces: 2,
useEasing: true,
useGrouping: false,
easingFn: (t, b, c, d) => { easingFn: (t, b, c, d) => {
if (this.ease === 'power1.out') { if (this.ease === 'power1.out') {
return c * (1 - Math.pow(1 - t / d, 1)) + b; return c * (1 - Math.pow(1 - t / d, 1)) + b;

View file

@ -4,7 +4,7 @@
</div> </div>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6"> <div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<div class="lg:col-span-4"> <div class="lg:col-span-3">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h3 class="section-heading text-2xl">Alle Spiele</h3> <h3 class="section-heading text-2xl">Alle Spiele</h3>
<div class="flex space-x-2"> <div class="flex space-x-2">
@ -18,10 +18,8 @@
</div> </div>
<div class="slider-container"> <div class="slider-container">
<div class="min-w-full space-y-4"> <div class="slider-grid">
<!-- Top row with 3 games --> <div class="card group" *ngFor="let game of featuredGames">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="card group" *ngFor="let game of featuredGames.slice(0, 3)">
<div class="relative overflow-hidden rounded-lg"> <div class="relative overflow-hidden rounded-lg">
<img <img
[src]="game.image" [src]="game.image"
@ -43,32 +41,52 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<!-- Bottom row with 2 games centered --> <div class="lg:col-span-1 space-y-6">
<div <div class="card p-4">
class="grid grid-cols-1 sm:grid-cols-2 gap-4 max-w-2xl mx-auto xl:max-w-3xl xl:gap-6" <h3 class="section-heading text-xl mb-4">Konto</h3>
<div class="space-y-4">
<button class="button-primary w-full py-2" (click)="openDepositModal()">Einzahlen</button>
<app-deposit
[isOpen]="isDepositModalOpen"
(closeModalEmitter)="closeDepositModal()"
></app-deposit>
<button
class="bg-deep-blue-light hover:bg-deep-blue-contrast w-full py-2 rounded"
(click)="openTransactionModal()"
> >
<div class="card group" *ngFor="let game of featuredGames.slice(3, 5)"> Transaktionen
<div class="relative overflow-hidden rounded-lg">
<img
[src]="game.image"
[alt]="game.name"
class="w-full aspect-[4/3] object-cover transition-transform duration-300 group-hover:scale-105"
/>
<div
class="absolute inset-0 bg-gradient-to-t from-deep-blue/95 via-deep-blue/50 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-300 ease-in-out"
>
<div
class="absolute bottom-4 left-4 right-4 transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300"
>
<h4 class="game-heading">{{ game.name }}</h4>
<button class="button-primary w-full py-2" (click)="navigateToGame(game.route)">
Jetzt Spielen
</button> </button>
<app-transaction-history
[isOpen]="isTransactionModalOpen"
(closeEventEmitter)="closeTransactionModal()"
/>
</div> </div>
</div> </div>
<app-confirmation
[successful]="isDepositSuccessful"
(closeConfirmation)="closeDepositConfirmationModal()"
></app-confirmation>
<div class="card p-4">
<h3 class="section-heading text-xl mb-4">Letzte Transaktionen</h3>
<div class="space-y-3">
<div
class="flex justify-between items-center"
*ngFor="let transaction of (recentTransactionData | async)?.transactions"
>
<div>
<p class="text-sm font-medium">{{ transaction.status }}</p>
<p class="text-xs text-text-secondary">
{{ transaction.createdAt | date: 'd.m.y H:m' }}
</p>
</div> </div>
</div> <span [class]="transaction.amount > 0 ? 'text-emerald' : 'text-accent-red'">
{{ transaction.amount | currency: 'EUR' }}
</span>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,18 +1,34 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core';
import { NgFor } from '@angular/common'; import { AsyncPipe, CurrencyPipe, DatePipe, NgFor } from '@angular/common';
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 { Game } from 'app/model/Game'; import { Game } from 'app/model/Game';
import { Observable } from 'rxjs';
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 { TransactionData } from '../../model/TransactionData';
@Component({ @Component({
selector: 'app-homepage', selector: 'app-homepage',
standalone: true, standalone: true,
imports: [NgFor], imports: [
CurrencyPipe,
NgFor,
DepositComponent,
ConfirmationComponent,
AsyncPipe,
DatePipe,
TransactionHistoryComponent,
],
templateUrl: './home.component.html', templateUrl: './home.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export default class HomeComponent implements OnInit { export default class HomeComponent implements OnInit {
isDepositModalOpen = false;
isDepositSuccessful = false; isDepositSuccessful = false;
isTransactionModalOpen = false;
constructor( constructor(
public route: ActivatedRoute, public route: ActivatedRoute,
@ -62,10 +78,35 @@ export default class HomeComponent implements OnInit {
}, },
]; ];
allGames: Game[] = [...this.featuredGames];
recentTransactionData: Observable<TransactionData> =
inject(TransactionService).getUsersTransactions(5);
openDepositModal() {
this.isDepositModalOpen = true;
}
closeDepositModal() {
this.isDepositModalOpen = false;
}
openDepositConfirmationModal() { openDepositConfirmationModal() {
this.isDepositSuccessful = true; this.isDepositSuccessful = true;
} }
openTransactionModal() {
this.isTransactionModalOpen = true;
}
closeDepositConfirmationModal() {
this.isDepositSuccessful = false;
}
closeTransactionModal() {
this.isTransactionModalOpen = false;
}
navigateToGame(route: string) { navigateToGame(route: string) {
this.router.navigate([route]); this.router.navigate([route]);
} }

View file

@ -21,7 +21,13 @@
(click)="showRegisterForm()" (click)="showRegisterForm()"
class="w-full sm:w-auto button-primary px-6 sm:px-8 py-3 shadow-lg" class="w-full sm:w-auto button-primary px-6 sm:px-8 py-3 shadow-lg"
> >
Jetzt registrieren Konto erstellen
</button>
<button
(click)="showLoginForm()"
class="w-full sm:w-auto bg-slate-700 text-white hover:bg-slate-600 px-6 sm:px-8 py-3 shadow-lg rounded"
>
Anmelden
</button> </button>
} }
</div> </div>
@ -40,108 +46,58 @@
<div class="game-card-content"> <div class="game-card-content">
<h3 class="game-heading-sm">Slots</h3> <h3 class="game-heading-sm">Slots</h3>
<p class="game-text">Klassische Spielautomaten</p> <p class="game-text">Klassische Spielautomaten</p>
@if (isLoggedIn()) {
<a <a
routerLink="game/slots" routerLink="game/slots"
class="button-primary w-full py-2 inline-block text-center" class="button-primary w-full py-2 inline-block text-center"
>Jetzt Spielen</a
> >
Jetzt Spielen
</a>
} @else {
<button
(click)="showLoginForm()"
class="button-primary w-full py-2 inline-block text-center"
>
Jetzt Spielen
</button>
}
</div> </div>
</div> </div>
<div class="hidden lg:block card"> <div class="hidden lg:block card">
<div class="game-card-content"> <div class="game-card-content">
<h3 class="game-heading-sm">Blackjack</h3> <h3 class="game-heading-sm">Blackjack</h3>
<p class="game-text">Klassisches Kartenspiel</p> <p class="game-text">Klassisches Kartenspiel</p>
@if (isLoggedIn()) {
<a <a
routerLink="game/blackjack" routerLink="game/blackjack"
class="button-primary w-full py-2 inline-block text-center" class="button-primary w-full py-2 inline-block text-center"
>Jetzt Spielen</a
> >
Jetzt Spielen
</a>
} @else {
<button
(click)="showLoginForm()"
class="button-primary w-full py-2 inline-block text-center"
>
Jetzt Spielen
</button>
}
</div>
</div>
<div class="hidden lg:block card">
<div class="game-card-content">
<h3 class="game-heading-sm">Coinflip</h3>
<p class="game-text">Münzwurf</p>
@if (isLoggedIn()) {
<a
routerLink="game/coinflip"
class="button-primary w-full py-2 inline-block text-center"
>
Jetzt Spielen
</a>
} @else {
<button
(click)="showLoginForm()"
class="button-primary w-full py-2 inline-block text-center"
>
Jetzt Spielen
</button>
}
</div> </div>
</div> </div>
</div> </div>
<div class="slider-grid"> <div class="slider-grid">
<div class="card">
<div class="game-card-content">
<h3 class="game-heading-sm">Poker</h3>
<p class="game-text">Texas Hold'em & mehr</p>
<a
routerLink="/game/poker"
class="button-primary w-full py-2 inline-block text-center"
>Jetzt Spielen</a
>
</div>
</div>
<div class="card"> <div class="card">
<div class="game-card-content"> <div class="game-card-content">
<h3 class="game-heading-sm">Dice</h3> <h3 class="game-heading-sm">Dice</h3>
<p class="game-text">Würfelspiel</p> <p class="game-text">Würfelspiel</p>
@if (isLoggedIn()) {
<a <a
routerLink="game/dice" routerLink="/game/dice"
class="button-primary w-full py-2 inline-block text-center" class="button-primary w-full py-2 inline-block text-center"
>Jetzt Spielen</a
> >
Jetzt Spielen
</a>
} @else {
<button
(click)="showLoginForm()"
class="button-primary w-full py-2 inline-block text-center"
>
Jetzt Spielen
</button>
}
</div> </div>
</div> </div>
<div class="hidden lg:block card"> <div class="hidden lg:block card">
<div class="game-card-content"> <div class="game-card-content">
<h3 class="game-heading-sm">Lootboxen</h3> <h3 class="game-heading-sm">Lootboxen</h3>
<p class="game-text">Überraschungskisten</p> <p class="game-text">Überraschungskisten</p>
@if (isLoggedIn()) {
<a <a
routerLink="game/lootboxes" routerLink="game/lootboxes"
class="button-primary w-full py-2 inline-block text-center" class="button-primary w-full py-2 inline-block text-center"
>Jetzt Spielen</a
> >
Jetzt Spielen
</a>
} @else {
<button
(click)="showLoginForm()"
class="button-primary w-full py-2 inline-block text-center"
>
Jetzt Spielen
</button>
}
</div> </div>
</div> </div>
</div> </div>
@ -209,7 +165,7 @@
<div class="stat-container"> <div class="stat-container">
<div class="stat-number">24/7</div> <div class="stat-number">24/7</div>
<div class="stat-text">Support</div> <div class="stat-text">Support <span class="text-emerald text-xs">*</span></div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -23,14 +23,15 @@ import RecoverPasswordComponent from '../auth/recover-password/recover-password.
}) })
export class LandingComponent implements OnInit, OnDestroy { export class LandingComponent implements OnInit, OnDestroy {
currentSlide = 0; currentSlide = 0;
private autoplayInterval: ReturnType<typeof setInterval> | undefined;
authService: AuthService = inject(AuthService); authService: AuthService = inject(AuthService);
route: ActivatedRoute = inject(ActivatedRoute); route: ActivatedRoute = inject(ActivatedRoute);
showLogin = signal(false); showLogin = signal(false);
showRegister = signal(false); showRegister = signal(false);
showRecoverPassword = signal(false); showRecoverPassword = signal(false);
isLoggedIn = signal(this.authService.isLoggedIn());
ngOnInit() { ngOnInit() {
this.startAutoplay();
document.body.style.overflow = 'auto'; document.body.style.overflow = 'auto';
if (this.route.snapshot.queryParamMap.get('login') === 'true') { if (this.route.snapshot.queryParamMap.get('login') === 'true') {
this.showLoginForm(); this.showLoginForm();
@ -38,6 +39,7 @@ export class LandingComponent implements OnInit, OnDestroy {
} }
ngOnDestroy() { ngOnDestroy() {
this.stopAutoplay();
document.body.style.overflow = 'auto'; document.body.style.overflow = 'auto';
} }
@ -71,13 +73,33 @@ export class LandingComponent implements OnInit, OnDestroy {
prevSlide() { prevSlide() {
this.currentSlide = this.currentSlide === 0 ? 1 : 0; this.currentSlide = this.currentSlide === 0 ? 1 : 0;
this.resetAutoplay();
} }
nextSlide() { nextSlide() {
this.currentSlide = this.currentSlide === 1 ? 0 : 1; this.currentSlide = this.currentSlide === 1 ? 0 : 1;
this.resetAutoplay();
} }
goToSlide(index: number) { goToSlide(index: number) {
this.currentSlide = index; this.currentSlide = index;
this.resetAutoplay();
}
private startAutoplay() {
this.autoplayInterval = setInterval(() => {
this.nextSlide();
}, 5000);
}
private stopAutoplay() {
if (this.autoplayInterval) {
clearInterval(this.autoplayInterval);
}
}
private resetAutoplay() {
this.stopAutoplay();
this.startAutoplay();
} }
} }

View file

@ -5,19 +5,24 @@
<h3 class="footer-heading">Casino Spiele</h3> <h3 class="footer-heading">Casino Spiele</h3>
<ul class="space-y-3"> <ul class="space-y-3">
<li> <li>
<a routerLink="/game/slots" class="footer-link">Slots</a> <a routerLink="/games" class="footer-link">Slots</a>
</li> </li>
<li> <li>
<a routerLink="/game/blackjack" class="footer-link">Blackjack</a> <a routerLink="/games" class="footer-link">Blackjack</a>
</li> </li>
<li> <li>
<a routerLink="/game/dice" class="footer-link">Dice</a> <a routerLink="/games" class="footer-link">Poker</a>
</li> </li>
<li> <li>
<a routerLink="/game/coinflip" class="footer-link">Coinflip</a> <a routerLink="/games" class="footer-link">Liars Dice</a>
</li> </li>
</ul>
</div>
<div class="footer-section">
<h3 class="footer-heading">Andere Spiele</h3>
<ul class="space-y-3">
<li> <li>
<a routerLink="/game/lootboxes" class="footer-link">Lootboxen</a> <a routerLink="/games" class="footer-link">Lootboxen</a>
</li> </li>
</ul> </ul>
</div> </div>
@ -49,6 +54,10 @@
<fa-icon [icon]="faGooglePay" class="footer-payment-icon"></fa-icon> <fa-icon [icon]="faGooglePay" class="footer-payment-icon"></fa-icon>
<span class="footer-payment-text">Google Pay</span> <span class="footer-payment-text">Google Pay</span>
</div> </div>
<div class="footer-payment-method">
<fa-icon [icon]="faApplePay" class="footer-payment-icon"></fa-icon>
<span class="footer-payment-text">Apple Pay</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -56,6 +65,10 @@
<div class="mt-12 pt-8 border-t border-deep-blue-light"> <div class="mt-12 pt-8 border-t border-deep-blue-light">
<div class="flex flex-col md:flex-row justify-between items-center"> <div class="flex flex-col md:flex-row justify-between items-center">
<div class="footer-copyright"> <div class="footer-copyright">
<span class="footer-disclaimer">
<span class="text-emerald">*</span> nicht vorhanden.
</span>
<br />
© {{ currentYear }} Trustworthy Casino. Keine Rechte vorbehalten. © {{ currentYear }} Trustworthy Casino. Keine Rechte vorbehalten.
</div> </div>
</div> </div>

View file

@ -1,14 +1,13 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faCreditCard, faMoneyBillTransfer, faWallet } from '@fortawesome/free-solid-svg-icons'; import { faCreditCard, faMoneyBillTransfer, faWallet } from '@fortawesome/free-solid-svg-icons';
import { faGooglePay, faPaypal } from '@fortawesome/free-brands-svg-icons'; import { faApplePay, faGooglePay, faPaypal } from '@fortawesome/free-brands-svg-icons';
import { RouterLink } from '@angular/router';
@Component({ @Component({
selector: 'app-footer', selector: 'app-footer',
standalone: true, standalone: true,
templateUrl: './footer.component.html', templateUrl: './footer.component.html',
imports: [FontAwesomeModule, RouterLink], imports: [FontAwesomeModule],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class FooterComponent { export class FooterComponent {
@ -19,4 +18,5 @@ export class FooterComponent {
faMoneyBillTransfer = faMoneyBillTransfer; faMoneyBillTransfer = faMoneyBillTransfer;
faWallet = faWallet; faWallet = faWallet;
faGooglePay = faGooglePay; faGooglePay = faGooglePay;
faApplePay = faApplePay;
} }

View file

@ -1,120 +1,40 @@
<nav class="bg-deep-blue-light border-b border-emerald-500/30 shadow-lg"> <nav class="bg-deep-blue border-b border-deep-blue-contrast">
<div class="max-w-full mx-auto px-6"> <div class="max-w-full mx-auto px-4">
<div class="flex justify-between items-center h-16"> <div class="flex justify-between items-center h-14">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-6">
<a routerLink="/" class="flex items-center space-x-3 group"> <a routerLink="/" class="nav-brand">
<div class="flex flex-col"> <span>Trustworthy Casino</span>
<span class="text-xl font-bold text-white"> Trustworthy Casino </span>
<span class="text-xs text-emerald-400 font-medium">Trust. Play. Win.</span>
</div>
</a> </a>
<div class="hidden md:flex items-center space-x-1"> <div class="hidden md:flex items-center space-x-1">
@if (isLoggedIn()) { <a routerLink="/home" class="nav-link">Spiele</a>
<a
routerLink="/home"
class="flex items-center px-4 py-2 text-white/90 hover:text-white font-medium rounded-lg hover:bg-white/10 transition-colors duration-200"
>
<img class="mr-2 w-4 h-4" src="assets/games.svg" alt="gamess" />
Spiele
</a>
} @else {
<button
(click)="showLogin.emit()"
class="flex items-center px-4 py-2 text-white/90 hover:text-white font-medium rounded-lg hover:bg-white/10 transition-colors duration-200"
>
<img class="mr-2 w-4 h-4" src="assets/games.svg" alt="gamess" />
Spiele
</button>
}
</div> </div>
</div> </div>
<div class="hidden md:flex items-center space-x-1"> <div class="hidden md:flex items-center space-x-4">
@if (!isLoggedIn()) { @if (!isLoggedIn()) {
<button <button (click)="showLogin.emit()" class="button-primary px-4 py-1.5">Anmelden</button>
(click)="showLogin.emit()"
class="flex items-center px-4 py-2 text-white font-medium border border-emerald-500 rounded-lg hover:bg-emerald-500/10 transition-colors duration-200"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"
/>
</svg>
Anmelden
</button>
<button <button
(click)="showRegister.emit()" (click)="showRegister.emit()"
class="flex items-center px-4 py-2 bg-emerald-600 text-white font-medium rounded-lg hover:bg-emerald-500 transition-colors duration-200" class="bg-emerald-700 text-white hover:bg-emerald-600 px-4 py-1.5 rounded"
> >
Jetzt registrieren Registrieren
</button> </button>
} }
@if (isLoggedIn()) { @if (isLoggedIn()) {
<div <div
class="flex items-center px-4 py-2 mr-2 bg-slate-700 border border-emerald-500/30 rounded-lg font-medium" class="text-white font-bold bg-deep-blue-contrast rounded-full px-4 py-2 text-sm hover:bg-deep-blue-contrast/80 hover:cursor-pointer hover:scale-105 transition-all active:scale-95 select-none duration-300"
> routerLink="/home"
<span class="text-emerald-400 text-sm mr-2">Guthaben:</span>
<span
[class]="balance() < 0 ? 'text-red-400 font-bold' : 'text-white font-bold'"
class="text-sm"
> >
<span [class]="balance() < 0 ? 'text-accent-red' : ''">
<app-animated-number [value]="balance()" [duration]="0.5"></app-animated-number> <app-animated-number [value]="balance()" [duration]="0.5"></app-animated-number>
</span> </span>
</div> </div>
<button (click)="logout()" class="button-primary px-4 py-1.5">Abmelden</button>
<button
class="flex items-center px-4 py-2 bg-emerald-600 text-white font-medium rounded-lg hover:bg-emerald-500 transition-colors duration-200"
(click)="openDepositModal()"
>
<img class="mr-2 w-3 h-3" src="assets/deposit.svg" alt="deposits" />
Einzahlen
</button>
<app-deposit
[isOpen]="isDepositModalOpen"
(closeModalEmitter)="closeDepositModal()"
></app-deposit>
<button
class="flex items-center px-4 py-2 bg-slate-700 text-white font-medium rounded-lg hover:bg-slate-600 border border-slate-600 transition-colors duration-200"
(click)="openTransactionModal()"
>
<img class="mr-2 w-4 h-4" src="assets/transaction.svg" alt="transactions" />
Transaktionen
</button>
<app-transaction-history
[isOpen]="isTransactionModalOpen"
(closeEventEmitter)="closeTransactionModal()"
/>
<button
(click)="logout()"
class="flex items-center px-4 py-2 text-red-400 font-medium border border-red-500/50 rounded-lg hover:bg-red-500/10 transition-colors duration-200"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
Abmelden
</button>
} }
</div> </div>
<div class="md:hidden"> <div class="md:hidden">
<button <button (click)="toggleMenu()" class="nav-toggle">
(click)="toggleMenu()"
class="p-2 text-white hover:text-emerald-400 rounded-lg transition-colors duration-200"
>
<svg <svg
class="h-6 w-6" class="h-6 w-6"
[class.hidden]="isMenuOpen" [class.hidden]="isMenuOpen"
@ -148,95 +68,25 @@
</div> </div>
<div [class]="isMenuOpen ? 'block' : 'hidden'" class="md:hidden"> <div [class]="isMenuOpen ? 'block' : 'hidden'" class="md:hidden">
<div class="px-2 pt-2 pb-4 space-y-3 bg-slate-700 rounded-lg mt-2"> <div class="nav-mobile-menu">
<a <a routerLink="/games" class="nav-mobile-link">Spiele</a>
routerLink="/home" <div class="pt-2 space-y-2">
class="flex items-center px-4 py-3 text-white/90 hover:text-white hover:bg-white/10 rounded-lg transition-colors duration-200"
>
<img class="mr-2 w-4 h-4" src="assets/games.svg" alt="gamess" />
Spiele
</a>
<div class="border-t border-slate-600 pt-3 space-y-3">
@if (!isLoggedIn()) { @if (!isLoggedIn()) {
<button <button
(click)="showLogin.emit()" (click)="showLogin.emit()"
class="w-full flex items-center justify-center px-4 py-3 text-white font-medium border border-emerald-500 rounded-lg hover:bg-emerald-500/10 transition-colors duration-200" class="button-primary w-full py-1.5 block text-center"
> >
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"
/>
</svg>
Anmelden Anmelden
</button> </button>
<button <button
(click)="showRegister.emit()" (click)="showRegister.emit()"
class="w-full flex items-center justify-center px-4 py-3 bg-emerald-600 text-white font-medium rounded-lg hover:bg-emerald-500 transition-colors duration-200" class="bg-emerald-700 text-white hover:bg-emerald-600 w-full py-1.5 rounded block text-center"
> >
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"
/>
</svg>
Registrieren Registrieren
</button> </button>
} }
@if (isLoggedIn()) { @if (isLoggedIn()) {
<div <button (click)="logout()" class="button-primary w-full py-1.5">Abmelden</button>
class="flex items-center justify-center px-4 py-3 bg-slate-700 border border-emerald-500/30 rounded-lg"
>
<span class="text-emerald-400 text-sm font-medium mr-2">Guthaben:</span>
<span
[class]="balance() < 0 ? 'text-red-400 font-bold' : 'text-white font-bold'"
class="text-sm"
>
<app-animated-number [value]="balance()" [duration]="0.5"></app-animated-number>
</span>
</div>
<button
(click)="openDepositModal()"
class="w-full flex items-center justify-center px-4 py-3 bg-emerald-600 text-white font-medium rounded-lg hover:bg-emerald-500 transition-colors duration-200"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
</svg>
Einzahlen
</button>
<button
(click)="openTransactionModal()"
class="w-full flex items-center justify-center px-4 py-3 bg-slate-700 text-white font-medium rounded-lg hover:bg-slate-600 border border-slate-600 transition-colors duration-200"
>
Transaktionen
</button>
<button
(click)="logout()"
class="w-full flex items-center justify-center px-4 py-3 text-red-400 font-medium border border-red-500/50 rounded-lg hover:bg-red-500/10 transition-colors duration-200"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
Abmelden
</button>
} }
</div> </div>
</div> </div>

View file

@ -12,20 +12,16 @@ import { RouterModule } from '@angular/router';
import { AuthService } from '@service/auth.service'; import { AuthService } from '@service/auth.service';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component'; import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component';
import { DepositComponent } from '../../../feature/deposit/deposit.component';
import { TransactionHistoryComponent } from '../../../feature/transaction-history/transaction-history.component';
@Component({ @Component({
selector: 'app-navbar', selector: 'app-navbar',
templateUrl: './navbar.component.html', templateUrl: './navbar.component.html',
standalone: true, standalone: true,
imports: [RouterModule, AnimatedNumberComponent, DepositComponent, TransactionHistoryComponent], imports: [RouterModule, AnimatedNumberComponent],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class NavbarComponent implements OnInit, OnDestroy { export class NavbarComponent implements OnInit, OnDestroy {
isMenuOpen = false; isMenuOpen = false;
isDepositModalOpen = false;
isTransactionModalOpen = false;
private authService: AuthService = inject(AuthService); private authService: AuthService = inject(AuthService);
isLoggedIn = signal(this.authService.isLoggedIn()); isLoggedIn = signal(this.authService.isLoggedIn());
@ -55,20 +51,4 @@ export class NavbarComponent implements OnInit, OnDestroy {
toggleMenu() { toggleMenu() {
this.isMenuOpen = !this.isMenuOpen; this.isMenuOpen = !this.isMenuOpen;
} }
openDepositModal() {
this.isDepositModalOpen = true;
}
closeDepositModal() {
this.isDepositModalOpen = false;
}
openTransactionModal() {
this.isTransactionModalOpen = true;
}
closeTransactionModal() {
this.isTransactionModalOpen = false;
}
} }

View file

@ -1,4 +0,0 @@
<svg style="color: white;" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 16 16">
<path fill="currentColor" d="m8 16l-2-3h1v-2h2v2h1zm7-15v8H1V1zm1-1H0v10h16z"/>
<path fill="currentColor" d="M8 2a3 3 0 1 1 0 6h5V7h1V3h-1V2zM5 5a3 3 0 0 1 3-3H3v1H2v4h1v1h5a3 3 0 0 1-3-3"/>
</svg>

Before

Width:  |  Height:  |  Size: 314 B

View file

@ -1,6 +0,0 @@
<svg style="color: white;" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512">
<path fill="currentColor"
d="M495.24 267.592L445.066 41.083A32.04 32.04 0 0 0 406.9 16.76L180.393 66.934a32 32 0 0 0-24.322 38.166l21.021 94.9H48a32.036 32.036 0 0 0-32 32v232a32.036 32.036 0 0 0 32 32h232a32.036 32.036 0 0 0 32-32V340.957l158.917-35.2a32.04 32.04 0 0 0 24.323-38.165M280 464H48V232h136.181l22.063 99.606a32.03 32.03 0 0 0 31.18 25.092a32.3 32.3 0 0 0 6.984-.769l35.6-7.886L280.02 464Zm184-189.487l-226.513 50.173l-50.173-226.51L413.824 48l50.193 226.505Z"/>
<path fill="currentColor"
d="M80 264h40v40H80zm0 128h40v40H80zm128 0h40v40h-40zm-64-64h40v40h-40zm81.456-205.433l39.054-8.644l8.644 39.055l-39.054 8.644zm152.672 97.223l39.054-8.65l8.65 39.054l-39.054 8.65zm-76.324-48.649l39.053-8.65l8.65 39.053l-39.052 8.65z"/>
</svg>

Before

Width:  |  Height:  |  Size: 882 B

View file

@ -1,12 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
style="color: white;"
>
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" color="currentColor">
<path d="M4.58 8.607L2 8.454C3.849 3.704 9.158 1 14.333 2.344c5.513 1.433 8.788 6.918 7.314 12.25c-1.219 4.411-5.304 7.337-9.8 7.406"/>
<path d="M12 22C6.5 22 2 17 2 11m11.604-1.278c-.352-.37-1.213-1.237-2.575-.62c-1.361.615-1.577 2.596.482 2.807c.93.095 1.537-.11 2.093.47c.556.582.659 2.198-.761 2.634s-2.341-.284-2.588-.509m1.653-6.484v.79m0 6.337v.873"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 639 B

View file

@ -71,10 +71,6 @@ a {
@apply font-bold text-text-primary text-sm mb-2; @apply font-bold text-text-primary text-sm mb-2;
} }
.game-heading {
@apply font-bold text-text-primary text-lg mb-2;
}
.game-heading-xl { .game-heading-xl {
@apply font-bold text-text-primary text-xl mb-2; @apply font-bold text-text-primary text-xl mb-2;
} }