From cde5752219d57029a7ae857e9b23a851b0d284c8 Mon Sep 17 00:00:00 2001 From: eviterin <ethan.viterin@gmail.com> Date: Sat, 16 Mar 2024 16:11:04 +0100 Subject: [PATCH] Readded code that was lost due to poor rebasing. Applied formatting fixes and ran eslint. --- packages/contracts/src/Inventory.sol | 2 +- packages/webapp/src/actions/getDeck.ts | 278 +++++++++-------- packages/webapp/src/actions/setDeck.ts | 191 ++++++------ .../src/components/collection/deckList.tsx | 112 ++++--- .../src/components/collection/deckPanel.tsx | 13 +- packages/webapp/src/pages/collection.tsx | 290 +++++++++--------- 6 files changed, 481 insertions(+), 405 deletions(-) diff --git a/packages/contracts/src/Inventory.sol b/packages/contracts/src/Inventory.sol index 8225a42f..379bc239 100644 --- a/packages/contracts/src/Inventory.sol +++ b/packages/contracts/src/Inventory.sol @@ -339,7 +339,7 @@ contract Inventory is Ownable { { return decks[player][deckID].cards; } - + // --------------------------------------------------------------------------------------------- // Returns the decks of a given player. diff --git a/packages/webapp/src/actions/getDeck.ts b/packages/webapp/src/actions/getDeck.ts index 2f396879..0162501f 100644 --- a/packages/webapp/src/actions/getDeck.ts +++ b/packages/webapp/src/actions/getDeck.ts @@ -1,120 +1,158 @@ -import { defaultErrorHandling } from "src/actions/errors" -import { contractWriteThrowing } from "src/actions/libContractWrite" -import { Address } from "src/chain" -import { deployment } from "src/deployment" -import { inventoryABI } from "src/generated" - -// ================================================================================================= - -export type getAllDecksArgs = { - playerAddress: Address - onSuccess: () => void -} - -// ------------------------------------------------------------------------------------------------- - -/** - * Fetches all decks of the given player by sending the `getAllDecks` transaction. - * - * Returns `true` iff the transaction is successful. - */ -export async function getAllDecks(args: getAllDecksArgs): Promise<any> { - try { - return await getAllDecksImpl(args) - } catch (err) { - defaultErrorHandling("getAllDecks", err) - return false - } -} - -/** - * Fetches the deck of the given player of a given ID by sending the `getDeck` transaction. - * - * Returns `true` iff the transaction is successful. - */ -export async function getDeck(args: GetDeckAtArgs): Promise<any> { - try { - return await getDeckImpl(args) - } catch (err) { - defaultErrorHandling("getDeck", err) - return false - } -} - -// ------------------------------------------------------------------------------------------------- - -async function getAllDecksImpl(args: getAllDecksArgs): Promise<any> { - try { - const result = await contractWriteThrowing({ - contract: deployment.Inventory, - abi: inventoryABI, - functionName: "getAllDecks", - args: [args.playerAddress], - }) - - args.onSuccess() - return result - } catch (error) { - console.error("Error fetching decks:", error) - return null - } - } - -// ------------------------------------------------------------------------------------------------- - -async function getDeckImpl(args: GetDeckAtArgs): Promise<any> { - try { - const result = await contractWriteThrowing({ - contract: deployment.Inventory, - abi: inventoryABI, - functionName: "getDeck", - args: [args.playerAddress, args.index], - }) - - args.onSuccess() - return result - } catch (error) { - console.error("Error fetching deck:", error) - return null - } -} - -// ------------------------------------------------------------------------------------------------- - -async function getNumDecksImpl(args: GetDeckArgs): Promise<any> { - try { - const result = await contractWriteThrowing({ - contract: deployment.Inventory, - abi: inventoryABI, - functionName: "getNumDecks", - args: [args.playerAddress], - }) - - args.onSuccess() - return result - } catch (error) { - console.error("Error fetching decks:", error) - return null - } -} - -// ------------------------------------------------------------------------------------------------- - -async function getDeckNamesImpl(args: GetDeckArgs): Promise<any> { - try { - const result = await contractWriteThrowing({ - contract: deployment.Inventory, - abi: inventoryABI, - functionName: "getDeckNames", - args: [args.playerAddress], - }) - - args.onSuccess() - return result - } catch (error) { - console.error("Error fetching decks:", error) - return null - } -} - -// ================================================================================================= \ No newline at end of file +import { defaultErrorHandling } from "src/actions/errors" +import { contractWriteThrowing } from "src/actions/libContractWrite" +import { Address } from "src/chain" +import { deployment } from "src/deployment" +import { inventoryABI } from "src/generated" + +// ================================================================================================= + +export type GetDeckArgs = { + playerAddress: Address + onSuccess: () => void +} + +export type GetDeckAtArgs = { + playerAddress: Address + onSuccess: () => void + index: number +} + +// ------------------------------------------------------------------------------------------------- + +/** + * Fetches all decks of the given player by sending the `getAllDecks` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function getAllDecks(args: GetDeckArgs): Promise<any> { + try { + return await getAllDecksImpl(args) + } catch (err) { + defaultErrorHandling("getAllDecks", err) + return false + } +} + +/** + * Fetches the deck of the given player of a given ID by sending the `getDeckReal` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function getDeck(args: GetDeckAtArgs): Promise<any> { + try { + return await getDeckImpl(args) + } catch (err) { + defaultErrorHandling("getDeck", err) + return false + } +} + +// ------------------------------------------------------------------------------------------------- + +/** + * Fetches deck count of the given player by sending the `getNumDecks` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function getNumDecks(args: GetDeckArgs): Promise<any> { + try { + return await getNumDecksImpl(args) + } catch (err) { + defaultErrorHandling("getNumDecks", err) + return false + } +} + +// ------------------------------------------------------------------------------------------------- + +/** + * Fetches deck count of the given player by sending the `getNumDecks` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function getDeckNames(args: GetDeckArgs): Promise<any> { + try { + return await getDeckNamesImpl(args) + } catch (err) { + defaultErrorHandling("getDeckNames", err) + return false + } +} + +// ------------------------------------------------------------------------------------------------- + +async function getAllDecksImpl(args: GetDeckArgs): Promise<any> { + try { + const result = await contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "getAllDecks", + args: [args.playerAddress], + }) + + args.onSuccess() + return result + } catch (error) { + console.error("Error fetching decks:", error) + return null + } +} + +// ------------------------------------------------------------------------------------------------- + +async function getDeckImpl(args: GetDeckAtArgs): Promise<any> { + try { + const result = await contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "getDeckReal", + args: [args.playerAddress, args.index], + }) + + args.onSuccess() + return result + } catch (error) { + console.error("Error fetching deck:", error) + return null + } +} + +// ------------------------------------------------------------------------------------------------- + +async function getNumDecksImpl(args: GetDeckArgs): Promise<any> { + try { + const result = await contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "getNumDecks", + args: [args.playerAddress], + }) + + args.onSuccess() + return result + } catch (error) { + console.error("Error fetching decks:", error) + return null + } +} + +// ------------------------------------------------------------------------------------------------- + +async function getDeckNamesImpl(args: GetDeckArgs): Promise<any> { + try { + const result = await contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "getDeckNames", + args: [args.playerAddress], + }) + + args.onSuccess() + return result + } catch (error) { + console.error("Error fetching decks:", error) + return null + } +} + +// ================================================================================================= diff --git a/packages/webapp/src/actions/setDeck.ts b/packages/webapp/src/actions/setDeck.ts index 4e20d60c..c114aeae 100644 --- a/packages/webapp/src/actions/setDeck.ts +++ b/packages/webapp/src/actions/setDeck.ts @@ -1,101 +1,90 @@ -import { defaultErrorHandling } from "src/actions/errors" -import { contractWriteThrowing } from "src/actions/libContractWrite" -import { Address } from "src/chain" -import { deployment } from "src/deployment" -import { inventoryABI } from "src/generated" -import { checkFresh, freshWrap } from "src/store/checkFresh" -import { Deck } from "src/store/types" - -// ================================================================================================= - -export type SaveArgs = { - deck: Deck - playerAddress: Address - onSuccess: () => void -} - -export type ModifyArgs = { - deck: Deck - playerAddress: Address - index: number - onSuccess: () => void -} - - -// ------------------------------------------------------------------------------------------------- - -/** - * Saves a deck created by the player by sending the `saveDeck` transaction. - * - * Returns `true` iff the transaction is successful. - */ -export async function save(args: SaveArgs): Promise<boolean> { - try { - return await saveImpl(args) - } catch (err) { - return defaultErrorHandling("save", err) - } -} - -/** - * Modifies a deck owned by the player by sending the `modifyDeck` transaction. - * - * Returns `true` iff the transaction is successful. - */ -export async function modify(args: ModifyArgs): Promise<boolean> { - try { - return await modifyImpl(args) - } catch (err) { - return defaultErrorHandling("modify", err) - } -} - -// ------------------------------------------------------------------------------------------------- - -async function saveImpl(args: SaveArgs): Promise<boolean> { - const cardBigInts = args.deck.cards.map(card => card.id) - - checkFresh(await freshWrap( - contractWriteThrowing({ - contract: deployment.Inventory, - abi: inventoryABI, - functionName: "addDeck", - args: [ - args.playerAddress, - { name: args.deck.name, cards: cardBigInts } - ], - }))) - - args.onSuccess() - return true -} - -async function modifyImpl(args: ModifyArgs): Promise<boolean> { -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> 35b5d7c (Modify deck support) - const cardBigInts = args.deck.cards.map(card => card.id) - console.log("INDEX: " + args.index) - checkFresh(await freshWrap( - contractWriteThrowing({ - contract: deployment.Inventory, - abi: inventoryABI, - functionName: "replaceDeck", - args: [ - args.playerAddress, - args.index, - { name: args.deck.name, cards: cardBigInts } - ], - }))) - - args.onSuccess() - return true -<<<<<<< HEAD -======= ->>>>>>> 3d86518 (Can now save deck onchain) -======= ->>>>>>> 35b5d7c (Modify deck support) -} - -// ================================================================================================= \ No newline at end of file +import { defaultErrorHandling } from "src/actions/errors" +import { contractWriteThrowing } from "src/actions/libContractWrite" +import { Address } from "src/chain" +import { deployment } from "src/deployment" +import { inventoryABI } from "src/generated" +import { checkFresh, freshWrap } from "src/store/checkFresh" +import { Deck } from "src/store/types" + +// ================================================================================================= + +export type SaveArgs = { + deck: Deck + playerAddress: Address + onSuccess: () => void +} + +export type ModifyArgs = { + deck: Deck + playerAddress: Address + index: number + onSuccess: () => void +} + +// ------------------------------------------------------------------------------------------------- + +/** + * Saves a deck created by the player by sending the `saveDeck` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function save(args: SaveArgs): Promise<boolean> { + try { + return await saveImpl(args) + } catch (err) { + return defaultErrorHandling("save", err) + } +} + +/** + * Modifies a deck owned by the player by sending the `modifyDeck` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function modify(args: ModifyArgs): Promise<boolean> { + try { + return await modifyImpl(args) + } catch (err) { + return defaultErrorHandling("modify", err) + } +} + +// ------------------------------------------------------------------------------------------------- + +async function saveImpl(args: SaveArgs): Promise<boolean> { + const cardBigInts = args.deck.cards.map((card) => card.id) + + checkFresh( + await freshWrap( + contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "addDeck", + args: [args.playerAddress, { name: args.deck.name, cards: cardBigInts }], + }) + ) + ) + + args.onSuccess() + return true +} + +async function modifyImpl(args: ModifyArgs): Promise<boolean> { + const cardBigInts = args.deck.cards.map((card) => card.id) + console.log("INDEX: " + args.index) + checkFresh( + await freshWrap( + contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "replaceDeck", + args: [args.playerAddress, args.index, { name: args.deck.name, cards: cardBigInts }], + }) + ) + ) + + args.onSuccess() + return true +} + +// ================================================================================================= diff --git a/packages/webapp/src/components/collection/deckList.tsx b/packages/webapp/src/components/collection/deckList.tsx index 7e4ffd8e..770ecf34 100644 --- a/packages/webapp/src/components/collection/deckList.tsx +++ b/packages/webapp/src/components/collection/deckList.tsx @@ -1,47 +1,79 @@ -import React from 'react' +import React, { useCallback, useEffect, useState } from "react" + +import { getDeckNames } from "src/actions/getDeck" import Link from "src/components/link" -import { Deck } from 'src/store/types' +import { Button } from "src/components/ui/button" +import * as store from "src/store/hooks" interface DeckCollectionDisplayProps { - decks: Deck[] - onDeckSelect: (deckID: number) => void - isLoadingDecks: boolean + onDeckSelect: (deckID: number) => void } -const DeckCollectionDisplay: React.FC<DeckCollectionDisplayProps> = ({ decks, onDeckSelect, isLoadingDecks }) => { - return ( - <div className="w-full flex flex-col items-center p-3"> - {/* New Deck Button */} - <Link - href={"/collection?newDeck=true"} - className="w-full px-4 py-2 mb-2 border rounded-md text-gray-100 bg-purple-900 hover:bg-gray-500 font-bold text-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"> - New Deck → - </Link> - - {/* Loading Decks Button */} - {isLoadingDecks && ( - <button - className="w-full px-4 py-2 mb-2 border rounded-md text-gray-100 bg-purple-500 font-bold text-center cursor-not-allowed" - disabled - > - Loading Decks... - </button> - )} - - {/* Deck Buttons */} - {decks.map((deck, deckID) => ( - <button - key={deckID} - onClick={() => onDeckSelect(deckID)} - className={`w-full px-4 py-2 mb-2 border rounded-md text-gray-100 ${isLoadingDecks ? 'bg-gray-400 cursor-not-allowed' : 'bg-purple-500 hover:bg-gray-500'} font-bold text-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`} - disabled={isLoadingDecks} - > - {deck.name} - </button> - ))} - - </div> - ) +const DeckCollectionDisplay: React.FC<DeckCollectionDisplayProps> = ({ onDeckSelect }) => { + const playerAddress = store.usePlayerAddress() + const [deckNames, setDeckNames] = useState<string[]>([]) + const [isLoadingDecks, setIsLoadingDecks] = useState(false) + + const loadDeckNames = useCallback(() => { + if (playerAddress) { + setIsLoadingDecks(true) + getDeckNames({ + playerAddress: playerAddress, + onSuccess: () => {}, + }) + .then((response) => { + if (!response.simulatedResult) return + const receivedDecks = response.simulatedResult as string[] + setDeckNames(receivedDecks) + }) + .catch((error) => { + console.error("Error fetching decks:", error) + }) + .finally(() => { + setIsLoadingDecks(false) + }) + } + }, [playerAddress]) + + useEffect(() => { + loadDeckNames() + }, [loadDeckNames]) + + return ( + <div className="flex w-full flex-col items-center p-3"> + {/* New Deck Button */} + <Button + width="full" + className="my-2 border-2 border-yellow-500 font-fable text-xl hover:scale-105 hover:border-yellow-400" + > + <Link href={"/collection?newDeck=true"}>New Deck →</Link> + </Button> + + {/* Loading Button */} + {isLoadingDecks && ( + <Button + width="full" + className="my-2 border-2 border-yellow-500 font-fable text-xl normal-case hover:scale-105 hover:border-yellow-400" + disabled={true} + > + Loading... + </Button> + )} + + {/* Deck Buttons */} + {deckNames.map((deckname, deckID) => ( + <Button + variant={"secondary"} + width="full" + className="my-1 border-2 border-yellow-500 font-fable text-xl normal-case hover:scale-105 hover:border-yellow-400" + key={deckID} + onClick={() => onDeckSelect(deckID)} + > + {deckname} + </Button> + ))} + </div> + ) } -export default DeckCollectionDisplay \ No newline at end of file +export default DeckCollectionDisplay diff --git a/packages/webapp/src/components/collection/deckPanel.tsx b/packages/webapp/src/components/collection/deckPanel.tsx index 46c23f45..9699d065 100644 --- a/packages/webapp/src/components/collection/deckPanel.tsx +++ b/packages/webapp/src/components/collection/deckPanel.tsx @@ -62,14 +62,15 @@ const DeckConstructionPanel: React.FC<DeckConstructionPanelProps> = ({ {/* Counter Row */} <div className="w-full py-1"> <div className="relative pt-1"> - <div className="overflow-hidden h-2 text-xs flex rounded bg-red-200"> - <div style={{ width: `${(selectedCards.length / MAX_CARDS) * 100}%` }} - className={`shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center ${selectedCards.length < MIN_CARDS ? 'bg-red-500' : selectedCards.length <= MAX_CARDS ? 'bg-green-500' : 'bg-yellow-500'}`}> + <div className="flex h-2 overflow-hidden rounded bg-red-200 text-xs"> + <div + style={{ width: `${(selectedCards.length / MAX_CARDS) * 100}%` }} + className={`flex flex-col justify-center whitespace-nowrap text-center text-white shadow-none ${selectedCards.length < MIN_CARDS ? "bg-red-500" : selectedCards.length <= MAX_CARDS ? "bg-green-500" : "bg-yellow-500"}`} + ></div> </div> </div> - </div> <div className="text-center text-sm font-medium"> - {selectedCards.length}/{MAX_CARDS} + {selectedCards.length}/{MAX_CARDS} </div> </div> @@ -77,7 +78,7 @@ const DeckConstructionPanel: React.FC<DeckConstructionPanelProps> = ({ <div className="flex w-full flex-wrap justify-center gap-2"> <Button variant="default" - className="border-2 border-yellow-500 normal-case hover:scale-105 font-fable text-xl hover:border-yellow-400" + className="border-2 border-yellow-500 font-fable text-xl normal-case hover:scale-105 hover:border-yellow-400" onClick={handleSave} > ✓ Save diff --git a/packages/webapp/src/pages/collection.tsx b/packages/webapp/src/pages/collection.tsx index d48f7803..fc61a276 100644 --- a/packages/webapp/src/pages/collection.tsx +++ b/packages/webapp/src/pages/collection.tsx @@ -1,23 +1,25 @@ +import React, { useEffect, useMemo, useState } from "react" import Head from "next/head" +import { useRouter } from "next/router" -import React, { useState, useMemo, useEffect, useCallback } from "react" +import debounce from "lodash/debounce" +import { navigate } from "utils/navigate" import { useAccount } from "wagmi" +import { getDeck } from "src/actions/getDeck" +import { modify, save } from "src/actions/setDeck" +import CardCollectionDisplay from "src/components/collection/cardCollectionDisplay" +import DeckList from "src/components/collection/deckList" +import DeckPanel from "src/components/collection/deckPanel" +import FilterPanel from "src/components/collection/filterPanel" import jotaiDebug from "src/components/lib/jotaiDebug" +import { LoadingModal } from "src/components/modals/loadingModal" import { Navbar } from "src/components/navbar" import { deployment } from "src/deployment" import { useInventoryCardsCollectionGetCollection } from "src/generated" import { FablePage } from "src/pages/_app" -import { Card, Deck } from "src/store/types" -import { navigate } from "utils/navigate" -import { getDeck } from "src/actions/getDeck" -import FilterPanel from 'src/components/collection/filterPanel' -import CardCollectionDisplay from 'src/components/collection/cardCollectionDisplay' -import DeckList from 'src/components/collection/deckList' -import DeckPanel from 'src/components/collection/deckPanel' -import { save, modify } from "src/actions/setDeck" import * as store from "src/store/hooks" -import { LoadingModal } from "src/components/modals/loadingModal" +import { Card, Deck } from "src/store/types" // NOTE(norswap & geniusgarlic): Just an example, when the game actually has effects & types, // fetch those from the chain instead of hardcoding them here. @@ -46,9 +48,12 @@ const Collection: FablePage = ({ isHydrated }) => { const [isEditing, setIsEditing] = useState(false) // Deck Collection Display - const [ editingDeckIndex, setEditingDeckIndex ] = useState<number|null>(null) + const [editingDeckIndex, setEditingDeckIndex] = useState<number|null>(null) const [decks, setDecks] = useState<Deck[]>([]) - const [ isLoadingDeck, setIsLoadingDeck ] = useState(false) + const [isLoadingDeck, setIsLoadingDeck] = useState(false) + + const activeEffects = Object.keys(effectMap).filter((key) => effectMap[key]) + const activeTypes = Object.keys(typeMap).filter((key) => typeMap[key]) const { data: unfilteredCards } = useInventoryCardsCollectionGetCollection({ address: deployment.InventoryCardsCollection, @@ -71,66 +76,70 @@ const Collection: FablePage = ({ isHydrated }) => { ) }) - const handleInputChangeBouncy = (event: React.ChangeEvent<HTMLInputElement>) => { - setSearchInput(event.target.value) - } - const handleInputChange = useMemo(() => debounce(handleInputChangeBouncy, 300), []) + const handleInputChangeBouncy = (event: React.ChangeEvent<HTMLInputElement>) => { + setSearchInput(event.target.value) + } + const handleInputChange = useMemo(() => debounce(handleInputChangeBouncy, 300), []) + + const handleEffectClick = (effectIndex: number) => { + const effect = effects[effectIndex] + setEffectMap({ ...effectMap, [effect]: !effectMap[effect] }) + } + + const handleTypeClick = (typeIndex: number) => { + const type = types[typeIndex] + setTypeMap({ ...typeMap, [type]: !typeMap[type] }) + } - const handleEffectClick = (effectIndex: number) => { - const effect = effects[effectIndex] - setEffectMap({...effectMap, [effect]: !effectMap[effect]}) - } + const handleSaveDeck = async (updatedDeck: Deck) => { + setIsSaving(true) - const handleTypeClick = (typeIndex: number) => { - const type = types[typeIndex] - setTypeMap({...typeMap, [type]: !typeMap[type]}) - } + if (editingDeckIndex !== null) { + // Update existing deck + await modifyOnchain(updatedDeck, editingDeckIndex) + } else { + // Add the new deck to the list + await saveOnchain(updatedDeck) + } - const handleSaveDeck = async (updatedDeck: Deck) => { - setIsSaving(true) + setIsSaving(false) - if (editingDeckIndex !== null) { - // Update existing deck - await modifyOnchain(updatedDeck, editingDeckIndex) - } else { - // Add the new deck to the list - await saveOnchain(updatedDeck) + setIsEditing(false) + setSelectedCards([]) + void navigate(router, "/collection") } - - setIsSaving(false) - setIsEditing(false) - setSelectedCards([]) - void navigate(router, '/collection') - } + function saveOnchain(deck: Deck): Promise<void> { + return new Promise((resolve) => { + save({ + deck, + playerAddress: playerAddress!, + onSuccess: () => { + resolve() + }, + }) + }) + } - function saveOnchain(deck: Deck): Promise<void> { - return new Promise((resolve) => { - save({ - deck, - playerAddress: playerAddress!, - onSuccess: () => { resolve() } - }) - }) - } + function modifyOnchain(deck: Deck, editingDeckIndex: number): Promise<void> { + return new Promise((resolve) => { + modify({ + deck, + playerAddress: playerAddress!, + index: BigInt(editingDeckIndex), + onSuccess: () => { + resolve() + }, + }) + }) + } - function modifyOnchain(deck: Deck, editingDeckIndex: number): Promise<void> { - return new Promise((resolve) => { - modify({ - deck, - playerAddress: playerAddress!, - index: BigInt(editingDeckIndex), - onSuccess: () => { resolve() } - }) - }) - } + const handleCancelEditing = () => { + setIsEditing(false) + setSelectedCards([]) + void navigate(router, "/collection") + } - const handleCancelEditing = () => { - setIsEditing(false) - setSelectedCards([]) - void navigate(router, '/collection') - } - const addToDeck = (card: Card) => { setSelectedCards((prevSelectedCards) => { // Add or remove card from the selectedCards @@ -156,39 +165,41 @@ const Collection: FablePage = ({ isHydrated }) => { } const handleDeckSelect = (deckID: number) => { - if (isLoadingDeck) return; - if (playerAddress) { - setIsLoadingDeck(true) - getDeck({ - playerAddress: playerAddress, - index: deckID, - onSuccess: () => { - }, - }).then(response => { - if(!response.simulatedResult) return; - - - const cardsReceived = response.simulatedResult.cards; - const cardObjects: Card[] = [] - cardsReceived.forEach(card => { - const cID = Number(card) - const co = cards.find(c => Number(c.id) === cID) - if(co) { cardObjects.push(co) } - }) - setSelectedCards(cardObjects); - - const deckName = response.simulatedResult.name; - setCurrentDeck({ name: deckName, cards: cardObjects }) - setEditingDeckIndex(deckID) - setIsEditing(true) - - }).catch(error => { - console.error("Error fetching deck:", error); - }).finally(_ => { - setIsLoadingDeck(false) - }) - } - } + if (isLoadingDeck) return + if (playerAddress) { + setIsLoadingDeck(true) + getDeck({ + playerAddress: playerAddress, + index: deckID, + onSuccess: () => {}, + }) + .then((response) => { + if (!response.simulatedResult) return + + const cardsReceived = response.simulatedResult.cards + const cardObjects: Card[] = [] + cardsReceived.forEach((card) => { + const cID = Number(card) + const co = cards.find((c) => Number(c.id) === cID) + if (co) { + cardObjects.push(co) + } + }) + setSelectedCards(cardObjects) + + const deckName = response.simulatedResult.name + setCurrentDeck({ name: deckName, cards: cardObjects }) + setEditingDeckIndex(deckID) + setIsEditing(true) + }) + .catch((error) => { + console.error("Error fetching deck:", error) + }) + .finally((_) => { + setIsLoadingDeck(false) + }) + } + } // Sets up an event listener for route changes when deck editor is rendered. useEffect(() => { @@ -214,53 +225,58 @@ const Collection: FablePage = ({ isHydrated }) => { <title>0xFable: My Collection</title> </Head> {jotaiDebug()} - <main className="flex h-screen flex-col"> - <Navbar /> - <div className="mx-6 mb-6 grid min-h-0 grow grid-cols-12 gap-4"> - {/* Left Panel - Search and Filters */} - <div className="col-span-3 flex overflow-y-auto rounded-xl border"> - <FilterPanel - effects={effects} - types={types} - effectMap={effectMap} - typeMap={typeMap} - handleEffectClick={handleEffectClick} - handleTypeClick={handleTypeClick} - handleInputChange={handleInputChange} - selectedCard={selectedCard} - /> - </div> - - {/* Middle Panel - Card Collection Display */} - <div className="col-span-7 flex overflow-y-auto rounded-xl border"> - <CardCollectionDisplay - cards={cards} - isHydrated={isHydrated} - setSelectedCard={setSelectedCard} - onCardToggle={onCardToggle} - selectedCards={selectedCards} - isEditing={isEditing} - /> - </div> - - {/* Right Panel - Deck List */} - <div className="col-span-2 flex overflow-y-auto rounded-xl border"> - {isEditing && currentDeck ? ( - <DeckPanel - deck={currentDeck} + {isLoadingDeck ? ( + <LoadingModal loading="Loading deck..." /> + ) : ( + <main className="flex h-screen flex-col"> + <Navbar /> + <div className="mx-6 mb-6 grid min-h-0 grow grid-cols-12 gap-4"> + {/* Left Panel - Search and Filters */} + <div className="col-span-3 flex overflow-y-auto rounded-xl border"> + <FilterPanel + effects={effects} + types={types} + effectMap={effectMap} + typeMap={typeMap} + handleEffectClick={handleEffectClick} + handleTypeClick={handleTypeClick} + handleInputChange={handleInputChange} + selectedCard={selectedCard} + /> + </div> + {/* Middle Panel - Card Collection Display */} + <div className="col-span-7 flex overflow-y-auto rounded-xl border"> + <CardCollectionDisplay + cards={cards} + isHydrated={isHydrated} + setSelectedCard={setSelectedCard} + onCardToggle={onCardToggle} selectedCards={selectedCards} - onCardSelect={addToDeck} - onSave={handleSaveDeck} - onCancel={handleCancelEditing} + isEditing={isEditing} /> + </div> + {/* Right Panel - Deck List */} + {isSaving ? ( + <LoadingModal loading="Saving deck..." /> ) : ( - <DeckList decks={decks || []} onDeckSelect={handleDeckSelect} /> + <div className="col-span-2 flex overflow-y-auto rounded-xl border"> + {isEditing && currentDeck ? ( + <DeckPanel + deck={currentDeck} + selectedCards={selectedCards} + onCardSelect={addToDeck} + onSave={handleSaveDeck} + onCancel={handleCancelEditing} + /> + ) : ( + <DeckList decks={decks || []} onDeckSelect={handleDeckSelect} /> + )} + </div> )} </div> - </div> - </main> + </main> + )} </> ) } - export default Collection