initial commit

This commit is contained in:
2026-07-21 14:14:44 +07:00
commit fa7dbb970d
1359 changed files with 104110 additions and 0 deletions
@@ -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>