Skip to content

Commit

Permalink
Merge pull request #55 from mlabs-haskell/neil/fix-state-management-bug
Browse files Browse the repository at this point in the history
Resolve state management bug
  • Loading branch information
nrutledge authored Aug 29, 2024
2 parents 1f82900 + e1127b6 commit 9ee9c62
Show file tree
Hide file tree
Showing 13 changed files with 244 additions and 151 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "set PORT=3006 && react-scripts start",
"start": "PORT=3006 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
Expand Down
45 changes: 34 additions & 11 deletions src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ import { apiCall, cut0x, Handlers, sign, transformKittyForUi } from "./utils";

export const api = {
"show-all-kitties": async () => {
const basicKitties = await apiCall("get-all-kitty-list", "GET");
const allKittyResponse = await apiCall("get-all-kitty-list", "GET");

const tradableKitties = await apiCall("get-all-tradable-kitty-list", "GET");
const kittyList = tradableKitties["td_kitty_list"] || [];
const kittiesToAdd = kittyList.map((tdKitty: any) => ({
if (allKittyResponse.error) {
throw new Error(`Error fetching kitties: ${allKittyResponse.data}`);
}

const ownerKitties = allKittyResponse["owner_kitty_list"] || [];

const tradableKittiesResponse = await apiCall("get-all-tradable-kitty-list", "GET");
const tradableKitties = tradableKittiesResponse["td_kitty_list"] || [];

const convertedTradableKitties = tradableKitties.map((tdKitty: any) => ({
kitty: {
...tdKitty["td_kitty"]["kitty_basic_data"],
price: tdKitty["td_kitty"]["price"],
Expand All @@ -16,16 +23,23 @@ export const api = {
}));

return {
...basicKitties,
owner_kitty_list: [...basicKitties.owner_kitty_list, ...kittiesToAdd],
...allKittyResponse,
owner_kitty_list: [...ownerKitties, ...convertedTradableKitties],
};
},
"show-owned-kitties": async (user: string) => {
const ownedKitties = await apiCall("get-owned-kitty-list", "GET", {
const response = await apiCall("get-owned-kitty-list", "GET", {
owner_public_key: cut0x(user),
});

if (response.error) {
throw new Error(`Error fetching owned kitties: ${response.data}`);
}

const kittyList = response["kitty_list"] || [];

return {
owner_kitty_list: ownedKitties["kitty_list"].map((kittyData: any) => ({
owner_kitty_list: kittyList.map((kittyData: any) => ({
kitty: kittyData,
owner_pub_key: cut0x(user),
})),
Expand Down Expand Up @@ -60,14 +74,23 @@ export const api = {
"child-kitty-name": name,
owner_public_key: cut0x(user),
};
const transaction = await apiCall(
const txResponse = await apiCall(
"get-txn-and-inpututxolist-for-breed-kitty",
"GET",
txBody,
);

const signedTransaction = await sign(transaction, accounts);
return await apiCall(updateHandle, "POST", {}, signedTransaction);
if (!txResponse.transaction) {
throw new Error(txResponse.message || "Getting transaction for breeding kitty failed");
}

const signedTransaction = await sign(txResponse, accounts);

const breedResponse = await apiCall(updateHandle, "POST", {}, signedTransaction);

if (!breedResponse.child_kitty) {
throw new Error(breedResponse.message || "Kitty breeding failed");
}
},
"set-kitty-property": async (kitty: Partial<Kitty>) => {
//price
Expand Down
4 changes: 4 additions & 0 deletions src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ const rootReducer = combineReducers({
trading: tradingSlice,
})

// State is mostly transient and persisting was only leading to stale data
// after loading/refreshing the page. For this reason, the whitelist is
// currently empty. Add to it only after careful consideration.
const persistConfig = {
key: 'root',
version: 1,
storage,
whitelist: [],
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export const store = configureStore({
Expand Down
15 changes: 15 additions & 0 deletions src/components/LoadingStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { CircularProgress, Flex } from "@chakra-ui/react";

type LoadingStatusProps = {
status: "idle" | "pending" | "succeeded" | "failed";
message?: string;
};

export const LoadingStatus: React.FC<LoadingStatusProps> = ({ status, message }) => {
return (
<Flex justifyContent="center" m="4" color={status === "failed" ? "red" : undefined}>
{status === "pending" && <CircularProgress isIndeterminate color="teal" />}
{message}
</Flex>
);
};
7 changes: 1 addition & 6 deletions src/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@ import { Button, Stack, Container, Flex } from '@chakra-ui/react';
import {SearchIcon, PersonCircleIcon} from "chakra-ui-ionicons";
import { WalletSelector } from "../features/wallet";
import { useAppSelector } from "../app/hooks";
import { selectAccount, selectIsConnected } from "../features/wallet/walletSlice";
import {getWalletBySource} from "@talisman-connect/wallets";
import { selectAccount } from "../features/wallet/walletSlice";

export const Root = () => {
const account = useAppSelector(selectAccount);
// @ts-ignore
const isConnected = !!window.accounts;
const wallet = getWalletBySource('talisman');

useEffect(()=>{
if (!account || !wallet) return;
},[account, wallet])

return (
<div className="App">
Expand Down
17 changes: 9 additions & 8 deletions src/features/kittiesList/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,20 @@ const kittiesSlice = createSlice({
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
builder.addCase(getKitties.pending, (state) => {
state.loading = "pending";
state.error = undefined;
});
builder.addCase(getKitties.fulfilled, (state, action) => {
if (action.payload?.message?.toLowerCase().includes("error")) {
state.error = action.payload.message;
}
if (!action.payload?.owner_kitty_list) {
state.loading = "failed";
return;
}
state.list = action.payload?.owner_kitty_list.map(transformKittyForUi);

state.loading = "succeeded";
state.error = undefined;
});
builder.addCase(getKitties.rejected, (state, action) => {
state.list = [];
state.loading = "failed";
state.error = action.error.message;
});
},
});

Expand Down
127 changes: 70 additions & 57 deletions src/features/wallet/index.tsx
Original file line number Diff line number Diff line change
@@ -1,94 +1,107 @@
import React, { useEffect, useState } from "react";
import { useEffect } from "react";
import { useToast, CircularProgress } from "@chakra-ui/react";
import { getWallets } from "@talismn/connect-wallets";
import { useAppDispatch } from "../../app/hooks";
import { connect, login } from "./walletSlice";
import { api } from "../../api/client";
import { getCoins } from "../trade";
import { getKitties } from "../kittiesList";
import { login } from "./walletSlice";
import { decodeAddress } from "@polkadot/keyring";
import { u8aToHex } from "@polkadot/util";

const DAPP_NAME = process.env.REACT_APP_DAPP_NAME || "development";

// get an array of wallets which are installed
const installedWallets = getWallets().filter((wallet) => wallet.installed);

// get talisman from the array of installed wallets
const talismanWallet = installedWallets.find(
(wallet) => wallet.extensionName === "talisman",
);

export const WalletSelector = () => {
const [init, setInit] = useState(false);
const dispatch = useAppDispatch();
const toast = useToast();

useEffect(() => {
setInit(true);
}, []);

useEffect(() => {
const fetchData = async () => {
const connectWallet = async () => {
toast({
title: "Talisman connecting",
description: "Getting your information from Tuxedo",
status: "info",
duration: 4000,
isClosable: true,
});
if (talismanWallet) {
await talismanWallet.enable(DAPP_NAME);
talismanWallet.subscribeAccounts(async (accounts) => {
if (!accounts) {
toast({
title: "Error",
description: "No accounts found",
status: "error",
duration: 4000,
isClosable: true,
});
return;
}

//finish connecting
const installedWallets = getWallets().filter(
(wallet) => wallet.installed
);

const talismanWallet = installedWallets.find(
(wallet) => wallet.extensionName === "talisman"
);

if (!talismanWallet) {
toast({
title: "Error connecting wallet",
description: "Talisman wallet not found",
status: "error",
duration: 4000,
isClosable: true,
});

return;
}

await talismanWallet.enable(DAPP_NAME);

talismanWallet.subscribeAccounts(async (accounts) => {
if (!accounts) {
toast({
title: "Talisman connected",
description: `Accounts: ${accounts.map((account) => account.name).join(" ,")} are connected`,
status: "success",
title: "Error connecting wallet",
description: "No Talisman accounts found",
status: "error",
duration: 4000,
isClosable: true,
});
//each account has its own signer, and it can't be saved to store
//maybe need to use other api to access it
// @ts-ignore
window.accounts = accounts;
dispatch(
login(
accounts.map((account) => ({
address: account.address,
source: account.source,
name: account.name,
key: u8aToHex(decodeAddress(account.address)),
})),
),
);
dispatch(connect());
return;
}

//each account has its own signer, and it can't be saved to store
//maybe need to use other api to access it
// @ts-ignore
window.accounts = accounts;

dispatch(
login(
accounts.map((account) => ({
address: account.address,
source: account.source,
name: account.name,
key: u8aToHex(decodeAddress(account.address)),
}))
)
);

toast({
title: "Talisman connected",
description: `Accounts: ${accounts
.map((account) => account.name)
.join(" ,")} are connected`,
status: "success",
duration: 4000,
isClosable: true,
});
}
});
};
if (init) {
fetchData().catch((error) => {

// Connect wallet after slight delay to avoid intermittent problem with
// talismanWallet not being found
const timeout = setTimeout(() => {
connectWallet().catch((error) => {
toast({
title: "Talisman connecting",
title: "Error connecting wallet",
description: error,
status: "error",
duration: 9000,
isClosable: true,
});
});
}
}, [init]);
}, 100);

return () => {
clearTimeout(timeout);
};
}, [dispatch, toast]);

return (
<div>
Expand Down
11 changes: 1 addition & 10 deletions src/features/wallet/walletSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import { wallet } from "../../types";

export interface WalletState {
accounts?:wallet[];
isConnected: boolean;
}

const initialState: WalletState = {
accounts: undefined,
isConnected: false,
};

// Then, handle actions in your reducers:
Expand All @@ -23,18 +21,11 @@ const walletSlice = createSlice({
logout: (state) => {
state.accounts = undefined;
},
connect: (state) => {
state.isConnected = true;
},
disConnect: (state)=> {
state.isConnected = false;
}
}
})

export const selectAccount = (state: RootState) => state.wallet.accounts?.[0];
export const selectAccounts = (state: RootState) => state.wallet.accounts;
export const selectIsConnected = (state: RootState) => state.wallet.isConnected;
export const { login, logout, connect, disConnect } = walletSlice.actions;
export const { login, logout } = walletSlice.actions;

export default walletSlice.reducer;
Loading

0 comments on commit 9ee9c62

Please sign in to comment.