Explorer users

alphons <alphons@heijden.com> 19 Jul 2026, 15:17
4e306cc96f7ddc7cb1ba99ad4f621785e005b2b9
15 files changed
  • src/GitServer/Localization/ar.json
  • src/GitServer/Localization/de.json
  • src/GitServer/Localization/en.json
  • src/GitServer/Localization/es.json
  • src/GitServer/Localization/fr.json
  • src/GitServer/Localization/ja.json
  • src/GitServer/Localization/nl.json
  • src/GitServer/Localization/pt.json
  • src/GitServer/Localization/ru.json
  • src/GitServer/Localization/zh.json
  • src/GitServer/wwwroot/Explore.cshtml
  • src/GitServer/wwwroot/ExploreUsers.cshtml
  • src/GitServer/wwwroot/ExploreUsers.cshtml.cs
  • src/GitServer/wwwroot/Shared/_ExploreNav.cshtml
  • src/GitServer/wwwroot/css/components.css
diff --git a/src/GitServer/Localization/ar.json b/src/GitServer/Localization/ar.json
index f66f3bb..8291c26 100644
--- a/src/GitServer/Localization/ar.json
+++ b/src/GitServer/Localization/ar.json
@@ -13,6 +13,9 @@
"search_placeholder": "بحث...",
"search_button": "بحث",
"explore_search_placeholder": "البحث في المستودعات...",
+ "explore_users_search_placeholder": "البحث عن المستخدمين...",
+ "explore_nav_repos": "المستودعات العامة",
+ "explore_nav_users": "المستخدمون",
"explore_results_for": "نتائج لـ",
"explore_no_results": "لم يتم العثور على نتائج.",
"pagination_prev": "السابق →",
diff --git a/src/GitServer/Localization/de.json b/src/GitServer/Localization/de.json
index 4617be6..a3715f7 100644
--- a/src/GitServer/Localization/de.json
+++ b/src/GitServer/Localization/de.json
@@ -13,6 +13,9 @@
"search_placeholder": "Suchen...",
"search_button": "Suchen",
"explore_search_placeholder": "Repositories suchen...",
+ "explore_users_search_placeholder": "Benutzer suchen...",
+ "explore_nav_repos": "Öffentliche Repositories",
+ "explore_nav_users": "Benutzer",
"explore_results_for": "Ergebnisse für",
"explore_no_results": "Keine Ergebnisse gefunden.",
"pagination_prev": "← Zurück",
diff --git a/src/GitServer/Localization/en.json b/src/GitServer/Localization/en.json
index 8bc6b30..b75c1f9 100644
--- a/src/GitServer/Localization/en.json
+++ b/src/GitServer/Localization/en.json
@@ -14,8 +14,11 @@
"search_placeholder": "Search...",
"search_button": "Search",
"explore_search_placeholder": "Search repositories...",
+ "explore_users_search_placeholder": "Search users...",
"explore_results_for": "results for",
"explore_no_results": "No results found.",
+ "explore_nav_repos": "Public repositories",
+ "explore_nav_users": "Users",
"pagination_prev": "← Previous",
"pagination_next": "Next →",
"commits_newer": "← Newer",
diff --git a/src/GitServer/Localization/es.json b/src/GitServer/Localization/es.json
index a8c6e8f..7f8ba24 100644
--- a/src/GitServer/Localization/es.json
+++ b/src/GitServer/Localization/es.json
@@ -13,6 +13,9 @@
"search_placeholder": "Buscar...",
"search_button": "Buscar",
"explore_search_placeholder": "Buscar repositorios...",
+ "explore_users_search_placeholder": "Buscar usuarios...",
+ "explore_nav_repos": "Repositorios públicos",
+ "explore_nav_users": "Usuarios",
"explore_results_for": "resultados para",
"explore_no_results": "No se encontraron resultados.",
"pagination_prev": "← Anterior",
diff --git a/src/GitServer/Localization/fr.json b/src/GitServer/Localization/fr.json
index 6d951a6..8313def 100644
--- a/src/GitServer/Localization/fr.json
+++ b/src/GitServer/Localization/fr.json
@@ -13,6 +13,9 @@
"search_placeholder": "Rechercher...",
"search_button": "Rechercher",
"explore_search_placeholder": "Rechercher des dépôts...",
+ "explore_users_search_placeholder": "Rechercher des utilisateurs...",
+ "explore_nav_repos": "Dépôts publics",
+ "explore_nav_users": "Utilisateurs",
"explore_results_for": "résultats pour",
"explore_no_results": "Aucun résultat trouvé.",
"pagination_prev": "← Précédent",
diff --git a/src/GitServer/Localization/ja.json b/src/GitServer/Localization/ja.json
index a11b21f..b4b2243 100644
--- a/src/GitServer/Localization/ja.json
+++ b/src/GitServer/Localization/ja.json
@@ -13,6 +13,9 @@
"search_placeholder": "検索...",
"search_button": "検索",
"explore_search_placeholder": "リポジトリを検索...",
+ "explore_users_search_placeholder": "ユーザーを検索...",
+ "explore_nav_repos": "公開リポジトリ",
+ "explore_nav_users": "ユーザー",
"explore_results_for": "件の結果",
"explore_no_results": "結果が見つかりませんでした。",
"pagination_prev": "← 前へ",
diff --git a/src/GitServer/Localization/nl.json b/src/GitServer/Localization/nl.json
index fb5aa7f..2979a03 100644
--- a/src/GitServer/Localization/nl.json
+++ b/src/GitServer/Localization/nl.json
@@ -14,6 +14,9 @@
"search_placeholder": "Zoeken...",
"search_button": "Zoeken",
"explore_search_placeholder": "Zoeken naar repositories...",
+ "explore_users_search_placeholder": "Gebruikers zoeken...",
+ "explore_nav_repos": "Publieke repositories",
+ "explore_nav_users": "Gebruikers",
"explore_results_for": "resultaten voor",
"explore_no_results": "Geen resultaten gevonden.",
"pagination_prev": "← Vorige",
diff --git a/src/GitServer/Localization/pt.json b/src/GitServer/Localization/pt.json
index 4010767..f0d2194 100644
--- a/src/GitServer/Localization/pt.json
+++ b/src/GitServer/Localization/pt.json
@@ -13,6 +13,9 @@
"search_placeholder": "Pesquisar...",
"search_button": "Pesquisar",
"explore_search_placeholder": "Pesquisar repositórios...",
+ "explore_users_search_placeholder": "Pesquisar usuários...",
+ "explore_nav_repos": "Repositórios públicos",
+ "explore_nav_users": "Usuários",
"explore_results_for": "resultados para",
"explore_no_results": "Nenhum resultado encontrado.",
"pagination_prev": "← Anterior",
diff --git a/src/GitServer/Localization/ru.json b/src/GitServer/Localization/ru.json
index bd936b8..8dc270c 100644
--- a/src/GitServer/Localization/ru.json
+++ b/src/GitServer/Localization/ru.json
@@ -13,6 +13,9 @@
"search_placeholder": "Поиск...",
"search_button": "Найти",
"explore_search_placeholder": "Поиск репозиториев...",
+ "explore_users_search_placeholder": "Поиск пользователей...",
+ "explore_nav_repos": "Публичные репозитории",
+ "explore_nav_users": "Пользователи",
"explore_results_for": "результатов для",
"explore_no_results": "Ничего не найдено.",
"pagination_prev": "← Назад",
diff --git a/src/GitServer/Localization/zh.json b/src/GitServer/Localization/zh.json
index 1d143f1..cb16f7b 100644
--- a/src/GitServer/Localization/zh.json
+++ b/src/GitServer/Localization/zh.json
@@ -13,6 +13,9 @@
"search_placeholder": "搜索...",
"search_button": "搜索",
"explore_search_placeholder": "搜索仓库...",
+ "explore_users_search_placeholder": "搜索用户...",
+ "explore_nav_repos": "公开仓库",
+ "explore_nav_users": "用户",
"explore_results_for": "个结果",
"explore_no_results": "未找到结果。",
"pagination_prev": "← 上一页",
diff --git a/src/GitServer/wwwroot/Explore.cshtml b/src/GitServer/wwwroot/Explore.cshtml
index 6b5b749..fcac149 100644
--- a/src/GitServer/wwwroot/Explore.cshtml
+++ b/src/GitServer/wwwroot/Explore.cshtml
@@ -7,6 +7,9 @@
<div class="page-header">
<h1>@L["nav_explore"]</h1>
</div>
+
+ @await Html.PartialAsync("_ExploreNav", "repos")
+
<form method="get" class="search-form search-form-full">
<input name="q" type="search" class="form-control" placeholder="@L["explore_search_placeholder"]"
value="@Model.Query" autofocus />
diff --git a/src/GitServer/wwwroot/ExploreUsers.cshtml b/src/GitServer/wwwroot/ExploreUsers.cshtml
new file mode 100644
index 0000000..4cd6186
--- /dev/null
+++ b/src/GitServer/wwwroot/ExploreUsers.cshtml
@@ -0,0 +1,57 @@
+@page "/explore/users"
+@model GitServer.wwwroot.ExploreUsersModel
+@{
+ ViewData["Title"] = L["explore_nav_users"];
+}
+<div class="container">
+ <div class="page-header">
+ <h1>@L["nav_explore"]</h1>
+ </div>
+
+ @await Html.PartialAsync("_ExploreNav", "users")
+
+ <form method="get" class="search-form search-form-full">
+ <input name="q" type="search" class="form-control" placeholder="@L["explore_users_search_placeholder"]"
+ value="@Model.Query" autofocus />
+ <button type="submit" class="btn btn-primary">@L["search_button"]</button>
+ </form>
+
+ @if (!string.IsNullOrEmpty(Model.Query))
+ {
+ <p class="search-results-info">@Model.Users.Count @L["explore_results_for"] "<strong>@Model.Query</strong>"</p>
+ }
+
+ @if (Model.Users.Any())
+ {
+ <div class="user-list">
+ @foreach (var user in Model.Users)
+ {
+ <a class="user-card" href="/User/@user.UserName">
+ <img src="@user.EffectiveAvatarUrl" alt="@user.UserName" class="avatar-img avatar-md" />
+ <div class="user-card-info">
+ <div class="user-card-name">@user.DisplayName</div>
+ <div class="text-muted user-card-username">@@@user.UserName</div>
+ @if (!string.IsNullOrEmpty(user.Bio))
+ {
+ <p class="user-card-bio">@user.Bio</p>
+ }
+ </div>
+ </a>
+ }
+ </div>
+ <div class="pagination">
+ @if (Model.Page > 0)
+ {
+ <a class="btn btn-secondary" href="/explore/users?q=@Model.Query&page=@(Model.Page - 1)">@L["pagination_prev"]</a>
+ }
+ @if (Model.Users.Count == 20)
+ {
+ <a class="btn btn-secondary" href="/explore/users?q=@Model.Query&page=@(Model.Page + 1)">@L["pagination_next"]</a>
+ }
+ </div>
+ }
+ else
+ {
+ <div class="empty-state">@L["explore_no_results"]</div>
+ }
+</div>
diff --git a/src/GitServer/wwwroot/ExploreUsers.cshtml.cs b/src/GitServer/wwwroot/ExploreUsers.cshtml.cs
new file mode 100644
index 0000000..5a77b51
--- /dev/null
+++ b/src/GitServer/wwwroot/ExploreUsers.cshtml.cs
@@ -0,0 +1,36 @@
+using GitServer.Data;
+using GitServer.Models;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+
+namespace GitServer.wwwroot;
+
+public class ExploreUsersModel(AppDbContext db) : PageModel
+{
+ public string Query { get; set; } = "";
+ public new int Page { get; set; }
+ public List<AppUser> Users { get; set; } = [];
+
+ public async Task OnGetAsync(string? q, int page = 0)
+ {
+ Query = q ?? "";
+ Page = page;
+
+ var query = db.Users.Where(u => u.EmailConfirmed);
+
+ if (!string.IsNullOrWhiteSpace(Query))
+ {
+ var lower = Query.Trim().ToLower();
+ query = query.Where(u =>
+ u.UserName!.ToLower().Contains(lower) ||
+ u.DisplayName.ToLower().Contains(lower) ||
+ (u.Bio != null && u.Bio.ToLower().Contains(lower)));
+ }
+
+ Users = await query
+ .OrderBy(u => u.UserName)
+ .Skip(page * 20)
+ .Take(20)
+ .ToListAsync();
+ }
+}
diff --git a/src/GitServer/wwwroot/Shared/_ExploreNav.cshtml b/src/GitServer/wwwroot/Shared/_ExploreNav.cshtml
new file mode 100644
index 0000000..03f5d1a
--- /dev/null
+++ b/src/GitServer/wwwroot/Shared/_ExploreNav.cshtml
@@ -0,0 +1,8 @@
+@model string
+@inject GitServer.Services.LocalizationService L
+<div class="repo-nav">
+ <a class="repo-nav-link @(Model == "repos" ? "active" : "")"
+ href="/explore">@L["explore_nav_repos"]</a>
+ <a class="repo-nav-link @(Model == "users" ? "active" : "")"
+ href="/explore/users">@L["explore_nav_users"]</a>
+</div>
diff --git a/src/GitServer/wwwroot/css/components.css b/src/GitServer/wwwroot/css/components.css
index 61a9902..1a5ff71 100644
--- a/src/GitServer/wwwroot/css/components.css
+++ b/src/GitServer/wwwroot/css/components.css
@@ -277,6 +277,54 @@ textarea.form-control { resize: vertical; }
height: 24px;
}
+.avatar-md {
+ width: 48px;
+ height: 48px;
+ flex-shrink: 0;
+}
+
+/* === User directory (Explore > Users) === */
+.user-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 0.75rem;
+}
+
+.user-card {
+ display: flex;
+ gap: 0.75rem;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 1rem;
+ color: var(--text);
+ transition: border-color 0.15s;
+}
+
+.user-card:hover { border-color: var(--accent); text-decoration: none; color: var(--text); }
+
+.user-card-info { min-width: 0; }
+
+.user-card-name {
+ font-weight: 600;
+ font-size: 0.9375rem;
+}
+
+.user-card-username {
+ font-size: 0.8125rem;
+ margin-bottom: 0.25rem;
+}
+
+.user-card-bio {
+ font-size: 0.8125rem;
+ color: var(--text-muted);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
.badge {
display: inline-block;
padding: 0.15rem 0.55rem;