fix(staff): fix staff/me profile resolution and add POS access

Root cause: BFF GetMyStaffProfile used ExtractUserIdFromJwt(authHeader)
which reads Authorization header — but BFF uses httpOnly cookie auth,
so authHeader was always null → userId match always failed → 404.

Fix: Extract userId/email from bff_session cookie instead. Also add
email fallback matching when userId match fails.

Additionally:
- Add "Mở POS" button on Staff Dashboard (orange, links to POS page)
- Add "Mở POS" link in StaffLayout sidebar for Cashier/Manager roles
- POS link uses shopId from staff's shopAssignment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ho Ngoc Hai
2026-03-30 11:41:40 +07:00
parent 8f39570407
commit 1b90b0119d
3 changed files with 36 additions and 6 deletions

View File

@@ -57,11 +57,11 @@
<span>Bàn / Order</span>
</NavLink>
}
@if (_staffRole == "Cashier")
@if (_staffRole == "Cashier" || _staffRole == "Manager")
{
<NavLink href="/staff/pos" class="staff-nav-item" ActiveClass="staff-nav-item--active">
<NavLink href="@(_shopId.HasValue ? $"/pos/{_shopId}/cafe" : "/staff/pos")" class="staff-nav-item" ActiveClass="staff-nav-item--active">
<i data-lucide="monitor"></i>
<span>Thu ngân</span>
<span>Mở POS</span>
</NavLink>
}
@if (_staffRole == "Manager")

View File

@@ -46,6 +46,14 @@
<i data-lucide="calendar-off"></i>
Xin nghỉ phép
</button>
@if (_profile?.ShopId.HasValue == true)
{
<button class="staff-btn-primary" style="background:#FF5C00;" @onclick="@(() => Nav.NavigateTo($"/pos/{_profile.ShopId}/cafe"))">
<i data-lucide="monitor"></i>
Mở POS
</button>
}
</div>
@* ═══ STAT CARDS ═══ *@

View File

@@ -170,8 +170,13 @@ public class StaffController : ControllerBase
public async Task<IActionResult> GetMyStaffProfile()
{
// EN: Get all staff for this merchant — AuthForwardingHandler auto-forwards the Bearer token.
// Fallback: if merchant-service rejects (staff user is not owner), try staff lookup by email.
// VI: Lấy tất cả nhân viên của merchant — AuthForwardingHandler tự động chuyển tiếp Bearer token.
// Fallback: nếu merchant-service từ chối (staff user không phải owner), thử tìm theo email.
var authHeader = Request.Headers["Authorization"].FirstOrDefault();
var (currentUserId, currentEmail) = ExtractUserClaimsFromJwt(
Request.Cookies.TryGetValue("bff_session", out var cookie) ? $"Bearer {cookie}" : authHeader);
var staffResp = await _merchant.GetAsync("/api/v1/merchants/me/staff");
if (!staffResp.IsSuccessStatusCode)
return StatusCode((int)staffResp.StatusCode, await staffResp.Content.ReadAsStringAsync());
@@ -191,9 +196,11 @@ public class StaffController : ControllerBase
else
return NotFound(new { success = false, message = "No staff data found" });
// EN: Extract userId from JWT 'sub' claim to match staff by userId.
// VI: Trích userId từ JWT 'sub' claim để match nhân viên theo userId.
var userId = ExtractUserIdFromJwt(authHeader);
// EN: Use userId + email already extracted from session cookie (line 177-178).
// Previous bug: ExtractUserIdFromJwt(authHeader) used Authorization header which is null for cookie-based BFF auth.
// VI: Dùng userId + email đã extract từ session cookie (dòng 177-178).
// Bug trước: ExtractUserIdFromJwt(authHeader) dùng Authorization header mà null khi dùng cookie-based BFF auth.
var userId = currentUserId;
// EN: Find matching staff member by userId, then by email fallback
// VI: Tìm nhân viên khớp theo userId, fallback theo email
@@ -211,6 +218,21 @@ public class StaffController : ControllerBase
}
}
// EN: Fallback: match by email if userId match failed
// VI: Fallback: match theo email nếu userId không khớp
if (matchedStaff == null && !string.IsNullOrEmpty(currentEmail) && items.ValueKind == JsonValueKind.Array)
{
foreach (var staff in items.EnumerateArray())
{
if (staff.TryGetProperty("email", out var emailProp) &&
string.Equals(emailProp.GetString(), currentEmail, StringComparison.OrdinalIgnoreCase))
{
matchedStaff = staff;
break;
}
}
}
if (matchedStaff == null)
return NotFound(new { success = false, message = "Staff profile not found for current user" });