Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement changing adventurer name #276

Merged
merged 9 commits into from
Oct 8, 2024
Merged
80 changes: 47 additions & 33 deletions indexer/src/adventurers.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,60 @@
import type { Config } from "https://esm.sh/@apibara/indexer";
import type { Block, Starknet } from "https://esm.sh/@apibara/indexer/starknet";
import type { Mongo } from "https://esm.sh/@apibara/indexer/sink/mongo";
import type { Console } from "https://esm.sh/@apibara/indexer/sink/console";
import type { Mongo } from "https://esm.sh/@apibara/indexer/sink/mongo";
import type { Block, Starknet } from "https://esm.sh/@apibara/indexer/starknet";
import { MONGO_CONNECTION_STRING } from "./utils/constants.ts";
import { getLevelFromXp } from "./utils/encode.ts";
import {
ADVENTURER_DIED,
ADVENTURER_UPGRADED,
AMBUSHED_BY_BEAST,
ATTACKED_BEAST,
ATTACKED_BY_BEAST,
DISCOVERED_BEAST,
DISCOVERED_GOLD,
DISCOVERED_HEALTH,
DISCOVERED_LOOT,
DODGED_OBSTACLE,
DROPPED_ITEMS,
EQUIPPED_ITEMS,
FLEE_FAILED,
FLEE_SUCCEEDED,
HIT_BY_OBSTACLE,
ITEMS_LEVELED_UP,
parseAdventurerDied,
parseAdventurerUpgraded,
parseAmbushedByBeast,
parseAttackedByBeast,
parseDiscoveredBeast,
parseDiscoveredGold,
parseDiscoveredHealth,
parseStartGame,
START_GAME,
PURCHASED_ITEMS,
ATTACKED_BY_BEAST,
ADVENTURER_DIED,
parseAdventurerDied,
parseAttackedByBeast,
AMBUSHED_BY_BEAST,
parseAmbushedByBeast,
ATTACKED_BEAST,
SLAYED_BEAST,
parseSlayedBeast,
FLEE_FAILED,
parseDiscoveredLoot,
parseDodgedObstacle,
parseDroppedItems,
parseEquippedItems,
parseFleeFailed,
FLEE_SUCCEEDED,
parseFleeSucceeded,
ITEMS_LEVELED_UP,
parseHitByObstacle,
parseItemsLeveledUp,
EQUIPPED_ITEMS,
parsePurchasedItems,
parseEquippedItems,
DROPPED_ITEMS,
parseDroppedItems,
HIT_BY_OBSTACLE,
parseHitByObstacle,
DODGED_OBSTACLE,
parseDodgedObstacle,
UPGRADES_AVAILABLE,
parseSlayedBeast,
parseStartGame,
parseTransfer,
parseUpdateAdventurerName,
parseUpgradesAvailable,
DISCOVERED_BEAST,
parseDiscoveredBeast,
DISCOVERED_LOOT,
parseDiscoveredLoot,
PURCHASED_ITEMS,
SLAYED_BEAST,
START_GAME,
TRANSFER,
parseTransfer,
UPDATE_ADVENTURER_NAME,
UPGRADES_AVAILABLE,
} from "./utils/events.ts";
import {
insertAdventurer,
updateAdventurer,
updateAdventurerName,
updateAdventurerOwner,
} from "./utils/helpers.ts";
import { MONGO_CONNECTION_STRING } from "./utils/constants.ts";
import { getLevelFromXp } from "./utils/encode.ts";

const GAME = Deno.env.get("GAME");
const START = +(Deno.env.get("START") || 0);
Expand Down Expand Up @@ -83,6 +86,7 @@ const filter = {
{ fromAddress: GAME, keys: [UPGRADES_AVAILABLE] },
{ fromAddress: GAME, keys: [ADVENTURER_UPGRADED] },
{ fromAddress: GAME, keys: [TRANSFER] },
{ fromAddress: GAME, keys: [UPDATE_ADVENTURER_NAME] },
],
};

