generated from nhcarrigan/template
feat: add about page with versions, changelog, and how-to-play
- New GET /about API endpoint caches Gitea releases for 5 minutes - AboutPanel displays client version (via Vite define), API version, collapsible changelog, and How to Play guide - GiteaRelease and AboutResponse types added to shared package
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { getAbout } from "../../api/client.js";
|
||||
import type { AboutResponse } from "@elysium/types";
|
||||
|
||||
const HOW_TO_PLAY = [
|
||||
{
|
||||
title: "⚔️ Adventurers",
|
||||
body: "Hire adventurers to earn gold and essence automatically. Each tier is more powerful than the last. Adventurers also contribute combat power for boss fights — the more you recruit, the stronger your party becomes.",
|
||||
},
|
||||
{
|
||||
title: "👆 Clicking",
|
||||
body: "Click the guild hall to earn gold manually. Upgrades and equipment can dramatically increase your gold per click. Clicking is especially powerful in the early game and when saving up for big purchases.",
|
||||
},
|
||||
{
|
||||
title: "🔧 Upgrades",
|
||||
body: "Purchase upgrades to multiply the gold and essence output of specific adventurer tiers, or boost your whole guild. Upgrades are permanent for the current run and compound with each other.",
|
||||
},
|
||||
{
|
||||
title: "📜 Quests",
|
||||
body: "Send your guild on quests that complete over time and reward gold, essence, crystals, equipment, and upgrades. Multiple quests can run simultaneously. Completing quests also unlocks new zones.",
|
||||
},
|
||||
{
|
||||
title: "👹 Boss Fights",
|
||||
body: "Challenge zone bosses to earn large one-time rewards and unlock new zones. Your party's combat power is based on the number and tier of adventurers you've recruited. Defeated bosses cannot be re-fought, but undefeated bosses regenerate HP over time.",
|
||||
},
|
||||
{
|
||||
title: "🗺️ Zones",
|
||||
body: "New zones unlock when you defeat the final boss AND complete the final quest of the previous zone. Each zone contains new bosses and quests with progressively greater rewards.",
|
||||
},
|
||||
{
|
||||
title: "🗡️ Equipment",
|
||||
body: "Earn equipment from boss drops and quest rewards. Each piece of equipment provides bonuses to gold income, click power, or adventurer output. Rarer equipment provides stronger bonuses.",
|
||||
},
|
||||
{
|
||||
title: "⭐ Prestige",
|
||||
body: "When you've progressed far enough, you can prestige to earn runestones — a permanent currency that persists across all runs. Prestige resets your current run but grants a production multiplier that stacks with every prestige. Name your prestige character to commemorate the run!",
|
||||
},
|
||||
{
|
||||
title: "🔮 Runestones & Prestige Upgrades",
|
||||
body: "Spend runestones in the Prestige Shop on permanent upgrades that carry over across all future runs. These upgrades multiply income, click power, essence, and crystal gain — making each new run more powerful than the last.",
|
||||
},
|
||||
{
|
||||
title: "🏆 Achievements",
|
||||
body: "Earn achievements by hitting milestones — total gold earned, bosses defeated, quests completed, and more. Achievements are purely cosmetic and track your long-term progress across all prestige runs.",
|
||||
},
|
||||
{
|
||||
title: "📅 Daily Challenges",
|
||||
body: "Complete daily challenges for bonus rewards including gold, essence, crystals, and runestones. Challenges reset each day and vary in difficulty. Completing all daily challenges gives an extra bonus reward.",
|
||||
},
|
||||
{
|
||||
title: "☁️ Cloud Saves",
|
||||
body: "Your progress is automatically saved to the cloud every 30 seconds whilst you play. You can also force a manual save at any time using the sync button in the resource bar. Your save is protected by HMAC validation to ensure data integrity.",
|
||||
},
|
||||
];
|
||||
|
||||
const formatDate = (dateStr: string): string =>
|
||||
new Date(dateStr).toLocaleDateString("en-GB", {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
export const AboutPanel = (): React.JSX.Element => {
|
||||
const [about, setAbout] = useState<AboutResponse | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [expandedRelease, setExpandedRelease] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
getAbout()
|
||||
.then(setAbout)
|
||||
.catch((err: unknown) => {
|
||||
setError(err instanceof Error ? err.message : "Failed to load about data.");
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="panel about-panel">
|
||||
<h2>ℹ️ About</h2>
|
||||
|
||||
<div className="about-versions">
|
||||
<div className="about-version-card">
|
||||
<span className="about-version-label">🌐 Client Version</span>
|
||||
<span className="about-version-value">{__WEB_VERSION__}</span>
|
||||
</div>
|
||||
<div className="about-version-card">
|
||||
<span className="about-version-label">⚙️ API Version</span>
|
||||
<span className="about-version-value">{about?.apiVersion ?? "Loading..."}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="stats-section-header">📋 Changelog</h3>
|
||||
{error !== null && <p className="about-error">{error}</p>}
|
||||
{about === null && error === null && <p className="about-loading">Loading changelog...</p>}
|
||||
{about !== null && about.releases.length === 0 && (
|
||||
<p className="about-empty">No releases yet.</p>
|
||||
)}
|
||||
{about !== null && about.releases.length > 0 && (
|
||||
<ul className="about-releases">
|
||||
{about.releases.map((release) => (
|
||||
<li key={release.tag_name} className="about-release">
|
||||
<button
|
||||
className="about-release-header"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setExpandedRelease(
|
||||
expandedRelease === release.tag_name ? null : release.tag_name,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<span className="about-release-tag">{release.name || release.tag_name}</span>
|
||||
<span className="about-release-date">{formatDate(release.published_at)}</span>
|
||||
<span className="about-release-chevron">
|
||||
{expandedRelease === release.tag_name ? "▲" : "▼"}
|
||||
</span>
|
||||
</button>
|
||||
{expandedRelease === release.tag_name && (
|
||||
<pre className="about-release-body">{release.body}</pre>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
<h3 className="stats-section-header">📖 How to Play</h3>
|
||||
<ul className="about-how-to-play">
|
||||
{HOW_TO_PLAY.map((section) => (
|
||||
<li key={section.title} className="about-htp-section">
|
||||
<h4 className="about-htp-title">{section.title}</h4>
|
||||
<p className="about-htp-body">{section.body}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user