initial commit
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
@page "/account/tenant-selection"
|
||||
@layout AuthenticationLayout
|
||||
@using Indotalent.Shared.Consts
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using Microsoft.JSInterop
|
||||
@inject NavigationManager Navigation
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<PageTitle>Select Tenant - @GlobalConsts.AppInitial</PageTitle>
|
||||
|
||||
<div class="page-header">
|
||||
<h2>Select Tenant</h2>
|
||||
<p>Please select a tenant to continue</p>
|
||||
</div>
|
||||
|
||||
<div class="w-100">
|
||||
@if (isLoading)
|
||||
{
|
||||
<div style="display: flex; justify-content: center; padding: 3rem 0;">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
|
||||
</div>
|
||||
}
|
||||
else if (!myTenants.Any())
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Variant="Variant.Outlined" Class="my-4" Style="border-radius: 8px;">
|
||||
You don't have access to any tenant. Please contact your Administrator.
|
||||
</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="max-height: 320px; overflow-y: auto; padding-right: 4px;">
|
||||
<MudList T="TenantDto" Clickable="true" SelectionMode="SelectionMode.SingleSelection" Class="pa-0">
|
||||
@foreach (var item in myTenants)
|
||||
{
|
||||
<MudListItem OnClick="() => HandleSelectTenant(item.TenantId)"
|
||||
Icon="@Icons.Material.Filled.Business"
|
||||
IconColor="Color.Primary"
|
||||
Class="py-3 my-2"
|
||||
Style="border: 1px solid #e2e8f0; border-radius: 8px; background-color: #f8fafc; transition: all 0.2s ease-in-out;">
|
||||
<ChildContent>
|
||||
<div class="d-flex flex-column">
|
||||
<MudText Typo="Typo.subtitle1" Style="font-weight: 700; color: #0f172a;">
|
||||
@item.TenantName
|
||||
</MudText>
|
||||
@if (!string.IsNullOrEmpty(item.Description))
|
||||
{
|
||||
<MudText Typo="Typo.caption" Style="color: #64748b;">
|
||||
@item.Description
|
||||
</MudText>
|
||||
}
|
||||
</div>
|
||||
</ChildContent>
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 1.5rem;">
|
||||
<span style="font-size: 0.875rem; color: #64748b;">
|
||||
Want to sign in with another account?
|
||||
<a class="auth-link" href="/account/logout">Sign Out</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool isLoading = true;
|
||||
private List<TenantDto> myTenants = new();
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
myTenants = await JSRuntime.InvokeAsync<List<TenantDto>>("apiAccountFetchMyTenants");
|
||||
isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleSelectTenant(string tenantId)
|
||||
{
|
||||
var result = await JSRuntime.InvokeAsync<ApiResult>("apiAccountConfirmTenant", tenantId);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
Snackbar.Add("Tenant workspace loaded successfully.", Severity.Success);
|
||||
Navigation.NavigateTo("/home", forceLoad: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("Failed to assign tenant workspace.", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public class TenantDto { public string TenantId { get; set; } = ""; public string TenantName { get; set; } = ""; public string? Description { get; set; } }
|
||||
public class ApiResult { public bool Success { get; set; } }
|
||||
}
|
||||
|
||||
<script>
|
||||
window.apiAccountFetchMyTenants = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/account/my-tenants', {
|
||||
method: 'GET',
|
||||
credentials: 'include'
|
||||
});
|
||||
if (response.status === 200) {
|
||||
return await response.json();
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
window.apiAccountConfirmTenant = async (tenantId) => {
|
||||
try {
|
||||
const response = await fetch('/api/account/confirm-tenant', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ tenantId }),
|
||||
credentials: 'include'
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return { success: false };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,67 @@
|
||||
using Indotalent.Data.Entities;
|
||||
using Indotalent.Infrastructure.Database;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Indotalent.Features.Account.TenantSelection.Cqrs;
|
||||
|
||||
public record GetTenantsQuery(string UserId) : IRequest<List<TenantLookupDto>>;
|
||||
|
||||
public class TenantLookupDto
|
||||
{
|
||||
public string TenantId { get; set; } = string.Empty;
|
||||
public string TenantName { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
|
||||
public class GetTenantsHandler : IRequestHandler<GetTenantsQuery, List<TenantLookupDto>>
|
||||
{
|
||||
private readonly AppDbContext _context;
|
||||
|
||||
public GetTenantsHandler(AppDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<List<TenantLookupDto>> Handle(GetTenantsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.Set<TenantUser>()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(tu => tu.UserId == request.UserId && tu.IsActive)
|
||||
.Select(tu => new TenantLookupDto
|
||||
{
|
||||
TenantId = tu.TenantId ?? string.Empty,
|
||||
TenantName = tu.Tenant != null ? (tu.Tenant.Name ?? "Unknown") : "Unknown",
|
||||
Description = tu.Tenant != null ? (tu.Tenant.Description ?? "") : ""
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public record ConfirmTenantRequest(string TenantId);
|
||||
public record ConfirmTenantResponse(bool Success, string Message);
|
||||
public record ConfirmTenantCommand(string UserId, ConfirmTenantRequest Data) : IRequest<ConfirmTenantResponse>;
|
||||
|
||||
public class ConfirmTenantHandler : IRequestHandler<ConfirmTenantCommand, ConfirmTenantResponse>
|
||||
{
|
||||
private readonly AppDbContext _context;
|
||||
|
||||
public ConfirmTenantHandler(AppDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<ConfirmTenantResponse> Handle(ConfirmTenantCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var hasAccess = await _context.Set<TenantUser>()
|
||||
.IgnoreQueryFilters()
|
||||
.AnyAsync(tu => tu.UserId == request.UserId && tu.TenantId == request.Data.TenantId && tu.IsActive, cancellationToken);
|
||||
|
||||
if (!hasAccess)
|
||||
{
|
||||
return new ConfirmTenantResponse(false, "Access denied to the selected organization.");
|
||||
}
|
||||
|
||||
return new ConfirmTenantResponse(true, "Tenant authorized.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user