Expand Down Expand Up @@ -352,6 +356,16 @@ export default function transform({ header, events }: Block) {
];
}
}
case UPDATE_ADVENTURER_NAME: {
console.log("UPDATE_ADVENTURER_NAME", "->", "ADVENTURER UPDATES");
const { value } = parseUpdateAdventurerName(event.data, 0);
return [
updateAdventurerName({
adventurerId: value.adventurerId,
adventurerName: value.name,
}),
];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Include timestamp when updating adventurer name for consistency

Consistently including a timestamp in update operations helps maintain a uniform data structure and aids in tracking changes over time. Consider adding a timestamp field to the updateAdventurerName function call.

Suggested change:

            return [
              updateAdventurerName({
                adventurerId: value.adventurerId,
                adventurerName: value.name,
+               timestamp: new Date().toISOString(),
              }),
            ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case UPDATE_ADVENTURER_NAME: {
console.log("UPDATE_ADVENTURER_NAME", "->", "ADVENTURER UPDATES");
const { value } = parseUpdateAdventurerName(event.data, 0);
return [
updateAdventurerName({
adventurerId: value.adventurerId,
adventurerName: value.name,
}),
];
}
case UPDATE_ADVENTURER_NAME: {
console.log("UPDATE_ADVENTURER_NAME", "->", "ADVENTURER UPDATES");
const { value } = parseUpdateAdventurerName(event.data, 0);
return [
updateAdventurerName({
adventurerId: value.adventurerId,
adventurerName: value.name,
timestamp: new Date().toISOString(),
}),
];
}

default: {
console.warn("Unknown event", event.keys[0]);
return [];
Expand Down
8 changes: 7 additions & 1 deletion indexer/src/utils/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
parseU128,
parseU16,
parseU256,
parseU64,
parseU32,
parseU64,
parseU8,
} from "./parser.ts";

Expand Down Expand Up @@ -53,6 +53,7 @@ export const NEW_COLLECTION_TOTAL = eventKey("NewCollectionTotal");
// Tokens
export const TRANSFER = eventKey("Transfer");
export const CLAIMED_FREE_GAME = eventKey("ClaimedFreeGame");
export const UPDATE_ADVENTURER_NAME = eventKey("UpdateAdventurerName");

export const parseStats = combineParsers({
strength: { index: 0, parser: parseU8 },
Expand Down Expand Up @@ -384,3 +385,8 @@ export const parseNewCollectionTotal = combineParsers({
xp: { index: 1, parser: parseU32 },
gamesPlayed: { index: 2, parser: parseU32 },
});

export const parseUpdateAdventurerName = combineParsers({
adventurerId: { index: 0, parser: parseFelt252 },
name: { index: 1, parser: parseFelt252 },
});
18 changes: 17 additions & 1 deletion indexer/src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { checkExistsInt, computeHash, getLevelFromXp } from "./encode.ts";
import { parseAdventurerState } from "./events.ts";
import { computeHash, checkExistsInt, getLevelFromXp } from "./encode.ts";

export function insertAdventurer({
id,
Expand Down Expand Up @@ -235,6 +235,22 @@ export function updateAdventurerOwner({
};
}

export function updateAdventurerName({ adventurerId, adventurerName }: any) {
const entity = {
id: checkExistsInt(parseInt(adventurerId)),
};

return {
entity,
update: {
$set: {
...entity,
name: checkExistsInt(BigInt(adventurerName).toString(16)),
},
},
};
}

export function insertDiscovery({
txHash,
adventurerId,
Expand Down
10 changes: 5 additions & 5 deletions ui/src/app/components/navigation/TxActivity.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useEffect, useState } from "react";
import { InvokeTransactionReceiptResponse } from "starknet";
import { useWaitForTransaction } from "@starknet-react/core";
import { displayAddress, padAddress } from "@/app/lib/utils";
import useLoadingStore from "@/app/hooks/useLoadingStore";
import LootIconLoader from "@/app/components/icons/Loader";
import useLoadingStore from "@/app/hooks/useLoadingStore";
import useTransactionCartStore from "@/app/hooks/useTransactionCartStore";
import useUIStore from "@/app/hooks/useUIStore";
import { networkConfig } from "@/app/lib/networkConfig";
import { displayAddress, padAddress } from "@/app/lib/utils";
import { useWaitForTransaction } from "@starknet-react/core";
import { useEffect, useState } from "react";
import { InvokeTransactionReceiptResponse } from "starknet";

export const TxActivity = () => {
const stopLoading = useLoadingStore((state) => state.stopLoading);
Expand Down
93 changes: 83 additions & 10 deletions ui/src/app/components/start/AdventurerListCard.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { useEffect, useState } from "react";
import Info from "@/app/components/adventurer/Info";
import { Button } from "@/app/components/buttons/Button";
import useAdventurerStore from "@/app/hooks/useAdventurerStore";
import useNetworkAccount from "@/app/hooks/useNetworkAccount";
import useTransactionCartStore from "@/app/hooks/useTransactionCartStore";
import { padAddress } from "@/app/lib/utils";
import { Adventurer } from "@/app/types";
import Info from "@/app/components/adventurer/Info";
import { useProvider } from "@starknet-react/core";
import { ChangeEvent, useEffect, useState } from "react";
import {
AccountInterface,
constants,
Contract,
validateAndParseAddress,
constants,
} from "starknet";
import useTransactionCartStore from "@/app/hooks/useTransactionCartStore";
import { useAccount, useProvider } from "@starknet-react/core";
import useAdventurerStore from "@/app/hooks/useAdventurerStore";
import { padAddress } from "@/app/lib/utils";
import { StarknetIdNavigator } from "starknetid.js";
import { CartridgeIcon, StarknetIdIcon } from "../icons/Icons";

Expand All @@ -25,15 +26,24 @@ export interface AdventurerListCardProps {
from: string,
recipient: string
) => Promise<void>;
changeAdventurerName: (
account: AccountInterface,
adventurerId: number,
name: string,
index: number
) => Promise<void>;
index: number;
}

export const AdventurerListCard = ({
adventurer,
gameContract,
handleSwitchAdventurer,
transferAdventurer,
changeAdventurerName,
index,
}: AdventurerListCardProps) => {
const { account, address } = useAccount();
const { account, address } = useNetworkAccount();
const { provider } = useProvider();
const starknetIdNavigator = new StarknetIdNavigator(
provider,
Expand All @@ -55,6 +65,10 @@ export const AdventurerListCard = ({
"send" | "addToCart" | null
>(null);

const [isEditOpen, setIsEditOpen] = useState(false);
const [adventurerName, setAdventurerName] = useState("");
const [isMaxLength, setIsMaxLength] = useState(false);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Initialize 'adventurerName' with current name for better user experience

Currently, adventurerName is initialized as an empty string. Consider initializing it with adventurer.name so that the input field is pre-populated with the current name when editing.

Apply this change:

-const [adventurerName, setAdventurerName] = useState("");
+const [adventurerName, setAdventurerName] = useState(adventurer.name || "");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [isEditOpen, setIsEditOpen] = useState(false);
const [adventurerName, setAdventurerName] = useState("");
const [isMaxLength, setIsMaxLength] = useState(false);
const [isEditOpen, setIsEditOpen] = useState(false);
const [adventurerName, setAdventurerName] = useState(adventurer.name || "");
const [isMaxLength, setIsMaxLength] = useState(false);

const setAdventurer = useAdventurerStore((state) => state.setAdventurer);

useEffect(() => {
Expand Down Expand Up @@ -144,6 +158,18 @@ export const AdventurerListCard = ({
setIsTransferOpen(false);
};

const handleNameChange = (
e: ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
const value = e.target.value;
setAdventurerName(value.slice(0, 31));
if (value.length >= 31) {
setIsMaxLength(true);
} else {
setIsMaxLength(false);
}
};

Comment on lines +167 to +177
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance reusability and maintainability of handleNameChange

The handleNameChange function is well-implemented, but we can improve it further:

  1. Extract the maximum length as a constant:

    const MAX_NAME_LENGTH = 31;
  2. Make the function more reusable by accepting the maximum length as a parameter:

    const handleNameChange = (
      e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>,
      maxLength: number = MAX_NAME_LENGTH
    ) => {
      const value = e.target.value;
      setAdventurerName(value.slice(0, maxLength));
      setIsMaxLength(value.length >= maxLength);
    };

These changes will make the code more maintainable and flexible for future modifications.

return (
<>
{adventurer && (
Expand All @@ -163,10 +189,18 @@ export const AdventurerListCard = ({
size={"lg"}
variant={"token"}
onClick={() => setIsTransferOpen(!isTransferOpen)}
className={`w-1/2 ${isTransferOpen && "animate-pulse"}`}
className={`w-1/4 ${isTransferOpen && "animate-pulse"}`}
>
Transfer
</Button>
<Button
size={"lg"}
variant={"token"}
onClick={() => setIsEditOpen(!isEditOpen)}
className="w-1/4"
>
Edit
</Button>
{isTransferOpen && (
<>
<div className="absolute bottom-20 bg-terminal-black border border-terminal-green flex flex-col gap-2 items-center justify-center w-3/4 p-2">
Expand Down Expand Up @@ -278,9 +312,48 @@ export const AdventurerListCard = ({
</div>
</>
)}
{isEditOpen && (
<>
<div className="absolute bottom-20 bg-terminal-black border border-terminal-green flex flex-col gap-2 items-center justify-center w-3/4 p-2">
<div className="flex flex-row items-center justify-center w-full">
<span className="uppercase text-2xl text-center flex-grow">
Change Adventurer Name
</span>
</div>
<div className="relative flex flex-col w-full items-center justify-center gap-10">
<input
type="text"
value={adventurerName}
onChange={handleNameChange}
className="p-1 h-12 text-2xl w-3/4 bg-terminal-black border border-terminal-green animate-pulse transform"
/>
{isMaxLength && (
<p className="absolute top-14">MAX LENGTH!</p>
)}
<div className="flex flex-row gap-2 items-center">
<Button
size={"lg"}
onClick={() => {
changeAdventurerName(
account!,
adventurer.id!,
adventurerName,
index
);
setIsEditOpen(false);
}}
disabled={adventurerName === ""}
>
Save
</Button>
</div>
</div>
</div>
</>
)}
</div>
)}
{isTransferOpen && (
{(isTransferOpen || isEditOpen) && (
<div className="absolute inset-0 bg-terminal-black/75 z-[1]" />
)}
<Info adventurer={adventurer} gameContract={gameContract} />
Expand Down
Loading
Loading