From 0d8c1145ead8ad453df4d7183b65f878a54e2ff4 Mon Sep 17 00:00:00 2001 From: dredshep Date: Wed, 3 Apr 2024 19:04:31 +0200 Subject: [PATCH] made token selection something pleasant in Swap with Radix --- bun.lockb | Bin 222441 -> 223163 bytes components/app/UserWallet.tsx | 4 +- components/app/atoms/Swap/MaxButton.tsx | 11 ++ .../TokenInput/InputBalanceAffordance.tsx | 11 ++ .../app/atoms/Swap/TokenInputBaseInput.tsx | 16 +++ .../TokenSelectionCloseButton.tsx | 6 ++ .../TokenSelectionSearchBar.tsx | 24 +++++ .../molecules/PlaceholderFromHexAddress.tsx | 33 ------ .../molecules/PlaceholderImageFromSeed.tsx | 31 ++++++ components/app/molecules/TokenInput.tsx | 100 ------------------ .../app/molecules/TokenSelectionItem.tsx | 32 ++++++ .../app/organisms/SwapForm/SwapForm.tsx | 2 +- .../app/organisms/SwapForm/TokenInput.tsx | 66 ++++++++++++ .../SwapForm/TokenSelectionModal.tsx | 69 ++++++++++++ .../SwapForm/TokenSelectionModalRadix.tsx | 79 ++++++++++++++ package.json | 2 + pages/_app.tsx | 13 +++ pages/api/getSwappableTokens.ts | 24 +++++ pages/app/pools/index.tsx | 6 +- pages/app/tokens/index.tsx | 10 +- store/swapStore.ts | 52 +++------ tailwind.config.ts | 1 + types/SecretString.ts | 1 + types/Token.ts | 6 ++ types/index.ts | 9 +- types/store/SharedSettings.ts | 4 + types/store/StoreState.ts | 20 ++++ types/store/TokenInputState.ts | 6 ++ types/store/TokenInputs.ts | 6 ++ types/store/index.ts | 4 + .../ImageWithPlaceholder/addressTo3Colors.tsx | 9 -- .../generateHexColorsFromSeed.ts | 26 +++++ utils/ImageWithPlaceholder/stringToHex.tsx | 10 -- utils/apis/getSwappableTokens.ts | 7 ++ 34 files changed, 496 insertions(+), 204 deletions(-) create mode 100644 components/app/atoms/Swap/MaxButton.tsx create mode 100644 components/app/atoms/Swap/TokenInput/InputBalanceAffordance.tsx create mode 100644 components/app/atoms/Swap/TokenInputBaseInput.tsx create mode 100644 components/app/atoms/Swap/TokenSelectionModal/TokenSelectionCloseButton.tsx create mode 100644 components/app/atoms/Swap/TokenSelectionModal/TokenSelectionSearchBar.tsx delete mode 100644 components/app/molecules/PlaceholderFromHexAddress.tsx create mode 100644 components/app/molecules/PlaceholderImageFromSeed.tsx delete mode 100644 components/app/molecules/TokenInput.tsx create mode 100644 components/app/molecules/TokenSelectionItem.tsx create mode 100644 components/app/organisms/SwapForm/TokenInput.tsx create mode 100644 components/app/organisms/SwapForm/TokenSelectionModal.tsx create mode 100644 components/app/organisms/SwapForm/TokenSelectionModalRadix.tsx create mode 100644 pages/api/getSwappableTokens.ts create mode 100644 types/SecretString.ts create mode 100644 types/Token.ts create mode 100644 types/store/SharedSettings.ts create mode 100644 types/store/StoreState.ts create mode 100644 types/store/TokenInputState.ts create mode 100644 types/store/TokenInputs.ts create mode 100644 types/store/index.ts delete mode 100644 utils/ImageWithPlaceholder/addressTo3Colors.tsx create mode 100644 utils/ImageWithPlaceholder/generateHexColorsFromSeed.ts delete mode 100644 utils/ImageWithPlaceholder/stringToHex.tsx create mode 100644 utils/apis/getSwappableTokens.ts diff --git a/bun.lockb b/bun.lockb index f79f216939a8f4cf2c66df94c8de88a3460f2216..05f5b1c691ecbad7d42695f848154d65850d1262 100755 GIT binary patch delta 43012 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 BW&i*H delta 42451 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 diff --git a/components/app/UserWallet.tsx b/components/app/UserWallet.tsx index 2ab3f97..536c3b3 100644 --- a/components/app/UserWallet.tsx +++ b/components/app/UserWallet.tsx @@ -1,7 +1,7 @@ // UserWallet.tsx import React from "react"; import { RxCaretDown } from "react-icons/rx"; -import PlaceholderFromHexAddress from "@/components/app/molecules/PlaceholderFromHexAddress"; +import PlaceholderImageFromSeed from "@/components/app/molecules/PlaceholderImageFromSeed"; import { SecretString } from "@/types"; interface UserWalletProps { @@ -27,7 +27,7 @@ const UserWallet: React.FC = ({ {isConnected ? ( <>
- +
diff --git a/components/app/atoms/Swap/MaxButton.tsx b/components/app/atoms/Swap/MaxButton.tsx new file mode 100644 index 0000000..aeb4ec5 --- /dev/null +++ b/components/app/atoms/Swap/MaxButton.tsx @@ -0,0 +1,11 @@ +const MaxButton = ({ onClick }: { onClick: () => void }) => { + return ( + + ); +}; +export default MaxButton; diff --git a/components/app/atoms/Swap/TokenInput/InputBalanceAffordance.tsx b/components/app/atoms/Swap/TokenInput/InputBalanceAffordance.tsx new file mode 100644 index 0000000..3c047ff --- /dev/null +++ b/components/app/atoms/Swap/TokenInput/InputBalanceAffordance.tsx @@ -0,0 +1,11 @@ +export default function InputBalanceAffordance({ + balance, +}: { + balance: number; +}) { + return ( +
+ {balance.toFixed(2)} +
+ ); +} diff --git a/components/app/atoms/Swap/TokenInputBaseInput.tsx b/components/app/atoms/Swap/TokenInputBaseInput.tsx new file mode 100644 index 0000000..e6f3160 --- /dev/null +++ b/components/app/atoms/Swap/TokenInputBaseInput.tsx @@ -0,0 +1,16 @@ +export default function TokenInputBaseInput({ + amount, + handleChange, +}: { + amount: string; + handleChange: (event: React.ChangeEvent) => void; +}) { + return ( + + ); +} diff --git a/components/app/atoms/Swap/TokenSelectionModal/TokenSelectionCloseButton.tsx b/components/app/atoms/Swap/TokenSelectionModal/TokenSelectionCloseButton.tsx new file mode 100644 index 0000000..22c171d --- /dev/null +++ b/components/app/atoms/Swap/TokenSelectionModal/TokenSelectionCloseButton.tsx @@ -0,0 +1,6 @@ +{ + /*
+

Select a token

+ +
*/ +} diff --git a/components/app/atoms/Swap/TokenSelectionModal/TokenSelectionSearchBar.tsx b/components/app/atoms/Swap/TokenSelectionModal/TokenSelectionSearchBar.tsx new file mode 100644 index 0000000..51d9962 --- /dev/null +++ b/components/app/atoms/Swap/TokenSelectionModal/TokenSelectionSearchBar.tsx @@ -0,0 +1,24 @@ +import { FaSearch } from "react-icons/fa"; + +export default function TokenSelectionSearchBar({ + searchTerm, + setSearchTerm, +}: { + searchTerm: string; + setSearchTerm: React.Dispatch>; +}) { + return ( +
+
+ + setSearchTerm(e.target.value)} + /> +
+
+ ); +} diff --git a/components/app/molecules/PlaceholderFromHexAddress.tsx b/components/app/molecules/PlaceholderFromHexAddress.tsx deleted file mode 100644 index ca243fe..0000000 --- a/components/app/molecules/PlaceholderFromHexAddress.tsx +++ /dev/null @@ -1,33 +0,0 @@ -// PlaceholderFromHexAddress.tsx -import React from "react"; -import ImageWithPlaceholder from "@/components/app/ImageWithPlaceholder"; -import addressTo3Colors from "@/utils/ImageWithPlaceholder/addressTo3Colors"; -import stringToHex from "@/utils/ImageWithPlaceholder/stringToHex"; -import { SecretString } from "@/types"; - -interface PlaceholderFromHexAddressProps { - userAddress: SecretString; - size?: number; // Changed to a single number for consistency -} - -const PlaceholderFromHexAddress: React.FC = ({ - userAddress, - size = 48, // Default size as a single number, assuming a square for simplicity -}) => { - const hexAddress = stringToHex(userAddress); - const colorsString = addressTo3Colors(hexAddress); - const placeholderUrl = `https://api.dicebear.com/6.x/shapes/svg?seed=${hexAddress}&height=${size}&width=${size}&${colorsString}`; - - return ( - - ); -}; - -export default PlaceholderFromHexAddress; diff --git a/components/app/molecules/PlaceholderImageFromSeed.tsx b/components/app/molecules/PlaceholderImageFromSeed.tsx new file mode 100644 index 0000000..e594180 --- /dev/null +++ b/components/app/molecules/PlaceholderImageFromSeed.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import ImageWithPlaceholder from "@/components/app/ImageWithPlaceholder"; +import { SecretString } from "@/types"; +import generateHexColorsFromSeed from "@/utils/ImageWithPlaceholder/generateHexColorsFromSeed"; + +interface PlaceholderImageFromSeedProps { + seed: SecretString; + size?: number; +} + +const PlaceholderImageFromSeed: React.FC = ({ + seed, + size = 48, +}) => { + const [color1, color2, color3] = generateHexColorsFromSeed(seed, 3); + const colorQueryString = `shape1Color=${color1}&shape2Color=${color2}&shape3Color=${color3}`; + const placeholderUrl = `https://api.dicebear.com/6.x/shapes/svg?seed=${seed}&height=${size}&width=${size}&${colorQueryString}`; + + return ( + + ); +}; + +export default PlaceholderImageFromSeed; diff --git a/components/app/molecules/TokenInput.tsx b/components/app/molecules/TokenInput.tsx deleted file mode 100644 index c77780f..0000000 --- a/components/app/molecules/TokenInput.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React, { useState } from "react"; -import { TokenInputs, useStore } from "@/store/swapStore"; // Adjust the import path as necessary -import PlaceholderFromHexAddress from "./PlaceholderFromHexAddress"; -import { RxCaretDown } from "react-icons/rx"; -import { Token } from "@/types"; - -interface TokenInputProps { - inputIdentifier: keyof TokenInputs; // This prop specifies which token input state to interact with (e.g., "swap.pay") - maxable?: boolean; - balance: number; // Assuming balance is still passed as a prop -} - -const tokens = [ - { symbol: "sSCRT", address: "secret1k0jntykt7e4g3y88ltc60czgjuqdy4c9e8fzek" }, - { symbol: "SEFI", address: "secret15l9cqgz5uezgydrglaak5ahfac69kmx2qpd6xt" }, - { symbol: "sAAVE", address: "secret1yxwnyk8htvvq25x2z87yj0r5tqpev452fk6h5h" }, -] as Token[]; - -const TokenInput: React.FC = ({ - inputIdentifier, - maxable = false, - balance, -}) => { - const [isModalOpen, setIsModalOpen] = useState(false); - const { tokenInputs, setTokenInputProperty } = useStore(); - - // Extracting the specific token input state based on the inputIdentifier - const { token, amount } = tokenInputs[inputIdentifier]; - - const handleTokenSelect = (selectedToken: Token) => { - setTokenInputProperty(inputIdentifier, "token", selectedToken); - setIsModalOpen(false); - }; - - const handleChange = (event: React.ChangeEvent) => { - const value = event.target.value; - const numValue = parseFloat(value); - - if (!isNaN(numValue) && numValue >= 0 && numValue <= balance) { - setTokenInputProperty(inputIdentifier, "amount", value); - } else if (value === "") { - setTokenInputProperty(inputIdentifier, "amount", ""); - } - }; - - const handleMax = () => { - setTokenInputProperty(inputIdentifier, "amount", balance.toString()); - }; - - return ( -
-
- -
- {balance.toFixed(2)} -
- {maxable && ( - - )} -
setIsModalOpen(true)} - > - - {token.symbol} - -
-
- {isModalOpen && ( -
-
-
    - {tokens.map((token, index) => ( -
  • handleTokenSelect(token)} - > - {token.symbol} -
  • - ))} -
-
-
- )} -
- ); -}; - -export default TokenInput; diff --git a/components/app/molecules/TokenSelectionItem.tsx b/components/app/molecules/TokenSelectionItem.tsx new file mode 100644 index 0000000..17b8cbc --- /dev/null +++ b/components/app/molecules/TokenSelectionItem.tsx @@ -0,0 +1,32 @@ +import { Token } from "@/types"; +import PlaceholderImageFromSeed from "@/components/app/molecules/PlaceholderImageFromSeed"; +import * as Dialog from "@radix-ui/react-dialog"; + +interface TokenSelectionItemProps { + token: Token; + network: string; // Assuming you can provide network information + balance: string; // Assuming you have balance information as a string + handleTokenSelect: (token: Token) => void; +} + +const TokenSelectionItem: React.FC = ({ + token, + network, + balance, + handleTokenSelect, +}) => { + return ( + handleTokenSelect(token)} asChild> +
+ +
+ {token.symbol} + {network} +
+ {balance} +
+
+ ); +}; + +export default TokenSelectionItem; diff --git a/components/app/organisms/SwapForm/SwapForm.tsx b/components/app/organisms/SwapForm/SwapForm.tsx index 9b24800..2792217 100644 --- a/components/app/organisms/SwapForm/SwapForm.tsx +++ b/components/app/organisms/SwapForm/SwapForm.tsx @@ -1,6 +1,6 @@ import React from "react"; import InputLabel from "@/components/app/atoms/InputLabel"; -import TokenInput from "@/components/app/molecules/TokenInput"; +import TokenInput from "@/components/app/organisms/SwapForm/TokenInput"; import SwapButton from "@/components/app/atoms/SwapButton"; import { useStore } from "@/store/swapStore"; // Ensure this path matches the location of your store import DynamicField from "@/components/app/molecules/DynamicField"; diff --git a/components/app/organisms/SwapForm/TokenInput.tsx b/components/app/organisms/SwapForm/TokenInput.tsx new file mode 100644 index 0000000..31baae7 --- /dev/null +++ b/components/app/organisms/SwapForm/TokenInput.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import { useStore } from "@/store/swapStore"; +import PlaceholderImageFromSeed from "../../molecules/PlaceholderImageFromSeed"; +import { RxCaretDown } from "react-icons/rx"; +import { TokenInputs } from "@/types"; +import TokenSelectionModal from "./TokenSelectionModalRadix"; +import MaxButton from "../../atoms/Swap/MaxButton"; +import TokenInputBaseInput from "../../atoms/Swap/TokenInputBaseInput"; +import InputBalanceAffordance from "../../atoms/Swap/TokenInput/InputBalanceAffordance"; +import * as Dialog from "@radix-ui/react-dialog"; + +interface TokenInputProps { + inputIdentifier: keyof TokenInputs; + maxable?: boolean; + balance: number; +} + +const TokenInput: React.FC = ({ + inputIdentifier, + maxable = false, + balance, +}) => { + const { tokenInputs, setTokenInputProperty } = useStore(); + const { token, amount } = tokenInputs[inputIdentifier]; + const [isModalOpen, setIsModalOpen] = React.useState(false); + const handleChange = (event: React.ChangeEvent) => { + const value = event.target.value; + const numValue = parseFloat(value); + + if (!isNaN(numValue) && numValue >= 0 && numValue <= balance) { + setTokenInputProperty(inputIdentifier, "amount", value); + } else if (value === "") { + setTokenInputProperty(inputIdentifier, "amount", ""); + } + }; + + const handleMax = () => { + setTokenInputProperty(inputIdentifier, "amount", balance.toString()); + }; + + return ( + +
+ + + {maxable && } + +
+ + {token.symbol} + +
+
+
+ { + setIsModalOpen(false); + }} + /> +
+ ); +}; + +export default TokenInput; diff --git a/components/app/organisms/SwapForm/TokenSelectionModal.tsx b/components/app/organisms/SwapForm/TokenSelectionModal.tsx new file mode 100644 index 0000000..b97ec55 --- /dev/null +++ b/components/app/organisms/SwapForm/TokenSelectionModal.tsx @@ -0,0 +1,69 @@ +import React, { useState } from "react"; +import { useStore } from "@/store/swapStore"; +import { Token, TokenInputs } from "@/types"; +import { X } from "lucide-react"; +import TokenSelectionItem from "@/components/app/molecules/TokenSelectionItem"; +import TokenSelectionSearchBar from "../../atoms/Swap/TokenSelectionModal/TokenSelectionSearchBar"; + +interface TokenSelectionModalProps { + isOpen: boolean; + onClose: () => void; + inputIdentifier: keyof TokenInputs; +} + +const TokenSelectionModal: React.FC = ({ + isOpen, + onClose, + inputIdentifier, +}) => { + const { setTokenInputProperty } = useStore(); + const [searchTerm, setSearchTerm] = useState(""); + + const tokens = useStore((state) => state.swappableTokens); + + const handleTokenSelect = (selectedToken: Token) => { + setTokenInputProperty(inputIdentifier, "token", selectedToken); + onClose(); + }; + + if (!isOpen) return null; + + return ( +
+
+
+

Select a token

+ +
+ + {/* Recent tokens and tokens list will go here */} +
+ YOUR TOKENS +
+
+ {tokens + .filter((token) => + token.symbol.toLowerCase().includes(searchTerm.toLowerCase()) + ) + .map((token, index) => ( + + ))} +
+
+
+ ); +}; + +export default TokenSelectionModal; diff --git a/components/app/organisms/SwapForm/TokenSelectionModalRadix.tsx b/components/app/organisms/SwapForm/TokenSelectionModalRadix.tsx new file mode 100644 index 0000000..6626c06 --- /dev/null +++ b/components/app/organisms/SwapForm/TokenSelectionModalRadix.tsx @@ -0,0 +1,79 @@ +import React, { useState } from "react"; +import * as Dialog from "@radix-ui/react-dialog"; +import { Cross1Icon } from "@radix-ui/react-icons"; // Assuming usage of Radix Icons for the X icon +import TokenSelectionItem from "@/components/app/molecules/TokenSelectionItem"; +import TokenSelectionSearchBar from "@/components/app/atoms/Swap/TokenSelectionModal/TokenSelectionSearchBar"; +import { useStore } from "@/store/swapStore"; +import { Token, TokenInputs } from "@/types"; + +interface TokenSelectionModalProps { + isOpen: boolean; + onClose: () => void; + inputIdentifier: keyof TokenInputs; +} + +const TokenSelectionModal: React.FC = ({ + isOpen, + onClose, + inputIdentifier, +}) => { + const { setTokenInputProperty } = useStore(); + const [searchTerm, setSearchTerm] = useState(""); + const tokens = useStore((state) => state.swappableTokens); + + const handleTokenSelect = (selectedToken: Token) => { + setTokenInputProperty(inputIdentifier, "token", selectedToken); + onClose(); + }; + + return ( + <> + + + +
+ + Select a token + + + + +
+ + + +
+ YOUR TOKENS +
+ +
+ {tokens + .filter((token) => + token.symbol.toLowerCase().includes(searchTerm.toLowerCase()) + ) + .map((token, index) => ( + // + + // {/* */} + ))} +
+
+ + ); +}; + +export default TokenSelectionModal; diff --git a/package.json b/package.json index 63ecf7b..d44326e 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "react-hook-form": "^7.51.1", "react-icons": "^5.0.1", "react-vega": "^7.6.0", + "seedrandom": "^3.0.5", "tailwind-merge": "^2.2.2", "tailwindcss-animate": "^1.0.7", "vega": "^5.28.0", @@ -40,6 +41,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/seedrandom": "^3.0.8", "autoprefixer": "^10.0.1", "eslint": "^8", "eslint-config-next": "14.1.3", diff --git a/pages/_app.tsx b/pages/_app.tsx index dbf5069..cd4f805 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -2,8 +2,21 @@ import "@radix-ui/themes/styles.css"; import "@/styles/globals.css"; import type { AppProps } from "next/app"; import { Theme } from "@radix-ui/themes"; +import { useEffect } from "react"; +import { getSwappableTokens } from "@/utils/apis/getSwappableTokens"; +import { useStore } from "@/store/swapStore"; export default function App({ Component, pageProps }: AppProps) { + const setSwappableTokens = useStore((state) => state.setSwappableTokens); + + useEffect(() => { + const fetchTokens = async () => { + const tokens = await getSwappableTokens(); + setSwappableTokens(tokens); + }; + + fetchTokens(); + }, [setSwappableTokens]); // Dependency array is empty to ensure this runs once return ( diff --git a/pages/api/getSwappableTokens.ts b/pages/api/getSwappableTokens.ts new file mode 100644 index 0000000..1c85324 --- /dev/null +++ b/pages/api/getSwappableTokens.ts @@ -0,0 +1,24 @@ +import { Token } from "@/types/Token"; +import { NextApiRequest, NextApiResponse } from "next"; + +export default function getSwappableTokens( + req: NextApiRequest, + res: NextApiResponse +) { + const dummyTokens = [ + { + symbol: "sSCRT", + address: "secret1k0jntykt7e4g3y88ltc60czgjuqdy4c9e8fzek", + }, + { + symbol: "SEFI", + address: "secret15l9cqgz5uezgydrglaak5ahfac69kmx2qpd6xt", + }, + { + symbol: "sAAVE", + address: "secret1yxwnyk8htvvq25x2z87yj0r5tqpev452fk6h5h", + }, + ] as Token[]; + + res.status(200).json(dummyTokens); +} diff --git a/pages/app/pools/index.tsx b/pages/app/pools/index.tsx index 3fcdd1d..baf5814 100644 --- a/pages/app/pools/index.tsx +++ b/pages/app/pools/index.tsx @@ -1,5 +1,5 @@ import AppLayout from "@/components/app/compositions/AppLayout"; -import PlaceholderFromHexAddress from "@/components/app/molecules/PlaceholderFromHexAddress"; +import PlaceholderImageFromSeed from "@/components/app/molecules/PlaceholderImageFromSeed"; import { SecretString } from "@/types"; import Link from "next/link"; @@ -53,8 +53,8 @@ export default function PoolsPage() { href={`/app/pool/${pool.userAddress}`} >
-
diff --git a/pages/app/tokens/index.tsx b/pages/app/tokens/index.tsx index ac741e3..6ad712f 100644 --- a/pages/app/tokens/index.tsx +++ b/pages/app/tokens/index.tsx @@ -1,11 +1,11 @@ import AppLayout from "@/components/app/compositions/AppLayout"; -import PlaceholderFromHexAddress from "@/components/app/molecules/PlaceholderFromHexAddress"; +import PlaceholderImageFromSeed from "@/components/app/molecules/PlaceholderImageFromSeed"; import { SecretString } from "@/types"; import Link from "next/link"; const tokens = [ { - userAddress: "0x6545454465153231231231", + userAddress: "secret16545454465153231231231", name: "SCRT", network: "Secret Network", price: "$0.10", @@ -14,7 +14,7 @@ const tokens = [ volume: "$48K", }, { - userAddress: "0xacd6a516c51a651da65c165d1", + userAddress: "secret1acd6a516c51a651da65c165d1", name: "ADMT", network: "Secret Network", price: "$0.20", @@ -53,8 +53,8 @@ export default function TokensPage() { href={`/app/token/${token.userAddress}`} >
-
diff --git a/store/swapStore.ts b/store/swapStore.ts index ee8a4f6..9c8c8a4 100644 --- a/store/swapStore.ts +++ b/store/swapStore.ts @@ -1,46 +1,26 @@ import { create } from "zustand"; -import { Token } from "@/types"; - -interface TokenInputState { - token: Token; - amount: string; -} - -// Extending TokenInputs to hold identifiers for gas and slippage -export interface TokenInputs { - "swap.pay": TokenInputState; - "swap.receive": TokenInputState; -} - -// New structure for shared settings like gas and slippage across forms -interface SharedSettings { - slippage: number; - gas: number; -} - -export interface StoreState { - tokenInputs: TokenInputs; - sharedSettings: SharedSettings; // Shared settings for transactions - setTokenInputProperty: ( - inputIdentifier: keyof TokenInputs, - property: T, - value: TokenInputState[T] - ) => void; - // Generalizing setters for shared settings - setSharedSetting: ( - setting: T, - value: SharedSettings[T] - ) => void; -} +import { + SharedSettings, + StoreState, + Token, + TokenInputState, + TokenInputs, +} from "@/types"; export const useStore = create((set) => ({ tokenInputs: { "swap.pay": { - token: { symbol: "sSCRT", address: "secret1k0jnty" }, + token: { + symbol: "sSCRT", + address: "secret1k0jntykt7e4g3y88ltc60czgjuqdy4c9e8fzek", + }, amount: "", }, "swap.receive": { - token: { symbol: "SEFI", address: "secret15l9" }, + token: { + symbol: "SEFI", + address: "secret15l9cqgz5uezgydrglaak5ahfac69kmx2qpd6xt", + }, amount: "", }, }, @@ -71,4 +51,6 @@ export const useStore = create((set) => ({ ...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 })); diff --git a/tailwind.config.ts b/tailwind.config.ts index 8256947..dcd4889 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -34,6 +34,7 @@ const config = { }, app: { box: "#30364e", + boxHighlight: "#444a5f", input: "#181b27", selectTrigger: "#242732", buttonDisabled: "#888ea6", diff --git a/types/SecretString.ts b/types/SecretString.ts new file mode 100644 index 0000000..fceb04a --- /dev/null +++ b/types/SecretString.ts @@ -0,0 +1 @@ +export type SecretString = `secret1${string}`; diff --git a/types/Token.ts b/types/Token.ts new file mode 100644 index 0000000..b707cbc --- /dev/null +++ b/types/Token.ts @@ -0,0 +1,6 @@ +import { SecretString } from "./SecretString"; + +export interface Token { + symbol: string; + address: SecretString; +} diff --git a/types/index.ts b/types/index.ts index 13abd35..8af07b0 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,6 +1,3 @@ -export type SecretString = `secret1${string}`; - -export interface Token { - symbol: string; - address: SecretString; -} +export * from "@/types/store"; +export * from "@/types/Token"; +export * from "@/types/SecretString"; diff --git a/types/store/SharedSettings.ts b/types/store/SharedSettings.ts new file mode 100644 index 0000000..0180b66 --- /dev/null +++ b/types/store/SharedSettings.ts @@ -0,0 +1,4 @@ +export interface SharedSettings { + slippage: number; + gas: number; +} diff --git a/types/store/StoreState.ts b/types/store/StoreState.ts new file mode 100644 index 0000000..bf7e31b --- /dev/null +++ b/types/store/StoreState.ts @@ -0,0 +1,20 @@ +import { Token } from "@/types/Token"; +import { SharedSettings } from "@/types/store/SharedSettings"; +import { TokenInputState } from "@/types/store/TokenInputState"; +import { TokenInputs } from "@/types/store/TokenInputs"; + +export interface StoreState { + tokenInputs: TokenInputs; + sharedSettings: SharedSettings; + setTokenInputProperty: ( + inputIdentifier: keyof TokenInputs, + property: T, + value: TokenInputState[T] + ) => void; + setSharedSetting: ( + setting: T, + value: SharedSettings[T] + ) => void; + swappableTokens: Token[]; + setSwappableTokens: (tokens: Token[]) => void; +} diff --git a/types/store/TokenInputState.ts b/types/store/TokenInputState.ts new file mode 100644 index 0000000..d51ad7c --- /dev/null +++ b/types/store/TokenInputState.ts @@ -0,0 +1,6 @@ +import { Token } from "../Token"; + +export interface TokenInputState { + token: Token; + amount: string; +} diff --git a/types/store/TokenInputs.ts b/types/store/TokenInputs.ts new file mode 100644 index 0000000..0f7fec8 --- /dev/null +++ b/types/store/TokenInputs.ts @@ -0,0 +1,6 @@ +import { TokenInputState } from "./TokenInputState"; + +export interface TokenInputs { + "swap.pay": TokenInputState; + "swap.receive": TokenInputState; +} diff --git a/types/store/index.ts b/types/store/index.ts new file mode 100644 index 0000000..2f9f931 --- /dev/null +++ b/types/store/index.ts @@ -0,0 +1,4 @@ +export * from "./SharedSettings"; +export * from "./TokenInputState"; +export * from "./TokenInputs"; +export * from "./StoreState"; diff --git a/utils/ImageWithPlaceholder/addressTo3Colors.tsx b/utils/ImageWithPlaceholder/addressTo3Colors.tsx deleted file mode 100644 index 08d4df7..0000000 --- a/utils/ImageWithPlaceholder/addressTo3Colors.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { SecretString } from "@/types"; - -export default function addressTo3Colors(address: SecretString): string { - const hexColors = address.slice(2); - const color1 = hexColors.slice(0, 6); - const color2 = hexColors.slice(6, 12); - const color3 = hexColors.slice(12, 18); - return `shape1Color=${color1}&shape2Color=${color2}&shape3Color=${color3}`; -} diff --git a/utils/ImageWithPlaceholder/generateHexColorsFromSeed.ts b/utils/ImageWithPlaceholder/generateHexColorsFromSeed.ts new file mode 100644 index 0000000..6d8cfee --- /dev/null +++ b/utils/ImageWithPlaceholder/generateHexColorsFromSeed.ts @@ -0,0 +1,26 @@ +import seedrandom from "seedrandom"; + +/** + * Generates an array of hex colors based on a seed. + * + * @param seed - The seed used to generate the colors. + * @param numberOfColors - The number of colors to generate. Default is 3. + * @returns An array of hex colors. + */ +export default function generateHexColorsFromSeed( + seed: string, + numberOfColors: number = 3 +): string[] { + const rng = seedrandom(seed); + const colors: string[] = []; + + for (let i = 0; i < numberOfColors; i++) { + // Generate a random color + let color = Math.floor(rng() * (0xffffff + 1)).toString(16); + // Pad the string with leading zeros, if necessary, to ensure it has length 6 + color = color.padStart(6, "0"); + colors.push(color); + } + + return colors; +} diff --git a/utils/ImageWithPlaceholder/stringToHex.tsx b/utils/ImageWithPlaceholder/stringToHex.tsx deleted file mode 100644 index 6a8a299..0000000 --- a/utils/ImageWithPlaceholder/stringToHex.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { SecretString } from "@/types"; - -function stringToHex(str: string): SecretString { - return str - .split("") - .map((c) => ("0" + c.charCodeAt(0).toString(16)).slice(-2)) - .join("") as SecretString; -} - -export default stringToHex; diff --git a/utils/apis/getSwappableTokens.ts b/utils/apis/getSwappableTokens.ts new file mode 100644 index 0000000..03211c6 --- /dev/null +++ b/utils/apis/getSwappableTokens.ts @@ -0,0 +1,7 @@ +import { Token } from "@/types"; + +export const getSwappableTokens = async (): Promise => { + const response = await fetch("/api/getSwappableTokens"); + const data: Token[] = await response.json(); + return data; +};