Compare commits

..

No commits in common. "674ed9e8e8e64b73c8682901aea3173a83c101ae" and "463f526fe74bc37185ce053f2435b8ecae3aee06" have entirely different histories.

77 changed files with 9311 additions and 6177 deletions

View File

@ -1,12 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Asp.Versioning.Mvc" /> <PackageReference Include="Asp.Versioning.Mvc" />
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" /> <PackageReference Include="AspNetCore.HealthChecks.SqlServer" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" /> <PackageReference Include="AspNetCore.HealthChecks.UI.Client" />
<PackageReference Include="Azure.Identity" /> <PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@ -1,6 +1,6 @@
using Api.Helpers; using Api.Helpers;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.OpenApi; using Microsoft.OpenApi.Models;
namespace Api.Configurations; namespace Api.Configurations;
@ -30,19 +30,34 @@ public static class SwaggerExtension
}); });
// Configures Bearer token authentication for Swagger // Configures Bearer token authentication for Swagger
options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{ {
Description = "JWT Authorization header using the Bearer scheme.", Description = @"JWT Authorization header using the Bearer scheme.
Enter 'Bearer' [space] and then your token in the text input below.
Example: 'Bearer 12345abcdef'",
Name = "Authorization", Name = "Authorization",
BearerFormat = "JWT",
In = ParameterLocation.Header, In = ParameterLocation.Header,
Type = SecuritySchemeType.Http, Type = SecuritySchemeType.ApiKey,
Scheme = JwtBearerDefaults.AuthenticationScheme Scheme = JwtBearerDefaults.AuthenticationScheme
}); });
// Adds security requirements for Bearer token authentication // Adds security requirements for Bearer token authentication
options.AddSecurityRequirement(document => new OpenApiSecurityRequirement options.AddSecurityRequirement(new OpenApiSecurityRequirement
{ {
[new OpenApiSecuritySchemeReference(JwtBearerDefaults.AuthenticationScheme, document)] = [] {
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = JwtBearerDefaults.AuthenticationScheme
},
Scheme = "Oauth2",
Name = JwtBearerDefaults.AuthenticationScheme,
In = ParameterLocation.Header
},
new List<string>()
}
}); });
}); });
} }

View File

@ -1,4 +1,4 @@
using Microsoft.OpenApi; using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerGen;
namespace Api.Helpers; namespace Api.Helpers;

View File

@ -3,7 +3,6 @@ using Api.Middleware;
using Application; using Application;
using Database; using Database;
using HealthChecks.UI.Client; using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Serilog; using Serilog;
using Utilities.Classes; using Utilities.Classes;
@ -25,6 +24,7 @@ try
.WriteTo.File(logPath, rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true); .WriteTo.File(logPath, rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true);
}); });
builder.Services.AddDatabase(builder.Configuration); builder.Services.AddDatabase(builder.Configuration);
builder.Services.AddApplication(); builder.Services.AddApplication();
@ -54,8 +54,6 @@ try
app.UseStaticFiles(); app.UseStaticFiles();
app.UseDefaultFiles(); app.UseDefaultFiles();
app.MapOpenApi();
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(); app.UseSwaggerUI();

View File

@ -1,5 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Folder Include="DataTransferObjects\Alliance\" /> <Folder Include="DataTransferObjects\Alliance\" />
<Folder Include="Classes\" /> <Folder Include="Classes\" />

View File

