/** * @file Public character page for viewing a player's character sheet. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable max-lines-per-function -- Complex component with many render paths */ /* eslint-disable max-lines -- Story section adds lines beyond the file limit */ /* eslint-disable complexity -- Many conditional render paths for optional fields */ import { STORY_CHAPTERS, type EquipmentBonus, type EquipmentType, type PublicProfileResponse, } from "@elysium/types"; import { type JSX, useEffect, useState } from "react"; import { logError } from "../../utils/logError.js"; interface CharacterPageProperties { readonly discordId: string; } const slotIcons: Record = { armour: "πŸ›‘οΈ", trinket: "πŸ’", weapon: "βš”οΈ", }; /** * Formats an equipment bonus as a human-readable string. * @param bonus - The equipment bonus to format. * @returns The formatted bonus string. */ const formatBonus = (bonus: EquipmentBonus): string => { const parts: Array = []; if (bonus.goldMultiplier !== undefined) { const pct = Math.round((bonus.goldMultiplier - 1) * 100); parts.push(`+${String(pct)}% Gold Income`); } if (bonus.combatMultiplier !== undefined) { const pct = Math.round((bonus.combatMultiplier - 1) * 100); parts.push(`+${String(pct)}% Combat Power`); } if (bonus.clickMultiplier !== undefined) { const pct = Math.round((bonus.clickMultiplier - 1) * 100); parts.push(`+${String(pct)}% Click Power`); } return parts.join(" Β· "); }; /** * Renders the public character page for a given Discord user. * @param props - The character page properties. * @param props.discordId - The Discord ID of the player to display. * @returns The JSX element. */ const CharacterPage = ({ discordId }: CharacterPageProperties): JSX.Element => { const [ profile, setProfile ] = useState(null); const [ error, setError ] = useState(null); const [ copied, setCopied ] = useState(false); useEffect(() => { fetch(`/api/profile/${discordId}`). then(async(response) => { if (!response.ok) { throw new Error("Player not found"); } // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- API response requires cast return await (response.json() as Promise); }). then(setProfile). catch((error_: unknown) => { setError( error_ instanceof Error ? error_.message : "Failed to load character sheet", ); }); }, [ discordId ]); function handleCopy(): void { void navigator.clipboard.writeText(window.location.href). then(() => { setCopied(true); setTimeout(() => { setCopied(false); }, 2000); }). catch((error_: unknown) => { logError("clipboard_copy", error_); }); } if (error !== null) { return (

{"⚠️ "} {error}

{"← Play Elysium"}
); } if (profile === null) { return (
{"Loading character sheet…"}
); } const discordIndex = Number.parseInt(discordId, 10) % 5; const avatarUrl = profile.avatar === null ? `https://cdn.discordapp.com/embed/avatars/${String(discordIndex)}.png` : `https://cdn.discordapp.com/avatars/${discordId}/${profile.avatar}.png?size=128`; const subtitleParts = [ profile.characterRace, profile.characterClass, ].filter((part) => { return part !== ""; }); const subtitle = subtitleParts.join(" Β· "); const activeTitleEntry = profile.activeTitle === "" ? undefined : profile.unlockedTitles.find((title) => { return title.id === profile.activeTitle; }); const activeTitleName = activeTitleEntry === undefined ? null : activeTitleEntry.name; const hasBadge = profile.apotheosisCount > 0 || profile.transcendenceCount > 0 || profile.prestigeCount > 0; const displayName = profile.characterName === "" ? profile.username : profile.characterName; return (
{`${displayName}'s

{displayName}

{activeTitleName === null ? null :

{activeTitleName}

} {profile.pronouns === "" ? null :

{profile.pronouns}

} {subtitle === "" ? null :

{subtitle}

} {hasBadge ?
{profile.apotheosisCount > 0 && {"✨ Apotheosis "} {profile.apotheosisCount} } {profile.transcendenceCount > 0 && {"🌌 Transcendence "} {profile.transcendenceCount} } {profile.prestigeCount > 0 && {"⭐ Prestige "} {profile.prestigeCount} }
: null}
{profile.bio === "" ? null :

{"βš”οΈ About"}

{profile.bio}

} {profile.guildName === "" ? null :

{"🏰 Guild"}

{profile.guildName}

{profile.guildDescription === "" ? null :

{profile.guildDescription}

}
} {profile.equippedItems.length > 0 &&

{"πŸ—‘οΈ Equipment"}

{profile.equippedItems.map((item) => { return (
{slotIcons[item.type]} {item.name} {item.rarity}

{formatBonus(item.bonus)}

); })}
} {profile.completedChapters.length === 0 ? null :

{"πŸ“– Story"}

{profile.completedChapters.map((completion) => { const chapter = STORY_CHAPTERS.find((candidate) => { return candidate.id === completion.chapterId; }); if (chapter === undefined) { return null; } const choice = chapter.choices.find((candidate) => { return candidate.id === completion.choiceId; }); if (choice === undefined) { return null; } return (
{chapter.title} {choice.label}

{choice.description}

); })}
}

{"Played by "} {"@"} {profile.username}

{"πŸ“Š View Stats"} {"βš”οΈ Play Elysium"}
); }; export { CharacterPage };