diff --git a/components/app/UserWallet.tsx b/components/app/UserWallet.tsx index 536c3b3..0c13634 100644 --- a/components/app/UserWallet.tsx +++ b/components/app/UserWallet.tsx @@ -3,30 +3,43 @@ import React from "react"; import { RxCaretDown } from "react-icons/rx"; import PlaceholderImageFromSeed from "@/components/app/molecules/PlaceholderImageFromSeed"; import { SecretString } from "@/types"; +import useKeplrConnect from "@/utils/hooks/useKeplrConnect"; +import keplrConnect from "@/utils/wallet/keplrConnect"; +import { useStore } from "@/store/swapStore"; +import keplrDisconnect from "@/utils/wallet/keplrDisconnect"; interface UserWalletProps { - isConnected: boolean; - userAddress: SecretString; - balanceSCRT: number; - balanceADMT: number; - onConnect: () => void; + // isConnected: boolean; + // userAddress: SecretString; + // balanceSCRT: number; + // balanceADMT: number; + // onConnect: () => void; } -const UserWallet: React.FC = ({ - isConnected, - userAddress, - balanceSCRT, - balanceADMT, - onConnect, -}) => { +const UserWallet: React.FC = ( + { + // isConnected, + // userAddress, + // balanceSCRT, + // balanceADMT, + // onConnect, + } +) => { + const { + wallet: { address: userAddress, ADMTBalance, SCRTBalance }, + } = useStore(); const truncatedAddress = - userAddress.slice(0, 6) + "..." + userAddress.slice(-4); + userAddress === null + ? "" + : userAddress.slice(0, 6) + "..." + userAddress.slice(-4); + // useKeplrConnect(); + const isConnected = userAddress !== null; return (
{isConnected ? ( <> -
+
keplrDisconnect()}>
@@ -35,13 +48,16 @@ const UserWallet: React.FC = ({
- {balanceSCRT} SCRT / {balanceADMT} ADMT + {SCRTBalance} SCRT / {ADMTBalance} ADMT
) : ( - )}
diff --git a/components/app/atoms/Swap/TokenSelectionModal/TokenSelectionCloseButton.tsx b/components/app/atoms/Swap/TokenSelectionModal/TokenSelectionCloseButton.tsx deleted file mode 100644 index 22c171d..0000000 --- a/components/app/atoms/Swap/TokenSelectionModal/TokenSelectionCloseButton.tsx +++ /dev/null @@ -1,6 +0,0 @@ -{ - /*
-

Select a token

- -
*/ -} diff --git a/components/app/molecules/TokenSelectionItem.tsx b/components/app/molecules/TokenSelectionItem.tsx index 17b8cbc..8326634 100644 --- a/components/app/molecules/TokenSelectionItem.tsx +++ b/components/app/molecules/TokenSelectionItem.tsx @@ -23,7 +23,7 @@ const TokenSelectionItem: React.FC = ({ {token.symbol} {network} - {balance} + {balance} ); diff --git a/components/app/organisms/Navbar.tsx b/components/app/organisms/Navbar.tsx index 40c1fec..b08321b 100644 --- a/components/app/organisms/Navbar.tsx +++ b/components/app/organisms/Navbar.tsx @@ -56,13 +56,7 @@ const Navbar: React.FC = () => { - console.log("connect")} - /> + ); }; diff --git a/components/app/organisms/SwapForm/RawAttempt.tsx b/components/app/organisms/SwapForm/RawAttempt.tsx deleted file mode 100644 index 2a3470e..0000000 --- a/components/app/organisms/SwapForm/RawAttempt.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Token } from "@/types"; -import SwapForm from "./SwapForm"; - -// Dummy token array - -export default function RawAttempt() { - return ; -} diff --git a/components/app/organisms/SwapForm/TokenSelectionModalRadix.tsx b/components/app/organisms/SwapForm/TokenSelectionModalRadix.tsx index 6626c06..72ce145 100644 --- a/components/app/organisms/SwapForm/TokenSelectionModalRadix.tsx +++ b/components/app/organisms/SwapForm/TokenSelectionModalRadix.tsx @@ -30,14 +30,14 @@ const TokenSelectionModal: React.FC = ({ <> - +
- + Select a token -
diff --git a/pages/app/index.tsx b/pages/app/index.tsx index 711f7b3..5ada294 100644 --- a/pages/app/index.tsx +++ b/pages/app/index.tsx @@ -1,6 +1,6 @@ import { Jura } from "next/font/google"; import AppLayout from "../../components/app/compositions/AppLayout"; -import RawAttempt from "@/components/app/organisms/SwapForm/RawAttempt"; +import SwapForm from "@/components/app/organisms/SwapForm/SwapForm"; const jura = Jura({ subsets: ["latin"] }); @@ -15,7 +15,6 @@ export default function Swap() { } > - {/* container div simply setting max width and perhaps other settings for content only */}
@@ -35,7 +34,7 @@ export default function Swap() {
+ Add liquidity for SRCT/SHD
- +
diff --git a/store/swapStore.ts b/store/swapStore.ts index 9c8c8a4..d194065 100644 --- a/store/swapStore.ts +++ b/store/swapStore.ts @@ -1,11 +1,12 @@ import { create } from "zustand"; import { + SecretString, SharedSettings, StoreState, - Token, TokenInputState, TokenInputs, } from "@/types"; +import updateState from "@/store/utils/updateState"; export const useStore = create((set) => ({ tokenInputs: { @@ -28,29 +29,44 @@ export const useStore = create((set) => ({ slippage: 0.5, gas: 0, }, + wallet: { + address: null, + SCRTBalance: "0", + ADMTBalance: "0", + }, + swappableTokens: [], + chainId: "secret-4", + connectionRefused: false, setTokenInputProperty: ( inputIdentifier: keyof TokenInputs, property: T, value: TokenInputState[T] ) => - set((state) => ({ - ...state, - tokenInputs: { - ...state.tokenInputs, - [inputIdentifier]: { - ...state.tokenInputs[inputIdentifier], - [property]: value, - }, - }, - })), + set((state) => + updateState(state, "tokenInputs", inputIdentifier, { + token: state.tokenInputs[inputIdentifier].token, + amount: state.tokenInputs[inputIdentifier].amount, + [property]: value, + }) + ), + setSharedSetting: ( setting: T, value: SharedSettings[T] - ) => - set((state) => ({ - ...state, - sharedSettings: { ...state.sharedSettings, [setting]: value }, - })), - swappableTokens: [], // Add this line to initialize the swappable tokens list - setSwappableTokens: (tokens) => set({ swappableTokens: tokens }), // Method to update the list + ) => set((state) => updateState(state, "sharedSettings", setting, value)), + + connectWallet: (address: SecretString) => + set((state) => updateState(state, "wallet", "address", address)), + + disconnectWallet: () => + set((state) => updateState(state, "wallet", "address", null)), + + updateBalance: (tokenSymbol: "SCRT" | "ADMT", balance: string) => + set((state) => + updateState(state, "wallet", `${tokenSymbol}Balance`, balance) + ), + + setSwappableTokens: (tokens) => set({ swappableTokens: tokens }), + setChainId: (chainId) => set({ chainId }), + setConnectionRefused: (refused) => set({ connectionRefused: refused }), })); diff --git a/store/utils/updateState.ts b/store/utils/updateState.ts new file mode 100644 index 0000000..223eaef --- /dev/null +++ b/store/utils/updateState.ts @@ -0,0 +1,39 @@ +/** + * Safely updates a nested property within a state object, ensuring type safety. + * + * @param currentState - The current state object. + * @param topLevelKey - The key of the top-level property within the state object to update. + * @param nestedKey - The key of the nested property within the top-level property to update. + * @param newValue - The new value to set for the nested property. + * @returns A new state object with the nested property updated. + */ +function updateState< + CurrentStateType extends Record, + TopLevelKey extends keyof CurrentStateType, + NestedKey extends keyof CurrentStateType[TopLevelKey] +>( + currentState: CurrentStateType, + topLevelKey: TopLevelKey, + nestedKey: NestedKey, + newValue: CurrentStateType[TopLevelKey][NestedKey] | Record +): CurrentStateType { + // Determine if newValue is a plain object for a potential merge operation. + const isObject = (obj: any): obj is Record => + obj && typeof obj === "object" && !Array.isArray(obj); + + // Perform the update operation. + const updatedState = { + ...currentState, + [topLevelKey]: { + ...currentState[topLevelKey], + [nestedKey]: + isObject(newValue) && isObject(currentState[topLevelKey][nestedKey]) + ? { ...currentState[topLevelKey][nestedKey], ...newValue } + : newValue, + }, + }; + + return updatedState; +} + +export default updateState; diff --git a/types/store/StoreState.ts b/types/store/StoreState.ts index bf7e31b..dc18ad3 100644 --- a/types/store/StoreState.ts +++ b/types/store/StoreState.ts @@ -1,11 +1,17 @@ import { Token } from "@/types/Token"; +import { WalletState } from "@/types/store/WalletState"; import { SharedSettings } from "@/types/store/SharedSettings"; -import { TokenInputState } from "@/types/store/TokenInputState"; import { TokenInputs } from "@/types/store/TokenInputs"; +import { TokenInputState } from "@/types/store/TokenInputState"; +import { SecretString } from "../SecretString"; export interface StoreState { tokenInputs: TokenInputs; sharedSettings: SharedSettings; + wallet: WalletState; + chainId: string; + swappableTokens: Token[]; + connectionRefused: boolean; setTokenInputProperty: ( inputIdentifier: keyof TokenInputs, property: T, @@ -15,6 +21,10 @@ export interface StoreState { setting: T, value: SharedSettings[T] ) => void; - swappableTokens: Token[]; + connectWallet: (address: SecretString) => void; + disconnectWallet: () => void; + updateBalance: (tokenSymbol: "SCRT" | "ADMT", balance: string) => void; + setChainId: (chainId: string) => void; setSwappableTokens: (tokens: Token[]) => void; + setConnectionRefused: (refused: boolean) => void; } diff --git a/types/store/WalletState.ts b/types/store/WalletState.ts new file mode 100644 index 0000000..767a9f1 --- /dev/null +++ b/types/store/WalletState.ts @@ -0,0 +1,7 @@ +import { SecretString } from "../SecretString"; + +export interface WalletState { + address: SecretString | null; + SCRTBalance: string; + ADMTBalance: string; +} diff --git a/utils/hooks/useKeplrConnect.ts b/utils/hooks/useKeplrConnect.ts new file mode 100644 index 0000000..3da9691 --- /dev/null +++ b/utils/hooks/useKeplrConnect.ts @@ -0,0 +1,58 @@ +// hooks/useKeplrConnection.ts +import { useEffect } from "react"; +import { useStore } from "@/store/swapStore"; + +type CustomWindow = typeof window & { + keplr: any; +}; + +const useKeplrConnect = (manual: boolean) => { + useEffect(() => { + // check if user already rejected connection + let attemptedConnection = false; + + const attemptConnection = async () => { + const { keplr }: CustomWindow = window as CustomWindow; + const connectionRefused = useStore.getState().connectionRefused; + + // if not manually connecting and user already rejected connection, do not attempt connection + if (keplr && !manual && connectionRefused) { + attemptedConnection = true; + try { + const chainId = "secret-4"; + await keplr.enable(chainId); + const offlineSigner = keplr.getOfflineSigner(chainId); + const accounts = await offlineSigner.getAccounts(); + + if (accounts && accounts.length > 0) { + const { address } = accounts[0]; + // Update the store with the user's address + useStore.getState().connectWallet(address); + + // Here, you might also want to fetch and update the SCRT and ADMT balances + // For example: + // useStore.getState().updateBalance('SCRT', '100'); // Fetch and use actual balance + // useStore.getState().updateBalance('ADMT', '200'); // Fetch and use actual balance + } + } catch (error) { + console.error("Error connecting to Keplr:", error); + // set connection refused to true so we only connect again if the user clicks the connect button + useStore.getState().setConnectionRefused(true); + } + } + }; + + if (manual) { + attemptConnection(); + } + + // Optional: Listen for account change + window.addEventListener("keplr_keystorechange", attemptConnection); + + return () => { + window.removeEventListener("keplr_keystorechange", attemptConnection); + }; + }, [manual]); // Ensure this runs whenever the manual param changes +}; + +export default useKeplrConnect; diff --git a/utils/wallet/keplrConnect.ts b/utils/wallet/keplrConnect.ts new file mode 100644 index 0000000..6331f29 --- /dev/null +++ b/utils/wallet/keplrConnect.ts @@ -0,0 +1,29 @@ +import { useStore } from "@/store/swapStore"; + +type CustomWindow = typeof window & { + keplr: any; +}; + +const keplrConnect = async () => { + const { keplr }: CustomWindow = window as CustomWindow; + if (keplr) { + try { + const chainId = "secret-4"; + await keplr.enable(chainId); + const offlineSigner = keplr.getOfflineSigner(chainId); + const accounts = await offlineSigner.getAccounts(); + + if (accounts && accounts.length > 0) { + const { address } = accounts[0]; + useStore.getState().connectWallet(address); + } + } catch (error) { + console.error("Error connecting to Keplr:", error); + useStore.getState().setConnectionRefused(true); + } + } else { + console.log("Keplr extension not found."); + } +}; + +export default keplrConnect; diff --git a/utils/wallet/keplrDisconnect.ts b/utils/wallet/keplrDisconnect.ts new file mode 100644 index 0000000..e986bc2 --- /dev/null +++ b/utils/wallet/keplrDisconnect.ts @@ -0,0 +1,13 @@ +import { useStore } from "@/store/swapStore"; + +const keplrDisconnect = () => { + useStore.getState().disconnectWallet(); + + useStore.getState().updateBalance("SCRT", "0"); + useStore.getState().updateBalance("ADMT", "0"); + useStore.getState().setConnectionRefused(true); + + console.log("Disconnected from Keplr."); +}; + +export default keplrDisconnect;