diff --git a/react-web/.env.production b/react-web/.env.production index bdf7549f..56afd081 100644 --- a/react-web/.env.production +++ b/react-web/.env.production @@ -1 +1,2 @@ -REACT_APP_BASE_URL="https://testing.dapps.iog.io/" \ No newline at end of file +REACT_APP_BASE_URL="https://testing.dapps.iog.io/" +REACT_APP_WALLET_NETWORK="1" \ No newline at end of file diff --git a/react-web/src/app/App.scss b/react-web/src/app/App.scss index 5423910b..4dfff414 100644 --- a/react-web/src/app/App.scss +++ b/react-web/src/app/App.scss @@ -9,4 +9,20 @@ header button { section > svg.spinner { top: 100px; left: 50%; +} + + +#contentWrapper { + margin: 0 auto; + padding: 40px; + color: #2d333a; + @media (max-width: 768px) { + & { + padding: 0; + } + } +} + +#globalBanners { + padding: 10px 40px; } \ No newline at end of file diff --git a/react-web/src/app/App.tsx b/react-web/src/app/App.tsx index 5277c1fe..bb6a2d10 100644 --- a/react-web/src/app/App.tsx +++ b/react-web/src/app/App.tsx @@ -1,12 +1,14 @@ -import React, { lazy, Suspense } from "react"; +import React, { lazy, memo, Suspense } from "react"; import { Routes, Route, Outlet } from "react-router-dom"; import { BASE_URL } from "constants/route"; +import Alert from '@mui/material/Alert'; import "./App.scss"; import Header from "components/Header/Header"; import PrivateRoutes from "components/PrivateRoutes/PrivateRoutes"; import NotFound from "components/NotFound/NotFound"; import Loader from "components/Loader/Loader"; +import { useAppSelector } from "store/store"; const Certification = lazy(() => import("../pages/certification/Certification")); const MaintenancePage = lazy(() => import("../pages/maintenance/Maintenance")); @@ -19,9 +21,32 @@ const Pricing = lazy(() => import("../pages/pricing/Pricing")); const PageLayout = () => { + const { network } = useAppSelector((state) => state.auth); + + // const networkNames:{[x:string]:string} = { + // '0': 'Testnet', + // '1': 'Mainnet' + // } + + const Banner = memo(() => { + const networkEnvVar: any = process.env.REACT_APP_WALLET_NETWORK + + return (<> + {network !== null && network !== 1 ? + // always show Info if not in Mainnet + Your connected wallet is not in Mainnet. : null} + {/* if not in Mainnet and app-wallet not Mainnet (i.e. in Testnet), show Warning to connect to Preprod. */} + {network !== null && network !== 1 && networkEnvVar !== '1' ? + Being in a test network, please make sure you are connected to wallet in Preprod to avail services without any issues. : null} + ) + }) + return ( <>
+
+ +
{/* Load page content here */}
}> diff --git a/react-web/src/components/ConnectWallet/ConnectWallet.scss b/react-web/src/components/ConnectWallet/ConnectWallet.scss index eef6a234..4c2ca698 100644 --- a/react-web/src/components/ConnectWallet/ConnectWallet.scss +++ b/react-web/src/components/ConnectWallet/ConnectWallet.scss @@ -60,4 +60,7 @@ span { padding: 2px 16px; } +} +.error { + color: red; } \ No newline at end of file diff --git a/react-web/src/components/ConnectWallet/ConnectWallet.tsx b/react-web/src/components/ConnectWallet/ConnectWallet.tsx index 4d40a805..b9477583 100644 --- a/react-web/src/components/ConnectWallet/ConnectWallet.tsx +++ b/react-web/src/components/ConnectWallet/ConnectWallet.tsx @@ -1,11 +1,12 @@ import React, { useEffect, useState, useCallback } from "react"; import { Address } from "@emurgo/cardano-serialization-lib-browser"; import { useAppDispatch } from "store/store"; -import { getProfileDetails } from "store/slices/auth.slice"; +import { getProfileDetails, setNetwork } from "store/slices/auth.slice"; import Modal from "components/Modal/Modal"; import Button from "components/Button/Button"; import Loader from "components/Loader/Loader"; +import Toast from "components/Toast/Toast"; import './ConnectWallet.scss'; @@ -24,6 +25,7 @@ const ConnectWallet = () => { const [walletName, setWalletName] = useState("") const [address, setAddress] = useState("") const [isOpen, setIsOpen] = useState(false) + const [errorToast, setErrorToast] = useState<{display: boolean; statusText?: string; message?: string;}>({display: false}); const [walletLoading, setWalletLoading] = useState(false) const openConnectWalletModal = useCallback(() => setIsOpen(true),[]) @@ -34,17 +36,27 @@ const ConnectWallet = () => { try { setWalletLoading(true) const enabledWallet = await CardanoNS[walletName].enable(); - setWallet(enabledWallet) - setWalletName(walletName) - if (enabledWallet) { - const response = await enabledWallet.getChangeAddress() - setAddress(Address.from_bytes(Buffer.from(response, "hex")).to_bech32()) - } + enabledWallet.getNetworkId().then(async (data: number) => { + dispatch(setNetwork(data)) + setWallet(enabledWallet) + setWalletName(walletName) + if (enabledWallet) { + const response = await enabledWallet.getChangeAddress() + setAddress(Address.from_bytes(Buffer.from(response, "hex")).to_bech32()) + } + }) } catch (e) { handleError(e); } } - const handleError = (err: any) => { - console.log(err) + const handleError = (error: any) => { + if (error.info) { + setErrorToast({display: true, message: error.info}) + } else if (error.response) { + setErrorToast({display: true, statusText: error.response.statusText, message: error.response.data || undefined}) + } else { + setErrorToast({display: true}) + } + setTimeout(() => { setErrorToast({display: false}) }, 3000) } useEffect(() => { @@ -88,8 +100,16 @@ const ConnectWallet = () => { }) } { walletLoading ? : null} + { + (errorToast && errorToast.display) ? ({errorToast.message}): null + } + {/* {(errorToast && errorToast.display) ? ( + ((errorToast.message && errorToast.statusText) ? + : + )) + : null} */} ) } diff --git a/react-web/src/components/Header/Header.tsx b/react-web/src/components/Header/Header.tsx index 8067f6eb..d74f7028 100644 --- a/react-web/src/components/Header/Header.tsx +++ b/react-web/src/components/Header/Header.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState, memo, useCallback } from "react"; import { useNavigate, Link } from "react-router-dom"; import { Address } from "@emurgo/cardano-serialization-lib-browser"; import { useAppDispatch, useAppSelector } from "store/store"; -import { logout, getProfileDetails } from "store/slices/auth.slice"; +import { logout, getProfileDetails, setNetwork } from "store/slices/auth.slice"; import "./Header.scss"; import AvatarDropDown from "components/AvatarDropdown/AvatarDropdown"; @@ -10,10 +10,11 @@ import ConnectWallet from "components/ConnectWallet/ConnectWallet"; import { useDelayedApi } from "hooks/useDelayedApi"; const Header = () => { - const { isLoggedIn, address, wallet } = useAppSelector((state) => state.auth); + const { isLoggedIn, address, wallet, network } = useAppSelector((state) => state.auth); const dispatch = useAppDispatch(); const [isActive, setIsActive] = useState(false); const [pollForAddress, setPollForAddress] = useState(false); + const [pollForNetwork, setPollForNetwork] = useState(false); const navigate = useNavigate(); useEffect(() => { @@ -25,6 +26,9 @@ const Header = () => { try { const enabledWallet = await window.cardano[walletNameCache].enable() dispatch(getProfileDetails({"address": addressCache, "wallet": enabledWallet, "walletName": walletNameCache})) + enabledWallet.getNetworkId().then(async (data: number) => { console.log('new network -', data) + dispatch(setNetwork(data)) + }) } catch(e) { console.log(e) } @@ -42,7 +46,15 @@ const Header = () => { useEffect(() => { setPollForAddress(wallet && address && isLoggedIn); - }, [wallet, address, isLoggedIn]); + setPollForNetwork(wallet && address && isLoggedIn && network !== null) + }, [wallet, address, isLoggedIn, network]); + + const forceUserLogout = () => { + // account/network has been changed. Force logout the user + setPollForAddress(false); + setPollForNetwork(false) + dispatch(logout()); + } useDelayedApi( async () => { @@ -53,17 +65,32 @@ const Header = () => { newAddress = Address.from_bytes(Buffer.from(response, "hex")).to_bech32() } if (newAddress && address !== newAddress) { - // account has been changed. Force logout the user - dispatch(logout()); - setPollForAddress(false); + forceUserLogout() } else { setPollForAddress(true); } }, - 3 * 1000, + 1 * 1000, pollForAddress ); + useDelayedApi( + async() => { + setPollForNetwork(false) + wallet.getNetworkId().then((id: number) => { + // Preview/Preprod/Testnet are all 0. Switching among them cannot be distinguished. + // But, switching to-and-fro Mainnet is triggered + if (id !== network) { + forceUserLogout(); + } else { + setPollForNetwork(true) + } + }) + }, + 1 * 1000, + pollForNetwork + ) + const hasCachedAddress = () => { return (!localStorage.getItem('address')?.length || !localStorage.getItem('walletName')?.length) } diff --git a/react-web/src/index.scss b/react-web/src/index.scss index ea7caa0e..438a5d58 100644 --- a/react-web/src/index.scss +++ b/react-web/src/index.scss @@ -15,17 +15,6 @@ body { // position: relative; } -#contentWrapper { - margin: 0 auto; - padding: 40px; - color: #2d333a; - @media (max-width: 768px) { - & { - padding: 0; - } - } -} - input { font-family: monospace, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", diff --git a/react-web/src/pages/certification/Certification.scss b/react-web/src/pages/certification/Certification.scss index 6138fa1e..7502e4f5 100644 --- a/react-web/src/pages/certification/Certification.scss +++ b/react-web/src/pages/certification/Certification.scss @@ -87,17 +87,20 @@ } #resultContainer { - + button.back-btn { + background: transparent; + cursor: pointer; + border: none; + img { + width: "30px"; + padding: "10px"; + } + } header { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 15px; - .back-btn { - background: transparent; - cursor: pointer; - border: none; - } &>div { display: flex; flex-wrap: wrap; diff --git a/react-web/src/pages/certification/Certification.tsx b/react-web/src/pages/certification/Certification.tsx index 52aa274c..1b449d8a 100644 --- a/react-web/src/pages/certification/Certification.tsx +++ b/react-web/src/pages/certification/Certification.tsx @@ -292,24 +292,21 @@ const Certification = () => { {formSubmitted && ( <>
+ {runStatus !== "finished" ? ( + + ) : null}
- {runStatus === "finished" ? ( - - ) : ( - "" - )}