diff --git a/frontend/package.json b/frontend/package.json index 8bb1635..b639d17 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "@tanstack/react-router": "^1.28.7", "react": "^18.2.0", "react-dom": "^18.2.0", + "sonner": "^1.4.41", "web3": "^4.7.0" }, "devDependencies": { diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index b899697..8b24db6 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + sonner: + specifier: ^1.4.41 + version: 1.4.41(react-dom@18.2.0)(react@18.2.0) web3: specifier: ^4.7.0 version: 4.7.0(typescript@5.4.5) @@ -2614,6 +2617,16 @@ packages: engines: {node: '>=8'} dev: true + /sonner@1.4.41(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-uG511ggnnsw6gcn/X+YKkWPo5ep9il9wYi3QJxHsYe7yTZ4+cOd1wuodOUmOpFuXL+/RE3R04LczdNCDygTDgQ==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index d9869aa..6f07e43 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -16,11 +16,17 @@ import { Route as rootRoute } from './routes/__root' // Create Virtual Routes +const MessageBoxLazyImport = createFileRoute('/message-box')() const AboutLazyImport = createFileRoute('/about')() const IndexLazyImport = createFileRoute('/')() // Create/Update Routes +const MessageBoxLazyRoute = MessageBoxLazyImport.update({ + path: '/message-box', + getParentRoute: () => rootRoute, +} as any).lazy(() => import('./routes/message-box.lazy').then((d) => d.Route)) + const AboutLazyRoute = AboutLazyImport.update({ path: '/about', getParentRoute: () => rootRoute, @@ -43,11 +49,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AboutLazyImport parentRoute: typeof rootRoute } + '/message-box': { + preLoaderRoute: typeof MessageBoxLazyImport + parentRoute: typeof rootRoute + } } } // Create and export the route tree -export const routeTree = rootRoute.addChildren([IndexLazyRoute, AboutLazyRoute]) +export const routeTree = rootRoute.addChildren([ + IndexLazyRoute, + AboutLazyRoute, + MessageBoxLazyRoute, +]) /* prettier-ignore-end */ diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index bc1bd8c..6add413 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -1,4 +1,5 @@ import { createRootRoute, Link, Outlet } from "@tanstack/react-router"; +import { Toaster } from "sonner"; export const Route = createRootRoute({ component: RootLayout, @@ -17,10 +18,14 @@ function RootLayout() { About + + Message Box +
+
); diff --git a/frontend/src/routes/about.lazy.tsx b/frontend/src/routes/about.lazy.tsx index fef69ec..d57f918 100644 --- a/frontend/src/routes/about.lazy.tsx +++ b/frontend/src/routes/about.lazy.tsx @@ -1,13 +1,13 @@ import { createLazyFileRoute } from "@tanstack/react-router"; export const Route = createLazyFileRoute("/about")({ - component: About, + component: AboutPage, }); -function About() { +function AboutPage() { return ( -
-

About

+
+

About

Lorem ipsum dolor sit, amet consectetur adipisicing elit. Nemo nulla neque quo officiis eum quia voluptates ratione, tenetur assumenda ad diff --git a/frontend/src/routes/index.lazy.tsx b/frontend/src/routes/index.lazy.tsx index f17c4bb..8fbb1bb 100644 --- a/frontend/src/routes/index.lazy.tsx +++ b/frontend/src/routes/index.lazy.tsx @@ -1,48 +1,25 @@ import { createLazyFileRoute } from "@tanstack/react-router"; -import { useState } from "react"; -import { messageBox, web3 } from "../web3"; export const Route = createLazyFileRoute("/")({ - component: Index, + component: IndexPage, }); -function Index() { - const [connectedAccount, setConnectedAccount] = useState("(nothingness)"); - const [message, setMessage] = useState("(nothingness)"); - - // https://docs.web3js.org/guides/getting_started/metamask/#react-app - async function connectMetamask() { - // Request user to connect accounts (MetaMask will prompt) - await window.ethereum.request({ method: "eth_requestAccounts" }); - - // Get the connected accounts - const accounts = await web3.eth.getAccounts(); - - // Show the first connected account in the page - setConnectedAccount(accounts[0]); - - const _message: string = await messageBox.methods.getMessage().call(); - setMessage(_message); - } - +function IndexPage() { return ( -

- - -
- Connected account address:{" "} - {connectedAccount} +
+
+

This is a launchpad.

+

+ (Don't you dare thinking otherwise.) +

-
- Message: {message} -
+

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci + nesciunt ipsa possimus sed? Maxime eos iusto facere natus commodi + consectetur ad pariatur minima quisquam. Soluta esse minus porro nemo + at. +

); } diff --git a/frontend/src/routes/message-box.lazy.tsx b/frontend/src/routes/message-box.lazy.tsx new file mode 100644 index 0000000..e6afacc --- /dev/null +++ b/frontend/src/routes/message-box.lazy.tsx @@ -0,0 +1,101 @@ +import { createLazyFileRoute } from "@tanstack/react-router"; +import { useState } from "react"; +import { tryWithToast } from "../utils"; +import { messageBox, web3 } from "../web3"; + +export const Route = createLazyFileRoute("/message-box")({ + component: MessageBoxPage, +}); + +function MessageBoxPage() { + const [connectedAccount, setConnectedAccount] = useState("(nothingness)"); + const [message, setMessage] = useState("(nothingness)"); + const [newMessage, setNewMessage] = useState(""); + + // https://docs.web3js.org/guides/getting_started/metamask/#react-app + async function connectMetamask() { + await tryWithToast("Connect MetaMask", async () => { + // Request user to connect accounts (MetaMask will prompt) + await window.ethereum.request({ method: "eth_requestAccounts" }); + + // Get the connected accounts + const accounts = await web3.eth.getAccounts(); + + // Show the first connected account in the page + setConnectedAccount(accounts[0]); + }); + } + + async function getMessage() { + await tryWithToast("Get Message", async () => { + const _message: string = await messageBox.methods.getMessage().call(); + setMessage(_message); + }); + } + + async function _setMessage() { + await tryWithToast("Set Message", async () => { + await messageBox.methods + .setMessage(newMessage) + .send({ from: connectedAccount }); + }); + } + + return ( +
+
+
+

✨Essentials✨

+ + +
+ Connected account address:{" "} + {connectedAccount} +
+
+
+ +
+
+

Get Message

+ +
+ Message: {message} +
+
+
+ +
+
+

Set Message

+ setNewMessage(event.target.value)} + /> + +
+
+
+ ); +} diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts new file mode 100644 index 0000000..dc3bec0 --- /dev/null +++ b/frontend/src/utils.ts @@ -0,0 +1,21 @@ +import { toast } from "sonner"; + +export async function tryWithToast( + taskName: string, + taskFn: () => Promise +) { + try { + await taskFn(); + toast.success(`${taskName}: Success`, { + className: "border-none bg-green-100", + }); + } catch (error: any) { + console.error(error); + toast.error(`${taskName}: Error`, { + description: + error.message || + "No error message. Check the DevTools console for details.", + className: "border-none bg-red-100", + }); + } +}