Skip to content

Commit aea7e20

Browse files
committed
Refactor error handling: replace 404 page with a reusable ErrorPageComponent and update routing logic
1 parent b8c0fe6 commit aea7e20

File tree

5 files changed

+169
-24
lines changed

5 files changed

+169
-24
lines changed

src/Server.UI/Components/Routes.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
@inject LayoutService LayoutService
33

44
<CascadingAuthenticationState>
5-
<Router AppAssembly="@typeof(Program).Assembly">
5+
<Router AppAssembly="@typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
66
<Found Context="routeData">
77
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
88
<Authorizing>
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
@inject IStringLocalizer<ErrorPageComponent> L
2+
@inject IJSRuntime JSRuntime
3+
4+
<div style="display: flex; justify-content: center; align-items: center; min-height: @MinHeight; padding: 2rem;">
5+
<MudPaper Class="pa-8" Elevation="1" Style="max-width: 500px; text-align: center;">
6+
7+
8+
<MudText Typo="Typo.h3" Class="mb-2" Style="font-weight: 300;">
9+
@ErrorCode
10+
</MudText>
11+
12+
<MudText Typo="Typo.h6" Class="mb-4" Color="Color.Default">
13+
@Title
14+
</MudText>
15+
16+
<MudText Typo="Typo.body1" Class="mb-6" Color="Color.Secondary" Style="line-height: 1.5;">
17+
@Description
18+
</MudText>
19+
20+
<MudStack Row Justify="Justify.Center" Spacing="2">
21+
@if (ShowHomeButton)
22+
{
23+
<MudButton Variant="Variant.Filled"
24+
Color="Color.Primary"
25+
StartIcon="@Icons.Material.Filled.Home"
26+
Href="/">
27+
@L["Return Home"]
28+
</MudButton>
29+
}
30+
31+
@if (ShowBackButton)
32+
{
33+
<MudButton Variant="Variant.Outlined"
34+
Color="Color.Primary"
35+
StartIcon="@Icons.Material.Filled.ArrowBack"
36+
OnClick="GoBack">
37+
@L["Go Back"]
38+
</MudButton>
39+
}
40+
41+
@if (ShowContactButton)
42+
{
43+
<MudButton Variant="Variant.Outlined"
44+
Color="Color.Primary"
45+
StartIcon="@Icons.Material.Filled.ContactSupport"
46+
OnClick="OnContactClick">
47+
@ContactButtonText
48+
</MudButton>
49+
}
50+
</MudStack>
51+
52+
</MudPaper>
53+
</div>
54+
55+
@code {
56+
[Parameter] public string ErrorCode { get; set; } = "404";
57+
[Parameter] public string Title { get; set; } = "";
58+
[Parameter] public string Description { get; set; } = "";
59+
[Parameter] public string MinHeight { get; set; } = "60vh";
60+
[Parameter] public bool ShowHomeButton { get; set; } = true;
61+
[Parameter] public bool ShowBackButton { get; set; } = false;
62+
[Parameter] public bool ShowContactButton { get; set; } = false;
63+
[Parameter] public string ContactButtonText { get; set; } = "";
64+
[Parameter] public EventCallback OnContactClick { get; set; }
65+
[Parameter] public ErrorType Type { get; set; } = ErrorType.NotFound;
66+
67+
public enum ErrorType
68+
{
69+
NotFound,
70+
AccessDenied,
71+
ServerError,
72+
Custom
73+
}
74+
75+
protected override void OnInitialized()
76+
{
77+
// Set defaults based on error type if not explicitly provided
78+
if (string.IsNullOrEmpty(Title))
79+
{
80+
Title = Type switch
81+
{
82+
ErrorType.AccessDenied => L["Access Denied"],
83+
ErrorType.ServerError => L["Server Error"],
84+
_ => L["Page Not Found"]
85+
};
86+
}
87+
88+
if (string.IsNullOrEmpty(Description))
89+
{
90+
Description = Type switch
91+
{
92+
ErrorType.AccessDenied => L["You don't have permission to access this resource. If you believe this is an error, please contact your administrator."],
93+
ErrorType.ServerError => L["An unexpected error occurred. Please try again later."],
94+
_ => L["The page you're looking for doesn't exist or has been moved. Please verify the URL and try again."]
95+
};
96+
}
97+
98+
if (string.IsNullOrEmpty(ContactButtonText))
99+
{
100+
ContactButtonText = Type == ErrorType.AccessDenied ? L["Contact Administrator"] : L["Contact Support"];
101+
}
102+
103+
if (Type == ErrorType.AccessDenied)
104+
{
105+
ErrorCode = "403";
106+
}
107+
}
108+
109+
private string GetErrorIcon()
110+
{
111+
return Type switch
112+
{
113+
ErrorType.AccessDenied => Icons.Material.Filled.NoAccounts,
114+
ErrorType.ServerError => Icons.Material.Filled.Error,
115+
_ => Icons.Material.Filled.SearchOff
116+
};
117+
}
118+
119+
private Color GetErrorColor()
120+
{
121+
return Type switch
122+
{
123+
ErrorType.AccessDenied => Color.Warning,
124+
ErrorType.ServerError => Color.Error,
125+
_ => Color.Error
126+
};
127+
}
128+
129+
private async Task GoBack()
130+
{
131+
await JSRuntime.InvokeVoidAsync("history.back");
132+
}
133+
}

src/Server.UI/DependencyInjection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public static WebApplication ConfigureServer(this WebApplication app, IConfigura
137137
app.UseHsts();
138138
}
139139
app.InitializeCacheFactory();
140-
app.UseStatusCodePagesWithRedirects("/404");
140+
app.UseStatusCodePagesWithReExecute("/not-found",createScopeForStatusCodePages: true);
141141
app.MapHealthChecks("/health");
142142
app.UseAuthentication();
143143
app.UseAuthorization();

src/Server.UI/Pages/404.razor

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/Server.UI/Pages/NotFound.razor

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
@page "/not-found"
2+
3+
@inject NavigationManager Navigation
4+
@inject AuthenticationStateProvider AuthenticationStateProvider
5+
@using CleanArchitecture.Blazor.Server.UI.Components.Shared
6+
7+
<PageTitle>@ApplicationSettings.AppName - Page Not Found - 404</PageTitle>
8+
9+
<ErrorPageComponent Type="@_errorType"
10+
MinHeight="80vh"
11+
ShowBackButton="true" />
12+
13+
@code {
14+
private ErrorPageComponent.ErrorType _errorType = ErrorPageComponent.ErrorType.NotFound;
15+
16+
protected override async Task OnInitializedAsync()
17+
{
18+
var currentPath = Navigation.ToBaseRelativePath(Navigation.Uri);
19+
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
20+
var isAuthenticated = authState.User?.Identity?.IsAuthenticated ?? false;
21+
22+
// Simple check: if user is authenticated and path contains admin-like keywords
23+
if (isAuthenticated && IsLikelyProtectedPath(currentPath))
24+
{
25+
_errorType = ErrorPageComponent.ErrorType.AccessDenied;
26+
}
27+
}
28+
29+
private bool IsLikelyProtectedPath(string path)
30+
{
31+
var protectedPaths = new[] { "admin", "management", "system", "users", "tenants", "roles", "audit" };
32+
return protectedPaths.Any(p => path.Contains(p, StringComparison.OrdinalIgnoreCase));
33+
}
34+
}

0 commit comments

Comments
 (0)