@ -1,6 +1,6 @@
using Application.Helpers; using System.Reflection;
using Application.Helpers;
using Application.Interfaces; using Application.Interfaces;
using Application.Profiles;
using Application.Repositories; using Application.Repositories;
using Application.Services; using Application.Services;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -13,7 +13,7 @@ public static class ApplicationDependencyInjection
{ {
public static IServiceCollection AddApplication(this IServiceCollection services) public static IServiceCollection AddApplication(this IServiceCollection services)
{ {
services.AddAutoMapper(_ => {}, typeof(AllianceProfile)); services.AddAutoMapper(Assembly.GetExecutingAssembly());
services.AddScoped<IAllianceRepository, AllianceRepository>(); services.AddScoped<IAllianceRepository, AllianceRepository>();
services.AddScoped<IAdmonitionRepository, AdmonitionRepository>(); services.AddScoped<IAdmonitionRepository, AdmonitionRepository>();

View File

@ -1,5 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.DataProtection" /> <PackageReference Include="Microsoft.AspNetCore.DataProtection" />
<PackageReference Include="Microsoft.AspNetCore.Identity" /> <PackageReference Include="Microsoft.AspNetCore.Identity" />

View File

@ -1,6 +1,6 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>

View File

@ -3,33 +3,37 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="MailKit" Version="4.15.0" /> <PackageVersion Include="MailKit" Version="4.11.0" />
<PackageVersion Include="Microsoft.AspNetCore.Http.Features" Version="5.0.17" /> <PackageVersion Include="Microsoft.AspNetCore.Http.Features" Version="5.0.17" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3" /> <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" /> <PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.3" /> <PackageVersion Include="Octokit" Version="14.0.0" />
<PackageVersion Include="Octokit" Version="14.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection" Version="10.0.3" /> <PackageVersion Include="Microsoft.AspNetCore.DataProtection" Version="9.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Identity" Version="2.3.9" /> <PackageVersion Include="Microsoft.AspNetCore.Identity" Version="2.3.1" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.3" /> <PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.3" /> <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.4" />
<PackageVersion Include="AutoMapper" Version="16.0.0" />
<PackageVersion Include="ExcelDataReader" Version="3.8.0" /> <PackageVersion Include="AutoMapper" Version="14.0.0" />
<PackageVersion Include="ExcelDataReader.DataSet" Version="3.8.0" /> <PackageVersion Include="ExcelDataReader" Version="3.7.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" /> <PackageVersion Include="ExcelDataReader.DataSet" Version="3.7.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="10.1.3" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" />
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.1" /> <PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="8.1.1" />
<PackageVersion Include="AspNetCore.HealthChecks.SqlServer" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" /> <PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.0" />
<PackageVersion Include="Azure.Identity" Version="1.17.1" /> <PackageVersion Include="AspNetCore.HealthChecks.SqlServer" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.3" /> <PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3" /> <PackageVersion Include="Azure.Identity" Version="1.13.2" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.3" /> <PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
<PackageVersion Include="Serilog" Version="4.3.1" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4" />
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageVersion Include="Serilog.Sinks.Seq" Version="9.0.0" /> <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.4" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.3" /> <PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Seq" Version="9.0.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="8.1.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -25,16 +25,6 @@ All beta versions (0.x.x) can be found here →
--- ---
### **[1.2.0]** *2026-02-17* 🚀
#### 🛠 Changed
- **Platform Upgrade**
The application has been upgraded to the latest platform versions:
- Backend: **.NET 10**
- Frontend: **Angular 21**
These upgrades improve performance, security, and future compatibility with upcoming features.
### **[1.1.0]** *2026-02-09* 🚀 ### **[1.1.0]** *2026-02-09* 🚀
#### ✨ Added #### ✨ Added
- **Desert Storm Player Sorting in Info Dialog** - **Desert Storm Player Sorting in Info Dialog**

View File

@ -21,7 +21,7 @@
"prefix": "app", "prefix": "app",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular/build:application", "builder": "@angular-devkit/build-angular:application",
"options": { "options": {
"outputPath": "dist/ui", "outputPath": "dist/ui",
"index": "src/index.html", "index": "src/index.html",
@ -75,7 +75,7 @@
"defaultConfiguration": "production" "defaultConfiguration": "production"
}, },
"serve": { "serve": {
"builder": "@angular/build:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"configurations": { "configurations": {
"production": { "production": {
"buildTarget": "Ui:build:production" "buildTarget": "Ui:build:production"
@ -87,13 +87,13 @@
"defaultConfiguration": "development" "defaultConfiguration": "development"
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular/build:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n",
"options": { "options": {
"buildTarget": "Ui:build" "buildTarget": "Ui:build"
} }
}, },
"test": { "test": {
"builder": "@angular/build:karma", "builder": "@angular-devkit/build-angular:karma",
"options": { "options": {
"polyfills": [ "polyfills": [
"zone.js", "zone.js",
@ -114,31 +114,5 @@
} }
} }
} }
},
"schematics": {
"@schematics/angular:component": {
"type": "component"
},
"@schematics/angular:directive": {
"type": "directive"
},
"@schematics/angular:service": {
"type": "service"
},
"@schematics/angular:guard": {
"typeSeparator": "."
},
"@schematics/angular:interceptor": {
"typeSeparator": "."
},
"@schematics/angular:module": {
"typeSeparator": "."
},
"@schematics/angular:pipe": {
"typeSeparator": "."
},
"@schematics/angular:resolver": {
"typeSeparator": "."
}
} }
} }

View File

@ -1,4 +1,4 @@
module.exports = function (config) { module.exports = function (config) {
config.set({ config.set({
basePath: '', basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'], frameworks: ['jasmine', '@angular-devkit/build-angular'],
@ -7,7 +7,7 @@ module.exports = function (config) {
require('karma-chrome-launcher'), require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'), require('karma-jasmine-html-reporter'),
require('karma-coverage'), require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
], ],
client: { client: {
jasmine: { jasmine: {

13935
Ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "ui", "name": "ui",
"version": "1.2.0", "version": "0.0.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve --host=127.0.0.1", "start": "ng serve --host=127.0.0.1",
@ -10,46 +10,46 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^21.1.4", "@angular/animations": "^18.2.7",
"@angular/common": "^21.1.4", "@angular/common": "^18.2.7",
"@angular/compiler": "^21.1.4", "@angular/compiler": "^18.2.7",
"@angular/core": "^21.1.4", "@angular/core": "^18.2.7",
"@angular/forms": "^21.1.4", "@angular/forms": "^18.2.7",
"@angular/platform-browser": "^21.1.4", "@angular/platform-browser": "^18.2.7",
"@angular/platform-browser-dynamic": "^21.1.4", "@angular/platform-browser-dynamic": "^18.2.7",
"@angular/router": "^21.1.4", "@angular/router": "^18.2.7",
"@auth0/angular-jwt": "^5.2.0", "@auth0/angular-jwt": "^5.2.0",
"@ng-bootstrap/ng-bootstrap": "^20.0.0", "@ng-bootstrap/ng-bootstrap": "^17.0.1",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@sweetalert2/theme-dark": "^5.0.27", "@sweetalert2/theme-dark": "^5.0.18",
"ag-charts-angular": "^13.1.0", "ag-charts-angular": "^11.0.4",
"bootstrap": "^5.3.8", "bootstrap": "^5.3.2",
"bootstrap-icons": "^1.13.1", "bootstrap-icons": "^1.11.3",
"bootswatch": "^5.3.8", "bootswatch": "^5.3.3",
"jest-editor-support": "*", "jest-editor-support": "*",
"moment": "^2.30.1", "moment": "^2.30.1",
"ngx-countup": "^21.0.0", "ngx-countup": "^13.2.0",
"ngx-mask": "^21.0.1", "ngx-mask": "^19.0.6",
"ngx-pagination": "^6.0.3", "ngx-pagination": "^6.0.3",
"ngx-spinner": "^21.0.0", "ngx-spinner": "^17.0.0",
"ngx-toastr": "^20.0.5", "ngx-toastr": "^19.0.0",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"sweetalert2": "^11.26.18", "sweetalert2": "^11.22.4",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.16.0" "zone.js": "~0.14.3"
}, },
"devDependencies": { "devDependencies": {
"@angular/build": "^21.1.4", "@angular-devkit/build-angular": "^18.2.8",
"@angular/cli": "^21.1.4", "@angular/cli": "^18.2.8",
"@angular/compiler-cli": "^21.1.4", "@angular/compiler-cli": "^18.2.7",
"@angular/localize": "^21.1.4", "@angular/localize": "^18.2.7",
"@types/jasmine": "~6.0.0", "@types/jasmine": "~5.1.0",
"jasmine-core": "~6.0.1", "jasmine-core": "~5.3.0",
"karma": "~6.4.0", "karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0", "karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0", "karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0", "karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.2.0", "karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.9.3" "typescript": "~5.5.4"
} }
} }

View File

@ -7,10 +7,9 @@ import {EmailConfirmationRequestModel} from "../../models/emailConfirmationReque
import {environment} from "../../../environments/environment"; import {environment} from "../../../environments/environment";
@Component({ @Component({
selector: 'app-email-confirmation', selector: 'app-email-confirmation',
templateUrl: './email-confirmation.component.html', templateUrl: './email-confirmation.component.html',
styleUrl: './email-confirmation.component.css', styleUrl: './email-confirmation.component.css'
standalone: false
}) })
export class EmailConfirmationComponent implements OnInit { export class EmailConfirmationComponent implements OnInit {

View File

@ -7,10 +7,9 @@ import {ForgotPasswordModel} from "../../models/forgotPassword.model";
import {environment} from "../../../environments/environment"; import {environment} from "../../../environments/environment";
@Component({ @Component({
selector: 'app-forgot-password', selector: 'app-forgot-password',
templateUrl: './forgot-password.component.html', templateUrl: './forgot-password.component.html',
styleUrl: './forgot-password.component.css', styleUrl: './forgot-password.component.css'
standalone: false
}) })
export class ForgotPasswordComponent { export class ForgotPasswordComponent {

View File

@ -13,87 +13,85 @@
<i class="input-group-text bi bi-envelope-at-fill"></i> <i class="input-group-text bi bi-envelope-at-fill"></i>
<!-- Email input control with dynamic classes for validation feedback --> <!-- Email input control with dynamic classes for validation feedback -->
<input [ngClass]="{'is-invalid': email?.invalid && (email?.dirty || !email?.untouched),'is-valid': email?.valid}" <input [ngClass]="{'is-invalid': email?.invalid && (email?.dirty || !email?.untouched),'is-valid': email?.valid}"
class="form-control" formControlName="email" id="email" placeholder="Email Address" type="email"> class="form-control" formControlName="email" id="email" placeholder="Email Address" type="email">
<!-- Email validation messages --> <!-- Email validation messages -->
@if (email?.errors && (email?.dirty || !email?.untouched)) { @if (email?.errors && (email?.dirty || !email?.untouched)) {
<div class="invalid-feedback"> <div class="invalid-feedback">
@if (email?.hasError('required')) { @if (email?.hasError('required')) {
<p>Email address is required</p> <p>Email address is required</p>
} }
@if (email?.hasError('email')) { @if (email?.hasError('email')) {
<p>Please enter a valid email address</p> <p>Please enter a valid email address</p>
}
</div>
}
</div>
</div>
<!-- Password input field with validation and toggle visibility -->
<div class="form-label-group">
<label class="form-label" for="password"></label>
<div class="input-group">
<!-- Password icon -->
<i class="input-group-text bi bi-key-fill"></i>
<!-- Password input control with dynamic classes for validation feedback and visibility toggle -->
<input
[ngClass]="{'is-invalid': password?.invalid && (password?.dirty || !password?.untouched),'is-valid': password?.valid}"
[type]="isPasswordType ? 'password' : 'text'" autocomplete="on" class="form-control"
formControlName="password" id="password" placeholder="Password">
<!-- Eye icon to toggle password visibility -->
<i (click)="isPasswordType = !isPasswordType"
[ngClass]="isPasswordType ? 'bi bi-eye-fill' : 'bi-eye-slash-fill'" class="input-group-text eye-icon bi">
</i>
<!-- Password validation messages -->
@if (password?.errors && (password?.dirty || !password?.untouched)) {
<div class="invalid-feedback">
@if (password?.hasError('required')) {
<p>Password is required</p>
}
</div>
} }
</div> </div>
</div> }
<!-- Forgot password link -->
<div class="mt-3">
<div class="text-center">
<p (click)="onForgotPassword()" class="custom-link">Forgot your password?</p>
</div>
</div>
<div class="mt-3">
<div class="text-center">
<p (click)="onSignUp()" class="custom-link">Dont have an account? Sign up</p>
</div>
</div>
<!-- Login button, disabled if form is invalid -->
<button [disabled]="loginForm.invalid" class="btn" type="submit">
<i class="bi bi-box-arrow-in-right"></i> Log In
</button>
<!-- Documentation link -->
<div class="mt-3 text-center">
<a href="../../../assets/docs/player-manager-doc-en.pdf" target="_blank" class="custom-link text-info text-decoration-none">
📘 View Documentation
</a>
</div>
</form>
</div>
@if (allianceCount !== null) {
<div class="alliance-counter">
<span>Currently </span>
<span class="count">
<span [countUp]="allianceCount"></span>
</span>
<span> alliances use the tool</span>
</div> </div>
} </div>
</div>
<footer class="footer d-flex flex-wrap justify-content-center justify-content-md-between align-items-center px-3 py-3 mt-auto shadow-sm gap-2 small text-center"> <!-- Password input field with validation and toggle visibility -->
<span (click)="onCompany()" style="cursor: pointer">&copy; {{currentYear}} Tomasi-Developing</span> <div class="form-label-group">
<span>Version: <span (click)="onVersion()" class="text-info" style="cursor: pointer">{{version}}</span></span> <label class="form-label" for="password"></label>
<span><a routerLink="/imprint" class="text-info text-decoration-none">Legal Notice / Privacy</a></span> <div class="input-group">
</footer> <!-- Password icon -->
<i class="input-group-text bi bi-key-fill"></i>
<!-- Password input control with dynamic classes for validation feedback and visibility toggle -->
<input
[ngClass]="{'is-invalid': password?.invalid && (password?.dirty || !password?.untouched),'is-valid': password?.valid}"
[type]="isPasswordType ? 'password' : 'text'" autocomplete="on" class="form-control"
formControlName="password" id="password" placeholder="Password">
<!-- Eye icon to toggle password visibility -->
<i (click)="isPasswordType = !isPasswordType"
[ngClass]="isPasswordType ? 'bi bi-eye-fill' : 'bi-eye-slash-fill'" class="input-group-text eye-icon bi">
</i>
<!-- Password validation messages -->
@if (password?.errors && (password?.dirty || !password?.untouched)) {
<div class="invalid-feedback">
@if (password?.hasError('required')) {
<p>Password is required</p>
}
</div>
}
</div>
</div>
<!-- Forgot password link -->
<div class="mt-3">
<div class="text-center">
<p (click)="onForgotPassword()" class="custom-link">Forgot your password?</p>
</div>
</div>
<div class="mt-3">
<div class="text-center">
<p (click)="onSignUp()" class="custom-link">Dont have an account? Sign up</p>
</div>
</div>
<!-- Login button, disabled if form is invalid -->
<button [disabled]="loginForm.invalid" class="btn" type="submit">
<i class="bi bi-box-arrow-in-right"></i> Log In
</button>
<!-- Documentation link -->
<div class="mt-3 text-center">
<a href="../../../assets/docs/player-manager-doc-en.pdf" target="_blank" class="custom-link text-info text-decoration-none">
📘 View Documentation
</a>
</div>
</form>
</div> </div>
<div class="alliance-counter" *ngIf="allianceCount !== null">
<span>Currently </span>
<span class="count">
<span [countUp]="allianceCount"></span>
</span>
<span> alliances use the tool</span>
</div>
</div>
<footer class="footer d-flex flex-wrap justify-content-center justify-content-md-between align-items-center px-3 py-3 mt-auto shadow-sm gap-2 small text-center">
<span (click)="onCompany()" style="cursor: pointer">&copy; {{currentYear}} Tomasi-Developing</span>
<span>Version: <span (click)="onVersion()" class="text-info" style="cursor: pointer">{{version}}</span></span>
<span><a routerLink="/imprint" class="text-info text-decoration-none">Legal Notice / Privacy</a></span>
</footer>
</div>

View File

@ -10,10 +10,9 @@ import {ForgotPasswordComponent} from "../forgot-password/forgot-password.compon
import {StatService} from "../../services/stat.service"; import {StatService} from "../../services/stat.service";
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
templateUrl: './login.component.html', templateUrl: './login.component.html',
styleUrl: './login.component.css', styleUrl: './login.component.css'
standalone: false
}) })
export class LoginComponent implements OnInit { export class LoginComponent implements OnInit {

View File

@ -44,118 +44,106 @@
<input [ngClass]="{ <input [ngClass]="{
'is-invalid': f['playerName'].invalid && (f['playerName'].dirty || !f['playerName'].untouched), 'is-invalid': f['playerName'].invalid && (f['playerName'].dirty || !f['playerName'].untouched),
'is-valid': f['playerName'].valid}" 'is-valid': f['playerName'].valid}"
type="text" class="form-control" id="playerName" placeholder="Player" formControlName="playerName"> type="text" class="form-control" id="playerName" placeholder="Player" formControlName="playerName">
<label for="playerName">Player Name</label> <label for="playerName">Player Name</label>
@if (f['playerName'].invalid && (f['playerName'].dirty || !f['playerName'].untouched)) { @if (f['playerName'].invalid && (f['playerName'].dirty || !f['playerName'].untouched)) {
<div class="invalid-feedback"> <div class="invalid-feedback">
@if (f['playerName'].hasError('required')) { @if (f['playerName'].hasError('required')) {
<p>Player name is required</p> <p>Player name is required</p>
} }
</div> </div>
} }
</div> </div>
<div class="form-floating mb-3 is-invalid"> <div class="form-floating mb-3 is-invalid">
<input [ngClass]="{ <input [ngClass]="{
'is-invalid': f['email'].invalid && (f['email'].dirty || !f['email'].untouched), 'is-invalid': f['email'].invalid && (f['email'].dirty || !f['email'].untouched),
'is-valid': f['email'].valid}" 'is-valid': f['email'].valid}"
type="email" class="form-control" id="email" placeholder="email" formControlName="email"> type="email" class="form-control" id="email" placeholder="email" formControlName="email">
<label for="email">Email</label> <label for="email">Email</label>
@if (f['email'].invalid && (f['email'].dirty || !f['email'].untouched)) { @if (f['email'].invalid && (f['email'].dirty || !f['email'].untouched)) {
<div class="invalid-feedback"> <div class="invalid-feedback">
@if (f['email'].hasError('required')) { @if (f['email'].hasError('required')) {
<p>Email is required</p> <p>Email is required</p>
} }
@if (f['email'].hasError('email')) { @if (f['email'].hasError('email')) {
<p>Invalid email address</p> <p>Invalid email address</p>
}
</div>
} }
</div> </div>
}
</div>
<div class="input-group has-validation mb-3"> <div class="input-group has-validation mb-3">
<div class="form-floating is-invalid"> <div class="form-floating is-invalid">
<!-- Password Input --> <!-- Password Input -->
<input <input
[ngClass]="{'is-invalid': f['password'].invalid && (f['password'].dirty || !f['password'].untouched),'is-valid': f['password'].valid}" [ngClass]="{'is-invalid': f['password'].invalid && (f['password'].dirty || !f['password'].untouched),'is-valid': f['password'].valid}"
[type]="isInputText ? 'text' : 'password'" autocomplete="on" class="form-control" [type]="isInputText ? 'text' : 'password'" autocomplete="on" class="form-control"
formControlName="password" formControlName="password"
id="password" placeholder="password"> id="password" placeholder="password">
<!-- Email Label --> <!-- Email Label -->
<label for="password">Password</label> <label for="password">Password</label>
</div>
<!-- Password Icon -->
<span class="input-group-text">
<i (click)="isInputText = !isInputText"
[ngClass]="isInputText ? 'bi-eye-slash-fill' : 'bi bi-eye-fill'"
class="input-group-text eye-icon bi"></i>
</span>
<!-- Password Invalid Feedback -->
@if (f['password'].invalid && (f['password'].dirty || !f['password'].untouched )) {
<div
class="invalid-feedback">
@if (f['password'].hasError('required')) {
<p>Password is required</p>
}
@if (!f['password'].hasError('required')) {
<div>
<div [ngClass]="f['password'].hasError('hasNumber') ? 'text-danger': 'text-success'">
<i
[ngClass]="f['password'].hasError('hasNumber') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must contain a number
</div>
<div [ngClass]="f['password'].hasError('minlength') ? 'text-danger': 'text-success'">
<i
[ngClass]="f['password'].hasError('minlength') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Minimum length required
</div>
<div [ngClass]="f['password'].hasError('hasCapitalCase') ? 'text-danger': 'text-success'">
<i
[ngClass]="f['password'].hasError('hasCapitalCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must contain a capital letter
</div>
<div [ngClass]="f['password'].hasError('hasSmallCase') ? 'text-danger': 'text-success'">
<i
[ngClass]="f['password'].hasError('hasSmallCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must contain a lowercase letter
</div>
<div [ngClass]="f['password'].hasError('hasSpecialCharacters') ? 'text-danger': 'text-success'">
<i
[ngClass]="f['password'].hasError('hasSpecialCharacters') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must contain special characters
</div>
</div>
}
</div>
}
</div>
<div class="form-floating is-invalid mb-3">
<!-- Confirm Password Input -->
<input [ngClass]="{'is-invalid': f['confirmPassword'].invalid && (f['confirmPassword'].dirty || !f['confirmPassword'].untouched),'is-valid': f['confirmPassword'].valid}"
type="password" autocomplete="on" class="form-control"
formControlName="confirmPassword"
id="confirmPassword" placeholder="confirmPassword">
<!-- Confirm Password Label -->
<label for="confirmPassword">Confirm Password</label>
</div>
<!-- Confirm Password Invalid Feedback -->
@if (f['confirmPassword'].invalid && (f['confirmPassword'].dirty || !f['confirmPassword'].untouched )) {
<div
class="invalid-feedback">
@if (f['confirmPassword'].hasError('required')) {
<p>Confirmation is required</p>
}
@if (f['confirmPassword'].hasError('passwordMismatch')) {
<p
>Passwords do not match</p>
}
</div>
}
<div class="d-grid gap-2 col-6 mx-auto">
<button [disabled]="registerForm.invalid" type="submit" class="btn btn-success">Register</button>
</div>
</form>
}
</div> </div>
<!-- Password Icon -->
<span class="input-group-text">
<i (click)="isInputText = !isInputText"
[ngClass]="isInputText ? 'bi-eye-slash-fill' : 'bi bi-eye-fill'"
class="input-group-text eye-icon bi"></i>
</span>
<!-- Password Invalid Feedback -->
<div *ngIf="f['password'].invalid && (f['password'].dirty || !f['password'].untouched )"
class="invalid-feedback">
<p *ngIf="f['password'].hasError('required')">Password is required</p>
<div *ngIf="!f['password'].hasError('required')">
<div [ngClass]="f['password'].hasError('hasNumber') ? 'text-danger': 'text-success'">
<i
[ngClass]="f['password'].hasError('hasNumber') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must contain a number
</div>
<div [ngClass]="f['password'].hasError('minlength') ? 'text-danger': 'text-success'">
<i
[ngClass]="f['password'].hasError('minlength') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Minimum length required
</div>
<div [ngClass]="f['password'].hasError('hasCapitalCase') ? 'text-danger': 'text-success'">
<i
[ngClass]="f['password'].hasError('hasCapitalCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must contain a capital letter
</div>
<div [ngClass]="f['password'].hasError('hasSmallCase') ? 'text-danger': 'text-success'">
<i
[ngClass]="f['password'].hasError('hasSmallCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must contain a lowercase letter
</div>
<div [ngClass]="f['password'].hasError('hasSpecialCharacters') ? 'text-danger': 'text-success'">
<i
[ngClass]="f['password'].hasError('hasSpecialCharacters') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must contain special characters
</div>
</div>
</div>
</div>
<div class="form-floating is-invalid mb-3">
<!-- Confirm Password Input -->
<input [ngClass]="{'is-invalid': f['confirmPassword'].invalid && (f['confirmPassword'].dirty || !f['confirmPassword'].untouched),'is-valid': f['confirmPassword'].valid}"
type="password" autocomplete="on" class="form-control"
formControlName="confirmPassword"
id="confirmPassword" placeholder="confirmPassword">
<!-- Confirm Password Label -->
<label for="confirmPassword">Confirm Password</label>
</div>
<!-- Confirm Password Invalid Feedback -->
<div *ngIf="f['confirmPassword'].invalid && (f['confirmPassword'].dirty || !f['confirmPassword'].untouched )"
class="invalid-feedback">
<p *ngIf="f['confirmPassword'].hasError('required')">Confirmation is required</p>
<p
*ngIf="f['confirmPassword'].hasError('passwordMismatch')">Passwords do not match</p>
</div>
<div class="d-grid gap-2 col-6 mx-auto">
<button [disabled]="registerForm.invalid" type="submit" class="btn btn-success">Register</button>
</div>
</form>
}
</div>

View File

@ -8,10 +8,9 @@ import {RegisterUserModel} from "../../models/registerUser.model";
import {environment} from "../../../environments/environment"; import {environment} from "../../../environments/environment";
@Component({ @Component({
selector: 'app-register', selector: 'app-register',
templateUrl: './register.component.html', templateUrl: './register.component.html',
styleUrl: './register.component.css', styleUrl: './register.component.css'
standalone: false
}) })
export class RegisterComponent implements OnInit { export class RegisterComponent implements OnInit {

View File

@ -41,79 +41,67 @@
@if(resetPasswordForm && (!showError && !isSuccess)) { @if(resetPasswordForm && (!showError && !isSuccess)) {
<div class="form-container"> <div class="form-container">
<form (ngSubmit)="onSubmit()" [formGroup]="resetPasswordForm"> <form (ngSubmit)="onSubmit()" [formGroup]="resetPasswordForm">
<!-- New Password Input --> <!-- New Password Input -->
<div class="form-group"> <div class="form-group">
<label class="form-label" for="password">New Password</label> <label class="form-label" for="password">New Password</label>
<div class="input-group"> <div class="input-group">
<input [ngClass]="{ 'is-invalid': password?.invalid && (password?.dirty || !password?.untouched), 'is-valid': password?.valid}" <input [ngClass]="{ 'is-invalid': password?.invalid && (password?.dirty || !password?.untouched), 'is-valid': password?.valid}"
[type]="isInputText ? 'text' : 'password'" class="form-control" formControlName="password" id="password"> [type]="isInputText ? 'text' : 'password'" class="form-control" formControlName="password" id="password">
<span (click)="isInputText = !isInputText" class="input-group-text eye-icon"> <span (click)="isInputText = !isInputText" class="input-group-text eye-icon">
<i [ngClass]="isInputText ? 'bi-eye-slash-fill' : 'bi-eye-fill'" class="bi"></i> <i [ngClass]="isInputText ? 'bi-eye-slash-fill' : 'bi-eye-fill'" class="bi"></i>
</span> </span>
@if (password?.hasError && (password?.dirty || !password?.untouched)) { <div *ngIf="password?.hasError && (password?.dirty || !password?.untouched)" class="invalid-feedback">
<div class="invalid-feedback"> <p *ngIf="password?.hasError('required')">Password is required</p>
@if (password?.hasError('required')) { <div *ngIf="!password?.hasError('required')">
<p>Password is required</p> <div [ngClass]="password?.hasError('hasNumber') ? 'text-danger': 'text-success'">
} <i [ngClass]="password?.hasError('hasNumber') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
@if (!password?.hasError('required')) { Must have at least 1 number!
<div> </div>
<div [ngClass]="password?.hasError('hasNumber') ? 'text-danger': 'text-success'"> <!-- Additional password complexity checks -->
<i [ngClass]="password?.hasError('hasNumber') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i> <div [ngClass]="password?.hasError('minlength') ? 'text-danger': 'text-success'">
Must have at least 1 number! <i [ngClass]="password?.hasError('minlength') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
</div> Must be at least 8 characters long!
<!-- Additional password complexity checks --> </div>
<div [ngClass]="password?.hasError('minlength') ? 'text-danger': 'text-success'"> <div [ngClass]="password?.hasError('hasCapitalCase') ? 'text-danger': 'text-success'">
<i [ngClass]="password?.hasError('minlength') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i> <i [ngClass]="password?.hasError('hasCapitalCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must be at least 8 characters long! Must contain at least 1 in capital letters!
</div> </div>
<div [ngClass]="password?.hasError('hasCapitalCase') ? 'text-danger': 'text-success'"> <div [ngClass]="password?.hasError('hasSmallCase') ? 'text-danger': 'text-success'">
<i [ngClass]="password?.hasError('hasCapitalCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i> <i [ngClass]="password?.hasError('hasSmallCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must contain at least 1 in capital letters! Must contain at least 1 lowercase letter!
</div> </div>
<div [ngClass]="password?.hasError('hasSmallCase') ? 'text-danger': 'text-success'"> <div [ngClass]="password?.hasError('hasSpecialCharacters') ? 'text-danger': 'text-success'">
<i [ngClass]="password?.hasError('hasSmallCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i> <i [ngClass]="password?.hasError('hasSpecialCharacters') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must contain at least 1 lowercase letter! Must contain at least 1 special character!
</div>
<div [ngClass]="password?.hasError('hasSpecialCharacters') ? 'text-danger': 'text-success'">
<i [ngClass]="password?.hasError('hasSpecialCharacters') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
Must contain at least 1 special character!
</div>
</div>
}
</div>
}
</div>
</div>
<!-- Confirm Password Input -->
<div class="form-group">
<label class="form-label" for="confirmPassword">Confirm Password</label>
<div class="input-group">
<input [ngClass]="{'is-invalid': confirmPassword?.invalid && (confirmPassword?.dirty || !confirmPassword?.untouched), 'is-valid': confirmPassword?.valid}"
[type]="isInputText ? 'text' : 'password'" class="form-control" formControlName="confirmPassword" id="confirmPassword">
<span (click)="isInputText = !isInputText" class="input-group-text eye-icon">
<i [ngClass]="isInputText ? 'bi-eye-slash-fill' : 'bi-eye-fill'" class="bi"></i>
</span>
@if (confirmPassword?.errors && (confirmPassword?.dirty || !confirmPassword?.untouched)) {
<div class="invalid-feedback">
@if (confirmPassword?.hasError('required')) {
<p>Repeat password is required</p>
}
@if (confirmPassword?.hasError('passwordMismatch')) {
<p>Passwords do not match</p>
}
</div>
}
</div> </div>
</div> </div>
</div>
<!-- Submit Button -->
<div class="mt-5 d-grid gap-2 col-6 mx-auto">
<button [disabled]="resetPasswordForm.invalid" class="btn btn-success" type="submit">Submit</button>
</div>
</form>
</div> </div>
} </div>
<!-- Confirm Password Input -->
<div class="form-group">
<label class="form-label" for="confirmPassword">Confirm Password</label>
<div class="input-group">
<input [ngClass]="{'is-invalid': confirmPassword?.invalid && (confirmPassword?.dirty || !confirmPassword?.untouched), 'is-valid': confirmPassword?.valid}"
[type]="isInputText ? 'text' : 'password'" class="form-control" formControlName="confirmPassword" id="confirmPassword">
<span (click)="isInputText = !isInputText" class="input-group-text eye-icon">
<i [ngClass]="isInputText ? 'bi-eye-slash-fill' : 'bi-eye-fill'" class="bi"></i>
</span>
<div *ngIf="confirmPassword?.errors && (confirmPassword?.dirty || !confirmPassword?.untouched)" class="invalid-feedback">
<p *ngIf="confirmPassword?.hasError('required')">Repeat password is required</p>
<p *ngIf="confirmPassword?.hasError('passwordMismatch')">Passwords do not match</p>
</div>
</div>
</div>
<!-- Submit Button -->
<div class="mt-5 d-grid gap-2 col-6 mx-auto">
<button [disabled]="resetPasswordForm.invalid" class="btn btn-success" type="submit">Submit</button>
</div>
</form>
</div> </div>
}
</div>

View File

@ -7,10 +7,9 @@ import {PasswordValidators} from "../../helpers/passwordValidators";
import {ResetPasswordModel} from "../../models/resetPassword.model"; import {ResetPasswordModel} from "../../models/resetPassword.model";
@Component({ @Component({
selector: 'app-reset-password', selector: 'app-reset-password',
templateUrl: './reset-password.component.html', templateUrl: './reset-password.component.html',
styleUrl: './reset-password.component.css', styleUrl: './reset-password.component.css'
standalone: false
}) })
export class ResetPasswordComponent implements OnInit { export class ResetPasswordComponent implements OnInit {

View File

@ -6,183 +6,171 @@
<input [ngClass]="{ <input [ngClass]="{
'is-invalid': f['playerName'].invalid && (f['playerName'].dirty || !f['playerName'].untouched), 'is-invalid': f['playerName'].invalid && (f['playerName'].dirty || !f['playerName'].untouched),
'is-valid': f['playerName'].valid}" 'is-valid': f['playerName'].valid}"
type="text" class="form-control" id="playerName" placeholder="Player" formControlName="playerName"> type="text" class="form-control" id="playerName" placeholder="Player" formControlName="playerName">
<label for="playerName">Player name</label> <label for="playerName">Player name</label>
@if (f['playerName'].invalid && (f['playerName'].dirty || !f['playerName'].untouched)) { @if (f['playerName'].invalid && (f['playerName'].dirty || !f['playerName'].untouched)) {
<div class="invalid-feedback"> <div class="invalid-feedback">
@if (f['playerName'].hasError('required')) { @if (f['playerName'].hasError('required')) {
<p>Player name is required</p> <p>Player name is required</p>
} }
</div> </div>
} }
</div> </div>
<div class="form-floating mb-3 is-invalid"> <div class="form-floating mb-3 is-invalid">
<input [ngClass]="{ <input [ngClass]="{
'is-invalid': f['email'].invalid && (f['email'].dirty || !f['email'].untouched), 'is-invalid': f['email'].invalid && (f['email'].dirty || !f['email'].untouched),
'is-valid': f['email'].valid}" 'is-valid': f['email'].valid}"
type="email" class="form-control" id="email" placeholder="email" formControlName="email"> type="email" class="form-control" id="email" placeholder="email" formControlName="email">
<label for="email">Email</label> <label for="email">Email</label>
@if (f['email'].invalid && (f['email'].dirty || !f['email'].untouched)) { @if (f['email'].invalid && (f['email'].dirty || !f['email'].untouched)) {
<div class="invalid-feedback"> <div class="invalid-feedback">
@if (f['email'].hasError('required')) { @if (f['email'].hasError('required')) {
<p>Email is required</p> <p>Email is required</p>
} }
@if (f['email'].hasError('email')) { @if (f['email'].hasError('email')) {
<p>Invalid email format</p> <p>Invalid email format</p>
}
</div>
} }
</div> </div>
}
</div>
<div class="form-floating mb-3 is-invalid"> <div class="form-floating mb-3 is-invalid">
<input [ngClass]="{ <input [ngClass]="{
'is-invalid': f['allianceServer'].invalid && (f['allianceServer'].dirty || !f['allianceServer'].untouched), 'is-invalid': f['allianceServer'].invalid && (f['allianceServer'].dirty || !f['allianceServer'].untouched),
'is-valid': f['allianceServer'].valid}" 'is-valid': f['allianceServer'].valid}"
type="number" class="form-control" id="allianceServer" placeholder="allianceServer" formControlName="allianceServer"> type="number" class="form-control" id="allianceServer" placeholder="allianceServer" formControlName="allianceServer">
<label for="allianceServer">Server</label> <label for="allianceServer">Server</label>
@if (f['allianceServer'].invalid && (f['allianceServer'].dirty || !f['allianceServer'].untouched)) { @if (f['allianceServer'].invalid && (f['allianceServer'].dirty || !f['allianceServer'].untouched)) {
<div class="invalid-feedback"> <div class="invalid-feedback">
@if (f['allianceServer'].hasError('required')) { @if (f['allianceServer'].hasError('required')) {
<p>Server is required</p> <p>Server is required</p>
} }
</div> </div>
} }
</div> </div>
<div class="form-floating mb-3 is-invalid"> <div class="form-floating mb-3 is-invalid">
<input [ngClass]="{ <input [ngClass]="{
'is-invalid': f['allianceName'].invalid && (f['allianceName'].dirty || !f['allianceName'].untouched), 'is-invalid': f['allianceName'].invalid && (f['allianceName'].dirty || !f['allianceName'].untouched),
'is-valid': f['allianceName'].valid}" 'is-valid': f['allianceName'].valid}"
type="text" class="form-control" id="allianceName" placeholder="allianceName" formControlName="allianceName"> type="text" class="form-control" id="allianceName" placeholder="allianceName" formControlName="allianceName">
<label for="allianceName">Alliance name</label> <label for="allianceName">Alliance name</label>
@if (f['allianceName'].invalid && (f['allianceName'].dirty || !f['allianceName'].untouched)) { @if (f['allianceName'].invalid && (f['allianceName'].dirty || !f['allianceName'].untouched)) {
<div class="invalid-feedback"> <div class="invalid-feedback">
@if (f['allianceName'].hasError('required')) { @if (f['allianceName'].hasError('required')) {
<p>Alliance name is required</p> <p>Alliance name is required</p>
} }
</div> </div>
} }
</div> </div>
<div class="form-floating mb-3 is-invalid"> <div class="form-floating mb-3 is-invalid">
<input [ngClass]="{ <input [ngClass]="{
'is-invalid': f['allianceAbbreviation'].invalid && (f['allianceAbbreviation'].dirty || !f['allianceAbbreviation'].untouched), 'is-invalid': f['allianceAbbreviation'].invalid && (f['allianceAbbreviation'].dirty || !f['allianceAbbreviation'].untouched),
'is-valid': f['allianceAbbreviation'].valid}" 'is-valid': f['allianceAbbreviation'].valid}"
type="text" class="form-control" id="allianceAbbreviation" placeholder="allianceAbbreviation" formControlName="allianceAbbreviation"> type="text" class="form-control" id="allianceAbbreviation" placeholder="allianceAbbreviation" formControlName="allianceAbbreviation">
<label for="allianceAbbreviation">Alliance abbreviation</label> <label for="allianceAbbreviation">Alliance abbreviation</label>
@if (f['allianceAbbreviation'].invalid && (f['allianceAbbreviation'].dirty || !f['allianceAbbreviation'].untouched)) { @if (f['allianceAbbreviation'].invalid && (f['allianceAbbreviation'].dirty || !f['allianceAbbreviation'].untouched)) {
<div class="invalid-feedback"> <div class="invalid-feedback">
@if (f['allianceAbbreviation'].hasError('required')) { @if (f['allianceAbbreviation'].hasError('required')) {
<p>Alliance abbreviation is required</p> <p>Alliance abbreviation is required</p>
} }
</div> </div>
} }
</div> </div>
<div class="input-group has-validation mb-3"> <div class="input-group has-validation mb-3">
<div class="form-floating is-invalid"> <div class="form-floating is-invalid">
<!-- Password Input --> <!-- Password Input -->
<input <input
[ngClass]="{'is-invalid': f['password'].invalid && (f['password'].dirty || !f['password'].untouched),'is-valid': f['password'].valid}" [ngClass]="{'is-invalid': f['password'].invalid && (f['password'].dirty || !f['password'].untouched),'is-valid': f['password'].valid}"
[type]="isInputText ? 'text' : 'password'" autocomplete="on" class="form-control" [type]="isInputText ? 'text' : 'password'" autocomplete="on" class="form-control"
formControlName="password" formControlName="password"
id="password" placeholder="password"> id="password" placeholder="password">
<!-- Email Label --> <!-- Email Label -->
<label for="password">Password</label> <label for="password">Password</label>
</div> </div>
<!-- Password Icon --> <!-- Password Icon -->
<span class="input-group-text"> <span class="input-group-text">
<i (click)="isInputText = !isInputText" <i (click)="isInputText = !isInputText"
[ngClass]="isInputText ? 'bi-eye-slash-fill' : 'bi bi-eye-fill'" [ngClass]="isInputText ? 'bi-eye-slash-fill' : 'bi bi-eye-fill'"
class="input-group-text eye-icon bi"></i> class="input-group-text eye-icon bi"></i>
</span> </span>
<!-- Password Invalid Feedback --> <!-- Password Invalid Feedback -->
@if (f['password'].invalid && (f['password'].dirty || !f['password'].untouched )) { <div *ngIf="f['password'].invalid && (f['password'].dirty || !f['password'].untouched )"
<div class="invalid-feedback">
class="invalid-feedback"> <p *ngIf="f['password'].hasError('required')">Required</p>
@if (f['password'].hasError('required')) { <div *ngIf="!f['password'].hasError('required')">
<p>Required</p> <div [ngClass]="f['password'].hasError('hasNumber') ? 'text-danger': 'text-success'">
} <i
@if (!f['password'].hasError('required')) { [ngClass]="f['password'].hasError('hasNumber') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
<div> Number
<div [ngClass]="f['password'].hasError('hasNumber') ? 'text-danger': 'text-success'"> </div>
<i <div [ngClass]="f['password'].hasError('minlength') ? 'text-danger': 'text-success'">
[ngClass]="f['password'].hasError('hasNumber') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i> <i
Number [ngClass]="f['password'].hasError('minlength') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
</div> Min length
<div [ngClass]="f['password'].hasError('minlength') ? 'text-danger': 'text-success'"> </div>
<i <div [ngClass]="f['password'].hasError('hasCapitalCase') ? 'text-danger': 'text-success'">
[ngClass]="f['password'].hasError('minlength') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i> <i
Min length [ngClass]="f['password'].hasError('hasCapitalCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
</div> capital
<div [ngClass]="f['password'].hasError('hasCapitalCase') ? 'text-danger': 'text-success'"> </div>
<i <div [ngClass]="f['password'].hasError('hasSmallCase') ? 'text-danger': 'text-success'">
[ngClass]="f['password'].hasError('hasCapitalCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i> <i
capital [ngClass]="f['password'].hasError('hasSmallCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
</div> lower case
<div [ngClass]="f['password'].hasError('hasSmallCase') ? 'text-danger': 'text-success'"> </div>
<i <div [ngClass]="f['password'].hasError('hasSpecialCharacters') ? 'text-danger': 'text-success'">
[ngClass]="f['password'].hasError('hasSmallCase') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i> <i
lower case [ngClass]="f['password'].hasError('hasSpecialCharacters') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i>
</div> special
<div [ngClass]="f['password'].hasError('hasSpecialCharacters') ? 'text-danger': 'text-success'"> </div>
<i </div>
[ngClass]="f['password'].hasError('hasSpecialCharacters') ? 'bi bi-x-square-fill' : 'bi bi-check-square-fill'"></i> </div>
special </div>
</div>
</div>
}
</div>
}
</div>
<div class="form-floating is-invalid mb-3"> <div class="form-floating is-invalid mb-3">
<!-- Confirm Password Input --> <!-- Confirm Password Input -->
<input [ngClass]="{'is-invalid': f['confirmPassword'].invalid && (f['confirmPassword'].dirty || !f['confirmPassword'].untouched),'is-valid': f['confirmPassword'].valid}" <input [ngClass]="{'is-invalid': f['confirmPassword'].invalid && (f['confirmPassword'].dirty || !f['confirmPassword'].untouched),'is-valid': f['confirmPassword'].valid}"
type="password" autocomplete="on" class="form-control" type="password" autocomplete="on" class="form-control"
formControlName="confirmPassword" formControlName="confirmPassword"
id="confirmPassword" placeholder="confirmPassword"> id="confirmPassword" placeholder="confirmPassword">
<!-- Confirm Password Label --> <!-- Confirm Password Label -->
<label for="confirmPassword">Confirm Password</label> <label for="confirmPassword">Confirm Password</label>
</div> </div>
<!-- Confirm Password Invalid Feedback --> <!-- Confirm Password Invalid Feedback -->
@if (f['confirmPassword'].invalid && (f['confirmPassword'].dirty || !f['confirmPassword'].untouched )) { <div *ngIf="f['confirmPassword'].invalid && (f['confirmPassword'].dirty || !f['confirmPassword'].untouched )"
<div class="invalid-feedback">
class="invalid-feedback"> <p *ngIf="f['confirmPassword'].hasError('required')">Confirmation password is required</p>
@if (f['confirmPassword'].hasError('required')) { <p
<p>Confirmation password is required</p> *ngIf="f['confirmPassword'].hasError('passwordMismatch')">Passwords do not match</p>
} </div>
@if (f['confirmPassword'].hasError('passwordMismatch')) {
<p
>Passwords do not match</p>
}
</div>
}
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<button type="button" (click)="onCancel()" class="btn btn-warning">Cancel</button> <button type="button" (click)="onCancel()" class="btn btn-warning">Cancel</button>
<button [disabled]="signUpForm.invalid" type="submit" class="btn btn-success">Sign Up</button> <button [disabled]="signUpForm.invalid" type="submit" class="btn btn-success">Sign Up</button>
</div> </div>
</form> </form>
} @else { } @else {
<div class="card border-success mt-5"> <div class="card border-success mt-5">
<div class="card-header text-center"> <div class="card-header text-center">
Alliance Successfully Registered! Alliance Successfully Registered!
</div> </div>
<div class="card-body text-success"> <div class="card-body text-success">
<h5 class="card-title">Welcome, {{ playerName }}!</h5> <h5 class="card-title">Welcome, {{ playerName }}!</h5>
<p class="card-text"> <p class="card-text">
Your alliance <strong>{{ allianceName }}</strong> has been successfully registered. Your alliance <strong>{{ allianceName }}</strong> has been successfully registered.
</p> </p>
<p class="card-text"> <p class="card-text">
A confirmation email has been sent to the email address you provided. Please check your inbox and confirm the email to complete the registration. A confirmation email has been sent to the email address you provided. Please check your inbox and confirm the email to complete the registration.
</p> </p>
<p class="card-text"> <p class="card-text">
If you can't find the email in your inbox, please also check your spam folder. If you can't find the email in your inbox, please also check your spam folder.
</p> </p>
</div> </div>
</div> </div>
} }
</div> </div>

View File

@ -8,10 +8,9 @@ import {ToastrService} from "ngx-toastr";
import {environment} from "../../../environments/environment"; import {environment} from "../../../environments/environment";
@Component({ @Component({
selector: 'app-sign-up', selector: 'app-sign-up',
templateUrl: './sign-up.component.html', templateUrl: './sign-up.component.html',
styleUrl: './sign-up.component.css', styleUrl: './sign-up.component.css'
standalone: false
}) })
export class SignUpComponent { export class SignUpComponent {

View File

@ -2,10 +2,9 @@ import {Component, inject, OnInit} from '@angular/core';
import {AuthenticationService} from "./services/authentication.service"; import {AuthenticationService} from "./services/authentication.service";
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.css', styleUrl: './app.component.css'
standalone: false
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
title = 'Last War - Player Management'; title = 'Last War - Player Management';

View File

@ -65,10 +65,9 @@ import { CustomEventCategoryComponent } from './pages/custom-event/custom-event-
import { CustomEventLeaderboardComponent } from './pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component'; import { CustomEventLeaderboardComponent } from './pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component';
import { CustomEventEventsComponent } from './pages/custom-event/custom-event-events/custom-event-events.component'; import { CustomEventEventsComponent } from './pages/custom-event/custom-event-events/custom-event-events.component';
import {NgxMaskDirective, NgxMaskPipe, provideNgxMask} from "ngx-mask"; import {NgxMaskDirective, NgxMaskPipe, provideNgxMask} from "ngx-mask";
import {CountUpModule} from "ngx-countup";
import { PlayerSquadsComponent } from './pages/player-squads/player-squads.component'; import { PlayerSquadsComponent } from './pages/player-squads/player-squads.component';
import { SquadEditModalComponent } from './modals/squad-edit-modal/squad-edit-modal.component'; import { SquadEditModalComponent } from './modals/squad-edit-modal/squad-edit-modal.component';
import {CommonModule} from "@angular/common";
import {CountUpDirective} from "ngx-countup";
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -126,30 +125,29 @@ import {CountUpDirective} from "ngx-countup";
PlayerSquadsComponent, PlayerSquadsComponent,
SquadEditModalComponent SquadEditModalComponent
], ],
imports: [ imports: [
CommonModule, BrowserModule,
BrowserModule, AppRoutingModule,
AppRoutingModule, BrowserAnimationsModule,
BrowserAnimationsModule, NgbModule,
NgbModule, FormsModule,
FormsModule, NgxPaginationModule,
NgxPaginationModule, ReactiveFormsModule,
ReactiveFormsModule, NgxSpinnerModule,
NgxSpinnerModule, NgbRatingModule,
NgbRatingModule, ToastrModule.forRoot({
ToastrModule.forRoot({ positionClass: 'toast-bottom-right',
positionClass: 'toast-bottom-right', }),
}), JwtModule.forRoot({
JwtModule.forRoot({ config: {
config: { tokenGetter: () => localStorage.getItem(''),
tokenGetter: () => localStorage.getItem(''), }
} }),
}), AgCharts,
AgCharts, NgxMaskDirective,
NgxMaskDirective, NgxMaskPipe,
NgxMaskPipe, CountUpModule
CountUpDirective ],
],
providers: [ providers: [
provideNgxMask(), provideNgxMask(),
provideHttpClient(withInterceptors([spinnerInterceptor, jwtInterceptor])) provideHttpClient(withInterceptors([spinnerInterceptor, jwtInterceptor]))

View File

@ -1,10 +1,9 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-under-development', selector: 'app-under-development',
templateUrl: './under-development.component.html', templateUrl: './under-development.component.html',
styleUrl: './under-development.component.css', styleUrl: './under-development.component.css'
standalone: false
}) })
export class UnderDevelopmentComponent { export class UnderDevelopmentComponent {

View File

@ -2,8 +2,7 @@ import {Pipe, PipeTransform} from '@angular/core';
import moment from "moment"; import moment from "moment";
@Pipe({ @Pipe({
name: 'week', name: 'week'
standalone: false
}) })
export class WeekPipe implements PipeTransform { export class WeekPipe implements PipeTransform {

View File

@ -5,10 +5,9 @@ import {PlayerModel} from "../../models/player.model";
import {FormArray, FormControl, FormGroup, Validators} from "@angular/forms"; import {FormArray, FormControl, FormGroup, Validators} from "@angular/forms";
@Component({ @Component({
selector: 'app-custom-event-participants-model', selector: 'app-custom-event-participants-model',
templateUrl: './custom-event-participants-model.component.html', templateUrl: './custom-event-participants-model.component.html',
styleUrl: './custom-event-participants-model.component.css', styleUrl: './custom-event-participants-model.component.css'
standalone: false
}) })
export class CustomEventParticipantsModelComponent implements OnInit { export class CustomEventParticipantsModelComponent implements OnInit {

View File

@ -4,10 +4,9 @@ import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap";
import {PlayerModel} from "../../models/player.model"; import {PlayerModel} from "../../models/player.model";
@Component({ @Component({
selector: 'app-desert-storm-participants-modal', selector: 'app-desert-storm-participants-modal',
templateUrl: './desert-storm-participants-modal.component.html', templateUrl: './desert-storm-participants-modal.component.html',
styleUrl: './desert-storm-participants-modal.component.css', styleUrl: './desert-storm-participants-modal.component.css'
standalone: false
}) })
export class DesertStormParticipantsModalComponent implements OnInit { export class DesertStormParticipantsModalComponent implements OnInit {

View File

@ -8,10 +8,9 @@ import {environment} from "../../../environments/environment";
@Component({ @Component({
selector: 'app-invite-user-modal', selector: 'app-invite-user-modal',
templateUrl: './invite-user-modal.component.html', templateUrl: './invite-user-modal.component.html',
styleUrl: './invite-user-modal.component.css', styleUrl: './invite-user-modal.component.css'
standalone: false
}) })
export class InviteUserModalComponent implements OnInit { export class InviteUserModalComponent implements OnInit {

View File

@ -4,10 +4,9 @@ import {PlayerService} from "../../services/player.service";
import {PlayerModel} from "../../models/player.model"; import {PlayerModel} from "../../models/player.model";
@Component({ @Component({
selector: 'app-marshal-guard-modal', selector: 'app-marshal-guard-modal',
templateUrl: './marshal-guard-modal.component.html', templateUrl: './marshal-guard-modal.component.html',
styleUrl: './marshal-guard-modal.component.css', styleUrl: './marshal-guard-modal.component.css'
standalone: false
}) })
export class MarshalGuardModalComponent implements OnInit { export class MarshalGuardModalComponent implements OnInit {

View File

@ -8,10 +8,9 @@ import {AdmonitionService} from "../../services/admonition.service";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
@Component({ @Component({
selector: 'app-player-admonition-modal', selector: 'app-player-admonition-modal',
templateUrl: './player-admonition-modal.component.html', templateUrl: './player-admonition-modal.component.html',
styleUrl: './player-admonition-modal.component.css', styleUrl: './player-admonition-modal.component.css'
standalone: false
}) })
export class PlayerAdmonitionModalComponent implements OnInit { export class PlayerAdmonitionModalComponent implements OnInit {

View File

@ -5,10 +5,9 @@ import {PlayerService} from "../../services/player.service";
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";
@Component({ @Component({
selector: 'app-player-dismiss-information-modal', selector: 'app-player-dismiss-information-modal',
templateUrl: './player-dismiss-information-modal.component.html', templateUrl: './player-dismiss-information-modal.component.html',
styleUrl: './player-dismiss-information-modal.component.css', styleUrl: './player-dismiss-information-modal.component.css'
standalone: false
}) })
export class PlayerDismissInformationModalComponent implements OnInit { export class PlayerDismissInformationModalComponent implements OnInit {

View File

@ -8,10 +8,9 @@ import {PlayerService} from "../../services/player.service";
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";
@Component({ @Component({
selector: 'app-player-edit-modal', selector: 'app-player-edit-modal',
templateUrl: './player-edit-modal.component.html', templateUrl: './player-edit-modal.component.html',
styleUrl: './player-edit-modal.component.css', styleUrl: './player-edit-modal.component.css'
standalone: false
}) })
export class PlayerEditModalComponent implements OnInit { export class PlayerEditModalComponent implements OnInit {

View File

@ -6,10 +6,9 @@ import {JwtTokenService} from "../../services/jwt-token.service";
import {PlayerService} from "../../services/player.service"; import {PlayerService} from "../../services/player.service";
@Component({ @Component({
selector: 'app-player-excel-import-modal', selector: 'app-player-excel-import-modal',
templateUrl: './player-excel-import-modal.component.html', templateUrl: './player-excel-import-modal.component.html',
styleUrl: './player-excel-import-modal.component.css', styleUrl: './player-excel-import-modal.component.css'
standalone: false
}) })
export class PlayerExcelImportModalComponent { export class PlayerExcelImportModalComponent {

View File

@ -8,10 +8,9 @@ import {FormControl, FormGroup, Validators} from "@angular/forms";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
@Component({ @Component({
selector: 'app-player-note-modal', selector: 'app-player-note-modal',
templateUrl: './player-note-modal.component.html', templateUrl: './player-note-modal.component.html',
styleUrl: './player-note-modal.component.css', styleUrl: './player-note-modal.component.css'
standalone: false
}) })
export class PlayerNoteModalComponent implements OnInit { export class PlayerNoteModalComponent implements OnInit {

View File

@ -8,10 +8,9 @@ import {FormControl, FormGroup, Validators} from "@angular/forms";
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";
@Component({ @Component({
selector: 'app-squad-edit-modal', selector: 'app-squad-edit-modal',
templateUrl: './squad-edit-modal.component.html', templateUrl: './squad-edit-modal.component.html',
styleUrl: './squad-edit-modal.component.css', styleUrl: './squad-edit-modal.component.css'
standalone: false
}) })
export class SquadEditModalComponent implements OnInit { export class SquadEditModalComponent implements OnInit {

View File

@ -6,10 +6,9 @@ import {UpdateUserModel, UserModel} from "../../models/user.model";
import {FormControl, FormGroup, Validators} from "@angular/forms"; import {FormControl, FormGroup, Validators} from "@angular/forms";
@Component({ @Component({
selector: 'app-user-edit-modal', selector: 'app-user-edit-modal',
templateUrl: './user-edit-modal.component.html', templateUrl: './user-edit-modal.component.html',
styleUrl: './user-edit-modal.component.css', styleUrl: './user-edit-modal.component.css'
standalone: false
}) })
export class UserEditModalComponent implements OnInit { export class UserEditModalComponent implements OnInit {

View File

@ -8,10 +8,9 @@ import {VsDuelLeagueService} from "../../services/vs-duel-league.service";
import {VsDuelLeagueModel} from "../../models/vsDuelLeague.model"; import {VsDuelLeagueModel} from "../../models/vsDuelLeague.model";
@Component({ @Component({
selector: 'app-vs-duel-edit-modal', selector: 'app-vs-duel-edit-modal',
templateUrl: './vs-duel-create-modal.component.html', templateUrl: './vs-duel-create-modal.component.html',
styleUrl: './vs-duel-create-modal.component.css', styleUrl: './vs-duel-create-modal.component.css'
standalone: false
}) })
export class VsDuelCreateModalComponent implements OnInit { export class VsDuelCreateModalComponent implements OnInit {

View File

@ -4,10 +4,9 @@ import {PlayerService} from "../../services/player.service";
import {PlayerModel} from "../../models/player.model"; import {PlayerModel} from "../../models/player.model";
@Component({ @Component({
selector: 'app-zombie-siege-participants-modal', selector: 'app-zombie-siege-participants-modal',
templateUrl: './zombie-siege-participants-modal.component.html', templateUrl: './zombie-siege-participants-modal.component.html',
styleUrl: './zombie-siege-participants-modal.component.css', styleUrl: './zombie-siege-participants-modal.component.css'
standalone: false
}) })
export class ZombieSiegeParticipantsModalComponent implements OnInit { export class ZombieSiegeParticipantsModalComponent implements OnInit {

View File

@ -10,8 +10,8 @@
<!-- Toggle button for collapsed navigation on smaller screens --> <!-- Toggle button for collapsed navigation on smaller screens -->
<button (click)="isShown = !isShown" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation" <button (click)="isShown = !isShown" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"
class="navbar-toggler" data-bs-target="#navbar" data-bs-toggle="collapse" class="navbar-toggler" data-bs-target="#navbar" data-bs-toggle="collapse"
type="button"> type="button">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
@ -67,9 +67,9 @@
<li class="nav-item"> <li class="nav-item">
<a href="https://ko-fi.com/C0C11EVSE0" <a href="https://ko-fi.com/C0C11EVSE0"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="kofi-custom-button"> class="kofi-custom-button">
<img src="https://storage.ko-fi.com/cdn/cup-border.png" alt="Ko-fi" /> <img src="https://storage.ko-fi.com/cdn/cup-border.png" alt="Ko-fi" />
<span>Donate</span> <span>Donate</span>
</a> </a>
@ -78,37 +78,33 @@
</ul> </ul>
<!-- User menu (visible only when user is logged in) --> <!-- User menu (visible only when user is logged in) -->
@if (loggedInUser) { <div *ngIf="loggedInUser" class="login">
<div class="login"> <ul class="navbar-nav me-auto">
<ul class="navbar-nav me-auto"> <li ngbDropdown display="dynamic" class="nav-item" role="presentation">
<li ngbDropdown display="dynamic" class="nav-item" role="presentation"> <button type="button" class="nav-link" ngbDropdownToggle>
<button type="button" class="nav-link" ngbDropdownToggle> <i class="bi bi-person-circle"></i>
<i class="bi bi-person-circle"></i> {{loggedInUser.userName}}
{{loggedInUser.userName}} </button>
</button> <div ngbDropdownMenu>
<div ngbDropdownMenu> <button (click)="isShown = false" routerLink="/account" ngbDropdownItem>Account</button>
<button (click)="isShown = false" routerLink="/account" ngbDropdownItem>Account</button> <button (click)="isShown = false" routerLink="/change-password" ngbDropdownItem>Change password</button>
<button (click)="isShown = false" routerLink="/change-password" ngbDropdownItem>Change password</button> <button (click)="isShown = false" routerLink="/feedback" ngbDropdownItem>Submit Feedback</button>
<button (click)="isShown = false" routerLink="/feedback" ngbDropdownItem>Submit Feedback</button> <div class="dropdown-divider"></div>
<div class="dropdown-divider"></div> <button (click)="onLogout()" ngbDropdownItem>Logout</button>
<button (click)="onLogout()" ngbDropdownItem>Logout</button> </div>
</div> </li>
</li> </ul>
</ul> </div>
</div>
}
<!-- Login button (visible only when user is not logged in) --> <!-- Login button (visible only when user is not logged in) -->
@if (!loggedInUser) { <div *ngIf="!loggedInUser" class="d-flex login" routerLink="/login">
<div class="d-flex login" routerLink="/login"> <button (click)="isShown = false" class="btn btn-secondary" routerLink="/login"><i
<button (click)="isShown = false" class="btn btn-secondary" routerLink="/login"><i
class="bi bi-box-arrow-right"></i> Login class="bi bi-box-arrow-right"></i> Login
</button> </button>
</div> </div>
} </div>
</div> </div>
</div> </nav>
</nav>
} }

View File

@ -5,10 +5,9 @@ import {Subscription} from "rxjs";
import {environment} from "../../environments/environment"; import {environment} from "../../environments/environment";
@Component({ @Component({
selector: 'app-navigation', selector: 'app-navigation',
templateUrl: './navigation.component.html', templateUrl: './navigation.component.html',
styleUrl: './navigation.component.css', styleUrl: './navigation.component.css'
standalone: false
}) })
export class NavigationComponent implements OnInit, OnDestroy { export class NavigationComponent implements OnInit, OnDestroy {

View File

@ -9,10 +9,9 @@ import Swal from "sweetalert2";
import {AuthenticationService} from "../../services/authentication.service"; import {AuthenticationService} from "../../services/authentication.service";
@Component({ @Component({
selector: 'app-account', selector: 'app-account',
templateUrl: './account.component.html', templateUrl: './account.component.html',
styleUrl: './account.component.css', styleUrl: './account.component.css'
standalone: false
}) })
export class AccountComponent implements OnInit{ export class AccountComponent implements OnInit{

View File

@ -16,7 +16,7 @@
} }
.api-key-input-group { .api-key-input-group {
max-width: 420px; max-width: 420px; // Begrenzung der Breite des Input-Feldes
} }
.api-key-input { .api-key-input {

View File

@ -5,10 +5,9 @@ import {ApiKeyService} from "../../../services/api-key.service";
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";
@Component({ @Component({
selector: 'app-alliance-api-key', selector: 'app-alliance-api-key',
templateUrl: './alliance-api-key.component.html', templateUrl: './alliance-api-key.component.html',
styleUrl: './alliance-api-key.component.css', styleUrl: './alliance-api-key.component.css'
standalone: false
}) })
export class AllianceApiKeyComponent implements OnInit { export class AllianceApiKeyComponent implements OnInit {

View File

@ -9,10 +9,9 @@ import {InviteUserModalComponent} from "../../../modals/invite-user-modal/invite
import {JwtTokenService} from "../../../services/jwt-token.service"; import {JwtTokenService} from "../../../services/jwt-token.service";
@Component({ @Component({
selector: 'app-alliance-user-administration', selector: 'app-alliance-user-administration',
templateUrl: './alliance-user-administration.component.html', templateUrl: './alliance-user-administration.component.html',
styleUrl: './alliance-user-administration.component.css', styleUrl: './alliance-user-administration.component.css'
standalone: false
}) })
export class AllianceUserAdministrationComponent implements OnInit { export class AllianceUserAdministrationComponent implements OnInit {

View File

@ -7,10 +7,9 @@ import {JwtTokenService} from "../../services/jwt-token.service";
@Component({ @Component({
selector: 'app-alliance', selector: 'app-alliance',
templateUrl: './alliance.component.html', templateUrl: './alliance.component.html',
styleUrl: './alliance.component.css', styleUrl: './alliance.component.css'
standalone: false
}) })
export class AllianceComponent implements OnInit { export class AllianceComponent implements OnInit {

View File

@ -9,10 +9,9 @@ import {HttpErrorResponse} from "@angular/common/http";
import {JwtTokenService} from "../../services/jwt-token.service"; import {JwtTokenService} from "../../services/jwt-token.service";
@Component({ @Component({
selector: 'app-change-password', selector: 'app-change-password',
templateUrl: './change-password.component.html', templateUrl: './change-password.component.html',
styleUrl: './change-password.component.css', styleUrl: './change-password.component.css'
standalone: false
}) })
export class ChangePasswordComponent { export class ChangePasswordComponent {

View File

@ -11,10 +11,9 @@ import {ToastrService} from "ngx-toastr";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
@Component({ @Component({
selector: 'app-custom-event-category', selector: 'app-custom-event-category',
templateUrl: './custom-event-category.component.html', templateUrl: './custom-event-category.component.html',
styleUrl: './custom-event-category.component.css', styleUrl: './custom-event-category.component.css'
standalone: false
}) })
export class CustomEventCategoryComponent implements OnInit { export class CustomEventCategoryComponent implements OnInit {
isCreateCustomEventCategory: any; isCreateCustomEventCategory: any;

View File

@ -4,10 +4,9 @@ import {CustomEventService} from "../../../services/custom-event.service";
import {CustomEventDetailModel} from "../../../models/customEvent.model"; import {CustomEventDetailModel} from "../../../models/customEvent.model";
@Component({ @Component({
selector: 'app-custom-event-detail', selector: 'app-custom-event-detail',
templateUrl: './custom-event-detail.component.html', templateUrl: './custom-event-detail.component.html',
styleUrl: './custom-event-detail.component.css', styleUrl: './custom-event-detail.component.css'
standalone: false
}) })
export class CustomEventDetailComponent implements OnInit { export class CustomEventDetailComponent implements OnInit {

View File

@ -17,10 +17,9 @@ import Swal from "sweetalert2";
import {forkJoin, Observable} from "rxjs"; import {forkJoin, Observable} from "rxjs";
@Component({ @Component({
selector: 'app-custom-event-events', selector: 'app-custom-event-events',
templateUrl: './custom-event-events.component.html', templateUrl: './custom-event-events.component.html',
styleUrl: './custom-event-events.component.css', styleUrl: './custom-event-events.component.css'
standalone: false
}) })
export class CustomEventEventsComponent implements OnInit { export class CustomEventEventsComponent implements OnInit {

View File

@ -10,10 +10,9 @@ import {
} from "../../../models/customEventLeaderboard.model"; } from "../../../models/customEventLeaderboard.model";
@Component({ @Component({
selector: 'app-custom-event-leaderboard', selector: 'app-custom-event-leaderboard',
templateUrl: './custom-event-leaderboard.component.html', templateUrl: './custom-event-leaderboard.component.html',
styleUrl: './custom-event-leaderboard.component.css', styleUrl: './custom-event-leaderboard.component.css'
standalone: false
}) })
export class CustomEventLeaderboardComponent implements OnInit { export class CustomEventLeaderboardComponent implements OnInit {

View File

@ -2,10 +2,9 @@ import {Component} from '@angular/core';
@Component({ @Component({
selector: 'app-custom-event', selector: 'app-custom-event',
templateUrl: './custom-event.component.html', templateUrl: './custom-event.component.html',
styleUrl: './custom-event.component.css', styleUrl: './custom-event.component.css'
standalone: false
}) })
export class CustomEventComponent { export class CustomEventComponent {

View File

@ -4,10 +4,9 @@ import {DesertStormService} from "../../../services/desert-storm.service";
import {DesertStormDetailModel} from "../../../models/desertStorm.model"; import {DesertStormDetailModel} from "../../../models/desertStorm.model";
@Component({ @Component({
selector: 'app-desert-storm-detail', selector: 'app-desert-storm-detail',
templateUrl: './desert-storm-detail.component.html', templateUrl: './desert-storm-detail.component.html',
styleUrl: './desert-storm-detail.component.css', styleUrl: './desert-storm-detail.component.css'
standalone: false
}) })
export class DesertStormDetailComponent implements OnInit { export class DesertStormDetailComponent implements OnInit {

View File

@ -19,10 +19,9 @@ import {ToastrService} from "ngx-toastr";
import {forkJoin, Observable} from "rxjs"; import {forkJoin, Observable} from "rxjs";
@Component({ @Component({
selector: 'app-desert-storm', selector: 'app-desert-storm',
templateUrl: './desert-storm.component.html', templateUrl: './desert-storm.component.html',
styleUrl: './desert-storm.component.css', styleUrl: './desert-storm.component.css'
standalone: false
}) })
export class DesertStormComponent implements OnInit { export class DesertStormComponent implements OnInit {

View File

@ -11,10 +11,9 @@ import {
} from "../../modals/player-dismiss-information-modal/player-dismiss-information-modal.component"; } from "../../modals/player-dismiss-information-modal/player-dismiss-information-modal.component";
@Component({ @Component({
selector: 'app-dismiss-player', selector: 'app-dismiss-player',
templateUrl: './dismiss-player.component.html', templateUrl: './dismiss-player.component.html',
styleUrl: './dismiss-player.component.css', styleUrl: './dismiss-player.component.css'
standalone: false
}) })
export class DismissPlayerComponent implements OnInit { export class DismissPlayerComponent implements OnInit {

View File

@ -6,10 +6,9 @@ import {ToastrService} from "ngx-toastr";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
@Component({ @Component({
selector: 'app-feedback', selector: 'app-feedback',
templateUrl: './feedback.component.html', templateUrl: './feedback.component.html',
styleUrl: './feedback.component.css', styleUrl: './feedback.component.css'
standalone: false
}) })
export class FeedbackComponent implements OnInit { export class FeedbackComponent implements OnInit {

View File

@ -1,10 +1,9 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-imprint', selector: 'app-imprint',
templateUrl: './imprint.component.html', templateUrl: './imprint.component.html',
styleUrl: './imprint.component.css', styleUrl: './imprint.component.css'
standalone: false
}) })
export class ImprintComponent { export class ImprintComponent {

View File

@ -4,10 +4,9 @@ import {MarshalGuardService} from "../../../services/marshal-guard.service";
import {MarshalGuardDetailModel} from "../../../models/marshalGuard.model"; import {MarshalGuardDetailModel} from "../../../models/marshalGuard.model";
@Component({ @Component({
selector: 'app-marshal-guard-detail', selector: 'app-marshal-guard-detail',
templateUrl: './marshal-guard-detail.component.html', templateUrl: './marshal-guard-detail.component.html',
styleUrl: './marshal-guard-detail.component.css', styleUrl: './marshal-guard-detail.component.css'
standalone: false
}) })
export class MarshalGuardDetailComponent implements OnInit { export class MarshalGuardDetailComponent implements OnInit {

View File

@ -22,10 +22,9 @@ import {Router} from "@angular/router";
import {forkJoin, Observable} from "rxjs"; import {forkJoin, Observable} from "rxjs";
@Component({ @Component({
selector: 'app-marshal-guard', selector: 'app-marshal-guard',
templateUrl: './marshal-guard.component.html', templateUrl: './marshal-guard.component.html',
styleUrl: './marshal-guard.component.css', styleUrl: './marshal-guard.component.css'
standalone: false
}) })
export class MarshalGuardComponent implements OnInit { export class MarshalGuardComponent implements OnInit {

View File

@ -6,10 +6,9 @@ import {PlayerMvpModel} from "../../models/player.model";
@Component({ @Component({
selector: 'app-mvp', selector: 'app-mvp',
templateUrl: './mvp.component.html', templateUrl: './mvp.component.html',
styleUrl: './mvp.component.css', styleUrl: './mvp.component.css'
standalone: false
}) })
export class MvpComponent implements OnInit { export class MvpComponent implements OnInit {

View File

@ -1,10 +1,9 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
@Component({ @Component({
selector: 'app-player-info-custom-event', selector: 'app-player-info-custom-event',
templateUrl: './player-info-custom-event.component.html', templateUrl: './player-info-custom-event.component.html',
styleUrl: './player-info-custom-event.component.css', styleUrl: './player-info-custom-event.component.css'
standalone: false
}) })
export class PlayerInfoCustomEventComponent { export class PlayerInfoCustomEventComponent {

View File

@ -1,10 +1,9 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
@Component({ @Component({
selector: 'app-player-info-desert-storm', selector: 'app-player-info-desert-storm',
templateUrl: './player-info-desert-storm.component.html', templateUrl: './player-info-desert-storm.component.html',
styleUrl: './player-info-desert-storm.component.css', styleUrl: './player-info-desert-storm.component.css'
standalone: false
}) })
export class PlayerInfoDesertStormComponent { export class PlayerInfoDesertStormComponent {

View File

@ -4,10 +4,9 @@ import {ToastrService} from "ngx-toastr";
import {MarshalGuardParticipantModel} from "../../../models/marshalGuardParticipant.model"; import {MarshalGuardParticipantModel} from "../../../models/marshalGuardParticipant.model";
@Component({ @Component({
selector: 'app-player-info-marshal-guard', selector: 'app-player-info-marshal-guard',
templateUrl: './player-info-marshal-guard.component.html', templateUrl: './player-info-marshal-guard.component.html',
styleUrl: './player-info-marshal-guard.component.css', styleUrl: './player-info-marshal-guard.component.css'
standalone: false
}) })
export class PlayerInfoMarshalGuardComponent { export class PlayerInfoMarshalGuardComponent {

View File

@ -6,11 +6,10 @@ import {ToastrService} from "ngx-toastr";
@Component({ @Component({
selector: 'app-player-info-vs-duel', selector: 'app-player-info-vs-duel',
templateUrl: './player-info-vs-duel.component.html', templateUrl: './player-info-vs-duel.component.html',
styleUrl: './player-info-vs-duel.component.css', styleUrl: './player-info-vs-duel.component.css',
providers: [DatePipe], providers: [DatePipe]
standalone: false
}) })
export class PlayerInfoVsDuelComponent { export class PlayerInfoVsDuelComponent {
@ -25,7 +24,9 @@ export class PlayerInfoVsDuelComponent {
options: AgChartOptions = { options: AgChartOptions = {
title: { text: 'Weekly Points' }, title: {
text: 'Weekly Points'
},
data: [], data: [],
series: [ series: [
{ {
@ -38,21 +39,22 @@ export class PlayerInfoVsDuelComponent {
fill: 'blue' fill: 'blue'
} }
], ],
axes: { axes: [
y: { {
type: 'number', type: 'number',
position: 'left', position: 'left',
label: { label: {
formatter: (params: any) => params.value.toLocaleString('en-US'), formatter: (params: any) => {
}, return params.value.toLocaleString('en-US')
}
}
}, },
x: { {
type: 'category', type: 'category',
position: 'bottom', position: 'bottom',
}, }
}, ]
}; }
getData(take: number) { getData(take: number) {
const chartData: {date: string, points: number}[] = []; const chartData: {date: string, points: number}[] = [];

View File

@ -8,10 +8,9 @@ import {PlayerAdmonitionModalComponent} from "../../modals/player-admonition-mod
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";
@Component({ @Component({
selector: 'app-player-information', selector: 'app-player-information',
templateUrl: './player-information.component.html', templateUrl: './player-information.component.html',
styleUrl: './player-information.component.css', styleUrl: './player-information.component.css'
standalone: false
}) })
export class PlayerInformationComponent implements OnInit { export class PlayerInformationComponent implements OnInit {

View File

@ -7,10 +7,9 @@ import Swal from "sweetalert2";
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";
@Component({ @Component({
selector: 'app-player-squads', selector: 'app-player-squads',
templateUrl: './player-squads.component.html', templateUrl: './player-squads.component.html',
styleUrl: './player-squads.component.css', styleUrl: './player-squads.component.css'
standalone: false
}) })
export class PlayerSquadsComponent implements OnInit { export class PlayerSquadsComponent implements OnInit {

View File

@ -18,10 +18,9 @@ import {
@Component({ @Component({
selector: 'app-player', selector: 'app-player',
templateUrl: './player.component.html', templateUrl: './player.component.html',
styleUrl: './player.component.css', styleUrl: './player.component.css'
standalone: false
}) })
export class PlayerComponent implements OnInit { export class PlayerComponent implements OnInit {

View File

@ -5,10 +5,9 @@ import {VsDuelDetailModel} from "../../../models/vsDuel.model";
@Component({ @Component({
selector: 'app-vs-duel-detail', selector: 'app-vs-duel-detail',
templateUrl: './vs-duel-detail.component.html', templateUrl: './vs-duel-detail.component.html',
styleUrl: './vs-duel-detail.component.css', styleUrl: './vs-duel-detail.component.css'
standalone: false
}) })
export class VsDuelDetailComponent implements OnInit { export class VsDuelDetailComponent implements OnInit {

View File

@ -11,10 +11,9 @@ import {VsDuelLeagueService} from "../../../services/vs-duel-league.service";
import {VsDuelLeagueModel} from "../../../models/vsDuelLeague.model"; import {VsDuelLeagueModel} from "../../../models/vsDuelLeague.model";
@Component({ @Component({
selector: 'app-vs-duel-edit', selector: 'app-vs-duel-edit',
templateUrl: './vs-duel-edit.component.html', templateUrl: './vs-duel-edit.component.html',
styleUrl: './vs-duel-edit.component.css', styleUrl: './vs-duel-edit.component.css'
standalone: false
}) })
export class VsDuelEditComponent implements OnInit { export class VsDuelEditComponent implements OnInit {

View File

@ -9,11 +9,10 @@ import Swal from "sweetalert2";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
@Component({ @Component({
selector: 'app-vs-duel', selector: 'app-vs-duel',
templateUrl: './vs-duel.component.html', templateUrl: './vs-duel.component.html',
styleUrl: './vs-duel.component.css', styleUrl: './vs-duel.component.css',
providers: [WeekPipe], providers: [WeekPipe]
standalone: false
}) })
export class VsDuelComponent implements OnInit { export class VsDuelComponent implements OnInit {

View File

@ -4,10 +4,9 @@ import {ActivatedRoute} from "@angular/router";
import {ZombieSiegeService} from "../../../services/zombie-siege.service"; import {ZombieSiegeService} from "../../../services/zombie-siege.service";
@Component({ @Component({
selector: 'app-zombie-siege-detail', selector: 'app-zombie-siege-detail',
templateUrl: './zombie-siege-detail.component.html', templateUrl: './zombie-siege-detail.component.html',
styleUrl: './zombie-siege-detail.component.css', styleUrl: './zombie-siege-detail.component.css'
standalone: false
}) })
export class ZombieSiegeDetailComponent implements OnInit { export class ZombieSiegeDetailComponent implements OnInit {

View File

@ -21,10 +21,9 @@ import {forkJoin, Observable} from "rxjs";
import {PagedResponseModel} from "../../models/pagedResponse.model"; import {PagedResponseModel} from "../../models/pagedResponse.model";
@Component({ @Component({
selector: 'app-zombie-siege', selector: 'app-zombie-siege',
templateUrl: './zombie-siege.component.html', templateUrl: './zombie-siege.component.html',
styleUrl: './zombie-siege.component.css', styleUrl: './zombie-siege.component.css',
standalone: false
}) })
export class ZombieSiegeComponent implements OnInit { export class ZombieSiegeComponent implements OnInit {

View File

@ -1,14 +1,9 @@
import { provideZoneChangeDetection } from "@angular/core"; /// <reference types="@angular/localize" />
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
import { ModuleRegistry, AllCommunityModule } from 'ag-charts-community'; platformBrowserDynamic().bootstrapModule(AppModule)
ModuleRegistry.registerModules([AllCommunityModule]);
platformBrowserDynamic()
.bootstrapModule(AppModule, {
applicationProviders: [provideZoneChangeDetection()],
})
.catch(err => console.error(err)); .catch(err => console.error(err));

View File

@ -14,11 +14,15 @@
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"experimentalDecorators": true, "experimentalDecorators": true,
"moduleResolution": "bundler", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"target": "ES2022", "target": "ES2022",
"module": "ES2022", "module": "ES2022",
"useDefineForClassFields": false "useDefineForClassFields": false,
"lib": [
"ES2022",
"dom"
]
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false, "enableI18nLegacyMessageIdFormat": false,

View File

@ -1,12 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Constants\" /> <Folder Include="Constants\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <PackageReference Include="MailKit" />
<PackageReference Include="MailKit" /> <PackageReference Include="Microsoft.AspNetCore.Http.Features" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Octokit" /> <PackageReference Include="Octokit" />
</ItemGroup> </ItemGroup>