diff --git a/dapps/react-dapp-auth/components/ThemeSwitcher.tsx b/dapps/react-dapp-auth/components/ThemeSwitcher.tsx new file mode 100644 index 0000000..265d2f6 --- /dev/null +++ b/dapps/react-dapp-auth/components/ThemeSwitcher.tsx @@ -0,0 +1,64 @@ +import { Flex, Switch } from "@chakra-ui/react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import moon from "../public/moon.svg"; +import sun from "../public/sun.svg"; + +const ThemeSwitcher: React.FC = () => { + const [darkMode, setDarkMode] = useState(true); + const switchRef = useRef(null); + + const darkTheme = useMemo( + () => ({ + "--primary-bg": "#1e1e1e", + "--secondary-bg": "#272a2a", + "--qr-bg": "#141414", + "--text-color": "white", + "--wc-btn-bg": "#19324d", + "--wc-btn-brdr": "#0f4b8a", + "--wc-btn-clr": "#66b1ff", + }), + [] + ); + + const lightTheme = useMemo( + () => ({ + "--primary-bg": "#F1F3F3", + "--secondary-bg": "white", + "--qr-bg": "#c8d0d0", + "--text-color": "black", + "--wc-btn-bg": "#E8F2FC", + "--wc-btn-brdr": "#CDE5FE", + "--wc-btn-clr": "#3396FF", + }), + [] + ); + + const setTheme = useCallback((theme: Record) => { + Object.entries(theme).forEach(([key, value]) => { + document.documentElement.style.setProperty(key, value); + }); + }, []); + + useEffect(() => { + if (darkMode) { + setTheme(darkTheme); + } else { + setTheme(lightTheme); + } + }, [darkMode, setTheme, darkTheme, lightTheme]); + + return ( + { + setDarkMode(target.checked); + }} + /> + ); +}; + +export default ThemeSwitcher; diff --git a/dapps/react-dapp-auth/package-lock.json b/dapps/react-dapp-auth/package-lock.json index 91edc7a..c07dbf0 100644 --- a/dapps/react-dapp-auth/package-lock.json +++ b/dapps/react-dapp-auth/package-lock.json @@ -13,19 +13,22 @@ "@emotion/styled": "^11.10.0", "@walletconnect/auth-client": "^0.1.10", "better-sqlite3": "^7.6.2", + "ethers": "^5.7.0", "events": "^3.3.0", "framer-motion": "^7.0.0", "next": "12.2.4", "qr-scanner": "^1.4.1", "qrcode": "^1.5.1", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "smart-truncate": "^1.0.1" }, "devDependencies": { "@types/node": "18.6.4", "@types/qrcode": "^1.4.3", "@types/react": "18.0.15", "@types/react-dom": "18.0.6", + "@types/smart-truncate": "^1.0.2", "eslint": "8.21.0", "eslint-config-next": "12.2.4", "typescript": "4.7.4" @@ -2909,6 +2912,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@types/smart-truncate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/smart-truncate/-/smart-truncate-1.0.2.tgz", + "integrity": "sha512-Zs6sRRlnal8ADE2E6LHcTujCfrGw0htB7iUnrvKEem+8QS6B8spFRvkdIdyY3C8KhnyJkGVR6zmcwWS9FNxfOw==", + "dev": true + }, "node_modules/@typescript-eslint/parser": { "version": "5.35.1", "dev": true, @@ -6431,6 +6440,11 @@ "node": ">=8" } }, + "node_modules/smart-truncate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/smart-truncate/-/smart-truncate-1.0.1.tgz", + "integrity": "sha512-f+kSLMDWKSCpYhwOZM+Da4d8klk5nY4gH57HOEOaaotsZEsTIm/51/9vKDNE26WjU72eBM1zEYdQl6uDwvZfLg==" + }, "node_modules/sonic-boom": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", @@ -8919,6 +8933,12 @@ "version": "0.16.2", "devOptional": true }, + "@types/smart-truncate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/smart-truncate/-/smart-truncate-1.0.2.tgz", + "integrity": "sha512-Zs6sRRlnal8ADE2E6LHcTujCfrGw0htB7iUnrvKEem+8QS6B8spFRvkdIdyY3C8KhnyJkGVR6zmcwWS9FNxfOw==", + "dev": true + }, "@typescript-eslint/parser": { "version": "5.35.1", "dev": true, @@ -11164,6 +11184,11 @@ "version": "3.0.0", "dev": true }, + "smart-truncate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/smart-truncate/-/smart-truncate-1.0.1.tgz", + "integrity": "sha512-f+kSLMDWKSCpYhwOZM+Da4d8klk5nY4gH57HOEOaaotsZEsTIm/51/9vKDNE26WjU72eBM1zEYdQl6uDwvZfLg==" + }, "sonic-boom": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", diff --git a/dapps/react-dapp-auth/package.json b/dapps/react-dapp-auth/package.json index dcc2ef3..fb894c7 100644 --- a/dapps/react-dapp-auth/package.json +++ b/dapps/react-dapp-auth/package.json @@ -14,19 +14,22 @@ "@emotion/styled": "^11.10.0", "@walletconnect/auth-client": "^0.1.10", "better-sqlite3": "^7.6.2", + "ethers": "^5.7.0", "events": "^3.3.0", "framer-motion": "^7.0.0", "next": "12.2.4", "qr-scanner": "^1.4.1", "qrcode": "^1.5.1", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "smart-truncate": "^1.0.1" }, "devDependencies": { "@types/node": "18.6.4", "@types/qrcode": "^1.4.3", "@types/react": "18.0.15", "@types/react-dom": "18.0.6", + "@types/smart-truncate": "^1.0.2", "eslint": "8.21.0", "eslint-config-next": "12.2.4", "typescript": "4.7.4" diff --git a/dapps/react-dapp-auth/pages/_app.tsx b/dapps/react-dapp-auth/pages/_app.tsx index 8e48a31..b76de50 100644 --- a/dapps/react-dapp-auth/pages/_app.tsx +++ b/dapps/react-dapp-auth/pages/_app.tsx @@ -1,28 +1,72 @@ import "../styles/globals.css"; import type { AppProps } from "next/app"; -import { ChakraProvider, Container, Flex, Image, Text } from "@chakra-ui/react"; +import { version } from "@walletconnect/auth-client/package.json"; +import { + ChakraProvider, + Box, + Flex, + Grid, + GridItem, + Image, + Text, +} from "@chakra-ui/react"; +import ThemeSwitcher from "../components/ThemeSwitcher"; function MyApp({ Component, pageProps }: AppProps) { return ( - - - - - - Request Authentication + + + + +
Example App
+ + WC + V{version} +
+
+ + + + - -
-
+ + + + + + +
); } diff --git a/dapps/react-dapp-auth/pages/index.tsx b/dapps/react-dapp-auth/pages/index.tsx index 408a9a5..d6906dd 100644 --- a/dapps/react-dapp-auth/pages/index.tsx +++ b/dapps/react-dapp-auth/pages/index.tsx @@ -1,29 +1,18 @@ -import { - Button, - Container, - Text, - Divider, - Flex, - Heading, - Image, - SimpleGrid, - useToast, -} from "@chakra-ui/react"; +import { Box } from "@chakra-ui/react"; import AuthClient from "@walletconnect/auth-client"; import { version } from "@walletconnect/auth-client/package.json"; import type { NextPage } from "next"; -import Link from "next/link"; -import Qrcode from "qrcode"; -import { Fragment, useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; +import DefaultView from "../views/DefaultView"; +import QrView from "../views/QrView"; +import SignedInView from "../views/SignedInView"; console.log(`AuthClient@${version}`); const Home: NextPage = () => { const [client, setClient] = useState(); const [uri, setUri] = useState(""); - const [accepted, setAccepted] = useState(false); - const toast = useToast(); - const canvasRef = useRef(null); + const [address, setAddress] = useState(""); const onSignIn = useCallback(() => { if (!client) return; @@ -60,82 +49,28 @@ const Home: NextPage = () => { useEffect(() => { if (!client) return; client.on("auth_response", (res) => { - if (!!res.params.result.s) { - setAccepted(true); + if (res.params.result.s) { + setAddress(res.params.result.p.iss.split(":")[4]); } }); }, [client]); + const [view, changeView] = useState<"default" | "qr" | "signedIn">("default"); + useEffect(() => { - if (uri && canvasRef.current) Qrcode.toCanvas(canvasRef.current, uri); - }, [uri, canvasRef]); + if (uri) changeView("qr"); + }, [uri, changeView]); + + useEffect(() => { + if (address) changeView("signedIn"); + }, [address, changeView]); + return ( - - - - - Sign In - - - - Initiate the auth cycle by sending an auth request - -
- - - - {uri && ( - - { - navigator.clipboard.writeText(uri).then(() => { - toast({ - title: "URI copied to clipboard", - status: "success", - duration: 1000, - }); - }); - }} - ref={canvasRef} - /> - - (You can copy the URI by clicking the QR code above) - - - )} - -
- {accepted && ( - - )} -
-
-
+ + {view === "default" && } + {view === "qr" && } + {view === "signedIn" && } + ); }; diff --git a/dapps/react-dapp-auth/public/auth.png b/dapps/react-dapp-auth/public/auth.png new file mode 100644 index 0000000..bd63439 Binary files /dev/null and b/dapps/react-dapp-auth/public/auth.png differ diff --git a/dapps/react-dapp-auth/public/copy.svg b/dapps/react-dapp-auth/public/copy.svg new file mode 100644 index 0000000..882d112 --- /dev/null +++ b/dapps/react-dapp-auth/public/copy.svg @@ -0,0 +1,3 @@ + + + diff --git a/dapps/react-dapp-auth/public/eth.png b/dapps/react-dapp-auth/public/eth.png new file mode 100644 index 0000000..0ecbd8c Binary files /dev/null and b/dapps/react-dapp-auth/public/eth.png differ diff --git a/dapps/react-dapp-auth/public/moon.png b/dapps/react-dapp-auth/public/moon.png new file mode 100644 index 0000000..ea5ab5f Binary files /dev/null and b/dapps/react-dapp-auth/public/moon.png differ diff --git a/dapps/react-dapp-auth/public/moon.svg b/dapps/react-dapp-auth/public/moon.svg new file mode 100644 index 0000000..93075a6 --- /dev/null +++ b/dapps/react-dapp-auth/public/moon.svg @@ -0,0 +1,3 @@ + + + diff --git a/dapps/react-dapp-auth/public/scan.svg b/dapps/react-dapp-auth/public/scan.svg new file mode 100644 index 0000000..cefceaf --- /dev/null +++ b/dapps/react-dapp-auth/public/scan.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/dapps/react-dapp-auth/public/sun.png b/dapps/react-dapp-auth/public/sun.png new file mode 100644 index 0000000..283f839 Binary files /dev/null and b/dapps/react-dapp-auth/public/sun.png differ diff --git a/dapps/react-dapp-auth/public/sun.svg b/dapps/react-dapp-auth/public/sun.svg new file mode 100644 index 0000000..6109f0e --- /dev/null +++ b/dapps/react-dapp-auth/public/sun.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/dapps/react-dapp-auth/public/wc-bg.png b/dapps/react-dapp-auth/public/wc-bg.png new file mode 100644 index 0000000..937ff93 Binary files /dev/null and b/dapps/react-dapp-auth/public/wc-bg.png differ diff --git a/dapps/react-dapp-auth/public/wc.png b/dapps/react-dapp-auth/public/wc.png new file mode 100644 index 0000000..5c85d5a Binary files /dev/null and b/dapps/react-dapp-auth/public/wc.png differ diff --git a/dapps/react-dapp-auth/styles/globals.css b/dapps/react-dapp-auth/styles/globals.css index b876e8e..8fe3bee 100644 --- a/dapps/react-dapp-auth/styles/globals.css +++ b/dapps/react-dapp-auth/styles/globals.css @@ -1,7 +1,20 @@ +:root { + --primary-bg: #1e1e1e; + --secondary-bg: #272a2a; + --qr-bg: #141414; + --text-color: white; + --wc-btn-bg: #19324d; + --wc-btn-brdr: #0f4b8a; + --wc-btn-clr: #66b1ff; +} + +* { + transition: background 0.25s ease-in-out; +} + html, body { - background-color: #141414 !important; - color: white !important; + color: var(--text-color) !important; padding: 0; margin: 0; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, @@ -26,3 +39,50 @@ a { background: black; } } + +.bg-primary { + background: var(--primary-bg); +} + +.bg-qr { + background: var(--qr-bg); +} + +.bg-secondary { + background: var(--secondary-bg); +} + +.wc-button { + background: var(--wc-btn-bg) !important; + border: solid 1px var(--wc-btn-brdr); + color: var(--wc-btn-clr) !important; +} + +.theme-switcher-dark > span > span { + background: url("/moon.svg") no-repeat #66b1ff !important; +} + +.theme-switcher-light > span > span { + background: url("/sun.svg") no-repeat #66b1ff !important; +} + +.theme-switcher > span { + border: solid 2px white; +} + +.theme-switcher-light > span { + background: url("/moon.svg") no-repeat #3b4040 !important; + background-repeat: no-repeat !important; + background-position: 90% center !important; +} + +.theme-switcher-dark > span { + background: url("/sun.svg") no-repeat #3b4040 !important; + background-repeat: no-repeat !important; + background-position: 10% center !important; +} + +.theme-switcher > span > span { + background-repeat: no-repeat !important; + background-position: center center !important; +} diff --git a/dapps/react-dapp-auth/views/DefaultView.tsx b/dapps/react-dapp-auth/views/DefaultView.tsx new file mode 100644 index 0000000..99c41d0 --- /dev/null +++ b/dapps/react-dapp-auth/views/DefaultView.tsx @@ -0,0 +1,51 @@ +import { Box, Button, Flex, Heading, Image, position } from "@chakra-ui/react"; + +const DefaultView: React.FC<{ onClick: () => void }> = ({ onClick }) => { + return ( + + + + + auth + + auth + + + + Sign in + + + + ); +}; + +export default DefaultView; diff --git a/dapps/react-dapp-auth/views/QrView.tsx b/dapps/react-dapp-auth/views/QrView.tsx new file mode 100644 index 0000000..7d2a1d7 --- /dev/null +++ b/dapps/react-dapp-auth/views/QrView.tsx @@ -0,0 +1,83 @@ +import { + Text, + Box, + Divider, + Flex, + Heading, + useToast, + Button, + Grid, + Image, +} from "@chakra-ui/react"; +import Qrcode from "qrcode"; +import { useEffect, useRef } from "react"; + +const QrView: React.FC<{ uri: string }> = ({ uri }) => { + const canvasRef = useRef(null); + const toast = useToast(); + + useEffect(() => { + if (uri && canvasRef.current) Qrcode.toCanvas(canvasRef.current, uri); + }, [uri, canvasRef]); + + const onClick = async () => { + await navigator.clipboard.writeText(uri); + toast({ + title: "Copied URI", + }); + }; + + return ( + + + + + + + + + scan + + Scan with your phone + + + + + Open your camera app or mobile wallet and scan the code to connect + + + + + + ); +}; + +export default QrView; diff --git a/dapps/react-dapp-auth/views/SignedInView.tsx b/dapps/react-dapp-auth/views/SignedInView.tsx new file mode 100644 index 0000000..1cbd526 --- /dev/null +++ b/dapps/react-dapp-auth/views/SignedInView.tsx @@ -0,0 +1,113 @@ +import { + Box, + Button, + Divider, + Flex, + Grid, + Image, + Spinner, + Text, +} from "@chakra-ui/react"; +import truncate from "smart-truncate"; +import { BigNumber, providers } from "ethers"; +import { useCallback, useEffect, useState } from "react"; + +const SignedInView: React.FC<{ address: string }> = ({ address }) => { + const [balance, setBalance] = useState(); + const [avatar, setAvatar] = useState(); + const [isLoading, setLoading] = useState(false); + + useEffect(() => { + const innerEffect = async (address: string) => { + setLoading(true); + const provider = providers.getDefaultProvider(); + const avatar = await provider.getAvatar(address); + const balance = await provider.getBalance(address); + setAvatar(avatar); + setBalance(balance.toNumber()); + setLoading(false); + }; + if (address) { + innerEffect(address); + } + }, [setBalance, address]); + + const onSignOut = useCallback(() => { + window.location.reload(); + }, []); + + return ( + + + + + {isLoading ? ( + + ) : ( + avatar && avatar + )} + + + + Connected + + + + + {truncate(address, 12, { position: 7 })} + + + + + Balance + {isLoading ? ( + + ) : ( + + ETH + {balance} ETH + + )} + + + + + + + ); +}; + +export default SignedInView;