💄 Restyled web pages

🧱 Add tailwindcss
This commit is contained in:
2025-05-17 15:40:39 +08:00
parent d59dba9c02
commit a77d00c3b9
19 changed files with 1344 additions and 231 deletions

View File

@ -2,157 +2,109 @@
@model DysonNetwork.Sphere.Pages.Checkpoint.CheckpointPage
@{
Layout = null;
ViewData["Title"] = "Security Checkpoint";
var cfg = ViewData.Model.Configuration;
var provider = cfg.GetSection("Captcha")["Provider"]?.ToLower();
var apiKey = cfg.GetSection("Captcha")["ApiKey"];
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Solar Network Captcha</title>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap"
rel="stylesheet"
/>
<style>
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #2d2d2d;
font-family: "Roboto Mono", monospace;
color: #c9d1d9;
}
.parent {
padding: 20px;
max-width: 480px;
margin: 0 auto;
}
h2 {
font-size: 18px;
font-weight: 300;
color: #ffffff;
margin-bottom: 15px;
}
.footer {
margin-top: 20px;
font-size: 11px;
opacity: 0.6;
}
.footer-product {
font-size: 12px;
font-weight: bold;
margin-bottom: 5px;
opacity: 0.8;
}
.g-recaptcha {
display: inline-block; /* Adjust as needed */
}
</style>
@section Scripts {
@switch (provider)
{
case "recaptcha":
<script src="https://www.recaptcha.net/recaptcha/api.js" async defer></script>
break;
case "cloudflare":
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
async
defer
></script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
break;
case "hcaptcha":
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
break;
}
</head>
<body>
<div class="parent">
<div class="container">
<h1>reCaptcha</h1>
@switch (provider)
{
case "cloudflare":
<div
class="cf-turnstile"
data-sitekey="@apiKey"
data-callback="onSuccess"
></div>
break;
case "recaptcha":
<div
class="g-recaptcha"
data-sitekey="@apiKey"
data-callback="onSuccess"
></div>
break;
case "hcaptcha":
<div
class="h-captcha"
data-sitekey="@apiKey"
data-callback="onSuccess"
></div>
break;
default:
<p style="color: yellow;">Captcha provider not configured correctly.</p>
break;
}
</div>
<div class="footer">
<div class="footer-product">Solar Network Anti-Robot</div>
<a
href="https://solsynth.dev"
style="color: #c9d1d9; text-decoration: none"
>Solsynth LLC</a>
&copy; @DateTime.Now.Year<br/>
Powered by
@switch (provider)
{
case "cloudflare":
<a href="https://www.cloudflare.com/turnstile/" style="color: #c9d1d9"
>Cloudflare Turnstile</a>
break;
case "recaptcha":
<a href="https://www.google.com/recaptcha/" style="color: #c9d1d9"
>Google reCaptcha</a>
break;
default:
<span>Nothing</span>
break;
}
<br/>
Hosted by
<a
href="https://github.com/Solsynth/DysonNetwork"
style="color: #c9d1d9"
>DysonNetwork.Sphere</a>
</div>
</div>
<script>
function getQueryParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
function onSuccess(token) {
window.parent.postMessage("captcha_tk=" + token, "*");
const redirectUri = getQueryParam("redirect_uri");
if (redirectUri) {
window.location.href = `${redirectUri}?captcha_tk=${encodeURIComponent(token)}`;
<script>
function getQueryParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
}
</script>
</body>
</html>
function onSuccess(token) {
window.parent.postMessage("captcha_tk=" + token, "*");
const redirectUri = getQueryParam("redirect_uri");
if (redirectUri) {
window.location.href = `${redirectUri}?captcha_tk=${encodeURIComponent(token)}`;
}
}
</script>
}
<div class="h-full flex items-center justify-center">
<div class="max-w-lg w-full mx-auto p-6">
<div class="text-center">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Security Check</h1>
<p class="mb-6 text-gray-900 dark:text-white">Please complete the contest below to confirm you're not a robot</p>
<div class="flex justify-center mb-8">
@switch (provider)
{
case "cloudflare":
<div class="cf-turnstile"
data-sitekey="@apiKey"
data-callback="onSuccess">
</div>
break;
case "recaptcha":
<div class="g-recaptcha"
data-sitekey="@apiKey"
data-callback="onSuccess">
</div>
break;
case "hcaptcha":
<div class="h-captcha"
data-sitekey="@apiKey"
data-callback="onSuccess">
</div>
break;
default:
<div class="p-4 bg-yellow-100 dark:bg-yellow-900 rounded-lg">
<p class="text-yellow-800 dark:text-yellow-200">
Captcha provider not configured correctly.
</p>
</div>
break;
}
</div>
</div>
<div class="mt-8 text-center text-sm">
<div class="font-semibold text-gray-700 dark:text-gray-300 mb-1">Solar Network Anti-Robot</div>
<div class="text-gray-600 dark:text-gray-400">
Powered by
@switch (provider)
{
case "cloudflare":
<a href="https://www.cloudflare.com/turnstile/"
class="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
Cloudflare Turnstile
</a>
break;
case "recaptcha":
<a href="https://www.google.com/recaptcha/"
class="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
Google reCaptcha
</a>
break;
default:
<span>Nothing</span>
break;
}
<br/>
Hosted by
<a href="https://github.com/Solsynth/DysonNetwork"
class="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
DysonNetwork.Sphere
</a>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,24 @@
@page
@model IndexModel
@{
ViewData["Title"] = "The Solar Network";
}
<div class="container-default h-full text-center flex flex-col justify-center items-center">
<div class="mx-auto max-w-2xl">
<h1 class="text-4xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-6xl">
Solar Network
</h1>
<p class="mt-6 text-lg leading-8 text-gray-600 dark:text-gray-300">
This Solar Network instance is up and running.
</p>
<div class="mt-10 flex items-center justify-center gap-x-6">
<a href="https://sn.solsynth.dev" target="_blank" class="btn-primary">
Get started
</a>
<a href="https://kb.solsynth.dev" target="_blank" class="text-sm font-semibold leading-6 text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300">
Learn more <span aria-hidden="true">→</span>
</a>
</div>
</div>
</div>

View File

@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace DysonNetwork.Sphere.Pages;
public class IndexModel : PageModel
{
public void OnGet()
{
// Add any page initialization logic here
}
}

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@ViewData["Title"]</title>
<link rel="stylesheet" href="~/css/styles.css" asp-append-version="true"/>
</head>
<body class="h-[calc(100dvh-118px)] mt-[64px] bg-white dark:bg-gray-900">
<header class="bg-white dark:bg-gray-800 shadow-sm fixed left-0 right-0 top-0 z-50">
<nav class="container-default">
<div class="flex justify-between h-16 items-center">
<div class="flex">
<a href="/" class="text-xl font-bold text-gray-900 dark:text-white">Solar Network</a>
</div>
</div>
</nav>
</header>
@* The header 64px + The footer 56px = 118px *@
<main class="h-full">
@RenderBody()
</main>
<footer class="bg-white dark:bg-gray-800 fixed bottom-0 left-0 right-0 shadow-[0_-1px_3px_0_rgba(0,0,0,0.1)]">
<div class="container-default" style="padding: 1rem 0;">
<p class="text-center text-gray-500 dark:text-gray-400">
&copy; @DateTime.Now.Year Solsynth LLC. All
rights reserved.
</p>
</div>
</footer>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@ -1,92 +1,76 @@
@page "/spells/{spellWord}"
@using DysonNetwork.Sphere.Account
@model DysonNetwork.Sphere.Pages.Spell.MagicSpellPage
@{
Layout = null;
var spell = ViewData["Spell"] as MagicSpell;
ViewData["Title"] = "Magic Spell";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Solar Network Magic Spell</title>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap"
rel="stylesheet"
/>
<style>
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #2d2d2d;
font-family: "Roboto Mono", monospace;
color: #c9d1d9;
}
<div class="h-full flex items-center justify-center">
<div class="max-w-lg w-full mx-auto p-6">
<div class="text-center">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-4">Magic Spell</h1>
.parent {
padding: 20px;
max-width: 480px;
margin: 0 auto;
}
@if (Model.IsSuccess)
{
<div class="p-4 bg-green-100 dark:bg-green-900 rounded-lg mb-6">
<p class="text-green-800 dark:text-green-200">The spell was applied successfully!</p>
<p class="text-green-800 dark:text-green-200 opacity-80">Now you can close this page.</p>
</div>
}
else if (Model.CurrentSpell == null)
{
<div class="p-4 bg-yellow-100 dark:bg-yellow-900 rounded-lg">
<p class="text-yellow-800 dark:text-yellow-200">The spell was expired or does not exist.</p>
</div>
}
else
{
<div class="px-4 py-12 bg-white dark:bg-gray-800 text-gray-900 dark:text-white shadow-lg rounded-lg mb-6">
<div class="mb-2">
<p>
<span class="font-medium">The spell is for </span>
<span class="font-bold">@System.Text.RegularExpressions.Regex.Replace(Model.CurrentSpell!.Type.ToString(), "([a-z])([A-Z])", "$1 $2")</span>
</p>
<p><span class="font-medium">for @@</span>@Model.CurrentSpell.Account?.Name</p>
</div>
<div class="text-sm opacity-80">
@if (Model.CurrentSpell.ExpiresAt.HasValue)
{
<p>Available until @Model.CurrentSpell.ExpiresAt.Value.ToDateTimeUtc().ToString("g")</p>
}
@if (Model.CurrentSpell.AffectedAt.HasValue)
{
<p>Available after @Model.CurrentSpell.AffectedAt.Value.ToDateTimeUtc().ToString("g")</p>
}
</div>
<p class="text-sm opacity-80">Would you like to apply this spell?</p>
</div>
h2 {
font-size: 18px;
font-weight: 300;
color: #ffffff;
margin-bottom: 15px;
}
<form method="post" class="mt-4">
<input type="hidden" asp-for="CurrentSpell!.Id"/>
<button type="submit"
class="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors
transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-blue-400">
Apply
</button>
</form>
}
</div>
.footer {
margin-top: 20px;
font-size: 11px;
opacity: 0.6;
}
.footer-product {
font-size: 12px;
font-weight: bold;
margin-bottom: 5px;
opacity: 0.8;
}
</style>
</head>
<body>
<div class="parent">
<div class="container">
<h1>Magic Spell</h1>
@if (spell == null)
{
<div>
<p style="color: yellow;">The spell was expired or not exists.</p>
<div class="mt-8 text-center text-sm">
<div class="font-semibold text-gray-700 dark:text-gray-300 mb-1">Solar Network</div>
<div class="text-gray-600 dark:text-gray-400">
<a href="https://solsynth.dev" class="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
Solsynth LLC
</a>
&copy; @DateTime.Now.Year
<br/>
Powered by
<a href="https://github.com/Solsynth/DysonNetwork"
class="hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
DysonNetwork.Sphere
</a>
</div>
}
else
{
<div>
<p style="color: green;">The spell was applied successfully!</p>
</div>
}
</div>
</div>
<div class="footer">
<div class="footer-product">Solar Network</div>
<a
href="https://solsynth.dev"
style="color: #c9d1d9; text-decoration: none"
>Solsynth LLC</a>
&copy; @DateTime.Now.Year<br/>
Powered by
<a
href="https://github.com/Solsynth/DysonNetwork"
style="color: #c9d1d9"
>DysonNetwork.Sphere</a>
</div>
</div>
</body>
</html>
</div>

View File

@ -8,23 +8,42 @@ namespace DysonNetwork.Sphere.Pages.Spell;
public class MagicSpellPage(AppDatabase db, MagicSpellService spells) : PageModel
{
[BindProperty]
public MagicSpell? CurrentSpell { get; set; }
public bool IsSuccess { get; set; }
public async Task<IActionResult> OnGetAsync(string spellWord)
{
spellWord = Uri.UnescapeDataString(spellWord);
var now = SystemClock.Instance.GetCurrentInstant();
var spell = await db.MagicSpells
CurrentSpell = await db.MagicSpells
.Where(e => e.Spell == spellWord)
.Where(e => e.ExpiresAt == null || now < e.ExpiresAt)
.Where(e => e.AffectedAt == null || now >= e.AffectedAt)
.Include(e => e.Account)
.FirstOrDefaultAsync();
ViewData["Spell"] = spell;
if (spell is not null)
{
await spells.ApplyMagicSpell(spell);
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (CurrentSpell?.Id == null)
return Page();
var now = SystemClock.Instance.GetCurrentInstant();
var spell = await db.MagicSpells
.Where(e => e.Id == CurrentSpell.Id)
.Where(e => e.ExpiresAt == null || now < e.ExpiresAt)
.Where(e => e.AffectedAt == null || now >= e.AffectedAt)
.FirstOrDefaultAsync();
if (spell == null)
return Page();
await spells.ApplyMagicSpell(spell);
IsSuccess = true;
return Page();
}
}

View File

@ -0,0 +1,3 @@
@using DysonNetwork.Sphere
@namespace DysonNetwork.Sphere.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}