/** * @file Resource bar component displaying player resources and profile actions. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable max-lines-per-function -- Large header with many resource and action elements */ /* eslint-disable complexity -- Many conditional resource and badge render paths */ import { useGame } from "../../context/gameContext.js"; import { RESOURCE_CAP } from "../../engine/tick.js"; import type { Resource } from "@elysium/types"; import type { JSX } from "react"; interface ResourceBarProperties { readonly resources: Resource; readonly runestones: number; readonly prestigeCount: number; readonly transcendenceCount: number; readonly apotheosisCount: number; readonly profileUrl: string; readonly onEditProfile: ()=> void; readonly lastSavedAt: number | null; readonly isSyncing: boolean; readonly onForceSync: ()=> Promise; } /** * Formats a timestamp as a human-readable relative time string. * @param timestamp - The Unix timestamp in milliseconds. * @returns The relative time string. */ const formatRelativeTime = (timestamp: number): string => { const seconds = Math.floor((Date.now() - timestamp) / 1000); if (seconds < 10) { return "just now"; } if (seconds < 60) { return `${String(seconds)}s ago`; } const minutes = Math.floor(seconds / 60); if (minutes < 60) { return `${String(minutes)}m ago`; } const hours = Math.floor(minutes / 60); return `${String(hours)}h ago`; }; const resourceFullTooltip = [ "This resource is full!", " Consider spending some or prestiging to keep earning.", ].join(""); /** * Renders the resource bar with player resources and profile actions. * @param props - The resource bar properties. * @param props.resources - The current player resources. * @param props.runestones - The current runestone count. * @param props.prestigeCount - The number of prestiges completed. * @param props.transcendenceCount - The number of transcendences completed. * @param props.apotheosisCount - The number of apotheoses completed. * @param props.profileUrl - The URL of the player's public profile. * @param props.onEditProfile - Callback to open the edit profile modal. * @param props.lastSavedAt - Timestamp of the last cloud save. * @param props.isSyncing - Whether a sync is currently in progress. * @param props.onForceSync - Callback to trigger a forced cloud sync. * @returns The JSX element. */ const ResourceBar = ({ resources, runestones, prestigeCount, transcendenceCount, apotheosisCount, profileUrl, onEditProfile, lastSavedAt, isSyncing, onForceSync, }: ResourceBarProperties): JSX.Element => { const { formatNumber, syncError } = useGame(); const { gold, essence, crystals } = resources; const resourceValues = [ gold, essence, crystals ]; const anyFull = resourceValues.some((v) => { return v >= RESOURCE_CAP; }); const goldFull = gold >= RESOURCE_CAP; const essenceFull = essence >= RESOURCE_CAP; const crystalsFull = crystals >= RESOURCE_CAP; function handleForceSync(): void { void onForceSync(); } return ( <>
{"🪙"} {formatNumber(gold)} {"Gold"} {goldFull ? {"FULL"} : null}
{"✨"} {formatNumber(essence)} {"Essence"} {essenceFull ? {"FULL"} : null}
{"💎"} {formatNumber(crystals)} {"Crystals"} {crystalsFull ? {"FULL"} : null}
{"🔮"} {formatNumber(runestones)} {"Runestones"}
{apotheosisCount > 0 &&
{"✨ Apotheosis "} {apotheosisCount}
} {transcendenceCount > 0 &&
{"🌌 Transcendence "} {transcendenceCount}
} {prestigeCount > 0 &&
{"⭐ Prestige "} {prestigeCount}
}
{"💜"} {"Donate"} {"💬"} {"Discord"} {"🆘"} {"Support"} {syncError === null ? null : {"❌ Save failed"} } {syncError === null && lastSavedAt !== null ? {"☁️ "} {formatRelativeTime(lastSavedAt)} : null} {"👤"} {"Profile"}
{anyFull ?
{"⚠️ One or more resources are full! Consider spending some or" + " prestiging to keep earning."}
: null} ); }; export { ResourceBar };