From 66031a068005c27c396763d8115b881eb26a64d4 Mon Sep 17 00:00:00 2001 From: JaniAnttonen Date: Fri, 4 Mar 2022 16:34:15 +0200 Subject: [PATCH 1/4] Add offline/online event listeners --- pages/_app.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index 2786075..3ee139f 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -4,9 +4,9 @@ import dynamic from "next/dynamic"; import Head from "next/head"; import { useRouter } from "next/router"; import { useEffect } from "react"; -import { Provider } from "urql"; import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; +import { Provider } from "urql"; import Box from "../components/Box"; import Footer from "../components/Footer"; import Navigation from "../components/Navigation"; @@ -15,7 +15,7 @@ import { state, useSnapshot } from "../state"; import { connectWallet, ensureCorrectChain, - initWalletSubscriptions, + initWalletSubscriptions } from "../state/actions/wallet"; import { darkTheme } from "../stitches.config"; import "../styles/globals.css"; @@ -51,10 +51,14 @@ function MyApp({ Component, pageProps }: AppProps) { window.ethereum.on("accountsChanged", connectWallet); window.ethereum.on("chainChanged", ensureCorrectChain); + window.addEventListener('offline', () => state.online = false); + window.addEventListener('online', () => state.online = true); return () => { window.ethereum.removeListener("accountsChanged", connectWallet); window.ethereum.removeListener("chainChanged", ensureCorrectChain); + window.removeEventListener('offline', () => state.online = false); + window.removeEventListener('online', () => state.online = true); }; }, [signer]); From 2a22a098d2aa29028acb21cb9e6b0954ba0f3a16 Mon Sep 17 00:00:00 2001 From: JaniAnttonen Date: Fri, 4 Mar 2022 16:35:38 +0200 Subject: [PATCH 2/4] Smoothen Loader, add a fade in --- components/Loader/index.tsx | 24 ++++++++++++++--- components/MyStakes/index.tsx | 4 +-- state/index.ts | 51 ++++++++++++++++++----------------- 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/components/Loader/index.tsx b/components/Loader/index.tsx index 4bfba2e..1992c28 100644 --- a/components/Loader/index.tsx +++ b/components/Loader/index.tsx @@ -3,10 +3,22 @@ import Box from "../Box"; const gradientAnimation = keyframes({ "0%": { + backgroundPosition: "-400% 50%" + }, + "50%": { backgroundPosition: "0% 50%" }, "100%": { - backgroundPosition: "100% 50%" + backgroundPosition: "400% 50%" + } +}) + +const fadeInAnimation = keyframes({ + "0%": { + opacity: "0" + }, + "100%": { + opacity: "1" } }) @@ -18,9 +30,15 @@ const Loader = styled(Box, { width: "100%", minWidth: "150px", minHeight: "1em", - background: "linear-gradient(90deg, rgba(241,235,212,0.2), rgba(16,7,15,0.2), rgba(241,235,212,0.2))", + opacity: 0, + background: "linear-gradient(90deg, rgba(241,235,212,0.1), rgba(16,7,15,0.15), rgba(241,235,212,0.1))", backgroundSize: "400% 400%", - animation: `${gradientAnimation} 2s linear infinite` + animationName: `${gradientAnimation}, ${fadeInAnimation}`, + animationDuration: "12s, 1s", + animationTimingFunction: "linear, ease", + animationIterationCount: "infinite, 1", + animationDelay: "0, 400ms", + animationFillMode: "forwards" }) export default Loader diff --git a/components/MyStakes/index.tsx b/components/MyStakes/index.tsx index ea2e6c6..1d1fa40 100644 --- a/components/MyStakes/index.tsx +++ b/components/MyStakes/index.tsx @@ -1,9 +1,9 @@ import { BigNumber, ethers } from "ethers"; import Image from "next/image"; +import { CaretDown } from "phosphor-react"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Column, Row } from "react-table"; import { useSnapshot } from "valtio"; -import { CaretDown } from "phosphor-react"; import { useGetAssetPairsQuery } from "../../generated/graphql"; import { Stake, state } from "../../state"; import { fetchStakes } from "../../state/actions/stakes"; @@ -342,7 +342,7 @@ export const MyStakes = () => { )} - {tableData || (!tableData && stakesLoading) ? ( + {tableData || stakesLoading ? ( void | Promise, - onCancel?: () => void, - confirmDisabled?: boolean, - confirmText?: string - cancelText?: string -} - + onConfirm?: () => void | Promise; + onCancel?: () => void; + confirmDisabled?: boolean; + confirmText?: string; + cancelText?: string; +}; type State = { ethereumProvider: - | providers.JsonRpcProvider - | providers.Web3Provider - | providers.WebSocketProvider - | providers.Provider - | providers.BaseProvider - | null; + | providers.JsonRpcProvider + | providers.Web3Provider + | providers.WebSocketProvider + | providers.Provider + | providers.BaseProvider + | null; polygonProvider: - | providers.JsonRpcProvider - | providers.Web3Provider - | providers.WebSocketProvider - | providers.Provider - | providers.BaseProvider - | null; + | providers.JsonRpcProvider + | providers.Web3Provider + | providers.WebSocketProvider + | providers.Provider + | providers.BaseProvider + | null; providerName: string | null; signer: Signer | null; balances: Balances; @@ -66,6 +65,7 @@ type State = { stakes: Stake[] | null; alert: AlertOpts | null; walletOpen: boolean; + online: boolean; }; export const initialState: State = { @@ -81,6 +81,7 @@ export const initialState: State = { alert: null, stakes: null, walletOpen: false, + online: true, }; const persistedKeys = { @@ -88,8 +89,8 @@ const persistedKeys = { }; enum VanillaEvents { - stakesChanged = 'vanilla-StakesChanged', - balancesChanged = 'vanilla-BalancesChanged', + stakesChanged = "vanilla-StakesChanged", + balancesChanged = "vanilla-BalancesChanged", } const state = proxy(initialState); @@ -102,5 +103,5 @@ export { snapshot, ref, persistedKeys, - VanillaEvents + VanillaEvents, }; From 54d5f377d69babfa3a5b453be7fd7864e9b0c7bc Mon Sep 17 00:00:00 2001 From: JaniAnttonen Date: Fri, 25 Mar 2022 12:51:27 +0000 Subject: [PATCH 3/4] Use state instead of CSS in the animation delay --- components/Loader/index.tsx | 16 +++- components/Table/index.tsx | 8 +- components/Wallet/ActiveWallet.tsx | 22 ++--- components/Wallet/index.tsx | 4 +- generated/graphql.ts | 12 +++ graphql.schema.json | 144 +++++++++++++++++++++++++++++ 6 files changed, 188 insertions(+), 18 deletions(-) diff --git a/components/Loader/index.tsx b/components/Loader/index.tsx index 1992c28..715ca26 100644 --- a/components/Loader/index.tsx +++ b/components/Loader/index.tsx @@ -1,3 +1,4 @@ +import { useEffect, useState } from "react"; import { keyframes, styled } from "../../stitches.config"; import Box from "../Box"; @@ -37,8 +38,21 @@ const Loader = styled(Box, { animationDuration: "12s, 1s", animationTimingFunction: "linear, ease", animationIterationCount: "infinite, 1", - animationDelay: "0, 400ms", animationFillMode: "forwards" }) +export const LoaderWithDelay = () => { + const delay = 200; + const [show, setVisibility] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => setVisibility(true), delay); + return () => { + clearTimeout(timer); + }; + }); + + return show ? : null; +}; + export default Loader diff --git a/components/Table/index.tsx b/components/Table/index.tsx index 939eb26..6064975 100644 --- a/components/Table/index.tsx +++ b/components/Table/index.tsx @@ -1,4 +1,5 @@ /* eslint-disable react/jsx-key */ +import { AnimatePresence, motion } from "framer-motion"; import { matchSorter } from "match-sorter"; import { ArrowDown, ArrowUp } from "phosphor-react"; import React, { useCallback, useEffect, useState } from "react"; @@ -9,13 +10,12 @@ import { useExpanded, useGlobalFilter, useSortBy, - useTable, + useTable } from "react-table"; -import { motion, AnimatePresence } from "framer-motion"; import { styled } from "../../stitches.config"; import Box from "../Box"; import Flex from "../Flex"; -import Loader from "../Loader"; +import { LoaderWithDelay } from "../Loader"; /** * There's some boilter plate here @@ -275,7 +275,7 @@ function Table({ {headerGroups[headerGroups.length - 1].headers.map((_column) => { return ( - + ); })} diff --git a/components/Wallet/ActiveWallet.tsx b/components/Wallet/ActiveWallet.tsx index 37883f8..abf2307 100644 --- a/components/Wallet/ActiveWallet.tsx +++ b/components/Wallet/ActiveWallet.tsx @@ -2,23 +2,23 @@ import type * as Stitches from "@stitches/react"; import { isAddress, juiceDecimals } from "@vanilladefi/core-sdk"; import { getJuiceStakingContract } from "@vanilladefi/stake-sdk"; import { ContractTransaction } from "ethers"; -import Link from "../Link"; -import { Copy, ArrowUp, ArrowDown, XCircle } from "phosphor-react"; -import { useCallback, useState, useRef, useEffect } from "react"; -import { toast } from "react-toastify"; +import { formatUnits } from "ethers/lib/utils"; +import { ArrowDown, ArrowUp, Copy, XCircle } from "phosphor-react"; +import { useCallback, useEffect, useRef, useState } from "react"; import CountUp from "react-countup"; +import { toast } from "react-toastify"; +import { PolygonScanIcon } from "../../assets"; import { state, useSnapshot, VanillaEvents } from "../../state"; import { connectWallet, disconnect } from "../../state/actions/wallet"; import { emitEvent, getTransactionLink, parseJuice } from "../../utils/helpers"; import Box from "../Box"; import Button from "../Button"; import Input from "../Input"; -import Loader from "../Loader"; +import Link from "../Link"; +import { LoaderWithDelay } from "../Loader"; import Text from "../Text"; import Curtain from "./Curtain"; -import { PolygonScanIcon } from "../../assets"; -import { formatUnits } from "ethers/lib/utils"; enum TxTypes { deposit, @@ -420,7 +420,7 @@ const ActiveWallet: React.FC<{ css?: Stitches.CSS }> = ({ css }) => { ) : ( - + )} = ({ css }) => { ) : ( - + )} @@ -492,7 +492,7 @@ const ActiveWallet: React.FC<{ css?: Stitches.CSS }> = ({ css }) => { In your wallet ) : ( - + )} @@ -719,7 +719,7 @@ const ActiveWallet: React.FC<{ css?: Stitches.CSS }> = ({ css }) => { Unstaked in Juicenet ) : ( - + )} {rawBalances.unstakedJuice?.isZero() && diff --git a/components/Wallet/index.tsx b/components/Wallet/index.tsx index e804ec9..e6d4b5c 100644 --- a/components/Wallet/index.tsx +++ b/components/Wallet/index.tsx @@ -2,7 +2,7 @@ import type * as Stitches from "@stitches/react"; import { state, useSnapshot } from "../../state"; import { connectWallet } from "../../state/actions/wallet"; import Box from "../Box"; -import Loader from "../Loader"; +import { LoaderWithDelay } from "../Loader"; const WalletButton: React.FC<{ css?: Stitches.CSS }> = ({ css }) => { const { walletAddress, truncatedWalletAddress, walletOpen } = @@ -53,7 +53,7 @@ const WalletButton: React.FC<{ css?: Stitches.CSS }> = ({ css }) => { {walletAddress ? ( <> - {truncatedWalletAddress ? truncatedWalletAddress : } + {truncatedWalletAddress ? truncatedWalletAddress : } ) : ( diff --git a/generated/graphql.ts b/generated/graphql.ts index a0d03e6..34dfb3a 100644 --- a/generated/graphql.ts +++ b/generated/graphql.ts @@ -141,7 +141,9 @@ export type HourlyPriceHistory = { export type HourlyPriceHistory_Filter = { assetPair?: Maybe; assetPair_contains?: Maybe; + assetPair_contains_nocase?: Maybe; assetPair_ends_with?: Maybe; + assetPair_ends_with_nocase?: Maybe; assetPair_gt?: Maybe; assetPair_gte?: Maybe; assetPair_in?: Maybe>; @@ -149,10 +151,14 @@ export type HourlyPriceHistory_Filter = { assetPair_lte?: Maybe; assetPair_not?: Maybe; assetPair_not_contains?: Maybe; + assetPair_not_contains_nocase?: Maybe; assetPair_not_ends_with?: Maybe; + assetPair_not_ends_with_nocase?: Maybe; assetPair_not_in?: Maybe>; assetPair_not_starts_with?: Maybe; + assetPair_not_starts_with_nocase?: Maybe; assetPair_starts_with?: Maybe; + assetPair_starts_with_nocase?: Maybe; closingPrice?: Maybe; closingPrice_gt?: Maybe; closingPrice_gte?: Maybe; @@ -211,7 +217,9 @@ export type HourlyPriceHistory_Filter = { openingPrice_not_in?: Maybe>; test?: Maybe; test_contains?: Maybe; + test_contains_nocase?: Maybe; test_ends_with?: Maybe; + test_ends_with_nocase?: Maybe; test_gt?: Maybe; test_gte?: Maybe; test_in?: Maybe>; @@ -219,10 +227,14 @@ export type HourlyPriceHistory_Filter = { test_lte?: Maybe; test_not?: Maybe; test_not_contains?: Maybe; + test_not_contains_nocase?: Maybe; test_not_ends_with?: Maybe; + test_not_ends_with_nocase?: Maybe; test_not_in?: Maybe>; test_not_starts_with?: Maybe; + test_not_starts_with_nocase?: Maybe; test_starts_with?: Maybe; + test_starts_with_nocase?: Maybe; timestamp?: Maybe; timestamp_gt?: Maybe; timestamp_gte?: Maybe; diff --git a/graphql.schema.json b/graphql.schema.json index 58a2c2a..2c07998 100644 --- a/graphql.schema.json +++ b/graphql.schema.json @@ -1354,6 +1354,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "assetPair_contains_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "assetPair_ends_with", "description": null, @@ -1366,6 +1378,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "assetPair_ends_with_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "assetPair_gt", "description": null, @@ -1458,6 +1482,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "assetPair_not_contains_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "assetPair_not_ends_with", "description": null, @@ -1470,6 +1506,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "assetPair_not_ends_with_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "assetPair_not_in", "description": null, @@ -1502,6 +1550,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "assetPair_not_starts_with_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "assetPair_starts_with", "description": null, @@ -1514,6 +1574,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "assetPair_starts_with_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "closingPrice", "description": null, @@ -2322,6 +2394,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "test_contains_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "test_ends_with", "description": null, @@ -2334,6 +2418,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "test_ends_with_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "test_gt", "description": null, @@ -2426,6 +2522,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "test_not_contains_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "test_not_ends_with", "description": null, @@ -2438,6 +2546,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "test_not_ends_with_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "test_not_in", "description": null, @@ -2470,6 +2590,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "test_not_starts_with_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "test_starts_with", "description": null, @@ -2482,6 +2614,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "test_starts_with_nocase", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "timestamp", "description": null, From f8a1281280bd5a9fa2d12b419e950bb06e4c0eac Mon Sep 17 00:00:00 2001 From: JaniAnttonen Date: Fri, 25 Mar 2022 13:56:00 +0000 Subject: [PATCH 4/4] Indicate offline status in the UI --- components/Wallet/index.tsx | 8 +++++--- pages/_app.tsx | 21 +++++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/components/Wallet/index.tsx b/components/Wallet/index.tsx index e6d4b5c..d48d46e 100644 --- a/components/Wallet/index.tsx +++ b/components/Wallet/index.tsx @@ -5,7 +5,7 @@ import Box from "../Box"; import { LoaderWithDelay } from "../Loader"; const WalletButton: React.FC<{ css?: Stitches.CSS }> = ({ css }) => { - const { walletAddress, truncatedWalletAddress, walletOpen } = + const { walletAddress, truncatedWalletAddress, walletOpen, online } = useSnapshot(state); const buttonStyles = { @@ -19,6 +19,8 @@ const WalletButton: React.FC<{ css?: Stitches.CSS }> = ({ css }) => { justifyContent: "center", py: "$4", px: "$3", + opacity: online ? 1 : 0.5, + cursor: online ? "pointer" : "not-allowed", ...css, }; @@ -57,8 +59,8 @@ const WalletButton: React.FC<{ css?: Stitches.CSS }> = ({ css }) => { ) : ( - connectWallet()}> - Connect + online && connectWallet()}> + { online ? "Connect" : "Offline" } )}{" "} diff --git a/pages/_app.tsx b/pages/_app.tsx index 3ee139f..957a41e 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -3,8 +3,8 @@ import type { AppProps } from "next/app"; import dynamic from "next/dynamic"; import Head from "next/head"; import { useRouter } from "next/router"; -import { useEffect } from "react"; -import { ToastContainer } from "react-toastify"; +import { ReactText, useEffect, useRef } from "react"; +import { toast, ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import { Provider } from "urql"; import Box from "../components/Box"; @@ -33,19 +33,22 @@ const WalletModal = dynamic(() => import("../components/Wallet/WalletModal"), { }); function MyApp({ Component, pageProps }: AppProps) { + const shareImg = "/images/share-image.png"; + if (pageProps.urqlState) { ssrCache.restoreData(pageProps.urqlState); } + const router = useRouter(); const origin = useOrigin(); - const shareImg = "/images/share-image.png"; + const { signer, online } = useSnapshot(state); + const offlineToastId = useRef(null); // One time initializations useEffect(() => { initWalletSubscriptions(); }, []); - const { signer } = useSnapshot(state); useEffect(() => { if (!signer || !window.ethereum) return; @@ -62,6 +65,16 @@ function MyApp({ Component, pageProps }: AppProps) { }; }, [signer]); + // Offline indicator + useEffect(() => { + if (!online) { + offlineToastId.current = toast.error("You are offline", { autoClose: false, closeOnClick: false }) + } else if (offlineToastId.current) { + toast.dismiss(offlineToastId.current.toString()) + offlineToastId.current = null + } + }, [online]) + return ( <>