From f7f31b58c14ec012c1747396d04b1c06649fdacc Mon Sep 17 00:00:00 2001 From: steinhelge Date: Mon, 24 Nov 2025 06:25:56 +0100 Subject: [PATCH] Add auto-load guest data on login --- .../Controllers/AuthController.cs | 15 ++ .../Services/AuthService.cs | 38 +++- .../Services/IAuthService.cs | 1 + .../src/pages/guest/GuestQrPage.tsx | 170 ++++++++++-------- 4 files changed, 147 insertions(+), 77 deletions(-) diff --git a/src/Hospitality.Backend/Controllers/AuthController.cs b/src/Hospitality.Backend/Controllers/AuthController.cs index 8c71f9d..0776110 100644 --- a/src/Hospitality.Backend/Controllers/AuthController.cs +++ b/src/Hospitality.Backend/Controllers/AuthController.cs @@ -52,4 +52,19 @@ public class AuthController : ControllerBase return Ok(userInfo); } + + [HttpGet("me/person")] + [Authorize] + public async Task GetCurrentUserPerson() + { + var email = User.FindFirstValue(ClaimTypes.Email); + if (email == null) + return Unauthorized(); + + var person = await _authService.GetUserPersonAsync(email); + if (person == null) + return NotFound(new { message = "Your account is not linked to a person. Please contact an administrator." }); + + return Ok(person); + } } diff --git a/src/Hospitality.Backend/Services/AuthService.cs b/src/Hospitality.Backend/Services/AuthService.cs index b4dc5e4..7497aab 100644 --- a/src/Hospitality.Backend/Services/AuthService.cs +++ b/src/Hospitality.Backend/Services/AuthService.cs @@ -4,7 +4,9 @@ using System.Text; using Hospitality.Backend.Configuration; using Hospitality.Backend.DTOs; using Hospitality.Domain.Entities; +using Hospitality.Infrastructure.Data; using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; @@ -15,15 +17,18 @@ public class AuthService : IAuthService private readonly UserManager _userManager; private readonly SignInManager _signInManager; private readonly JwtSettings _jwtSettings; + private readonly HospitalityDbContext _context; public AuthService( UserManager userManager, SignInManager signInManager, - IOptions jwtSettings) + IOptions jwtSettings, + HospitalityDbContext context) { _userManager = userManager; _signInManager = signInManager; _jwtSettings = jwtSettings.Value; + _context = context; } public async Task LoginAsync(LoginRequest request) @@ -68,6 +73,37 @@ public class AuthService : IAuthService return new UserInfoResponse(user.Email!, roles.ToArray()); } + public async Task GetUserPersonAsync(string email) + { + var user = await _userManager.FindByEmailAsync(email); + if (user == null || user.PersonId == null) + return null; + + var person = await _context.People + .Include(p => p.Quotas) + .ThenInclude(q => q.Product) + .FirstOrDefaultAsync(p => p.Id == user.PersonId.Value); + + if (person == null) + return null; + + return new + { + name = person.Name, + email = person.Email, + qrCode = person.QrCode.ToString(), + quotas = person.Quotas.Select(q => new + { + productName = q.Product.Name, + productType = q.Product.Type.ToString(), + initialAmount = q.InitialAmount, + usedAmount = q.UsedAmount, + remainingAmount = q.RemainingAmount + }).ToList() + }; + } + + private string GenerateJwtToken(ApplicationUser user, string[] roles) { var claims = new List diff --git a/src/Hospitality.Backend/Services/IAuthService.cs b/src/Hospitality.Backend/Services/IAuthService.cs index 6c7eb9e..cfd8ef9 100644 --- a/src/Hospitality.Backend/Services/IAuthService.cs +++ b/src/Hospitality.Backend/Services/IAuthService.cs @@ -7,4 +7,5 @@ public interface IAuthService Task LoginAsync(LoginRequest request); Task RegisterAsync(RegisterRequest request); Task GetUserInfoAsync(string email); + Task GetUserPersonAsync(string email); } diff --git a/src/hospitality-web/src/pages/guest/GuestQrPage.tsx b/src/hospitality-web/src/pages/guest/GuestQrPage.tsx index 3ce823f..10a66aa 100644 --- a/src/hospitality-web/src/pages/guest/GuestQrPage.tsx +++ b/src/hospitality-web/src/pages/guest/GuestQrPage.tsx @@ -1,92 +1,110 @@ -import { useState } from 'react'; -import { Card, Form, Button, ProgressBar, ListGroup } from 'react-bootstrap'; +import { useState, useEffect } from 'react'; +import { Container, Card, Alert, Spinner, ProgressBar } from 'react-bootstrap'; import { QRCodeSVG } from 'qrcode.react'; -import { usePersonByQrCode } from '../../hooks/useQrCode'; +import { api } from '../../lib/api'; + +interface Quota { + productName: string; + remainingAmount: number; + initialAmount: number; +} + +interface PersonData { + name: string; + email: string; + qrCode: string; + quotas: Quota[]; +} export default function GuestQrPage() { - const [qrCode, setQrCode] = useState(''); - const [showQr, setShowQr] = useState(false); - const { data: person } = usePersonByQrCode(qrCode); + const [personData, setPersonData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - setShowQr(true); - }; + useEffect(() => { + // Auto-load guest's own data on mount + const fetchGuestData = async () => { + try { + setLoading(true); + setError(''); + + // Get current user's person data + const response = await api.get('/auth/me/person'); + + setPersonData(response.data); + } catch (err: any) { + setError(err.response?.data?.message || 'Failed to load your information. Make sure your account is linked to a person.'); + } finally { + setLoading(false); + } + }; + + fetchGuestData(); + }, []); return ( -
-
-

Guest View

+ +

My QR Code & Quotas

- {!showQr ? ( - - -
- - Enter Your QR Code - setQrCode(e.target.value)} - placeholder="Your QR code (GUID)" - required - /> - - -
+ {loading && ( +
+ + Loading... + +
+ )} + + {error && {error}} + + {personData && ( + <> + + +

{personData.name}

+ {personData.email &&

{personData.email}

} + +
+
+ +
+
+ + + Show this QR code to staff for scanning +
- ) : ( - - - {person && ( - <> -

{person.name}

-
-
- + + +
My Quotas
+
+ + {personData.quotas.length === 0 ? ( +

No quotas assigned

+ ) : ( +
+ {personData.quotas.map((quota, index) => ( +
+
+ {quota.productName} + + {quota.remainingAmount} / {quota.initialAmount} remaining + +
+ 0 ? 'success' : 'danger'} + style={{ height: '8px' }} + />
-
- -

Your Quotas

- - {person.quotas?.map((quota) => ( - -
-
-
{quota.productName}
- {quota.productType} -
-
-
{quota.remainingAmount}
- of {quota.initialAmount} remaining -
-
- -
- ))} -
- - - + ))} +
)} - )} -
-
+ + )} + ); }