From 5afac0b5f0d209a086ad07d0214ed76a5b9ec778 Mon Sep 17 00:00:00 2001 From: Iris Date: Tue, 24 Oct 2023 10:36:15 +0200 Subject: [PATCH 01/20] feat: start porting to starknet-react v2 --- components/UI/changeWallet.tsx | 11 +++-- components/UI/navbar.tsx | 77 ++++++++++++++++-------------- components/UI/wallets.tsx | 11 +++-- components/navbar/walletButton.tsx | 43 +++++++++-------- components/quests/reward.tsx | 36 +++++++++----- hooks/useTransactionManager.ts | 13 +++++ package.json | 8 ++-- pages/[addressOrDomain].tsx | 58 +++++++++++----------- pages/_app.tsx | 30 ++++++++++-- pages/not-connected.tsx | 11 +++-- 10 files changed, 182 insertions(+), 116 deletions(-) create mode 100644 hooks/useTransactionManager.ts diff --git a/components/UI/changeWallet.tsx b/components/UI/changeWallet.tsx index f8367aa8..c669c372 100644 --- a/components/UI/changeWallet.tsx +++ b/components/UI/changeWallet.tsx @@ -1,6 +1,6 @@ import React from "react"; import styles from "../../styles/components/wallets.module.css"; -import { Connector, useConnectors } from "@starknet-react/core"; +import { Connector, useConnect } from "@starknet-react/core"; import Button from "./button"; import { FunctionComponent } from "react"; import { Modal } from "@mui/material"; @@ -17,13 +17,14 @@ const ChangeWallet: FunctionComponent = ({ closeWallet, hasWallet, }) => { - const { connect, connectors } = useConnectors(); + const { connect, connectors } = useConnect(); const downloadLinks = useGetDiscoveryWallets( getDiscoveryWallets.getDiscoveryWallets() ); function connectWallet(connector: Connector): void { - connect(connector); + console.log("connecting", connector); + connect({ connector }); closeWallet(); } @@ -59,7 +60,9 @@ const ChangeWallet: FunctionComponent = ({ diff --git a/hooks/useTransactionManager.ts b/hooks/useTransactionManager.ts new file mode 100644 index 00000000..06edf648 --- /dev/null +++ b/hooks/useTransactionManager.ts @@ -0,0 +1,13 @@ +import { useAtom } from "jotai"; +import { atomWithStorage } from "jotai/utils"; + +const transactionsAtom = atomWithStorage("userTransactions", []); + +export function useTransactionManager() { + const [value, setValue] = useAtom(transactionsAtom); + + return { + hashes: value, + addTransaction: (hash: string) => setValue((prev) => [...prev, hash]), + }; +} diff --git a/package.json b/package.json index 5c3f36be..715800a3 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,15 @@ "@nimiq/style": "^0.8.5", "@react-three/drei": "^9.80.3", "@react-three/fiber": "^8.13.6", - "@starknet-react/core": "^1.0.3", + "@starknet-react/chains": "^0.1.0-next.0", + "@starknet-react/core": "^2.0.0-next.0", "@use-gesture/react": "^10.2.27", "@vercel/analytics": "^0.1.5", "big.js": "^6.2.1", "bn.js": "^5.2.1", "chart.js": "^4.3.0", - "get-starknet-core": "^3.1.0", + "get-starknet-core": "^3.2.0", + "jotai": "^2.5.0", "lottie-react": "^2.4.0", "maath": "^0.7.0", "mongodb": "^4.12.1", @@ -38,7 +40,7 @@ "react-intersection-observer": "^9.5.2", "react-loader-spinner": "^5.2.0", "react-use": "^17.4.0", - "starknet": "5.14.1", + "starknet": "^5.19.5", "starknetid.js": "^1.5.2", "three": "^0.155.0", "twitter-api-sdk": "^1.2.1" diff --git a/pages/[addressOrDomain].tsx b/pages/[addressOrDomain].tsx index 3b2bdedf..092d4ca8 100644 --- a/pages/[addressOrDomain].tsx +++ b/pages/[addressOrDomain].tsx @@ -3,7 +3,7 @@ import type { NextPage } from "next"; import styles from "../styles/profile.module.css"; import { useRouter } from "next/router"; import { isHexString } from "../utils/stringService"; -import { Connector, useAccount, useConnectors } from "@starknet-react/core"; +import { Connector, useAccount, useConnect } from "@starknet-react/core"; import { StarknetIdJsContext } from "../context/StarknetIdJsProvider"; import { hexToDecimal } from "../utils/feltService"; import { minifyAddress } from "../utils/stringService"; @@ -18,7 +18,7 @@ const AddressOrDomain: NextPage = () => { const router = useRouter(); const { addressOrDomain } = router.query; const { address } = useAccount(); - const { connectors, connect } = useConnectors(); + const { connectors, connect } = useConnect(); const { starknetIdNavigator } = useContext(StarknetIdJsContext); const [initProfile, setInitProfile] = useState(false); const [identity, setIdentity] = useState(); @@ -32,37 +32,37 @@ const AddressOrDomain: NextPage = () => { useEffect(() => setNotFound(false), [dynamicRoute]); - useLayoutEffect(() => { - async function tryAutoConnect(connectors: Connector[]) { - const lastConnectedConnectorId = - localStorage.getItem("lastUsedConnector"); - if (lastConnectedConnectorId === null) { - return; - } + // useLayoutEffect(() => { + // async function tryAutoConnect(connectors: Connector[]) { + // const lastConnectedConnectorId = + // localStorage.getItem("lastUsedConnector"); + // if (lastConnectedConnectorId === null) { + // return; + // } - const lastConnectedConnector = connectors.find( - (connector) => connector.id === lastConnectedConnectorId - ); - if (lastConnectedConnector === undefined) { - return; - } + // const lastConnectedConnector = connectors.find( + // (connector) => connector.id === lastConnectedConnectorId + // ); + // if (lastConnectedConnector === undefined) { + // return; + // } - try { - if (!(await lastConnectedConnector.ready())) { - // Not authorized anymore. - return; - } + // try { + // if (!(await lastConnectedConnector.ready())) { + // // Not authorized anymore. + // return; + // } - await connect(lastConnectedConnector); - } catch { - // no-op - } - } + // await connect(lastConnectedConnector); + // } catch { + // // no-op + // } + // } - if (!address) { - tryAutoConnect(connectors); - } - }, []); + // if (!address) { + // tryAutoConnect(connectors); + // } + // }, []); useEffect(() => { setInitProfile(false); diff --git a/pages/_app.tsx b/pages/_app.tsx index 94131e33..5e129280 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -4,19 +4,36 @@ import type { AppProps } from "next/app"; import Navbar from "../components/UI/navbar"; import Head from "next/head"; import { ThemeProvider } from "@mui/material"; -import { InjectedConnector, StarknetConfig } from "@starknet-react/core"; +import { + StarknetConfig, + alchemyProvider, + argent, + braavos, + publicProvider, +} from "@starknet-react/core"; import { Analytics } from "@vercel/analytics/react"; import { StarknetIdJsProvider } from "../context/StarknetIdJsProvider"; import { createTheme } from "@mui/material/styles"; import Footer from "../components/UI/footer"; import { QuestsContextProvider } from "../context/QuestsProvider"; import { WebWalletConnector } from "@argent/starknet-react-webwallet-connector"; +import { goerli, mainnet } from "@starknet-react/chains"; function MyApp({ Component, pageProps }: AppProps) { + const chains = [ + process.env.NEXT_PUBLIC_IS_TESTNET === "true" ? goerli : mainnet, + ]; + const providers = [ + process.env.NEXT_PUBLIC_IS_TESTNET === "true" + ? publicProvider() + : alchemyProvider({ + apiKey: process.env.NEXT_PUBLIC_ALCHEMY_KEY as string, + }), + ]; const connectors = useMemo( () => [ - new InjectedConnector({ options: { id: "braavos" } }), - new InjectedConnector({ options: { id: "argentX" } }), + braavos(), + argent(), new WebWalletConnector({ url: process.env.NEXT_PUBLIC_IS_TESTNET === "true" @@ -44,7 +61,12 @@ function MyApp({ Component, pageProps }: AppProps) { }); return ( - + diff --git a/pages/not-connected.tsx b/pages/not-connected.tsx index 543a21f9..bf0fa7c3 100644 --- a/pages/not-connected.tsx +++ b/pages/not-connected.tsx @@ -1,5 +1,5 @@ import { NextPage } from "next"; -import { useAccount, useConnectors } from "@starknet-react/core"; +import { useAccount, useConnect } from "@starknet-react/core"; import React, { useEffect, useState } from "react"; import { useRouter } from "next/router"; import Wallets from "../components/UI/wallets"; @@ -7,7 +7,7 @@ import ErrorScreen from "../components/UI/screens/errorScreen"; const NotConnected: NextPage = () => { const { address } = useAccount(); - const { available, connect } = useConnectors(); + const { connect } = useConnect(); const { push } = useRouter(); const [hasWallet, setHasWallet] = useState(true); @@ -21,9 +21,10 @@ const NotConnected: NextPage = () => { errorMessage="You're not connected !" buttonText="Connect wallet" onClick={ - available.length === 1 - ? () => connect(available[0]) - : () => setHasWallet(true) + () => setHasWallet(true) + // available.length === 1 + // ? () => connect(available[0]) + // : () => setHasWallet(true) } /> setHasWallet(false)} hasWallet={hasWallet} /> From 0fa151e81f6a336c839fd0468641c6131a940f92 Mon Sep 17 00:00:00 2001 From: Iris Date: Wed, 25 Oct 2023 13:54:02 +0200 Subject: [PATCH 02/20] feat: port to starknet-react v2 + add notifications --- abi/erc20_abi.json | 340 ++++++++++++++++++ .../iconsComponents/icons/closeCircleIcon.tsx | 22 ++ .../UI/iconsComponents/icons/doneIcon.tsx | 20 ++ .../UI/iconsComponents/icons/loaderIcon.tsx | 20 ++ .../icons/notificationIcon.tsx | 24 ++ .../icons/notificationIconUnread.tsx | 29 ++ components/UI/modalNotifications.tsx | 58 +++ components/UI/navbar.tsx | 63 +++- .../UI/notifications/notificationDetail.tsx | 57 +++ components/navbar/walletButton.tsx | 41 +-- components/quests/questDetails.tsx | 1 + components/quests/reward.tsx | 38 +- hooks/useTransactionManager.ts | 69 +++- pages/index.tsx | 44 ++- styles/components/notifications.module.css | 136 +++++++ types/frontTypes.d.ts | 18 +- utils/timeService.ts | 88 +++++ 17 files changed, 996 insertions(+), 72 deletions(-) create mode 100644 abi/erc20_abi.json create mode 100644 components/UI/iconsComponents/icons/closeCircleIcon.tsx create mode 100644 components/UI/iconsComponents/icons/doneIcon.tsx create mode 100644 components/UI/iconsComponents/icons/loaderIcon.tsx create mode 100644 components/UI/iconsComponents/icons/notificationIcon.tsx create mode 100644 components/UI/iconsComponents/icons/notificationIconUnread.tsx create mode 100644 components/UI/modalNotifications.tsx create mode 100644 components/UI/notifications/notificationDetail.tsx create mode 100644 styles/components/notifications.module.css create mode 100644 utils/timeService.ts diff --git a/abi/erc20_abi.json b/abi/erc20_abi.json new file mode 100644 index 00000000..50cbbc9c --- /dev/null +++ b/abi/erc20_abi.json @@ -0,0 +1,340 @@ +[ + { + "members": [ + { + "name": "low", + "offset": 0, + "type": "felt" + }, + { + "name": "high", + "offset": 1, + "type": "felt" + } + ], + "name": "Uint256", + "size": 2, + "type": "struct" + }, + { + "data": [ + { + "name": "from_", + "type": "felt" + }, + { + "name": "to", + "type": "felt" + }, + { + "name": "value", + "type": "Uint256" + } + ], + "keys": [], + "name": "Transfer", + "type": "event" + }, + { + "data": [ + { + "name": "owner", + "type": "felt" + }, + { + "name": "spender", + "type": "felt" + }, + { + "name": "value", + "type": "Uint256" + } + ], + "keys": [], + "name": "Approval", + "type": "event" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "name", + "type": "felt" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "symbol", + "type": "felt" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "totalSupply", + "type": "Uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "decimals", + "type": "felt" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "account", + "type": "felt" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "Uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "owner", + "type": "felt" + }, + { + "name": "spender", + "type": "felt" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "remaining", + "type": "Uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "permittedMinter", + "outputs": [ + { + "name": "minter", + "type": "felt" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "name": "res", + "type": "felt" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "get_version", + "outputs": [ + { + "name": "version", + "type": "felt" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "get_identity", + "outputs": [ + { + "name": "identity", + "type": "felt" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "init_vector_len", + "type": "felt" + }, + { + "name": "init_vector", + "type": "felt*" + } + ], + "name": "initialize", + "outputs": [], + "type": "function" + }, + { + "inputs": [ + { + "name": "recipient", + "type": "felt" + }, + { + "name": "amount", + "type": "Uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "success", + "type": "felt" + } + ], + "type": "function" + }, + { + "inputs": [ + { + "name": "sender", + "type": "felt" + }, + { + "name": "recipient", + "type": "felt" + }, + { + "name": "amount", + "type": "Uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "success", + "type": "felt" + } + ], + "type": "function" + }, + { + "inputs": [ + { + "name": "spender", + "type": "felt" + }, + { + "name": "amount", + "type": "Uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "success", + "type": "felt" + } + ], + "type": "function" + }, + { + "inputs": [ + { + "name": "spender", + "type": "felt" + }, + { + "name": "added_value", + "type": "Uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "name": "success", + "type": "felt" + } + ], + "type": "function" + }, + { + "inputs": [ + { + "name": "spender", + "type": "felt" + }, + { + "name": "subtracted_value", + "type": "Uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "name": "success", + "type": "felt" + } + ], + "type": "function" + }, + { + "inputs": [ + { + "name": "recipient", + "type": "felt" + }, + { + "name": "amount", + "type": "Uint256" + } + ], + "name": "permissionedMint", + "outputs": [], + "type": "function" + }, + { + "inputs": [ + { + "name": "account", + "type": "felt" + }, + { + "name": "amount", + "type": "Uint256" + } + ], + "name": "permissionedBurn", + "outputs": [], + "type": "function" + } +] diff --git a/components/UI/iconsComponents/icons/closeCircleIcon.tsx b/components/UI/iconsComponents/icons/closeCircleIcon.tsx new file mode 100644 index 00000000..1feeeb39 --- /dev/null +++ b/components/UI/iconsComponents/icons/closeCircleIcon.tsx @@ -0,0 +1,22 @@ +import React, { FunctionComponent } from "react"; + +const CloseCircleIcon: FunctionComponent = ({ width, color }) => { + return ( + + + + ); +}; + +export default CloseCircleIcon; diff --git a/components/UI/iconsComponents/icons/doneIcon.tsx b/components/UI/iconsComponents/icons/doneIcon.tsx new file mode 100644 index 00000000..f53eac71 --- /dev/null +++ b/components/UI/iconsComponents/icons/doneIcon.tsx @@ -0,0 +1,20 @@ +import React, { FunctionComponent } from "react"; + +const DoneIcon: FunctionComponent = ({ width, color }) => { + return ( + + + + ); +}; + +export default DoneIcon; diff --git a/components/UI/iconsComponents/icons/loaderIcon.tsx b/components/UI/iconsComponents/icons/loaderIcon.tsx new file mode 100644 index 00000000..bf198e8c --- /dev/null +++ b/components/UI/iconsComponents/icons/loaderIcon.tsx @@ -0,0 +1,20 @@ +import React, { FunctionComponent } from "react"; + +const LoaderIcon: FunctionComponent = ({ width, color }) => { + return ( + + + + ); +}; + +export default LoaderIcon; diff --git a/components/UI/iconsComponents/icons/notificationIcon.tsx b/components/UI/iconsComponents/icons/notificationIcon.tsx new file mode 100644 index 00000000..53571498 --- /dev/null +++ b/components/UI/iconsComponents/icons/notificationIcon.tsx @@ -0,0 +1,24 @@ +import React, { FunctionComponent } from "react"; + +const NotificationIcon: FunctionComponent = ({ width, color }) => { + return ( + + + + + ); +}; + +export default NotificationIcon; diff --git a/components/UI/iconsComponents/icons/notificationIconUnread.tsx b/components/UI/iconsComponents/icons/notificationIconUnread.tsx new file mode 100644 index 00000000..962c0488 --- /dev/null +++ b/components/UI/iconsComponents/icons/notificationIconUnread.tsx @@ -0,0 +1,29 @@ +import React, { FunctionComponent } from "react"; + +const NotificationUnreadIcon: FunctionComponent = ({ + width, + color, + secondColor, +}) => { + return ( + + + + + + ); +}; + +export default NotificationUnreadIcon; diff --git a/components/UI/modalNotifications.tsx b/components/UI/modalNotifications.tsx new file mode 100644 index 00000000..e5fdfb98 --- /dev/null +++ b/components/UI/modalNotifications.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import styles from "../../styles/components/notifications.module.css"; +import { FunctionComponent } from "react"; +import { Modal } from "@mui/material"; +import NotificationDetail from "./notifications/notificationDetail"; + +type ModalNotificationsProps = { + closeModal: () => void; + open: boolean; + notifications: SQNotification[]; +}; + +const ModalNotifications: FunctionComponent = ({ + closeModal, + open, + notifications, +}) => { + return ( + +
+ +

Notifications

+
+
+ {notifications.length > 0 ? ( + notifications.map((notification, index) => { + return ( + + ); + }) + ) : ( +

You don't have any notifications yet. Start some quests!

+ )} +
+
+
+ ); +}; +export default ModalNotifications; diff --git a/components/UI/navbar.tsx b/components/UI/navbar.tsx index 311ab201..7f4fcbe3 100644 --- a/components/UI/navbar.tsx +++ b/components/UI/navbar.tsx @@ -25,6 +25,11 @@ import { useRouter } from "next/router"; import theme from "../../styles/theme"; import { FaDiscord, FaTwitter } from "react-icons/fa"; import WalletButton from "../navbar/walletButton"; +import NotificationIcon from "./iconsComponents/icons/notificationIcon"; +import ModalNotifications from "./modalNotifications"; +import { useTransactionManager } from "../../hooks/useTransactionManager"; +import { hexToDecimal } from "../../utils/feltService"; +import NotificationUnreadIcon from "./iconsComponents/icons/notificationIconUnread"; const Navbar: FunctionComponent = () => { const [nav, setNav] = useState(false); @@ -44,7 +49,10 @@ const Navbar: FunctionComponent = () => { process.env.NEXT_PUBLIC_IS_TESTNET === "true" ? "testnet" : "mainnet"; const [navbarBg, setNavbarBg] = useState(false); const [showWallet, setShowWallet] = useState(false); - const router = useRouter(); + // const router = useRouter(); + const [showNotifications, setShowNotifications] = useState(false); + const { notifications, unreadNotifications, updateReadStatus } = + useTransactionManager(hexToDecimal(address)); // useEffect(() => { // // to handle autoconnect starknet-react adds connector id in local storage @@ -78,11 +86,7 @@ const Navbar: FunctionComponent = () => { useEffect(() => { if (!isConnected) return; - - console.log("chain", chain); - provider.getChainId().then((chainId) => { - console.log("chainId", chainId); const isWrongNetwork = (chainId === constants.StarknetChainId.SN_GOERLI && network === "mainnet") || @@ -92,17 +96,17 @@ const Navbar: FunctionComponent = () => { }); }, [provider, network, isConnected]); - const tryConnect = useCallback( - async (connector: Connector) => { - if (address) return; - if (await connector.ready()) { - connect({ connector }); + // const tryConnect = useCallback( + // async (connector: Connector) => { + // if (address) return; + // if (await connector.ready()) { + // connect({ connector }); - return; - } - }, - [address, connectors] - ); + // return; + // } + // }, + // [address, connectors] + // ); function disconnectByClick(): void { disconnect(); @@ -139,8 +143,7 @@ const Navbar: FunctionComponent = () => { // Refresh available connectors before showing wallet modal function refreshAndShowWallet(): void { - //todo: no way to refresh ? - // refresh(); + // refresh(); - seems it doesn't exist anymore setHasWallet(true); } @@ -159,6 +162,11 @@ const Navbar: FunctionComponent = () => { }; }, []); + function openNotificationModal(): void { + setShowNotifications(true); + updateReadStatus(); + } + return ( <>