generated from nhcarrigan/template
feat: poll server for exploration claimability before showing collect button
Resolves #127
This commit is contained in:
@@ -17,6 +17,7 @@ import type {
|
||||
BuyPrestigeUpgradeResponse,
|
||||
CraftRecipeRequest,
|
||||
CraftRecipeResponse,
|
||||
ExploreClaimableResponse,
|
||||
ExploreCollectRequest,
|
||||
ExploreCollectResponse,
|
||||
ExploreStartRequest,
|
||||
@@ -244,6 +245,19 @@ const collectExploration = async(
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether a given exploration area is ready to claim on the server.
|
||||
* @param areaId - The area ID to check.
|
||||
* @returns Whether the exploration is claimable.
|
||||
*/
|
||||
const checkExplorationClaimable = async(
|
||||
areaId: string,
|
||||
): Promise<ExploreClaimableResponse> => {
|
||||
return await fetchJson<ExploreClaimableResponse>(
|
||||
`/explore/claimable?areaId=${encodeURIComponent(areaId)}`,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Crafts a recipe on the server.
|
||||
* @param body - The craft recipe request payload.
|
||||
@@ -316,6 +330,7 @@ export {
|
||||
buyEchoUpgrade,
|
||||
buyPrestigeUpgrade,
|
||||
challengeBoss,
|
||||
checkExplorationClaimable,
|
||||
collectExploration,
|
||||
craftRecipe,
|
||||
debugHardReset,
|
||||
|
||||
@@ -7,12 +7,17 @@
|
||||
/* eslint-disable max-lines-per-function -- Complex component with many render paths */
|
||||
/* eslint-disable complexity -- Complex component with many conditional render paths */
|
||||
/* eslint-disable max-lines -- Exploration panel requires many render paths and result display */
|
||||
import { type JSX, useState } from "react";
|
||||
/* eslint-disable max-statements -- Component function requires many state declarations and handlers */
|
||||
import { type JSX, useEffect, useRef, useState } from "react";
|
||||
import { checkExplorationClaimable } from "../../api/client.js";
|
||||
import { useGame } from "../../context/gameContext.js";
|
||||
import { EXPLORATION_AREAS } from "../../data/explorations.js";
|
||||
import { cdnImage } from "../../utils/cdn.js";
|
||||
import { ZoneSelector } from "./zoneSelector.js";
|
||||
import type { ExploreCollectResponse } from "@elysium/types";
|
||||
import type {
|
||||
ExploreClaimableResponse,
|
||||
ExploreCollectResponse,
|
||||
} from "@elysium/types";
|
||||
|
||||
/**
|
||||
* Formats a duration in seconds to a human-readable string.
|
||||
@@ -83,6 +88,61 @@ const ExplorationPanel = (): JSX.Element => {
|
||||
});
|
||||
const [ pendingAreaId, setPendingAreaId ] = useState<string | null>(null);
|
||||
const [ lastResult, setLastResult ] = useState<CollectResult | null>(null);
|
||||
const [ claimableAreaIds, setClaimableAreaIds ]
|
||||
= useState<ReadonlySet<string>>(new Set());
|
||||
|
||||
const stateReference = useRef(state);
|
||||
stateReference.current = state;
|
||||
|
||||
const claimableReference = useRef(claimableAreaIds);
|
||||
claimableReference.current = claimableAreaIds;
|
||||
|
||||
useEffect(() => {
|
||||
const pollClaimable = async(): Promise<void> => {
|
||||
const currentState = stateReference.current;
|
||||
if (currentState === null) {
|
||||
return;
|
||||
}
|
||||
const inProgressArea = currentState.exploration?.areas.find((a) => {
|
||||
return a.status === "in_progress";
|
||||
});
|
||||
if (inProgressArea === undefined) {
|
||||
return;
|
||||
}
|
||||
if (claimableReference.current.has(inProgressArea.id)) {
|
||||
return;
|
||||
}
|
||||
const areaData = EXPLORATION_AREAS.find((a) => {
|
||||
return a.id === inProgressArea.id;
|
||||
});
|
||||
if (areaData === undefined) {
|
||||
return;
|
||||
}
|
||||
const remaining = timeRemaining(
|
||||
inProgressArea.endsAt,
|
||||
inProgressArea.startedAt ?? 0,
|
||||
areaData.durationSeconds,
|
||||
);
|
||||
if (remaining > 0) {
|
||||
return;
|
||||
}
|
||||
const result: ExploreClaimableResponse
|
||||
= await checkExplorationClaimable(inProgressArea.id);
|
||||
if (result.claimable) {
|
||||
setClaimableAreaIds((previous) => {
|
||||
return new Set([ ...previous, inProgressArea.id ]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
void pollClaimable();
|
||||
}, 1000);
|
||||
|
||||
return (): void => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (state === null) {
|
||||
return (
|
||||
@@ -134,6 +194,11 @@ const ExplorationPanel = (): JSX.Element => {
|
||||
try {
|
||||
const result = await collectExploration(areaId);
|
||||
setLastResult({ areaId: areaId, response: result });
|
||||
setClaimableAreaIds((previous) => {
|
||||
const next = new Set(previous);
|
||||
next.delete(areaId);
|
||||
return next;
|
||||
});
|
||||
} finally {
|
||||
setPendingAreaId(null);
|
||||
}
|
||||
@@ -269,7 +334,7 @@ const ExplorationPanel = (): JSX.Element => {
|
||||
const endsAt = areaState?.endsAt;
|
||||
const isReady
|
||||
= status === "in_progress"
|
||||
&& timeRemaining(endsAt, startedAt, area.durationSeconds) <= 0;
|
||||
&& claimableAreaIds.has(area.id);
|
||||
const isPending = pendingAreaId === area.id;
|
||||
|
||||
function handleStartClick(): void {
|
||||
|
||||
Reference in New Issue
Block a user