diff --git a/src/Koogle.Application/DTOs/AuthDto.cs b/src/Koogle.Application/DTOs/AuthDto.cs index 88f1bda..3a55e43 100644 --- a/src/Koogle.Application/DTOs/AuthDto.cs +++ b/src/Koogle.Application/DTOs/AuthDto.cs @@ -71,6 +71,11 @@ namespace Koogle.Application.DTOs /// Optional club name to assign user to during registration. /// public string? ClubName { get; set; } + + /// + /// Optional invitation token for club membership during registration. + /// + public string? InviteToken { get; set; } } /// diff --git a/src/Koogle.Web/Components/Pages/Account/Login.razor b/src/Koogle.Web/Components/Pages/Account/Login.razor index da4780a..e8f9b12 100644 --- a/src/Koogle.Web/Components/Pages/Account/Login.razor +++ b/src/Koogle.Web/Components/Pages/Account/Login.razor @@ -14,6 +14,12 @@ + @if (!string.IsNullOrWhiteSpace(_inviteToken)) + { + + Bitte melde dich an, um die Club-Einladung anzunehmen. + + } @if (_registered) { Registrierung erfolgreich! Bitte anmelden. @@ -86,6 +92,7 @@ private string? _error; private string _antiToken = ""; private bool _registered; + private string? _inviteToken; protected override void OnInitialized() { @@ -101,6 +108,12 @@ { _registered = true; } + if (query.TryGetValue("invite", out var invite)) + { + _inviteToken = invite.ToString(); + // After login, redirect to join by invite + _returnUrl = $"/club/join/{_inviteToken}"; + } // Antiforgery Token generieren (klassischer MVC Token) var http = HttpContextAccessor.HttpContext!; diff --git a/src/Koogle.Web/Components/Pages/Account/Register.razor b/src/Koogle.Web/Components/Pages/Account/Register.razor index a806296..22a53d9 100644 --- a/src/Koogle.Web/Components/Pages/Account/Register.razor +++ b/src/Koogle.Web/Components/Pages/Account/Register.razor @@ -9,8 +9,18 @@
+ @if (!string.IsNullOrWhiteSpace(_inviteToken)) + { + + } + @if (!string.IsNullOrWhiteSpace(_inviteToken)) + { + + Du wurdest zu einem Club eingeladen. Registriere dich, um beizutreten. + + } @if (!string.IsNullOrWhiteSpace(_error)) { @_error @@ -75,6 +85,7 @@ @code { private string? _error; + private string? _inviteToken; private string _antiToken = ""; protected override void OnInitialized() @@ -87,6 +98,11 @@ _error = MapErrorCodes(err.ToString()); } + if (query.TryGetValue("invite", out var invite)) + { + _inviteToken = invite.ToString(); + } + // Antiforgery Token generieren var http = HttpContextAccessor.HttpContext!; var tokens = Antiforgery.GetAndStoreTokens(http); diff --git a/src/Koogle.Web/Components/Pages/Club/JoinByInvite.razor b/src/Koogle.Web/Components/Pages/Club/JoinByInvite.razor new file mode 100644 index 0000000..f31113b --- /dev/null +++ b/src/Koogle.Web/Components/Pages/Club/JoinByInvite.razor @@ -0,0 +1,201 @@ +@page "/club/join/{Token}" + +@using Koogle.Application.DTOs +@using Koogle.Application.Interfaces +@using Microsoft.AspNetCore.Components.Authorization + +@inject IClubService ClubService +@inject IUserService UserService +@inject AuthenticationStateProvider AuthStateProvider +@inject NavigationManager NavigationManager +@inject ISnackbar Snackbar + +Club beitreten + + + Einladung zum Club + + @if (_isLoading) + { + + } + else if (_invalidToken) + { + +
+ + Ungueltige Einladung + + @_errorMessage + + + Club manuell suchen + +
+
+ } + else if (_requestSent) + { + +
+ + Beitrittsantrag gesendet! + + Dein Antrag fuer @_invitation?.ClubName wurde gesendet. + Ein Admin wird ihn pruefen. + + + Zum Dashboard + +
+
+ } + else if (_invitation is not null) + { + +
+ + @_invitation.ClubName + + Du wurdest eingeladen, diesem Club beizutreten. + + + @if (!string.IsNullOrEmpty(_error)) + { + @_error + } + + + @if (_isSubmitting) + { + + } + Beitreten + +
+
+ } +
+ +@code { + [Parameter] + public string Token { get; set; } = ""; + + private ClubInvitationDto? _invitation; + private UserDto? _currentUser; + private bool _isLoading = true; + private bool _invalidToken; + private bool _requestSent; + private bool _isSubmitting; + private string? _error; + private string? _errorMessage; + + protected override async Task OnInitializedAsync() + { + await ValidateAndProcess(); + } + + private async Task ValidateAndProcess() + { + _isLoading = true; + + try + { + // Check if user is authenticated + var authState = await AuthStateProvider.GetAuthenticationStateAsync(); + var isAuthenticated = authState.User.Identity?.IsAuthenticated ?? false; + + if (!isAuthenticated) + { + // Redirect to register with token + NavigationManager.NavigateTo($"/account/register?invite={Uri.EscapeDataString(Token)}"); + return; + } + + // Validate token + _invitation = await ClubService.GetInvitationByTokenAsync(Token); + + if (_invitation is null) + { + _invalidToken = true; + _errorMessage = "Diese Einladung existiert nicht oder wurde geloescht."; + return; + } + + if (!_invitation.IsValid) + { + _invalidToken = true; + _errorMessage = "Diese Einladung ist abgelaufen oder wurde bereits zu oft verwendet."; + return; + } + + // Load current user + _currentUser = await UserService.GetCurrentUserAsync(); + + if (_currentUser is null) + { + _invalidToken = true; + _errorMessage = "Benutzerprofil konnte nicht geladen werden."; + return; + } + + // Check if already member + var existingMembership = _currentUser.ClubMemberships + .FirstOrDefault(m => m.ClubId == _invitation.ClubId); + + if (existingMembership is not null) + { + _invalidToken = true; + _errorMessage = "Du bist bereits Mitglied dieses Clubs oder hast einen offenen Antrag."; + return; + } + } + catch (Exception ex) + { + _invalidToken = true; + _errorMessage = $"Fehler beim Laden der Einladung: {ex.Message}"; + } + finally + { + _isLoading = false; + } + } + + private async Task SubmitRequest() + { + if (_invitation is null || _currentUser is null) + return; + + _isSubmitting = true; + _error = null; + + try + { + var success = await UserService.RequestClubMembershipByInviteAsync( + _currentUser.ProfileId, + Token); + + if (success) + { + _requestSent = true; + Snackbar.Add("Beitrittsantrag erfolgreich gesendet", Severity.Success); + } + else + { + _error = "Der Antrag konnte nicht gesendet werden."; + } + } + catch (Exception ex) + { + _error = $"Fehler: {ex.Message}"; + } + finally + { + _isSubmitting = false; + } + } +} diff --git a/src/Koogle.Web/Controllers/AuthController.cs b/src/Koogle.Web/Controllers/AuthController.cs index 829e055..087f8d9 100644 --- a/src/Koogle.Web/Controllers/AuthController.cs +++ b/src/Koogle.Web/Controllers/AuthController.cs @@ -62,12 +62,20 @@ namespace Koogle.Web.Controllers var result = await _userService.RegisterUserAsync(input); if (result.Succeeded) { + // If invite token was provided, redirect to join page + if (!string.IsNullOrWhiteSpace(input.InviteToken)) + { + return LocalRedirect($"/account/login?registered=true&invite={Uri.EscapeDataString(input.InviteToken)}"); + } return LocalRedirect("/account/login?registered=true"); } // Build error string from IdentityResult var errors = string.Join(",", result.Errors.Select(e => e.Code)); - return LocalRedirect($"/account/register?error={errors}"); + var inviteParam = !string.IsNullOrWhiteSpace(input.InviteToken) + ? $"&invite={Uri.EscapeDataString(input.InviteToken)}" + : ""; + return LocalRedirect($"/account/register?error={errors}{inviteParam}"); } ///