Account about page

This commit is contained in:
2025-11-22 01:47:10 +08:00
parent 70a18b07ff
commit a16da37221
26 changed files with 462 additions and 40 deletions

View File

@@ -3,3 +3,10 @@ http://localhost:3001 {
header_up X-SiteName "ciallo"
}
}
http://localhost:3002 {
reverse_proxy /drive/* localhost:5001
reverse_proxy localhost:8007 {
header_up X-SiteName "regular"
}
}

View File

@@ -12,6 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Markdig" Version="0.43.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
<PrivateAssets>all</PrivateAssets>

View File

@@ -0,0 +1,243 @@
@page
@using NodaTime.Serialization.Protobuf
@model AboutModel
@{
Layout = "_LayoutContained";
}
@if (Model.UserAccount != null)
{
@if (!string.IsNullOrEmpty(Model.UserBackgroundUrl))
{
<img src="@Model.UserBackgroundUrl" class="w-full max-h-48 object-cover mb-8" alt="Background Image"/>
}
<div class="container mx-auto px-8 pb-8">
<div class="flex items-center gap-6 mb-8">
@if (!string.IsNullOrEmpty(Model.UserPictureUrl))
{
<img src="@Model.UserPictureUrl" class="w-20 h-20 rounded-full object-cover" alt="Avatar"/>
}
else
{
<div class="w-20 h-20 rounded-full bg-gray-300 flex items-center justify-center">
<span class="text-2xl">@(Model.UserAccount.Name != null ? Model.UserAccount.Name[0] : '?')</span>
</div>
}
<div>
<div class="text-2xl font-bold">
@(Model.UserAccount.Nick ?? Model.UserAccount.Name)
</div>
<div class="text-body-2 text-base-content/60">@@@Model.UserAccount.Name</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="flex flex-col gap-4">
<div class="card bg-base-100 border">
<div class="card-body">
<h2 class="card-title"><span class="mdi mdi-home"></span> Info</h2>
<div class="space-y-2">
@if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.TimeZone))
{
<div class="flex justify-between">
<span class="flex gap-2">
<span class="mdi mdi-map-clock"></span>
Time Zone
</span>
<span class="text-right">
@Model.GetCurrentTimeInTimeZone(Model.UserAccount.Profile.TimeZone)
<span class="text-base-content/50">·</span>
@Model.GetOffsetUtcString(Model.UserAccount.Profile.TimeZone)
<span class="text-base-content/50">·</span>
@Model.UserAccount.Profile.TimeZone
</span>
</div>
}
@if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.Location))
{
<div class="flex justify-between">
<span class="flex gap-2">
<span class="mdi mdi-map-marker"></span> Location
</span>
<span>@Model.UserAccount.Profile.Location</span>
</div>
}
@if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.FirstName) || !string.IsNullOrEmpty(Model.UserAccount.Profile?.LastName))
{
<div class="flex justify-between">
<span class="flex gap-2">
<span class="mdi mdi-card-bulleted-outline"></span> Name
</span>
<span>
@string.Join(" ", new[]
{
Model.UserAccount.Profile.FirstName,
Model.UserAccount.Profile.MiddleName,
Model.UserAccount.Profile.LastName
}.Where(s => !string.IsNullOrEmpty(s)))
</span>
</div>
}
@if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.Gender))
{
<div class="flex justify-between">
<span class="flex gap-2">
<span class="mdi mdi-human-non-binary"></span> Gender
</span>
<span>
@Model.UserAccount.Profile.Gender
@if (!string.IsNullOrEmpty(Model.UserAccount.Profile?.Pronouns))
{
<span class="text-base-content/50">·</span>
@Model.UserAccount.Profile.Pronouns
}
</span>
</div>
}
<div class="flex justify-between">
<span class="flex gap-2">
<span class="mdi mdi-history"></span> Joined at
</span>
<span>
@Model.UserAccount.CreatedAt?.ToDateTimeOffset().ToString("MMMM d, yyyy")
</span>
</div>
@if (Model.UserAccount.Profile?.Birthday != null)
{
<div class="flex justify-between">
<span class="flex gap-2">
<span class="mdi mdi-cake-variant"></span> Birthday
</span>
<span>
@Model.CalculateAge(Model.UserAccount.Profile.Birthday.ToInstant()) yrs old
<span class="text-base-content/50">·</span>
@Model.UserAccount.Profile.Birthday?.ToDateTimeOffset().ToString("MMMM d")
</span>
</div>
}
</div>
</div>
</div>
@if (Model.UserAccount.PerkSubscription != null)
{
<div class="card bg-base-100 border">
<div class="card-body">
<div class="flex justify-between items-center">
<div class="flex flex-col">
<div class="text-xl font-bold">
@Model.GetPerkInfo(Model.UserAccount.PerkSubscription.Identifier).Name Tier
</div>
<div class="text-sm text-base-content/70">Stellar Program Member</div>
</div>
<div class="text-4xl"
style="color: @Model.GetPerkInfo(Model.UserAccount.PerkSubscription.Identifier).Color">
</div>
</div>
</div>
</div>
}
<div class="card bg-base-100 border">
<div class="card-body">
<div class="flex justify-between mb-2">
<div>Level @Model.UserAccount.Profile?.Level</div>
<div>@Model.UserAccount.Profile?.Experience XP</div>
</div>
<progress class="progress progress-success"
value="@(Model.UserAccount.Profile?.LevelingProgress ?? 0)" max="1"></progress>
</div>
</div>
</div>
@if (Model.UserAccount.Profile?.Links.Count > 0)
{
<div>
<div class="card bg-base-100 border">
<div class="card-body">
<h2 class="card-title">
<span class="mdi mdi-link-variant"></span> Links
</h2>
<ul class="list pt-2">
@foreach (var link in Model.UserAccount.Profile.Links)
{
<li class="list-row p-0">
<div class="list-col-grow">
<div>@(link.Name)</div>
<div class="text-xs font-semibold opacity-60">@(link.Url)</div>
</div>
<a class="btn btn-square btn-ghost" href="@link.Url" target="_blank">
<span class="mdi mdi-launch"></span>
</a>
</li>
}
</ul>
</div>
</div>
</div>
}
@if (Model.UserAccount.Contacts.Count > 0)
{
<div>
<div class="card bg-base-100 border">
<div class="card-body">
<h2 class="card-title">
<span class="mdi mdi-link-variant"></span> Links
</h2>
<ul class="list pt-2">
@foreach (var contact in Model.UserAccount.Contacts)
{
<li class="list-row p-0" x-data="{ content: '@contact.Content' }">
<div class="list-col-grow">
<div>@(contact.Content)</div>
<div class="text-xs font-semibold opacity-60">@(contact.Type.ToString())</div>
</div>
<button class="btn btn-square btn-ghost" x-clipboard="content">
<span class="mdi mdi-content-copy"></span>
</button>
</li>
}
</ul>
</div>
</div>
</div>
}
<div>
@if (!string.IsNullOrEmpty(Model.HtmlBio))
{
<div class="card bg-base-100 border">
<div class="card-body">
<h2 class="card-title">
<span class="mdi mdi-information"></span> Bio
</h2>
<div class="prose prose-sm max-w-none">
@Html.Raw(Model.HtmlBio)
</div>
</div>
</div>
}
</div>
</div>
</div>
}
else
{
<div class="hero min-h-[50vh]">
<div class="hero-content text-center">
<div>
<div class="text-6xl mb-4">
<span class="mdi mdi-account"></span>
</div>
<h1 class="text-3xl font-bold">User not found</h1>
<p class="py-6">The user profile you're trying to access is not found.</p>
</div>
</div>
</div>
}

View File

@@ -0,0 +1,89 @@
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Registry;
using DysonNetwork.Zone.Publication;
using Markdig;
using Microsoft.AspNetCore.Mvc.RazorPages;
using NodaTime;
namespace DysonNetwork.Zone.Pages;
public class AboutModel(RemoteAccountService ras) : PageModel
{
public SnPublicationSite? Site { get; set; }
public Account? UserAccount { get; set; }
public string? HtmlBio { get; set; }
public string? UserPictureUrl => UserAccount?.Profile?.Picture?.Id != null
? $"/drive/files/{UserAccount.Profile.Picture.Id}"
: null;
public string? UserBackgroundUrl => UserAccount?.Profile?.Background?.Id != null
? $"/drive/files/{UserAccount.Profile.Background.Id}?original=true"
: null;
public async Task OnGetAsync()
{
Site = HttpContext.Items[PublicationSiteMiddleware.SiteContextKey] as SnPublicationSite;
if (Site != null)
{
UserAccount = await ras.GetAccount(Site.AccountId);
if (UserAccount?.Profile?.Bio != null)
{
var pipeline = new MarkdownPipelineBuilder().Build();
HtmlBio = Markdown.ToHtml(UserAccount.Profile.Bio, pipeline);
}
}
}
public int CalculateAge(Instant birthday)
{
var birthDate = birthday.ToDateTimeOffset();
var today = DateTimeOffset.Now;
var age = today.Year - birthDate.Year;
if (birthDate > today.AddYears(-age)) age--;
return age;
}
public string GetOffsetUtcString(string targetTimeZone)
{
try
{
var tz = TimeZoneInfo.FindSystemTimeZoneById(targetTimeZone);
var offset = tz.GetUtcOffset(DateTimeOffset.Now);
var sign = offset >= TimeSpan.Zero ? "+" : "-";
return $"{sign}{Math.Abs(offset.Hours):D2}:{Math.Abs(offset.Minutes):D2}";
}
catch
{
return "+00:00";
}
}
public string GetCurrentTimeInTimeZone(string targetTimeZone)
{
try
{
var tz = TimeZoneInfo.FindSystemTimeZoneById(targetTimeZone);
var now = TimeZoneInfo.ConvertTime(DateTimeOffset.Now, tz);
return now.ToString("t", System.Globalization.CultureInfo.InvariantCulture);
}
catch
{
return DateTime.Now.ToString("t", System.Globalization.CultureInfo.InvariantCulture);
}
}
public (string Name, string Color) GetPerkInfo(string identifier)
{
return identifier switch
{
"solian.stellar.primary" => ("Stellar", "#2196f3"),
"solian.stellar.nova" => ("Nova", "#39c5bb"),
"solian.stellar.supernova" => ("Supernova", "#ffc109"),
_ => ("Unknown", "#2196f3")
};
}
}

View File

@@ -1,10 +1,12 @@
@page
@model IndexModel
@{
Layout = "_LayoutContained";
ViewData["Title"] = "Solar Network Pages";
}
<div class="h-screen flex justify-center items-center">
<div class="h-full flex justify-center items-center">
<div class="text-center max-w-96">
<img src="~/favicon.png" width="80" height="80" alt="Logo" class="mb-1 mx-auto"/>
<h1 class="text-2xl">Hello World 👋</h1>

View File

@@ -6,6 +6,8 @@
<title>@ViewData["Title"]</title>
<script type="importmap"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@@ryangjchandler/alpine-clipboard@2.x.x/dist/alpine-clipboard.js" defer></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@@mdi/font@7.4.47/css/materialdesignicons.min.css" />
<link rel="stylesheet" href="~/css/site.dist.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/DysonNetwork.Zone.styles.css" asp-append-version="true"/>
<link rel="icon" type="image/png" href="~/favicon.png" />

View File

@@ -0,0 +1,37 @@
@using DysonNetwork.Zone.Publication
@using DysonNetwork.Shared.Models
@{
Layout = "_Layout";
var site = Context.Items[PublicationSiteMiddleware.SiteContextKey] as SnPublicationSite;
var siteDisplayName = site?.Name ?? "Solar Network";
}
<div class="navbar backdrop-blur-md bg-white/1 shadow-xl px-5">
<div class="flex-1">
<a class="btn btn-ghost text-xl" asp-page="/Index">@siteDisplayName</a>
</div>
<div class="flex-none">
<ul class="menu menu-horizontal px-1">
<li><a>Posts</a></li>
<li><a asp-page="/About">About</a></li>
</ul>
</div>
</div>
<main class="content-main">
@RenderBody()
</main>
<style>
.navbar {
height: 64px;
position: fixed;
top: 0;
left: 0;
right: 0;
}
.content-main {
height: calc(100vh - 64px);
margin-top: 64px;
}
</style>

View File

@@ -26,6 +26,8 @@ builder.Services.AddRingService();
builder.Services.AddAccountService();
builder.Services.AddSphereService();
builder.Services.Configure<RouteOptions>(options => { options.LowercaseUrls = true; });
builder.AddSwaggerManifest(
"DysonNetwork.Zone",
"The zone service in the Solar Network."