From 6babfc9b6de3f274ee5631d74f2d771966a31c75 Mon Sep 17 00:00:00 2001 From: darren wang Date: Fri, 3 May 2024 17:33:00 -0700 Subject: [PATCH 1/3] add toasts + owner Explorer link --- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 18 +++++++++++++++++ frontend/src/app/globals.css | 1 - frontend/src/app/home/Pet/Actions.tsx | 5 +++++ frontend/src/app/home/Pet/Details.tsx | 20 +++++++++++++++++-- frontend/src/app/layout.tsx | 14 +++++++++++++ .../src/components/WalletButtons/index.tsx | 18 +++++++++++++++-- 7 files changed, 72 insertions(+), 5 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index fb509566..8b45fba6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-icons": "^4.10.1", + "sonner": "^1.4.41", "tailwind-merge": "^1.14.0", "tailwindcss": "3.3.3" }, diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 3ae6e9dd..0b6a27ea 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + dependencies: '@aptos-labs/ts-sdk': specifier: ^1.5.1 @@ -46,6 +50,9 @@ dependencies: react-icons: specifier: ^4.10.1 version: 4.10.1(react@18.2.0) + sonner: + specifier: ^1.4.41 + version: 1.4.41(react-dom@18.2.0)(react@18.2.0) tailwind-merge: specifier: ^1.14.0 version: 1.14.0 @@ -249,6 +256,7 @@ packages: /@emotion/memoize@0.7.4: resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} + requiresBuild: true dev: false optional: true @@ -3491,6 +3499,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.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 93b3f015..b227b2ed 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -4,7 +4,6 @@ *, ::before, ::after { box-sizing: border-box; - word-spacing: -0.25rem; } body { diff --git a/frontend/src/app/home/Pet/Actions.tsx b/frontend/src/app/home/Pet/Actions.tsx index 755da88b..332a81b7 100644 --- a/frontend/src/app/home/Pet/Actions.tsx +++ b/frontend/src/app/home/Pet/Actions.tsx @@ -10,6 +10,7 @@ import { NEXT_PUBLIC_ENERGY_INCREASE, } from "@/utils/env"; import { ABI } from "@/utils/abi"; +import { toast } from "sonner"; const aptosClient = getAptosClient(); @@ -75,8 +76,10 @@ export function Actions({ }); } catch (error: any) { console.error(error); + toast.error("Failed to feed your pet. Please try again."); } finally { setTransactionInProgress(false); + toast.success(`Thanks for feeding your pet, ${pet.name}!`); } }; @@ -111,8 +114,10 @@ export function Actions({ }); } catch (error: any) { console.error(error); + toast.error("Failed to play with your pet. Please try again."); } finally { setTransactionInProgress(false); + toast.success(`Thanks for playing with your pet, ${pet.name}!`); } }; diff --git a/frontend/src/app/home/Pet/Details.tsx b/frontend/src/app/home/Pet/Details.tsx index 10ad2ecc..4f48ae6d 100644 --- a/frontend/src/app/home/Pet/Details.tsx +++ b/frontend/src/app/home/Pet/Details.tsx @@ -1,13 +1,14 @@ "use client"; import { AiFillSave } from "react-icons/ai"; -import { FaCopy } from "react-icons/fa"; +import { FaCopy, FaExternalLinkAlt } from "react-icons/fa"; import { HealthBar } from "@/components/HealthBar"; import { Pet } from "."; import { Dispatch, SetStateAction, useState } from "react"; import { useWallet } from "@aptos-labs/wallet-adapter-react"; import { getAptosClient } from "@/utils/aptosClient"; import { ABI } from "@/utils/abi"; +import { toast } from "sonner"; export interface PetDetailsProps { pet: Pet; @@ -45,11 +46,17 @@ export function PetDetails({ pet, setPet }: PetDetailsProps) { }); } catch (error: any) { console.error(error); + toast.error("Failed to update name. Please try again."); + } finally { + toast.success( + `Name was successfully updated from ${pet.name} to ${newName}!` + ); } }; const handleCopyOwnerAddrOrName = () => { navigator.clipboard.writeText(owner); + toast.success("Owner address copied to clipboard."); }; const nameFieldComponent = ( @@ -76,7 +83,16 @@ export function PetDetails({ pet, setPet }: PetDetailsProps) { const ownerFieldComponent = (
- + + + +
+ {children} diff --git a/frontend/src/components/WalletButtons/index.tsx b/frontend/src/components/WalletButtons/index.tsx index 7c8de5d9..9f6d0af2 100644 --- a/frontend/src/components/WalletButtons/index.tsx +++ b/frontend/src/components/WalletButtons/index.tsx @@ -8,18 +8,30 @@ import { WalletName, } from "@aptos-labs/wallet-adapter-react"; import { cn } from "@/utils/styling"; +import { toast } from "sonner"; const buttonStyles = "nes-btn is-primary"; export const WalletButtons = () => { const { wallets, connected, disconnect, isLoading } = useWallet(); + const onWalletDisconnectRequest = async () => { + try { + disconnect(); + } catch (error) { + console.warn(error); + toast.error("Failed to disconnect wallet. Please try again."); + } finally { + toast.success("Wallet successfully disconnected!"); + } + }; + if (connected) { return (
Disconnect
@@ -50,7 +62,9 @@ const WalletView = ({ wallet }: { wallet: Wallet }) => { await connect(walletName); } catch (error) { console.warn(error); - window.alert("Failed to connect wallet"); + toast.error("Failed to connect wallet. Please try again."); + } finally { + toast.success("Wallet successfully connected!"); } }; From dadcd461df32ad7564c601062042e9a4483ae2c4 Mon Sep 17 00:00:00 2001 From: darren wang Date: Fri, 3 May 2024 17:38:31 -0700 Subject: [PATCH 2/3] mobile optimizations --- frontend/src/app/home/NotConnected.tsx | 2 +- frontend/src/app/home/Pet/index.tsx | 8 ++--- frontend/src/app/page.tsx | 35 ++++--------------- .../components/AptogotchiCollection/index.tsx | 2 +- .../src/components/WalletButtons/index.tsx | 2 +- 5 files changed, 13 insertions(+), 36 deletions(-) diff --git a/frontend/src/app/home/NotConnected.tsx b/frontend/src/app/home/NotConnected.tsx index 3e8486bb..ff80f802 100644 --- a/frontend/src/app/home/NotConnected.tsx +++ b/frontend/src/app/home/NotConnected.tsx @@ -16,7 +16,7 @@ export function NotConnected() { return (
-
+

Welcome

{text}

diff --git a/frontend/src/app/home/Pet/index.tsx b/frontend/src/app/home/Pet/index.tsx index 7f664916..d30f8be1 100644 --- a/frontend/src/app/home/Pet/index.tsx +++ b/frontend/src/app/home/Pet/index.tsx @@ -39,9 +39,9 @@ export function Pet({ pet, setPet }: PetProps) { const [selectedAction, setSelectedAction] = useState("play"); return ( -
-
-
+
+
+
-
+
{ - const fixedStyle = { - width: "1200px", - height: "800px", - border: "6px solid", - margin: "auto", - }; - - return ( -
-
{children}
-
- ); -}; export default function Home() { return ( -
- +
+
- -
+
+
); } function Header() { return ( -
-

Aptogotchi

+
+

Aptogotchi

); diff --git a/frontend/src/components/AptogotchiCollection/index.tsx b/frontend/src/components/AptogotchiCollection/index.tsx index 56d62789..89393a49 100644 --- a/frontend/src/components/AptogotchiCollection/index.tsx +++ b/frontend/src/components/AptogotchiCollection/index.tsx @@ -15,7 +15,7 @@ export function AptogotchiCollection() { if (loading || !collection) return null; return ( -
+

{`There are a total of ${collection.current_supply} Aptogotchis in existence.`}

{`Meet your fellow Aptogotchis: ${firstFewAptogotchiName?.join(", ")}${ (firstFewAptogotchiName?.length || 0) < collection.current_supply diff --git a/frontend/src/components/WalletButtons/index.tsx b/frontend/src/components/WalletButtons/index.tsx index 9f6d0af2..419a4898 100644 --- a/frontend/src/components/WalletButtons/index.tsx +++ b/frontend/src/components/WalletButtons/index.tsx @@ -28,7 +28,7 @@ export const WalletButtons = () => { if (connected) { return ( -

+
Date: Fri, 3 May 2024 17:46:22 -0700 Subject: [PATCH 3/3] create new Pet Provider --- frontend/src/app/home/Connected.tsx | 48 +++++++++++-------- frontend/src/app/home/Pet/Actions.tsx | 23 ++++----- frontend/src/app/home/Pet/Details.tsx | 21 ++++---- frontend/src/app/home/Pet/Image.tsx | 5 +- frontend/src/app/home/Pet/ShufflePetImage.tsx | 2 +- frontend/src/app/home/Pet/Summary.tsx | 10 ++-- frontend/src/app/home/Pet/index.tsx | 18 +++---- frontend/src/app/layout.tsx | 5 +- .../src/components/WalletButtons/index.tsx | 2 +- frontend/src/context/PetContext.tsx | 37 ++++++++++++++ 10 files changed, 106 insertions(+), 65 deletions(-) create mode 100644 frontend/src/context/PetContext.tsx diff --git a/frontend/src/app/home/Connected.tsx b/frontend/src/app/home/Connected.tsx index 4092aa7b..eb8c8936 100644 --- a/frontend/src/app/home/Connected.tsx +++ b/frontend/src/app/home/Connected.tsx @@ -1,45 +1,55 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; +import { useEffect, useCallback } from "react"; import { Pet } from "./Pet"; import { useWallet } from "@aptos-labs/wallet-adapter-react"; import { Mint } from "./Mint"; import { getAptosClient } from "@/utils/aptosClient"; import { Modal } from "@/components/Modal"; import { ABI } from "@/utils/abi"; +import { usePet } from "@/context/PetContext"; const TESTNET_ID = "2"; const aptosClient = getAptosClient(); export function Connected() { - const [pet, setPet] = useState(); + const { pet, setPet } = usePet(); const { account, network } = useWallet(); const fetchPet = useCallback(async () => { if (!account?.address) return; - const [hasPet] = await aptosClient.view({ + const hasPet = await aptosClient.view({ payload: { function: `${ABI.address}::main::has_aptogotchi`, functionArguments: [account.address], }, }); - if (hasPet as boolean) { - const response = await aptosClient.view({ - payload: { - function: `${ABI.address}::main::get_aptogotchi`, - functionArguments: [account.address], - }, - }); - const [name, birthday, energyPoints, parts] = response; - const typedParts = parts as { body: number; ear: number; face: number }; - setPet({ - name: name as string, - birthday: birthday as number, - energy_points: energyPoints as number, - parts: typedParts, - }); + + if (hasPet) { + let response; + + try { + response = await aptosClient.view({ + payload: { + function: `${ABI.address}::main::get_aptogotchi`, + functionArguments: [account.address], + }, + }); + + const [name, birthday, energyPoints, parts] = response; + const typedParts = parts as { body: number; ear: number; face: number }; + + setPet({ + name: name as string, + birthday: birthday as number, + energy_points: energyPoints as number, + parts: typedParts, + }); + } catch (error) { + console.error(error); + } } }, [account?.address]); @@ -52,7 +62,7 @@ export function Connected() { return (
{network?.chainId !== TESTNET_ID && } - {pet ? : } + {pet ? : }
); } diff --git a/frontend/src/app/home/Pet/Actions.tsx b/frontend/src/app/home/Pet/Actions.tsx index 332a81b7..9b2eaa95 100644 --- a/frontend/src/app/home/Pet/Actions.tsx +++ b/frontend/src/app/home/Pet/Actions.tsx @@ -1,8 +1,7 @@ "use client"; -import { Dispatch, SetStateAction, useState } from "react"; +import { useState } from "react"; import { useWallet } from "@aptos-labs/wallet-adapter-react"; -import { Pet } from "."; import { getAptosClient } from "@/utils/aptosClient"; import { NEXT_PUBLIC_ENERGY_CAP, @@ -11,24 +10,20 @@ import { } from "@/utils/env"; import { ABI } from "@/utils/abi"; import { toast } from "sonner"; +import { usePet } from "@/context/PetContext"; const aptosClient = getAptosClient(); export type PetAction = "feed" | "play"; export interface ActionsProps { - pet: Pet; selectedAction: PetAction; setSelectedAction: (action: PetAction) => void; - setPet: Dispatch>; } -export function Actions({ - selectedAction, - setSelectedAction, - setPet, - pet, -}: ActionsProps) { +export function Actions({ selectedAction, setSelectedAction }: ActionsProps) { + const { pet, setPet } = usePet(); + const [transactionInProgress, setTransactionInProgress] = useState(false); const { account, network, signAndSubmitTransaction } = useWallet(); @@ -79,7 +74,7 @@ export function Actions({ toast.error("Failed to feed your pet. Please try again."); } finally { setTransactionInProgress(false); - toast.success(`Thanks for feeding your pet, ${pet.name}!`); + toast.success(`Thanks for feeding your pet, ${pet?.name}!`); } }; @@ -117,15 +112,15 @@ export function Actions({ toast.error("Failed to play with your pet. Please try again."); } finally { setTransactionInProgress(false); - toast.success(`Thanks for playing with your pet, ${pet.name}!`); + toast.success(`Thanks for playing with your pet, ${pet?.name}!`); } }; const feedDisabled = selectedAction === "feed" && - pet.energy_points === Number(NEXT_PUBLIC_ENERGY_CAP); + pet?.energy_points === Number(NEXT_PUBLIC_ENERGY_CAP); const playDisabled = - selectedAction === "play" && pet.energy_points === Number(0); + selectedAction === "play" && pet?.energy_points === Number(0); return (
diff --git a/frontend/src/app/home/Pet/Details.tsx b/frontend/src/app/home/Pet/Details.tsx index 4f48ae6d..42f99ea2 100644 --- a/frontend/src/app/home/Pet/Details.tsx +++ b/frontend/src/app/home/Pet/Details.tsx @@ -3,28 +3,25 @@ import { AiFillSave } from "react-icons/ai"; import { FaCopy, FaExternalLinkAlt } from "react-icons/fa"; import { HealthBar } from "@/components/HealthBar"; -import { Pet } from "."; -import { Dispatch, SetStateAction, useState } from "react"; +import { useState } from "react"; import { useWallet } from "@aptos-labs/wallet-adapter-react"; import { getAptosClient } from "@/utils/aptosClient"; import { ABI } from "@/utils/abi"; import { toast } from "sonner"; - -export interface PetDetailsProps { - pet: Pet; - setPet: Dispatch>; -} +import { usePet } from "@/context/PetContext"; const aptosClient = getAptosClient(); -export function PetDetails({ pet, setPet }: PetDetailsProps) { - const [newName, setNewName] = useState(pet.name); +export function PetDetails() { + const { pet, setPet } = usePet(); + + const [newName, setNewName] = useState(pet?.name || ""); const { account, network, signAndSubmitTransaction } = useWallet(); const owner = account?.ansName ? `${account?.ansName}.apt` : account?.address || ""; - const canSave = newName !== pet.name; + const canSave = newName !== pet?.name; const handleNameChange = async () => { if (!account || !network) return; @@ -49,7 +46,7 @@ export function PetDetails({ pet, setPet }: PetDetailsProps) { toast.error("Failed to update name. Please try again."); } finally { toast.success( - `Name was successfully updated from ${pet.name} to ${newName}!` + `Name was successfully updated from ${pet?.name} to ${newName}!` ); } }; @@ -117,7 +114,7 @@ export function PetDetails({ pet, setPet }: PetDetailsProps) {
diff --git a/frontend/src/app/home/Pet/Image.tsx b/frontend/src/app/home/Pet/Image.tsx index 9a12da48..7b27ddfa 100644 --- a/frontend/src/app/home/Pet/Image.tsx +++ b/frontend/src/app/home/Pet/Image.tsx @@ -6,12 +6,15 @@ import { PetAction } from "@/app/home/Pet/Actions"; export interface PetImageProps { selectedAction?: PetAction; - petParts: PetParts; + petParts: PetParts | undefined; avatarStyle?: boolean; } export function PetImage(props: PetImageProps) { const { avatarStyle, petParts, selectedAction } = props; + + if (!petParts) return
; + const head = BASE_PATH + "head.png"; const body = BASE_PATH + bodies[petParts.body]; const ear = BASE_PATH + ears[petParts.ear]; diff --git a/frontend/src/app/home/Pet/ShufflePetImage.tsx b/frontend/src/app/home/Pet/ShufflePetImage.tsx index 745838b0..6a6d60ef 100644 --- a/frontend/src/app/home/Pet/ShufflePetImage.tsx +++ b/frontend/src/app/home/Pet/ShufflePetImage.tsx @@ -1,7 +1,7 @@ "use client"; import React from "react"; -import { Pet, PetParts } from "."; +import { PetParts } from "."; import { PetImage } from "./Image"; import { ShuffleButton } from "@/components/ShuffleButton"; import { diff --git a/frontend/src/app/home/Pet/Summary.tsx b/frontend/src/app/home/Pet/Summary.tsx index 93a92842..c19fbfc0 100644 --- a/frontend/src/app/home/Pet/Summary.tsx +++ b/frontend/src/app/home/Pet/Summary.tsx @@ -1,13 +1,13 @@ "use client"; import { useTypingEffect } from "@/utils/useTypingEffect"; -import { Pet } from "."; +import { usePet } from "@/context/PetContext"; -export interface SummaryProps { - pet: Pet; -} +export function Summary() { + const { pet } = usePet(); + + if (!pet) return null; -export function Summary({ pet }: SummaryProps) { let text = `${pet.name} is doing great! 😄`; if (pet.energy_points >= 8) { diff --git a/frontend/src/app/home/Pet/index.tsx b/frontend/src/app/home/Pet/index.tsx index d30f8be1..d6512f34 100644 --- a/frontend/src/app/home/Pet/index.tsx +++ b/frontend/src/app/home/Pet/index.tsx @@ -1,11 +1,12 @@ "use client"; -import { Dispatch, SetStateAction, useState } from "react"; +import { useState } from "react"; import { Actions, PetAction } from "./Actions"; import { PetDetails } from "./Details"; import { PetImage } from "./Image"; import { Summary } from "./Summary"; import { AptogotchiCollection } from "@/components/AptogotchiCollection"; +import { usePet } from "@/context/PetContext"; export interface Pet { name: string; @@ -30,12 +31,9 @@ export const DEFAULT_PET = { }, }; -interface PetProps { - pet: Pet; - setPet: Dispatch>; -} +export function Pet() { + const { pet, setPet } = usePet(); -export function Pet({ pet, setPet }: PetProps) { const [selectedAction, setSelectedAction] = useState("play"); return ( @@ -44,19 +42,17 @@ export function Pet({ pet, setPet }: PetProps) {
- +
- +
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 0ee7d143..0538cd32 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -5,6 +5,7 @@ import { PropsWithChildren } from "react"; import { GeoTargetly } from "@/utils/GeoTargetly"; import "nes.css/css/nes.min.css"; import { Toaster } from "sonner"; +import { PetProvider } from "@/context/PetContext"; import "./globals.css"; const kongtext = localFont({ @@ -52,7 +53,9 @@ export default function RootLayout({ children }: PropsWithChildren) { closeButton expand={true} /> - {children} + + {children} + diff --git a/frontend/src/components/WalletButtons/index.tsx b/frontend/src/components/WalletButtons/index.tsx index 419a4898..d96ee785 100644 --- a/frontend/src/components/WalletButtons/index.tsx +++ b/frontend/src/components/WalletButtons/index.tsx @@ -10,7 +10,7 @@ import { import { cn } from "@/utils/styling"; import { toast } from "sonner"; -const buttonStyles = "nes-btn is-primary"; +const buttonStyles = "nes-btn is-primary m-auto sm:m-0 sm:px-4"; export const WalletButtons = () => { const { wallets, connected, disconnect, isLoading } = useWallet(); diff --git a/frontend/src/context/PetContext.tsx b/frontend/src/context/PetContext.tsx new file mode 100644 index 00000000..0b85eab9 --- /dev/null +++ b/frontend/src/context/PetContext.tsx @@ -0,0 +1,37 @@ +"use client"; + +import React, { + createContext, + useContext, + useState, + Dispatch, + SetStateAction, +} from "react"; +import { Pet } from "@/app/home/Pet"; + +interface PetContextType { + pet: Pet | undefined; + setPet: Dispatch>; +} + +const PetContext = createContext(undefined); + +export const PetProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const [pet, setPet] = useState(undefined); + + return ( + + {children} + + ); +}; + +export const usePet = () => { + const context = useContext(PetContext); + if (!context) { + throw new Error("usePet must be used within a PetProvider"); + } + return context; +};