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

Resolve state management bug #55

Merged
merged 9 commits into from
Aug 29, 2024
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