Compare commits
2 commits
40b14747fc
...
b48b7a0b1e
Author | SHA1 | Date | |
---|---|---|---|
|
b48b7a0b1e | ||
474f39097d |
14 changed files with 232 additions and 624 deletions
|
@ -1,28 +1,34 @@
|
||||||
name: Build docs
|
name: Build docs
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
pull_request:
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-docs:
|
build-docs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
|
||||||
image: git.kjan.de/actions/runner-latex:latest
|
|
||||||
env:
|
env:
|
||||||
# Edit here with the names of your latex file and directory (can use ".")
|
# Edit here with the names of your latex file and directory (can use ".")
|
||||||
DIR: docs
|
DIR: docs
|
||||||
FILE: projektdokumentation.tex
|
FILE: projektdokumentation.tex
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install TeXlive
|
||||||
|
env:
|
||||||
|
ACCEPT_EULA: y
|
||||||
|
DEBIAN_FRONTEND: noninteractive
|
||||||
|
run: |
|
||||||
|
echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true" | sudo debconf-set-selections
|
||||||
|
sudo apt-get update && sudo apt-get install texlive-lang-german texlive-xetex texlive texlive-publishers texlive-science latexmk cm-super ttf-mscorefonts-installer -y
|
||||||
|
sudo fc-cache -f -v
|
||||||
|
|
||||||
- name: LaTeX compile
|
- name: LaTeX compile
|
||||||
working-directory: ${{ env.DIR }}
|
working-directory: ${{ env.DIR }}
|
||||||
run: latexmk -pdf -xelatex ${{ env.FILE }}
|
run: latexmk -pdf -xelatex ${{ env.FILE }}
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: https://git.kjan.de/actions/upload-artifact@v4
|
uses: https://git.kjan.de/actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: Doku
|
name: Doku
|
||||||
path: docs/projektdokumentation.pdf
|
path: docs/projektdokumentation.pdf
|
||||||
|
|
|
@ -19,9 +19,6 @@
|
||||||
\usepackage{enumitem}
|
\usepackage{enumitem}
|
||||||
\usepackage{tikz}
|
\usepackage{tikz}
|
||||||
\usetikzlibrary{arrows.meta, positioning}
|
\usetikzlibrary{arrows.meta, positioning}
|
||||||
\usepackage{pdflscape}
|
|
||||||
\usepackage{afterpage}
|
|
||||||
\usepackage{needspace}
|
|
||||||
|
|
||||||
% Seitenränder
|
% Seitenränder
|
||||||
\geometry{
|
\geometry{
|
||||||
|
@ -80,9 +77,9 @@
|
||||||
\centering
|
\centering
|
||||||
\vspace*{2cm}
|
\vspace*{2cm}
|
||||||
|
|
||||||
{\Huge\bfseries Trustworthy Casino\par}
|
{\Huge\bfseries Casino Gaming Platform GURWEABSOHN\par}
|
||||||
\vspace{1.5cm}
|
\vspace{1.5cm}
|
||||||
{\Large\itshape Projektdokumentation für das Mittelstufenprojekt\par}
|
{\Large\itshape Projektdokumentation für die IHK-Abschlussprüfung\par}
|
||||||
\vspace{2cm}
|
\vspace{2cm}
|
||||||
{\large\bfseries Fachinformatiker für Anwendungsentwicklung\par}
|
{\large\bfseries Fachinformatiker für Anwendungsentwicklung\par}
|
||||||
|
|
||||||
|
@ -90,10 +87,12 @@
|
||||||
|
|
||||||
{\large
|
{\large
|
||||||
\begin{tabular}{ll}
|
\begin{tabular}{ll}
|
||||||
|
\textbf{Prüfling:} & [Name des Prüflings] \\
|
||||||
|
\textbf{Prüflingsnummer:} & [Prüflingsnummer] \\
|
||||||
\textbf{Ausbildungsbetrieb:} & Hitec GmbH \\
|
\textbf{Ausbildungsbetrieb:} & Hitec GmbH \\
|
||||||
\textbf{Projektbetreuer:} & Herr Heidemann / Frau Deeken \\
|
\textbf{Projektbetreuer:} & [Betreuer] \\
|
||||||
\textbf{Projektdauer:} & 10 Wochen \\
|
\textbf{Projektdauer:} & 70 Stunden \\
|
||||||
\textbf{Abgabedatum:} & 12.06.2025 \\
|
\textbf{Abgabedatum:} & \today \\
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
\par}
|
\par}
|
||||||
|
|
||||||
|
@ -110,16 +109,17 @@
|
||||||
\chapter{Beschreibung}
|
\chapter{Beschreibung}
|
||||||
|
|
||||||
\section{Umfeld}
|
\section{Umfeld}
|
||||||
Das Projekt wurde im Rahmen des Mittelstufenprojektes entwickelt.
|
Das Projekt wurde im Rahmen der Abschlussprüfung zum Fachinformatiker für Anwendungsentwicklung bei der Hitec GmbH entwickelt.
|
||||||
|
|
||||||
\subsection{Produktportfolio der Hitec GmbH}
|
\subsection{Produktportfolio der Hitec GmbH}
|
||||||
Die Hitec GmbH ist ein innovatives IT-Unternehmen, das sich auf die Entwicklung moderner Webanwendungen und digitaler Lösungen spezialisiert hat. Das Unternehmen deckt ein breites Spektrum an Technologien ab.
|
Die Hitec GmbH ist ein innovatives IT-Unternehmen, das sich auf die Entwicklung moderner Webanwendungen und digitaler Lösungen spezialisiert hat. Das Unternehmen deckt ein breites Spektrum an Technologien ab, von klassischen Enterprise-Anwendungen bis hin zu modernen Cloud-nativen Lösungen.
|
||||||
|
|
||||||
\subsection{Weg zur Produktidee}
|
\subsection{Weg zur Produktidee}
|
||||||
Die Casino Gaming Platform entstand als LF08 Projekt mit dem Ziel, eine vollständige Online-Casino-Plattform zu entwickeln. Die Produktidee basiert auf:
|
Die Casino Gaming Platform entstand als LF08 Projekt mit dem Ziel, eine vollständige Online-Casino-Plattform zu entwickeln. Die Produktidee basiert auf:
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item \textbf{Gamification-Trend:} Wachsende Nachfrage nach Online-Gaming-Plattformen
|
\item \textbf{Gamification-Trend:} Wachsende Nachfrage nach Online-Gaming-Plattformen
|
||||||
|
\item \textbf{Technologie-Demonstration:} Showcase moderner Full-Stack-Entwicklung (Angular 20 + Spring Boot 3.5)
|
||||||
\item \textbf{Bildungsziel:} Praktische Anwendung von Enterprise-Patterns und modernen Web-Technologien
|
\item \textbf{Bildungsziel:} Praktische Anwendung von Enterprise-Patterns und modernen Web-Technologien
|
||||||
\item \textbf{Marktforschung:} Integration aktueller Standards (OAuth2, Stripe, responsive Design)
|
\item \textbf{Marktforschung:} Integration aktueller Standards (OAuth2, Stripe, responsive Design)
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
@ -182,14 +182,14 @@ Detaillierte Beschreibung des Problems oder der Marktlücke, welche unser Produk
|
||||||
\chapter{Durchführung}
|
\chapter{Durchführung}
|
||||||
|
|
||||||
\section{Vorgehensweise}
|
\section{Vorgehensweise}
|
||||||
Für die Projektdurchführung wurde Scrum als Vorgehensmodell gewählt bzw. vorgeschrieben. Dadurch wird am Anfang des Projektes und eines Sprints festgelegt, was gemacht werden soll, und jeder ist auf dem gleichen Wissensstand. Des Weiteren gibt es tägliche Updates vom Fortschritt, Probleme können schnell erkannt und angesprochen werden. Dazu kann man auch neue Anforderungen flexibel mit einbringen.
|
Für die Projektdurchführung wurde Scrum als Vorgehensmodell gewählt. Dadurch wird am Anfang des Projektes und eines Sprints festgelegt, was gemacht werden soll, und jeder ist auf dem gleichen Wissensstand. Des Weiteren gibt es tägliche Updates vom Fortschritt, Probleme können schnell erkannt und angesprochen werden. Dazu kann man auch neue Anforderungen flexibel mit einbringen.
|
||||||
|
|
||||||
\subsection{Verwendete Tools und Methoden}
|
\subsection{Verwendete Tools und Methoden}
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item \textbf{Scrum:} Agile Projektmanagement-Methodik
|
\item \textbf{Scrum:} Agile Projektmanagement-Methodik
|
||||||
\item \textbf{Jira:} Ticketing und Sprint-Planning
|
\item \textbf{Jira:} Ticketing und Sprint-Planning
|
||||||
\item \textbf{Git:} Versionskontrolle mit Feature-Branch-Workflow
|
\item \textbf{Git:} Versionskontrolle mit Feature-Branch-Workflow
|
||||||
\item \textbf{CI/CD:} Automatisierte Build- und Deployment-Pipelines welche gleichzeitig auch die Qualitätssicherung übernimmt
|
\item \textbf{CI/CD:} Automatisierte Build- und Deployment-Pipelines
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
\section{Umsetzung}
|
\section{Umsetzung}
|
||||||
|
@ -266,203 +266,33 @@ Guards → Components → Services → HTTP Interceptors → Backend APIs
|
||||||
Angular Router → Lazy Loading → Feature Modules
|
Angular Router → Lazy Loading → Feature Modules
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
|
||||||
\subsection{Systemarchitektur nach C4-Modell}
|
\subsection{Systemarchitektur}
|
||||||
Die Casino Gaming Platform basiert auf einer modernen, schichtbasierten Architektur mit klarer Trennung zwischen Frontend und Backend. Die Darstellung erfolgt mittels des C4-Architekturmodells (Context, Container, Component, Code), welches eine hierarchische Sichtweise auf die Softwarearchitektur ermöglicht.
|
Die Casino Gaming Platform basiert auf einer modernen Microservice-Architektur mit klarer Trennung zwischen Frontend und Backend.
|
||||||
|
|
||||||
\clearpage
|
\subsubsection{Gesamtarchitektur}
|
||||||
\begin{landscape}
|
|
||||||
\subsubsection{Ebene 1: Systemkontext-Diagramm}
|
|
||||||
Das Systemkontext-Diagramm stellt die Casino Gaming Platform in ihrem geschäftlichen Umfeld dar und zeigt die Beziehungen zu externen Akteuren und Systemen. Es verdeutlicht, wer das System nutzt und mit welchen externen Systemen es interagiert.
|
|
||||||
|
|
||||||
\vspace{0.5cm}
|
|
||||||
\begin{tikzpicture}[
|
\begin{tikzpicture}[
|
||||||
person/.style={rectangle, draw=blue!80, fill=blue!15, minimum width=3cm, minimum height=1.8cm, align=center, rounded corners=3pt, thick},
|
box/.style={rectangle, draw, minimum width=3cm, minimum height=1.5cm, align=center},
|
||||||
system/.style={rectangle, draw=blue!80, fill=blue!60, minimum width=5cm, minimum height=2.5cm, align=center, text=white, rounded corners=5pt, thick},
|
->, >=Stealth, node distance=2cm and 1.5cm
|
||||||
external/.style={rectangle, draw=gray!80, fill=gray!20, minimum width=3.5cm, minimum height=1.8cm, align=center, rounded corners=3pt, thick},
|
|
||||||
->, >=Stealth, node distance=4cm, thick
|
|
||||||
]
|
]
|
||||||
|
|
||||||
% Akteure
|
% Top row
|
||||||
\node[person] (player) at (-8, 3) {\textbf{Casino-Spieler}\\Nutzt die Plattform\\für Online-Gaming};
|
\node[box] (angular) {Angular\\Frontend};
|
||||||
\node[person] (admin) at (-8, -3) {\textbf{System-Administrator}\\Verwaltet Plattform\\und Benutzer};
|
\node[box, right=of angular] (spring) {Spring Boot\\Backend};
|
||||||
|
\node[box, right=of spring] (postgres) {PostgreSQL\\Database};
|
||||||
|
|
||||||
% Hauptsystem
|
% Bottom row
|
||||||
\node[system] (casino) at (0, 0) {\textbf{Casino Gaming Platform}\\Bietet Online-Casino-Spiele\\mit virtueller Währung und\\Zahlungsabwicklung};
|
\node[box, below=of angular] (keycloak) {Keycloak\\Auth Server};
|
||||||
|
\node[box, below=of spring] (stripe) {Stripe API\\Payment};
|
||||||
|
|
||||||
% Externe Systeme
|
% Arrows top row
|
||||||
\node[external] (stripe) at (8, 3) {\textbf{Stripe API}\\Zahlungsabwicklung};
|
\draw (angular) -- (spring);
|
||||||
\node[external] (email) at (8, 0) {\textbf{E-Mail-Service}\\Verifizierung \&\\Benachrichtigungen};
|
|
||||||
\node[external] (oauth) at (8, -3) {\textbf{OAuth-Provider}\\GitHub, Google};
|
|
||||||
|
|
||||||
% Beziehungen
|
|
||||||
\draw (player) -- (casino) node[midway, above] {Spielt Spiele, tätigt Einzahlungen};
|
|
||||||
\draw (admin) -- (casino) node[midway, below] {Administriert System};
|
|
||||||
\draw (casino) -- (stripe) node[midway, above] {Verarbeitet Zahlungen};
|
|
||||||
\draw (casino) -- (email) node[midway, above] {Sendet E-Mails};
|
|
||||||
\draw (casino) -- (oauth) node[midway, below] {Authentifiziert Benutzer};
|
|
||||||
|
|
||||||
\end{tikzpicture}
|
|
||||||
\end{landscape}
|
|
||||||
|
|
||||||
\clearpage
|
|
||||||
\begin{landscape}
|
|
||||||
\subsubsection{Ebene 2: Container-Diagramm}
|
|
||||||
Das Container-Diagramm zeigt die High-Level-Architektur der Software und stellt die verschiedenen ausführbaren Einheiten (Container) sowie deren Kommunikation dar. Jeder Container repräsentiert eine separate deploybare/ausführbare Einheit wie eine Webanwendung, Datenbank oder Microservice.
|
|
||||||
|
|
||||||
\vspace{0.5cm}
|
|
||||||
\begin{tikzpicture}[
|
|
||||||
webapp/.style={rectangle, draw=blue!80, fill=blue!25, minimum width=4cm, minimum height=2.5cm, align=center, rounded corners=4pt, thick},
|
|
||||||
api/.style={rectangle, draw=green!80, fill=green!25, minimum width=4cm, minimum height=2.5cm, align=center, rounded corners=4pt, thick},
|
|
||||||
database/.style={rectangle, draw=red!80, fill=red!25, minimum width=3.5cm, minimum height=2.2cm, align=center, rounded corners=4pt, thick},
|
|
||||||
external/.style={rectangle, draw=gray!80, fill=gray!20, minimum width=3.2cm, minimum height=1.8cm, align=center, rounded corners=3pt, thick},
|
|
||||||
->, >=Stealth, node distance=4cm, thick
|
|
||||||
]
|
|
||||||
|
|
||||||
% Akteur
|
|
||||||
\node[webapp] (person) at (-10, 5) {\textbf{Casino-Spieler}\\Person\\Nutzt Webbrowser\\oder Mobile App};
|
|
||||||
|
|
||||||
% Frontend-Container
|
|
||||||
\node[webapp] (frontend) at (-10, 1) {\textbf{Webanwendung}\\Angular 20, TypeScript\\Liefert statische Inhalte\\und Casino-Spiele-UI};
|
|
||||||
|
|
||||||
% Backend-Container
|
|
||||||
\node[api] (backend) at (0, 1) {\textbf{API-Anwendung}\\Spring Boot, Java\\Stellt Spiellogik,\\Benutzerverwaltung und\\Geschäftsregeln bereit};
|
|
||||||
|
|
||||||
% Datenbank
|
|
||||||
\node[database] (database) at (0, -4) {\textbf{Datenbank}\\PostgreSQL\\Speichert Benutzerkonten,\\Spielhistorie und\\Transaktionen};
|
|
||||||
|
|
||||||
% Externe Systeme
|
|
||||||
\node[external] (stripe) at (9, 4) {\textbf{Stripe API}\\Zahlungssystem};
|
|
||||||
\node[external] (email) at (9, 1) {\textbf{Mailpit}\\E-Mail-Service};
|
|
||||||
\node[external] (oauth) at (9, -2) {\textbf{OAuth-Provider}\\Authentifizierungsdienste};
|
|
||||||
|
|
||||||
% Beziehungen
|
|
||||||
\draw (person) -- (frontend) node[midway, right] {HTTPS};
|
|
||||||
\draw (frontend) -- (backend) node[midway, above] {REST API\\JSON/HTTPS};
|
|
||||||
\draw (backend) -- (database) node[midway, left] {JDBC\\SQL};
|
|
||||||
\draw (backend) -- (stripe) node[midway, above] {HTTPS\\Webhooks};
|
|
||||||
\draw (backend) -- (email) node[midway, above] {SMTP};
|
|
||||||
\draw (backend) -- (oauth) node[midway, below] {OAuth2\\HTTPS};
|
|
||||||
|
|
||||||
\end{tikzpicture}
|
|
||||||
\end{landscape}
|
|
||||||
|
|
||||||
\clearpage
|
|
||||||
\begin{landscape}
|
|
||||||
\subsubsection{Ebene 3: Komponenten-Diagramm - Backend API}
|
|
||||||
Das Komponenten-Diagramm zeigt die interne Struktur des Backend API-Containers und stellt die wichtigsten Softwarekomponenten sowie deren Abhängigkeiten dar. Es verdeutlicht die Architektur nach dem MVC-Pattern mit Controller-, Service- und Repository-Schichten.
|
|
||||||
|
|
||||||
\vspace{0.5cm}
|
|
||||||
\begin{tikzpicture}[
|
|
||||||
component/.style={rectangle, draw=yellow!80, fill=yellow!15, minimum width=3.2cm, minimum height=1.8cm, align=center, rounded corners=3pt, thick},
|
|
||||||
controller/.style={rectangle, draw=blue!80, fill=blue!20, minimum width=3.2cm, minimum height=1.8cm, align=center, rounded corners=3pt, thick},
|
|
||||||
service/.style={rectangle, draw=green!80, fill=green!20, minimum width=3.2cm, minimum height=1.6cm, align=center, rounded corners=3pt, thick},
|
|
||||||
repository/.style={rectangle, draw=orange!80, fill=orange!20, minimum width=3.2cm, minimum height=1.4cm, align=center, rounded corners=3pt, thick},
|
|
||||||
->, >=Stealth, node distance=3cm, thick
|
|
||||||
]
|
|
||||||
|
|
||||||
% Controller (API-Schicht)
|
|
||||||
\node[controller] (auth-ctrl) at (-8, 5) {\textbf{Auth Controller}\\Anmeldung, Registrierung,\\OAuth};
|
|
||||||
\node[controller] (game-ctrl) at (-3, 5) {\textbf{Spiele-Controller}\\Blackjack, Slots,\\Würfel, etc.};
|
|
||||||
\node[controller] (deposit-ctrl) at (2, 5) {\textbf{Einzahlungs-Controller}\\Zahlungs-\\abwicklung};
|
|
||||||
\node[controller] (user-ctrl) at (7, 5) {\textbf{Benutzer-Controller}\\Profil-\\verwaltung};
|
|
||||||
|
|
||||||
% Services (Geschäftslogik-Schicht)
|
|
||||||
\node[service] (auth-svc) at (-8, 2.5) {\textbf{Auth Service}\\Authentifizierungs-\\logik};
|
|
||||||
\node[service] (game-svc) at (-3, 2.5) {\textbf{Spiele-Services}\\Spiellogik und\\Regeln};
|
|
||||||
\node[service] (balance-svc) at (2, 2.5) {\textbf{Guthaben-Service}\\Transaktions-\\verwaltung};
|
|
||||||
\node[service] (user-svc) at (7, 2.5) {\textbf{Benutzer-Service}\\Benutzer-\\verwaltung};
|
|
||||||
|
|
||||||
% Repositories (Datenschicht)
|
|
||||||
\node[repository] (user-repo) at (-5.5, 0) {\textbf{Benutzer-Repository}\\JPA/Hibernate};
|
|
||||||
\node[repository] (game-repo) at (-0.5, 0) {\textbf{Spiele-Repository}\\JPA/Hibernate};
|
|
||||||
\node[repository] (transaction-repo) at (4.5, 0) {\textbf{Transaktions-Repo}\\JPA/Hibernate};
|
|
||||||
|
|
||||||
% Sicherheitskomponenten
|
|
||||||
\node[component] (security) at (-13, 2.5) {\textbf{Security Config}\\JWT-Filter,\\CORS, OAuth2};
|
|
||||||
|
|
||||||
% Relationships
|
|
||||||
\draw (auth-ctrl) -- (auth-svc);
|
|
||||||
\draw (game-ctrl) -- (game-svc);
|
|
||||||
\draw (deposit-ctrl) -- (balance-svc);
|
|
||||||
\draw (user-ctrl) -- (user-svc);
|
|
||||||
|
|
||||||
\draw (auth-svc) -- (user-repo);
|
|
||||||
\draw (game-svc) -- (game-repo);
|
|
||||||
\draw (balance-svc) -- (transaction-repo);
|
|
||||||
\draw (user-svc) -- (user-repo);
|
|
||||||
|
|
||||||
\draw (security) -- (auth-svc);
|
|
||||||
|
|
||||||
\end{tikzpicture}
|
|
||||||
\end{landscape}
|
|
||||||
|
|
||||||
\clearpage
|
|
||||||
\begin{landscape}
|
|
||||||
\subsubsection{Ebene 4: Code-Diagramm - Blackjack Service Implementierung}
|
|
||||||
Das Code-Diagramm zeigt exemplarisch die Implementierungsdetails des Blackjack Game Service auf Klassenebene. Es stellt die wichtigsten Klassen, Interfaces und deren Beziehungen dar, die für die Blackjack-Spiellogik verantwortlich sind.
|
|
||||||
|
|
||||||
\vspace{0.5cm}
|
|
||||||
\begin{tikzpicture}[
|
|
||||||
class/.style={rectangle, draw=cyan!80, fill=cyan!15, minimum width=3.5cm, minimum height=3cm, align=center, rounded corners=4pt, thick},
|
|
||||||
interface/.style={rectangle, draw=yellow!80, fill=yellow!15, minimum width=3cm, minimum height=2cm, align=center, rounded corners=4pt, thick},
|
|
||||||
->, >=Stealth, node distance=4.5cm, thick
|
|
||||||
]
|
|
||||||
|
|
||||||
% Interface - mit mehr Abstand
|
|
||||||
\node[interface] (game-interface) at (0, 6) {\textbf{<<Interface>>}\\GameService\\+spielStarten()\\+zugVerarbeiten()\\+ergebnisBerechnen()};
|
|
||||||
|
|
||||||
% Haupt-Service-Klasse
|
|
||||||
\node[class] (blackjack-service) at (0, 1) {\textbf{BlackjackService}\\implementiert GameService\\- kartendeck: Deck\\- spielRepository: Repository\\+ neuesSpielStarten(einsatz)\\+ ziehen(spielId)\\+ halten(spielId)\\+ verdoppeln(spielId)};
|
|
||||||
|
|
||||||
% Unterstützende Klassen - mit größerem Abstand
|
|
||||||
\node[class] (card-deck) at (-8, 1) {\textbf{Kartendeck}\\- karten: List<Karte>\\+ mischen()\\+ karteGeben()\\+ zurücksetzen()};
|
|
||||||
|
|
||||||
\node[class] (game-entity) at (8, 1) {\textbf{BlackjackSpielEntity}\\- id: Long\\- spielerKarten: List<Karte>\\- dealerKarten: List<Karte>\\- einsatzBetrag: BigDecimal\\- spielZustand: SpielZustand};
|
|
||||||
|
|
||||||
\node[class] (balance-service) at (0, -3.5) {\textbf{GuthabenService}\\+ guthabenAktualisieren()\\+ ausreichendGuthaben()\\+ transaktionErstellen()};
|
|
||||||
|
|
||||||
% Beziehungen
|
|
||||||
\draw (blackjack-service) -- (game-interface) node[midway, right] {implementiert};
|
|
||||||
\draw (blackjack-service) -- (card-deck) node[midway, above] {verwendet};
|
|
||||||
\draw (blackjack-service) -- (game-entity) node[midway, above] {verwaltet};
|
|
||||||
\draw (blackjack-service) -- (balance-service) node[midway, right] {abhängig von};
|
|
||||||
|
|
||||||
\end{tikzpicture}
|
|
||||||
\end{landscape}
|
|
||||||
|
|
||||||
\clearpage
|
|
||||||
\begin{landscape}
|
|
||||||
\subsubsection{Deployment-Diagramm}
|
|
||||||
Das Deployment-Diagramm zeigt die Verteilung der Software-Container auf die physische/virtuelle Infrastruktur und stellt die Laufzeitumgebung der Anwendung dar. Es verdeutlicht, wie die verschiedenen Komponenten in der Produktionsumgebung deployed werden.
|
|
||||||
|
|
||||||
\vspace{0.5cm}
|
|
||||||
\begin{tikzpicture}[
|
|
||||||
node/.style={rectangle, draw=gray!80, fill=gray!15, minimum width=5cm, minimum height=4cm, align=center, rounded corners=5pt, thick},
|
|
||||||
container/.style={rectangle, draw=blue!80, fill=blue!20, minimum width=3cm, minimum height=2cm, align=center, rounded corners=4pt, thick},
|
|
||||||
->, >=Stealth, node distance=6cm, thick
|
|
||||||
]
|
|
||||||
|
|
||||||
% Infrastruktur-Knoten
|
|
||||||
\node[node] (docker-host) at (0, 0) {\textbf{Docker-Host}\\Ubuntu Linux\\Docker Engine 24.x};
|
|
||||||
|
|
||||||
% Container innerhalb des Docker-Hosts - mit mehr Abstand
|
|
||||||
\node[container] (nginx) at (-7, 3.5) {\textbf{nginx}\\Webserver\\Port 80/443};
|
|
||||||
\node[container] (angular) at (-2, 3.5) {\textbf{Frontend}\\Angular-App\\Statische Dateien};
|
|
||||||
\node[container] (spring) at (2, 3.5) {\textbf{Backend}\\Spring Boot\\Port 8080};
|
|
||||||
\node[container] (postgres) at (7, 3.5) {\textbf{Datenbank}\\PostgreSQL\\Port 5432};
|
|
||||||
\node[container] (mailpit) at (2, -3.5) {\textbf{Mailpit}\\E-Mail-Service\\Port 1025/8025};
|
|
||||||
|
|
||||||
% Externe Dienste - mit mehr Abstand
|
|
||||||
\node[node] (cloud) at (9, 0) {\textbf{Cloud-Dienste}\\Stripe API\\OAuth-Provider};
|
|
||||||
|
|
||||||
% Beziehungen
|
|
||||||
\draw (nginx) -- (angular);
|
|
||||||
\draw (spring) -- (postgres);
|
\draw (spring) -- (postgres);
|
||||||
\draw (spring) -- (mailpit);
|
|
||||||
\draw (spring) -- (cloud);
|
% Arrows to bottom row
|
||||||
|
\draw (angular.south) -- (keycloak.north);
|
||||||
|
\draw (spring.south) -- (stripe.north);
|
||||||
|
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{landscape}
|
|
||||||
|
|
||||||
\subsubsection{Backend-Paketstruktur}
|
\subsubsection{Backend-Paketstruktur}
|
||||||
Das Backend folgt dem Domain-Driven Design (DDD) Ansatz:
|
Das Backend folgt dem Domain-Driven Design (DDD) Ansatz:
|
||||||
|
@ -505,73 +335,8 @@ Das Backend folgt dem Domain-Driven Design (DDD) Ansatz:
|
||||||
.3 UserService.java.
|
.3 UserService.java.
|
||||||
}
|
}
|
||||||
|
|
||||||
\clearpage
|
\subsubsection{Frontend-Modulstruktur}
|
||||||
\begin{landscape}
|
Das Frontend ist modular aufgebaut mit Feature-Modulen:
|
||||||
\subsubsection{Ebene 3: Komponenten-Diagramm - Frontend-Anwendung}
|
|
||||||
Das Komponenten-Diagramm zeigt die interne Struktur der Angular Frontend-Anwendung und stellt die modulare Architektur mit Feature-Modulen, Services und Guards dar. Es verdeutlicht die Anwendung des Angular-Framework-Patterns.
|
|
||||||
|
|
||||||
\vspace{0.5cm}
|
|
||||||
\begin{tikzpicture}[
|
|
||||||
module/.style={rectangle, draw=purple!80, fill=purple!20, minimum width=3.5cm, minimum height=2.2cm, align=center, rounded corners=4pt, thick},
|
|
||||||
component/.style={rectangle, draw=blue!80, fill=blue!20, minimum width=3cm, minimum height=1.6cm, align=center, rounded corners=3pt, thick},
|
|
||||||
service/.style={rectangle, draw=green!80, fill=green!20, minimum width=3cm, minimum height=1.6cm, align=center, rounded corners=3pt, thick},
|
|
||||||
guard/.style={rectangle, draw=orange!80, fill=orange!20, minimum width=3cm, minimum height=1.6cm, align=center, rounded corners=3pt, thick},
|
|
||||||
->, >=Stealth, node distance=3.5cm, thick
|
|
||||||
]
|
|
||||||
|
|
||||||
% Kern-Module
|
|
||||||
\node[module] (app) at (4, 6) {\textbf{App}\\Haupt-Modul};
|
|
||||||
\node[module] (shared) at (-6, 2) {\textbf{Shared}\\Gemeinsame Komponenten};
|
|
||||||
|
|
||||||
% Feature-Module
|
|
||||||
\node[module] (auth) at (-1, 2) {\textbf{Auth}\\Anmeldung, OAuth2};
|
|
||||||
\node[module] (games) at (3.75, 2) {\textbf{Spiele}\\Blackjack, Slots, Würfel};
|
|
||||||
\node[module] (deposit) at (8.25, 2) {\textbf{Einzahlung}\\Stripe};
|
|
||||||
\node[module] (home) at (12.25, 2){\textbf{Home}\\Dashboard};
|
|
||||||
|
|
||||||
% Services
|
|
||||||
\node[service] (auth-svc) at (-1, -1.5) {\textbf{Auth}\\JWT, Benutzerstatus};
|
|
||||||
\node[service] (user-svc) at (12.25, -1.5) {\textbf{User}\\Profil, Guthaben};
|
|
||||||
\node[service] (game-svc) at (3.75, -1.5) {\textbf{Spiele}\\API, Logik};
|
|
||||||
\node[service] (payment-svc) at (8.25, -1.5) {\textbf{Payment}\\Stripe};
|
|
||||||
|
|
||||||
% Guards
|
|
||||||
\node[guard] (auth-guard) at (-1.5, -4.5) {\textbf{Auth}\\Routen-Schutz};
|
|
||||||
\node[guard] (http-interceptor) at (2, -4.5) {\textbf{HTTP}\\JWT, CORS};
|
|
||||||
|
|
||||||
% Relationships
|
|
||||||
\draw (app) -- (shared);
|
|
||||||
\draw (app) -- (auth);
|
|
||||||
\draw (app) -- (games);
|
|
||||||
\draw (app) -- (deposit);
|
|
||||||
\draw (app) -- (home);
|
|
||||||
|
|
||||||
\draw (auth) -- (auth-svc);
|
|
||||||
\draw (games) -- (game-svc);
|
|
||||||
\draw (deposit) -- (payment-svc);
|
|
||||||
\draw (home) -- (user-svc);
|
|
||||||
|
|
||||||
\draw (auth-guard) -- (auth-svc);
|
|
||||||
\draw (http-interceptor) -- (auth-svc);
|
|
||||||
|
|
||||||
\end{tikzpicture}
|
|
||||||
\end{landscape}
|
|
||||||
|
|
||||||
\subsubsection{Architektur-Zusammenfassung}
|
|
||||||
Die Casino Gaming Platform implementiert eine moderne, mehrschichtige Architektur, die folgende Prinzipien befolgt:
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item \textbf{Separation of Concerns:} Klare Trennung zwischen Präsentations-, Geschäftslogik- und Datenschicht
|
|
||||||
\item \textbf{Modularität:} Feature-basierte Modularisierung sowohl im Frontend als auch Backend
|
|
||||||
\item \textbf{Skalierbarkeit:} Container-basierte Architektur ermöglicht horizontale Skalierung
|
|
||||||
\item \textbf{Sicherheit:} Umfassende Sicherheitskonzepte mit JWT, OAuth2 und CORS-Schutz
|
|
||||||
\item \textbf{Wartbarkeit:} Standardisierte Patterns (MVC, Repository, Service Layer) erleichtern die Wartung
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
Die C4-Darstellung zeigt die Architektur auf verschiedenen Abstraktionsebenen und ermöglicht es unterschiedlichen Stakeholdern, die für sie relevanten Aspekte zu verstehen - von der Geschäftslogik bis hin zu technischen Implementierungsdetails.
|
|
||||||
|
|
||||||
\subsubsection{Frontend-Paketstruktur (Detailansicht)}
|
|
||||||
Die detaillierte Paketstruktur des Frontend zeigt die modulare Organisation:
|
|
||||||
|
|
||||||
\dirtree{%
|
\dirtree{%
|
||||||
.1 app/feature/.
|
.1 app/feature/.
|
||||||
|
|
|
@ -21,8 +21,7 @@
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
"input": "public"
|
"input": "public"
|
||||||
},
|
}
|
||||||
"src/assets"
|
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.css"
|
"src/styles.css"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,63 +46,33 @@
|
||||||
<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>
|
</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">Coinflip</h3>
|
<h3 class="game-heading-sm">Coinflip</h3>
|
||||||
<p class="game-text">Münzwurf</p>
|
<p class="game-text">Münzwurf</p>
|
||||||
@if (isLoggedIn()) {
|
|
||||||
<a
|
<a
|
||||||
routerLink="game/coinflip"
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,42 +82,22 @@
|
||||||
<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>
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue