feat(staff): add password change for existing staff members
- Add "Đổi mật khẩu" toggle in staff edit form with new password
and confirm password fields, validation (min 8 chars, match check)
- Add ResetStaffPasswordAsync() in PosDataService
- Add POST /api/bff/staff/reset-password BFF endpoint proxying to
IAM service /api/v1/users/{userId}/reset-password
- Reset password state fields when opening edit form
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,22 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* EN: Password change section for existing staff / VI: Phần đổi mật khẩu cho nhân viên đã có *@
|
||||
<div style="margin-top:12px;padding:12px;border-radius:8px;background:rgba(139,92,246,0.05);border:1px solid rgba(139,92,246,0.2);">
|
||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;font-weight:600;">
|
||||
<input type="checkbox" @bind="_changePassword" /> Đổi mật khẩu
|
||||
</label>
|
||||
@if (_changePassword)
|
||||
{
|
||||
<div style="margin-top:10px;display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Mật khẩu mới *</label><input type="password" @bind="_newStaffPassword" placeholder="Min 8 ký tự" style="width:100%;padding:8px 12px;border-radius:8px;border:1px solid var(--admin-border-subtle);background:var(--admin-bg-elevated);color:var(--admin-text-primary);" /></div>
|
||||
<div><label style="font-size:12px;font-weight:600;display:block;margin-bottom:4px;">Xác nhận mật khẩu *</label><input type="password" @bind="_confirmPassword" placeholder="Nhập lại mật khẩu" style="width:100%;padding:8px 12px;border-radius:8px;border:1px solid var(--admin-border-subtle);background:var(--admin-bg-elevated);color:var(--admin-text-primary);" /></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div style="display:flex;gap:8px;margin-top:16px;">
|
||||
<button class="admin-btn-primary" @onclick="@(_editingStaffId.HasValue ? SaveStaffEdit : AddStaff)" style="display:inline-flex;align-items:center;gap:6px;"><i data-lucide="check" style="width:14px;height:14px;"></i>@(_editingStaffId.HasValue ? "Cập nhật" : "Lưu")</button>
|
||||
<button @onclick="@(() => _showStaffForm = false)" style="display:inline-flex;align-items:center;gap:6px;padding:8px 16px;border-radius:8px;border:1px solid var(--admin-border-subtle);background:transparent;color:var(--admin-text-secondary);cursor:pointer;"><i data-lucide="x" style="width:14px;height:14px;"></i>Hủy</button>
|
||||
@@ -147,6 +163,8 @@ else if (_staff.Any())
|
||||
private string _newStaffFirstName = "";
|
||||
private string _newStaffLastName = "";
|
||||
private string _newStaffPassword = "";
|
||||
private bool _changePassword;
|
||||
private string _confirmPassword = "";
|
||||
// Staff extended fields state
|
||||
private string _newStaffAddress = "";
|
||||
// Image upload state for staff docs
|
||||
@@ -222,6 +240,9 @@ else if (_staff.Any())
|
||||
_staffDocFrontPreview = s.DocumentFrontUrl;
|
||||
_staffDocBackPreview = s.DocumentBackUrl;
|
||||
_staffFormMessage = null;
|
||||
_changePassword = false;
|
||||
_newStaffPassword = "";
|
||||
_confirmPassword = "";
|
||||
_showStaffForm = true;
|
||||
}
|
||||
|
||||
@@ -232,6 +253,18 @@ else if (_staff.Any())
|
||||
{
|
||||
_staffFormMessage = "Vui lòng nhập Mã NV."; _staffFormSuccess = false; return;
|
||||
}
|
||||
// EN: Validate password change if requested / VI: Validate đổi mật khẩu nếu yêu cầu
|
||||
if (_changePassword)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_newStaffPassword) || _newStaffPassword.Length < 8)
|
||||
{
|
||||
_staffFormMessage = "Mật khẩu mới phải có ít nhất 8 ký tự."; _staffFormSuccess = false; return;
|
||||
}
|
||||
if (_newStaffPassword != _confirmPassword)
|
||||
{
|
||||
_staffFormMessage = "Mật khẩu xác nhận không khớp."; _staffFormSuccess = false; return;
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
var docFrontUrl = await UploadFileIfNeeded(_staffDocFrontFile) ?? (_staffDocFrontPreview?.StartsWith("data:") == true ? null : _staffDocFrontPreview);
|
||||
@@ -239,8 +272,35 @@ else if (_staff.Any())
|
||||
await DataService.UpdateStaffAsync(_editingStaffId.Value, new PosDataService.CreateStaffRequest(
|
||||
_merchantId.Value, _newStaffCode, _newStaffPhone, _newStaffEmail, _newStaffRole,
|
||||
_newStaffFirstName, _newStaffLastName, _newStaffAddress, null, docFrontUrl, docBackUrl));
|
||||
_staffFormMessage = $"Đã cập nhật NV '{_newStaffCode}' thành công!"; _staffFormSuccess = true;
|
||||
|
||||
// EN: Change password if requested / VI: Đổi mật khẩu nếu yêu cầu
|
||||
if (_changePassword && !string.IsNullOrWhiteSpace(_newStaffPassword))
|
||||
{
|
||||
var editingStaff = _staff.FirstOrDefault(s => s.Id == _editingStaffId);
|
||||
if (editingStaff?.UserId != null)
|
||||
{
|
||||
var (pwOk, pwErr) = await DataService.ResetStaffPasswordAsync(editingStaff.UserId.Value, _newStaffPassword);
|
||||
if (!pwOk)
|
||||
{
|
||||
_staffFormMessage = $"Đã cập nhật thông tin NV, nhưng đổi mật khẩu thất bại: {pwErr}";
|
||||
_staffFormSuccess = false;
|
||||
_staff = await DataService.GetStaffForShopAsync(ShopId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_staffFormMessage = "Đã cập nhật thông tin NV, nhưng nhân viên chưa có tài khoản IAM để đổi mật khẩu.";
|
||||
_staffFormSuccess = false;
|
||||
_staff = await DataService.GetStaffForShopAsync(ShopId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_staffFormMessage = $"Đã cập nhật NV '{_newStaffCode}' thành công!{(_changePassword ? " Mật khẩu đã được đổi." : "")}";
|
||||
_staffFormSuccess = true;
|
||||
_editingStaffId = null;
|
||||
_changePassword = false; _newStaffPassword = ""; _confirmPassword = "";
|
||||
_staffDocFrontFile = null; _staffDocBackFile = null; _staffDocFrontPreview = null; _staffDocBackPreview = null;
|
||||
_staff = await DataService.GetStaffForShopAsync(ShopId);
|
||||
}
|
||||
|
||||
@@ -1191,6 +1191,19 @@ public class PosDataService
|
||||
return (false, err);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Reset a staff member's password via IAM service (admin action).
|
||||
/// VI: Đặt lại mật khẩu nhân viên qua IAM service (hành động admin).
|
||||
/// </summary>
|
||||
public async Task<(bool Ok, string? Error)> ResetStaffPasswordAsync(Guid userId, string newPassword)
|
||||
{
|
||||
var resp = await _http.PostAsJsonAsync("api/bff/staff/reset-password",
|
||||
new { userId, newPassword }, _writeOptions);
|
||||
if (resp.IsSuccessStatusCode) return (true, null);
|
||||
var err = await TryExtractError(resp);
|
||||
return (false, err);
|
||||
}
|
||||
|
||||
// ═══ STORAGE / DRIVE ═══
|
||||
|
||||
public record StorageFileInfo(Guid Id, string FileName, string? ContentType, long FileSizeBytes, string? AccessLevel, DateTime UploadedAt);
|
||||
|
||||
@@ -124,6 +124,22 @@ public class StaffController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Reset a staff member's password via IAM service (admin action).
|
||||
/// VI: Đặt lại mật khẩu nhân viên qua IAM service (hành động admin).
|
||||
/// </summary>
|
||||
[HttpPost("staff/reset-password")]
|
||||
public async Task<IActionResult> ResetStaffPassword([FromBody] JsonElement body)
|
||||
{
|
||||
var userId = body.TryGetProperty("userId", out var uid) ? uid.GetString() : null;
|
||||
var newPassword = body.TryGetProperty("newPassword", out var np) ? np.GetString() : null;
|
||||
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(newPassword))
|
||||
return BadRequest(new { success = false, message = "userId và newPassword là bắt buộc." });
|
||||
|
||||
var payload = new { userId, newPassword };
|
||||
return await _iam.PostAsJsonAsync($"/api/v1/users/{userId}/reset-password", payload).ProxyAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Update a staff member.
|
||||
/// VI: Cập nhật nhân viên.
|
||||
|
||||
Reference in New Issue
Block a user