Merge branch 'main' into add-project-documentation
All checks were successful
CI / Get Changed Files (pull_request) Successful in 8s
CI / eslint (pull_request) Has been skipped
CI / oxlint (pull_request) Has been skipped
Pull Request Labeler / labeler (pull_request_target) Successful in 4s
CI / Backend Tests (pull_request) Has been skipped
CI / prettier (pull_request) Has been skipped
Label PRs based on size / Check PR size (pull_request) Successful in 13s
CI / Checkstyle Main (pull_request) Has been skipped
CI / test-build (pull_request) Has been skipped
CI / Docker frontend validation (pull_request) Has been skipped
CI / Docker backend validation (pull_request) Has been skipped
Claude PR Review / claude-code (pull_request) Successful in 25s
All checks were successful
CI / Get Changed Files (pull_request) Successful in 8s
CI / eslint (pull_request) Has been skipped
CI / oxlint (pull_request) Has been skipped
Pull Request Labeler / labeler (pull_request_target) Successful in 4s
CI / Backend Tests (pull_request) Has been skipped
CI / prettier (pull_request) Has been skipped
Label PRs based on size / Check PR size (pull_request) Successful in 13s
CI / Checkstyle Main (pull_request) Has been skipped
CI / test-build (pull_request) Has been skipped
CI / Docker frontend validation (pull_request) Has been skipped
CI / Docker backend validation (pull_request) Has been skipped
Claude PR Review / claude-code (pull_request) Successful in 25s
This commit is contained in:
commit
64cd36536e
20 changed files with 624 additions and 282 deletions
28
.gitea/workflows/docs.yml
Normal file
28
.gitea/workflows/docs.yml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
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
|
|
@ -1,59 +1,137 @@
|
||||||
# Starter für das LF08 Projekt
|
# Casino Gaming Platform - Backend API
|
||||||
|
|
||||||
## Requirements
|
A Spring Boot backend application providing REST APIs for a casino gaming platform with multiple games, user management, authentication, and payment processing.
|
||||||
* Docker https://docs.docker.com/get-docker/
|
|
||||||
* Docker compose (bei Windows und Mac schon in Docker enthalten) https://docs.docker.com/compose/install/
|
|
||||||
|
|
||||||
## Endpunkt
|
## Features
|
||||||
```
|
|
||||||
http://localhost:8080
|
|
||||||
```
|
|
||||||
## Swagger
|
|
||||||
```
|
|
||||||
http://localhost:8080/swagger
|
|
||||||
```
|
|
||||||
|
|
||||||
|
### Games
|
||||||
|
- **Blackjack** - Classic card game with deck management
|
||||||
|
- **Coinflip** - Simple heads/tails betting game
|
||||||
|
- **Dice** - Dice rolling game
|
||||||
|
- **Slots** - Slot machine with symbols and payouts
|
||||||
|
- **Lootboxes** - Reward system with configurable prizes
|
||||||
|
|
||||||
# Postgres
|
### User Management
|
||||||
### Terminal öffnen
|
- User registration and authentication
|
||||||
für alles gilt, im Terminal im Ordner docker/local sein
|
- 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
|
```bash
|
||||||
|
# Build the application
|
||||||
|
./gradlew build
|
||||||
|
|
||||||
|
# Clean build
|
||||||
|
./gradlew clean build
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
./gradlew bootRun
|
||||||
|
|
||||||
|
# Generate JAR file
|
||||||
|
./gradlew bootJar
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```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.
|
|
||||||
|
|
||||||
### Postgres stoppen
|
# Stop PostgreSQL container
|
||||||
```bash
|
|
||||||
docker compose down
|
docker compose down
|
||||||
```
|
|
||||||
|
|
||||||
### Postgres Datenbank wipen, z.B. bei Problemen
|
# Reset database (if needed)
|
||||||
```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
|
||||||
```
|
```
|
||||||
|
|
||||||
### Intellij-Ansicht für Postgres Datenbank einrichten
|
### Database Configuration
|
||||||
```bash
|
Database connection settings are configured in `src/main/resources/application.properties`
|
||||||
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
|
|
||||||
|
|
||||||
### Keycloak Token
|
### IntelliJ Database Setup
|
||||||
1. Auf der Projektebene [GetBearerToken.http](../GetBearerToken.http) öffnen.
|
1. Start the PostgreSQL Docker container
|
||||||
2. Neben der Request auf den grünen Pfeil drücken
|
2. Open `application.properties` and copy the database URL
|
||||||
3. Aus dem Reponse das access_token kopieren
|
3. In IntelliJ, open the Database tab (right panel)
|
||||||
|
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
|
|
@ -39,7 +39,7 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.stripe:stripe-java:29.1.0")
|
implementation("com.stripe:stripe-java:29.2.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.4.5")
|
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.5.0")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.4.5")
|
implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.5.0")
|
||||||
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.11.5")
|
implementation("io.jsonwebtoken:jjwt-api:0.12.6")
|
||||||
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
|
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
|
||||||
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
|
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-mail")
|
implementation("org.springframework.boot:spring-boot-starter-mail")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ public class JwtUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Claims extractAllClaims(String token) {
|
private Claims extractAllClaims(String token) {
|
||||||
return Jwts.parserBuilder()
|
return Jwts.parser()
|
||||||
.setSigningKey(getSigningKey())
|
.setSigningKey(getSigningKey())
|
||||||
.build()
|
.build()
|
||||||
.parseClaimsJws(token)
|
.parseClaimsJws(token)
|
||||||
|
|
|
@ -1,18 +1,106 @@
|
||||||
# Casino Gaming Platform - Frontend
|
# Casino Gaming Platform - Frontend
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## Development
|
## 🎮 Features
|
||||||
|
|
||||||
### Commands
|
- **Multiple Games**: Blackjack, Coinflip, Dice, Slots, Lootboxes
|
||||||
|
- **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
|
||||||
|
|
||||||
- **Build**: `bun run build` or `bunx @angular/cli build`
|
## 🚀 Getting Started
|
||||||
- **Start Dev Server**: `bun run start` or `bunx @angular/cli serve --proxy-config src/proxy.conf.json`
|
|
||||||
- **Format Code**: `bun run format` or `prettier --write "src/**/*.{ts,html,css,scss}"`
|
### Prerequisites
|
||||||
- **Lint**: `bun run lint` or `ng lint`
|
|
||||||
- **Test**: `bun run test` or `bunx @angular/cli test`
|
- [Bun](https://bun.sh/) (recommended) or Node.js 18+
|
||||||
|
- 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
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
"input": "public"
|
"input": "public"
|
||||||
}
|
},
|
||||||
|
"src/assets"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.css"
|
"src/styles.css"
|
||||||
|
|
|
@ -62,14 +62,12 @@ 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: 'open/:id',
|
path: 'lootboxes/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',
|
||||||
|
|
|
@ -61,6 +61,9 @@ 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;
|
||||||
|
|
|
@ -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-3">
|
<div class="lg:col-span-4">
|
||||||
<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,24 +18,54 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="slider-container">
|
<div class="slider-container">
|
||||||
<div class="slider-grid">
|
<div class="min-w-full space-y-4">
|
||||||
<div class="card group" *ngFor="let game of featuredGames">
|
<!-- Top row with 3 games -->
|
||||||
<div class="relative overflow-hidden rounded-lg">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
<img
|
<div class="card group" *ngFor="let game of featuredGames.slice(0, 3)">
|
||||||
[src]="game.image"
|
<div class="relative overflow-hidden rounded-lg">
|
||||||
[alt]="game.name"
|
<img
|
||||||
class="w-full aspect-[4/3] object-cover transition-transform duration-300 group-hover:scale-105"
|
[src]="game.image"
|
||||||
/>
|
[alt]="game.name"
|
||||||
<div
|
class="w-full aspect-[4/3] object-cover transition-transform duration-300 group-hover:scale-105"
|
||||||
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
|
<div
|
||||||
class="absolute bottom-4 left-4 right-4 transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300"
|
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"
|
||||||
>
|
>
|
||||||
<h4 class="game-heading">{{ game.name }}</h4>
|
<div
|
||||||
<button class="button-primary w-full py-2" (click)="navigateToGame(game.route)">
|
class="absolute bottom-4 left-4 right-4 transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300"
|
||||||
Jetzt Spielen
|
>
|
||||||
</button>
|
<h4 class="game-heading">{{ game.name }}</h4>
|
||||||
|
<button class="button-primary w-full py-2" (click)="navigateToGame(game.route)">
|
||||||
|
Jetzt Spielen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bottom row with 2 games centered -->
|
||||||
|
<div
|
||||||
|
class="grid grid-cols-1 sm:grid-cols-2 gap-4 max-w-2xl mx-auto xl:max-w-3xl xl:gap-6"
|
||||||
|
>
|
||||||
|
<div class="card group" *ngFor="let game of featuredGames.slice(3, 5)">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,53 +73,5 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="lg:col-span-1 space-y-6">
|
|
||||||
<div class="card p-4">
|
|
||||||
<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()"
|
|
||||||
>
|
|
||||||
Transaktionen
|
|
||||||
</button>
|
|
||||||
<app-transaction-history
|
|
||||||
[isOpen]="isTransactionModalOpen"
|
|
||||||
(closeEventEmitter)="closeTransactionModal()"
|
|
||||||
/>
|
|
||||||
</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>
|
|
||||||
<span [class]="transaction.amount > 0 ? 'text-emerald' : 'text-accent-red'">
|
|
||||||
{{ transaction.amount | currency: 'EUR' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,34 +1,18 @@
|
||||||
import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { AsyncPipe, CurrencyPipe, DatePipe, NgFor } from '@angular/common';
|
import { 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: [
|
imports: [NgFor],
|
||||||
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,
|
||||||
|
@ -78,35 +62,10 @@ 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]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,7 @@
|
||||||
(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"
|
||||||
>
|
>
|
||||||
Konto erstellen
|
Jetzt registrieren
|
||||||
</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>
|
||||||
|
@ -46,58 +40,108 @@
|
||||||
<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>
|
||||||
<a
|
@if (isLoggedIn()) {
|
||||||
routerLink="game/slots"
|
<a
|
||||||
class="button-primary w-full py-2 inline-block text-center"
|
routerLink="game/slots"
|
||||||
>Jetzt Spielen</a
|
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 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>
|
||||||
<a
|
@if (isLoggedIn()) {
|
||||||
routerLink="game/blackjack"
|
<a
|
||||||
class="button-primary w-full py-2 inline-block text-center"
|
routerLink="game/blackjack"
|
||||||
>Jetzt Spielen</a
|
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 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>
|
||||||
<a
|
@if (isLoggedIn()) {
|
||||||
routerLink="/game/dice"
|
<a
|
||||||
class="button-primary w-full py-2 inline-block text-center"
|
routerLink="game/dice"
|
||||||
>Jetzt Spielen</a
|
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 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>
|
||||||
<a
|
@if (isLoggedIn()) {
|
||||||
routerLink="game/lootboxes"
|
<a
|
||||||
class="button-primary w-full py-2 inline-block text-center"
|
routerLink="game/lootboxes"
|
||||||
>Jetzt Spielen</a
|
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>
|
||||||
|
@ -165,7 +209,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 <span class="text-emerald text-xs">*</span></div>
|
<div class="stat-text">Support</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,15 +23,14 @@ 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();
|
||||||
|
@ -39,7 +38,6 @@ export class LandingComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.stopAutoplay();
|
|
||||||
document.body.style.overflow = 'auto';
|
document.body.style.overflow = 'auto';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,33 +71,13 @@ 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,24 +5,19 @@
|
||||||
<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="/games" class="footer-link">Slots</a>
|
<a routerLink="/game/slots" class="footer-link">Slots</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a routerLink="/games" class="footer-link">Blackjack</a>
|
<a routerLink="/game/blackjack" class="footer-link">Blackjack</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a routerLink="/games" class="footer-link">Poker</a>
|
<a routerLink="/game/dice" class="footer-link">Dice</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a routerLink="/games" class="footer-link">Liars Dice</a>
|
<a routerLink="/game/coinflip" class="footer-link">Coinflip</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="/games" class="footer-link">Lootboxen</a>
|
<a routerLink="/game/lootboxes" class="footer-link">Lootboxen</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -54,10 +49,6 @@
|
||||||
<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>
|
||||||
|
@ -65,10 +56,6 @@
|
||||||
<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>
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
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 { faApplePay, faGooglePay, faPaypal } from '@fortawesome/free-brands-svg-icons';
|
import { 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],
|
imports: [FontAwesomeModule, RouterLink],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class FooterComponent {
|
export class FooterComponent {
|
||||||
|
@ -18,5 +19,4 @@ export class FooterComponent {
|
||||||
faMoneyBillTransfer = faMoneyBillTransfer;
|
faMoneyBillTransfer = faMoneyBillTransfer;
|
||||||
faWallet = faWallet;
|
faWallet = faWallet;
|
||||||
faGooglePay = faGooglePay;
|
faGooglePay = faGooglePay;
|
||||||
faApplePay = faApplePay;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,120 @@
|
||||||
<nav class="bg-deep-blue border-b border-deep-blue-contrast">
|
<nav class="bg-deep-blue-light border-b border-emerald-500/30 shadow-lg">
|
||||||
<div class="max-w-full mx-auto px-4">
|
<div class="max-w-full mx-auto px-6">
|
||||||
<div class="flex justify-between items-center h-14">
|
<div class="flex justify-between items-center h-16">
|
||||||
<div class="flex items-center space-x-6">
|
<div class="flex items-center space-x-2">
|
||||||
<a routerLink="/" class="nav-brand">
|
<a routerLink="/" class="flex items-center space-x-3 group">
|
||||||
<span>Trustworthy Casino</span>
|
<div class="flex flex-col">
|
||||||
|
<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">
|
||||||
<a routerLink="/home" class="nav-link">Spiele</a>
|
@if (isLoggedIn()) {
|
||||||
|
<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-4">
|
<div class="hidden md:flex items-center space-x-1">
|
||||||
@if (!isLoggedIn()) {
|
@if (!isLoggedIn()) {
|
||||||
<button (click)="showLogin.emit()" class="button-primary px-4 py-1.5">Anmelden</button>
|
<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="bg-emerald-700 text-white hover:bg-emerald-600 px-4 py-1.5 rounded"
|
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"
|
||||||
>
|
>
|
||||||
Registrieren
|
Jetzt registrieren
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (isLoggedIn()) {
|
@if (isLoggedIn()) {
|
||||||
<div
|
<div
|
||||||
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"
|
class="flex items-center px-4 py-2 mr-2 bg-slate-700 border border-emerald-500/30 rounded-lg font-medium"
|
||||||
routerLink="/home"
|
|
||||||
>
|
>
|
||||||
<span [class]="balance() < 0 ? 'text-accent-red' : ''">
|
<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"
|
||||||
|
>
|
||||||
<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 (click)="toggleMenu()" class="nav-toggle">
|
<button
|
||||||
|
(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"
|
||||||
|
@ -68,25 +148,95 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [class]="isMenuOpen ? 'block' : 'hidden'" class="md:hidden">
|
<div [class]="isMenuOpen ? 'block' : 'hidden'" class="md:hidden">
|
||||||
<div class="nav-mobile-menu">
|
<div class="px-2 pt-2 pb-4 space-y-3 bg-slate-700 rounded-lg mt-2">
|
||||||
<a routerLink="/games" class="nav-mobile-link">Spiele</a>
|
<a
|
||||||
<div class="pt-2 space-y-2">
|
routerLink="/home"
|
||||||
|
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="button-primary w-full py-1.5 block text-center"
|
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"
|
||||||
>
|
>
|
||||||
|
<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="bg-emerald-700 text-white hover:bg-emerald-600 w-full py-1.5 rounded block text-center"
|
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="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()) {
|
||||||
<button (click)="logout()" class="button-primary w-full py-1.5">Abmelden</button>
|
<div
|
||||||
|
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>
|
||||||
|
|
|
@ -12,16 +12,20 @@ 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],
|
imports: [RouterModule, AnimatedNumberComponent, DepositComponent, TransactionHistoryComponent],
|
||||||
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());
|
||||||
|
|
||||||
|
@ -51,4 +55,20 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
4
frontend/src/assets/deposit.svg
Normal file
4
frontend/src/assets/deposit.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<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>
|
After Width: | Height: | Size: 314 B |
6
frontend/src/assets/games.svg
Normal file
6
frontend/src/assets/games.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<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>
|
After Width: | Height: | Size: 882 B |
12
frontend/src/assets/transaction.svg
Normal file
12
frontend/src/assets/transaction.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<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>
|
After Width: | Height: | Size: 639 B |
|
@ -71,6 +71,10 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue