diff --git a/frontend/src/components/Form.jsx b/frontend/src/components/Form.jsx index 743e8a1..badf756 100644 --- a/frontend/src/components/Form.jsx +++ b/frontend/src/components/Form.jsx @@ -1,44 +1,44 @@ export default function Form({ onSubmit, currentAccountId }) { - return ( -
-
-

Guest Book

-

Sign the guest book, {currentAccountId}!

-
- - -
-
- -
- - - Ⓝ - -
-
- -
-
- ); + return ( +
+
+

Guest Book

+

Sign the guest book, {currentAccountId}!

+
+ + +
+
+ +
+ + + Ⓝ + +
+
+ +
+
+ ); } diff --git a/frontend/src/components/Messages.jsx b/frontend/src/components/Messages.jsx index 64f030f..a658431 100644 --- a/frontend/src/components/Messages.jsx +++ b/frontend/src/components/Messages.jsx @@ -1,23 +1,23 @@ export default function Messages({ messages }) { - return ( -
-

Messages

- {messages.map((message, i) => ( -
-
-
- {message.sender} - {message.premium && ( - Premium - )} -
-

{message.text}

-
-
- ))} -
- ); + return ( +
+

Messages

+ {messages.map((message, i) => ( +
+
+
+ {message.sender} + {message.premium && ( + Premium + )} +
+

{message.text}

+
+
+ ))} +
+ ); } diff --git a/frontend/src/components/Navigation.jsx b/frontend/src/components/Navigation.jsx index 6849b8b..6246013 100644 --- a/frontend/src/components/Navigation.jsx +++ b/frontend/src/components/Navigation.jsx @@ -1,14 +1,14 @@ -import Image from 'next/image'; -import Link from 'next/link'; -import { useEffect, useState, useContext } from 'react'; +import Image from "next/image"; +import Link from "next/link"; +import { useEffect, useState, useContext } from "react"; -import { NearContext } from '@/context'; -import NearLogo from '/public/near-logo.svg'; +import { NearContext } from "@/context"; +import NearLogo from "/public/near-logo.svg"; export const Navigation = () => { const { signedAccountId, wallet } = useContext(NearContext); - const [action, setAction] = useState(() => { }); - const [label, setLabel] = useState('Loading...'); + const [action, setAction] = useState(() => {}); + const [label, setLabel] = useState("Loading..."); useEffect(() => { if (!wallet) return; @@ -18,7 +18,7 @@ export const Navigation = () => { setLabel(`Logout ${signedAccountId}`); } else { setAction(() => wallet.signIn); - setLabel('Login'); + setLabel("Login"); } }, [signedAccountId, wallet]); @@ -26,12 +26,22 @@ export const Navigation = () => { ); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/SignIn.jsx b/frontend/src/components/SignIn.jsx index ef15766..fd01de5 100644 --- a/frontend/src/components/SignIn.jsx +++ b/frontend/src/components/SignIn.jsx @@ -1,18 +1,18 @@ export default function SignIn() { - return ( - <> -

- This app demonstrates a key element of NEAR’s UX: once an app has - permission to make calls on behalf of a user (that is, once a user signs - in), the app can make calls to the blockchain for them without prompting - extra confirmation. So you’ll see that if you don’t include a donation, - your message gets posted right to the guest book. -

-

- But, if you do add a donation, then NEAR will double-check that you’re - ok with sending money to this app. -

-

Go ahead and sign in to try it out!

- - ); + return ( + <> +

+ This app demonstrates a key element of NEAR’s UX: once an app has + permission to make calls on behalf of a user (that is, once a user signs + in), the app can make calls to the blockchain for them without prompting + extra confirmation. So you’ll see that if you don’t include a donation, + your message gets posted right to the guest book. +

+

+ But, if you do add a donation, then NEAR will double-check that you’re + ok with sending money to this app. +

+

Go ahead and sign in to try it out!

+ + ); } diff --git a/frontend/src/config.js b/frontend/src/config.js index b7cd6c2..d52d598 100644 --- a/frontend/src/config.js +++ b/frontend/src/config.js @@ -1,5 +1,5 @@ const contractPerNetwork = { - testnet: "guestbook.near-examples.testnet", + testnet: "guestbook.near-examples.testnet", }; export const NetworkId = "testnet"; diff --git a/frontend/src/context.js b/frontend/src/context.js index 7422309..6678cfd 100644 --- a/frontend/src/context.js +++ b/frontend/src/context.js @@ -1,4 +1,4 @@ -import { createContext } from 'react'; +import { createContext } from "react"; /** * @typedef NearContext @@ -9,5 +9,5 @@ import { createContext } from 'react'; /** @type {import ('react').Context} */ export const NearContext = createContext({ wallet: undefined, - signedAccountId: '' -}); \ No newline at end of file + signedAccountId: "", +}); diff --git a/frontend/src/pages/_app.js b/frontend/src/pages/_app.js index 41f0911..8cde3db 100644 --- a/frontend/src/pages/_app.js +++ b/frontend/src/pages/_app.js @@ -1,18 +1,23 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState } from "react"; -import '@/styles/globals.css'; -import { NearContext } from '@/context'; -import { Navigation } from '@/components/Navigation'; +import "@/styles/globals.css"; +import { NearContext } from "@/context"; +import { Navigation } from "@/components/Navigation"; -import { Wallet } from '@/wallets/near'; -import { NetworkId, GuestbookNearContract } from '@/config'; +import { Wallet } from "@/wallets/near"; +import { NetworkId, GuestbookNearContract } from "@/config"; -const wallet = new Wallet({ createAccessKeyFor: GuestbookNearContract, networkId: NetworkId }); +const wallet = new Wallet({ + createAccessKeyFor: GuestbookNearContract, + networkId: NetworkId, +}); export default function MyApp({ Component, pageProps }) { - const [signedAccountId, setSignedAccountId] = useState(''); + const [signedAccountId, setSignedAccountId] = useState(""); - useEffect(() => { wallet.startUp(setSignedAccountId) }, []); + useEffect(() => { + wallet.startUp(setSignedAccountId); + }, []); return ( @@ -20,4 +25,4 @@ export default function MyApp({ Component, pageProps }) { ); -} \ No newline at end of file +} diff --git a/frontend/src/pages/index.js b/frontend/src/pages/index.js index 70bd3f4..3406487 100644 --- a/frontend/src/pages/index.js +++ b/frontend/src/pages/index.js @@ -10,65 +10,64 @@ import { NearContext } from "@/context"; import { GuestbookNearContract } from "@/config"; export default function Home() { - const { signedAccountId, wallet } = useContext(NearContext); - const [messages, setMessages] = useState([]); + const { signedAccountId, wallet } = useContext(NearContext); + const [messages, setMessages] = useState([]); - useEffect(() => { - getLast10Messages() - .then(messages => setMessages(messages.reverse())); - }, []); + useEffect(() => { + getLast10Messages().then((messages) => setMessages(messages.reverse())); + }, []); - const getLast10Messages = async () => { - const total_messages = await wallet.viewMethod({ - contractId: GuestbookNearContract, - method: "total_messages", - }); - const from_index = total_messages >= 10 ? total_messages - 10 : 0; - return wallet.viewMethod({ - contractId: GuestbookNearContract, - method: "get_messages", - args: { from_index: String(from_index), limit: "10" }, - }); - }; + const getLast10Messages = async () => { + const total_messages = await wallet.viewMethod({ + contractId: GuestbookNearContract, + method: "total_messages", + }); + const from_index = total_messages >= 10 ? total_messages - 10 : 0; + return wallet.viewMethod({ + contractId: GuestbookNearContract, + method: "get_messages", + args: { from_index: String(from_index), limit: "10" }, + }); + }; - const onSubmit = async (e) => { - e.preventDefault(); + const onSubmit = async (e) => { + e.preventDefault(); - const { fieldset, message, donation } = e.target.elements; + const { fieldset, message, donation } = e.target.elements; - fieldset.disabled = true; + fieldset.disabled = true; - // Add message to the guest book - const deposit = utils.format.parseNearAmount(donation.value); - await wallet.callMethod({ - contractId: GuestbookNearContract, - method: "add_message", - args: { text: message.value }, - deposit, - }); + // Add message to the guest book + const deposit = utils.format.parseNearAmount(donation.value); + await wallet.callMethod({ + contractId: GuestbookNearContract, + method: "add_message", + args: { text: message.value }, + deposit, + }); - // Get updated messages - const messages = await getLast10Messages(); - setMessages(messages.reverse()); + // Get updated messages + const messages = await getLast10Messages(); + setMessages(messages.reverse()); - message.value = ""; - donation.value = "0"; - fieldset.disabled = false; - message.focus(); - }; + message.value = ""; + donation.value = "0"; + fieldset.disabled = false; + message.focus(); + }; - return ( -
-
-

📖 NEAR Guest Book

- {signedAccountId ? ( -
- ) : ( - - )} -
+ return ( +
+
+

📖 NEAR Guest Book

+ {signedAccountId ? ( + + ) : ( + + )} +
- {!!messages.length && } -
- ); + {!!messages.length && } +
+ ); } diff --git a/frontend/src/styles/app.module.css b/frontend/src/styles/app.module.css index 67b1c24..8c1e4a5 100644 --- a/frontend/src/styles/app.module.css +++ b/frontend/src/styles/app.module.css @@ -3,6 +3,6 @@ flex-direction: column; align-items: flex-start; min-height: 100vh; - max-width: 600px; - margin: 0 auto; -} \ No newline at end of file + max-width: 600px; + margin: 0 auto; +} diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index f34437c..a2d80cd 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -1,5 +1,5 @@ -@import 'bootstrap'; -@import 'bootstrap-icons'; +@import "bootstrap"; +@import "bootstrap-icons"; :root { --max-width: 1100px; @@ -9,24 +9,30 @@ --background-start-rgb: 214, 219, 220; --background-end-rgb: 255, 255, 255; - --primary-glow: conic-gradient(from 180deg at 50% 50%, - #16abff33 0deg, - #0885ff33 55deg, - #54d6ff33 120deg, - #0071ff33 160deg, - transparent 360deg); - --secondary-glow: radial-gradient(rgba(255, 255, 255, 1), - rgba(255, 255, 255, 0)); + --primary-glow: conic-gradient( + from 180deg at 50% 50%, + #16abff33 0deg, + #0885ff33 55deg, + #54d6ff33 120deg, + #0071ff33 160deg, + transparent 360deg + ); + --secondary-glow: radial-gradient( + rgba(255, 255, 255, 1), + rgba(255, 255, 255, 0) + ); --tile-start-rgb: 239, 245, 249; --tile-end-rgb: 228, 232, 233; - --tile-border: conic-gradient(#00000080, - #00000040, - #00000030, - #00000020, - #00000010, - #00000010, - #00000080); + --tile-border: conic-gradient( + #00000080, + #00000040, + #00000030, + #00000020, + #00000010, + #00000010, + #00000080 + ); --callout-rgb: 238, 240, 241; --callout-border-rgb: 172, 175, 176; @@ -41,20 +47,24 @@ --background-end-rgb: 0, 0, 0; --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); - --secondary-glow: linear-gradient(to bottom right, - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0.3)); + --secondary-glow: linear-gradient( + to bottom right, + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0.3) + ); --tile-start-rgb: 2, 13, 46; --tile-end-rgb: 2, 5, 19; - --tile-border: conic-gradient(#ffffff80, - #ffffff40, - #ffffff30, - #ffffff20, - #ffffff10, - #ffffff10, - #ffffff80); + --tile-border: conic-gradient( + #ffffff80, + #ffffff40, + #ffffff30, + #ffffff20, + #ffffff10, + #ffffff10, + #ffffff80 + ); --callout-rgb: 20, 20, 20; --callout-border-rgb: 108, 108, 108; @@ -77,10 +87,22 @@ body { body { color: rgb(var(--foreground-rgb)); - background: linear-gradient(to bottom, + background: linear-gradient( + to bottom, transparent, - rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb)); - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Ubuntu, Droid Sans, Helvetica Neue, sans-serif; + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); + font-family: + -apple-system, + BlinkMacSystemFont, + Segoe UI, + Roboto, + Noto Sans, + Ubuntu, + Droid Sans, + Helvetica Neue, + sans-serif; } a { diff --git a/frontend/src/wallets/near.js b/frontend/src/wallets/near.js index c7f038d..28b589d 100644 --- a/frontend/src/wallets/near.js +++ b/frontend/src/wallets/near.js @@ -1,16 +1,16 @@ // near api js -import { providers } from 'near-api-js'; +import { providers } from "near-api-js"; // wallet selector -import { distinctUntilChanged, map } from 'rxjs'; -import '@near-wallet-selector/modal-ui/styles.css'; -import { setupModal } from '@near-wallet-selector/modal-ui'; -import { setupWalletSelector } from '@near-wallet-selector/core'; -import { setupHereWallet } from '@near-wallet-selector/here-wallet'; -import { setupMyNearWallet } from '@near-wallet-selector/my-near-wallet'; +import { distinctUntilChanged, map } from "rxjs"; +import "@near-wallet-selector/modal-ui/styles.css"; +import { setupModal } from "@near-wallet-selector/modal-ui"; +import { setupWalletSelector } from "@near-wallet-selector/core"; +import { setupHereWallet } from "@near-wallet-selector/here-wallet"; +import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; -const THIRTY_TGAS = '30000000000000'; -const NO_DEPOSIT = '0'; +const THIRTY_TGAS = "30000000000000"; +const NO_DEPOSIT = "0"; export class Wallet { /** @@ -22,7 +22,7 @@ export class Wallet { * const wallet = new Wallet({ networkId: 'testnet', createAccessKeyFor: 'contractId' }); * wallet.startUp((signedAccountId) => console.log(signedAccountId)); */ - constructor({ networkId = 'testnet', createAccessKeyFor = undefined }) { + constructor({ networkId = "testnet", createAccessKeyFor = undefined }) { this.createAccessKeyFor = createAccessKeyFor; this.networkId = networkId; } @@ -30,25 +30,29 @@ export class Wallet { /** * To be called when the website loads * @param {Function} accountChangeHook - a function that is called when the user signs in or out# - * @returns {Promise} - the accountId of the signed-in user + * @returns {Promise} - the accountId of the signed-in user */ startUp = async (accountChangeHook) => { this.selector = setupWalletSelector({ network: this.networkId, - modules: [setupMyNearWallet(), setupHereWallet()] + modules: [setupMyNearWallet(), setupHereWallet()], }); const walletSelector = await this.selector; const isSignedIn = walletSelector.isSignedIn(); - const accountId = isSignedIn ? walletSelector.store.getState().accounts[0].accountId : ''; + const accountId = isSignedIn + ? walletSelector.store.getState().accounts[0].accountId + : ""; walletSelector.store.observable .pipe( - map(state => state.accounts), - distinctUntilChanged() + map((state) => state.accounts), + distinctUntilChanged(), ) - .subscribe(accounts => { - const signedAccount = accounts.find((account) => account.active)?.accountId; + .subscribe((accounts) => { + const signedAccount = accounts.find( + (account) => account.active, + )?.accountId; accountChangeHook(signedAccount); }); @@ -59,7 +63,9 @@ export class Wallet { * Displays a modal to login the user */ signIn = async () => { - const modal = setupModal(await this.selector, { contractId: this.createAccessKeyFor }); + const modal = setupModal(await this.selector, { + contractId: this.createAccessKeyFor, + }); modal.show(); }; @@ -84,16 +90,15 @@ export class Wallet { const provider = new providers.JsonRpcProvider({ url }); let res = await provider.query({ - request_type: 'call_function', + request_type: "call_function", account_id: contractId, method_name: method, - args_base64: Buffer.from(JSON.stringify(args)).toString('base64'), - finality: 'optimistic', + args_base64: Buffer.from(JSON.stringify(args)).toString("base64"), + finality: "optimistic", }); return JSON.parse(Buffer.from(res.result).toString()); }; - /** * Makes a call to a contract * @param {Object} options - the options for the call @@ -104,14 +109,20 @@ export class Wallet { * @param {string} options.deposit - the amount of yoctoNEAR to deposit * @returns {Promise} - the resulting transaction */ - callMethod = async ({ contractId, method, args = {}, gas = THIRTY_TGAS, deposit = NO_DEPOSIT }) => { + callMethod = async ({ + contractId, + method, + args = {}, + gas = THIRTY_TGAS, + deposit = NO_DEPOSIT, + }) => { // Sign a transaction with the "FunctionCall" action const selectedWallet = await (await this.selector).wallet(); const outcome = await selectedWallet.signAndSendTransaction({ receiverId: contractId, actions: [ { - type: 'FunctionCall', + type: "FunctionCall", params: { methodName: method, args, @@ -136,7 +147,7 @@ export class Wallet { const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); // Retrieve transaction result from the network - const transaction = await provider.txStatus(txhash, 'unnused'); + const transaction = await provider.txStatus(txhash, "unnused"); return providers.getTransactionLastResult(transaction); }; -} \ No newline at end of file +}