From 961489449fe03e89ed4d0d24391856502b79b201 Mon Sep 17 00:00:00 2001 From: rob chang Date: Thu, 19 Dec 2024 11:38:00 -0500 Subject: [PATCH 1/8] feat: mintcard 7702 lo-fi --- .../ui-demo/src/components/icons/loading.tsx | 3 +- .../src/components/shared/7702/Button.tsx | 18 ++ .../components/shared/7702/MintCard7702.tsx | 187 ++++++++++++++++++ .../src/components/shared/7702/MintStages.tsx | 51 +++++ 4 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 examples/ui-demo/src/components/shared/7702/Button.tsx create mode 100644 examples/ui-demo/src/components/shared/7702/MintCard7702.tsx create mode 100644 examples/ui-demo/src/components/shared/7702/MintStages.tsx diff --git a/examples/ui-demo/src/components/icons/loading.tsx b/examples/ui-demo/src/components/icons/loading.tsx index ef89746f77..eb5041e0d8 100644 --- a/examples/ui-demo/src/components/icons/loading.tsx +++ b/examples/ui-demo/src/components/icons/loading.tsx @@ -1,5 +1,5 @@ import { useTheme } from "@/state/useTheme"; -export const LoadingIcon = () => { +export const LoadingIcon = ({ className }: { className?: string }) => { const theme = useTheme(); const animationClass = theme === "dark" ? "animate-ui-loading-dark" : "animate-ui-loading-light"; @@ -11,6 +11,7 @@ export const LoadingIcon = () => { viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" + className={className} > & { className?: string } +>) => { + return ( + + ); +}; diff --git a/examples/ui-demo/src/components/shared/7702/MintCard7702.tsx b/examples/ui-demo/src/components/shared/7702/MintCard7702.tsx new file mode 100644 index 0000000000..6db2037f84 --- /dev/null +++ b/examples/ui-demo/src/components/shared/7702/MintCard7702.tsx @@ -0,0 +1,187 @@ +import { useSmartAccountClient } from "@account-kit/react"; +import { useQuery } from "@tanstack/react-query"; +import { AccountKitNftMinterABI, nftContractAddress } from "@/utils/config"; +import { useCallback, useState } from "react"; +import Image from "next/image"; +import { LoadingIcon } from "@/components/icons/loading"; +import { Button } from "./Button"; +import { useToast } from "@/hooks/useToast"; +import { useSendUserOperation } from "@account-kit/react"; +import { encodeFunctionData } from "viem"; +import { MintStages } from "./MintStages"; + +const initialValuePropState = { + signing: "initial", + gas: "initial", + batch: "initial", +} satisfies MintStatus; + +type NFTLoadingState = "initial" | "loading" | "success"; +export type MintStatus = { + signing: NFTLoadingState; + gas: NFTLoadingState; + batch: NFTLoadingState; +}; + +export const MintCard7702 = () => { + const [status, setStatus] = useState(initialValuePropState); + const [nftTransfered, setNftTransfered] = useState(false); + const isLoading = Object.values(status).some((x) => x === "loading"); + const { setToast } = useToast(); + const { client } = useSmartAccountClient({ type: "LightAccount" }); + const handleSuccess = () => { + setStatus(() => ({ + batch: "success", + gas: "success", + signing: "success", + })); + setToast({ + type: "success", + text: ( + <> + + {`You've collected your NFT!`} + + + {`You've successfully collected your NFT! Refresh to mint + again.`} + + + ), + open: true, + }); + }; + + const handleError = (error: Error) => { + console.error(error); + setStatus(initialValuePropState); + setNftTransfered(false); + setToast({ + type: "error", + text: "There was a problem with that action", + open: true, + }); + }; + + const { data: uri } = useQuery({ + queryKey: ["contractURI", nftContractAddress], + queryFn: async () => { + const uri = await client?.readContract({ + address: nftContractAddress, + abi: AccountKitNftMinterABI, + functionName: "baseURI", + }); + return uri; + }, + enabled: !!client && !!client?.readContract, + }); + const { sendUserOperationResult, sendUserOperation } = useSendUserOperation({ + client, + waitForTxn: true, + onError: handleError, + onSuccess: handleSuccess, + onMutate: () => { + setTimeout(() => { + setStatus((prev) => ({ ...prev, signing: "success" })); + }, 500); + setTimeout(() => { + setStatus((prev) => ({ ...prev, gas: "success" })); + }, 750); + }, + }); + + const handleCollectNFT = useCallback(async () => { + if (!client) { + console.error("no client"); + return; + } + setNftTransfered(true); + + setStatus({ + signing: "loading", + gas: "loading", + batch: "loading", + }); + sendUserOperation({ + uo: { + target: nftContractAddress, + data: encodeFunctionData({ + abi: AccountKitNftMinterABI, + functionName: "mintTo", + args: [client.getAddress()], + }), + }, + }); + }, [client, sendUserOperation]); + + return ( +
+ {uri ? ( +
+
+ An NFT +
+
+ ) : ( +
+ +
+ )} +
+

+ Gasless transactions +

+

+ Sponsor gas and sign in the background for a one-click transaction + experience. +

+
+ {!nftTransfered ? ( +
+

Gas Fee

+

+ + $0.02 + + + Free + +

+
+ ) : ( + + )} + +
+ ); +}; diff --git a/examples/ui-demo/src/components/shared/7702/MintStages.tsx b/examples/ui-demo/src/components/shared/7702/MintStages.tsx new file mode 100644 index 0000000000..2ebaa715f5 --- /dev/null +++ b/examples/ui-demo/src/components/shared/7702/MintStages.tsx @@ -0,0 +1,51 @@ +import { CheckCircleFilledIcon } from "@/components/icons/check-circle-filled"; +import { LoadingIcon } from "@/components/icons/loading"; +import { MintStatus } from "./MintCard7702"; + +export const MintStages = ({ status }: { status: MintStatus }) => { + return ( +
+ + + +
+ ); +}; + +const Stage = ({ + icon, + description, + className, +}: { + icon: "loading" | "success" | "initial"; + description: string | JSX.Element; + className?: string; +}) => { + return ( +
+
{getMintIcon(icon)}
+

{description}

+
+ ); +}; + +const getMintIcon = (icon: "loading" | "success" | "initial") => { + switch (icon) { + case "loading": + case "initial": + return ; + case "success": + return ( + + ); + } +}; From c81276e7c75176d914ed0674975ebebea5d36c2d Mon Sep 17 00:00:00 2001 From: Caleb Briancesco Date: Thu, 19 Dec 2024 11:35:12 -0600 Subject: [PATCH 2/8] feat: adding wallet type switch and transactions hook to mock transactions --- .../components/auth/hooks/useTransaction.ts | 84 +++++++++++++++++++ examples/ui-demo/src/app/page.tsx | 2 + .../configuration/Configuration.tsx | 51 +++++++++++ .../ui-demo/src/components/icons/settings.tsx | 21 +++++ .../components/shared/WalletTypeSwitch.tsx | 53 ++++++++++++ 5 files changed, 211 insertions(+) create mode 100644 account-kit/react/src/components/auth/hooks/useTransaction.ts create mode 100644 examples/ui-demo/src/components/configuration/Configuration.tsx create mode 100644 examples/ui-demo/src/components/icons/settings.tsx create mode 100644 examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx diff --git a/account-kit/react/src/components/auth/hooks/useTransaction.ts b/account-kit/react/src/components/auth/hooks/useTransaction.ts new file mode 100644 index 0000000000..f180fcaf7f --- /dev/null +++ b/account-kit/react/src/components/auth/hooks/useTransaction.ts @@ -0,0 +1,84 @@ +import { useState } from "react"; + +export enum TransactionStatus { + none = "none", + pending = "pending", + success = "success", + error = "error", +} + +export interface Transaction { + title: string; + status: TransactionStatus; +} + +export interface TransactionConfig { + stepsNumber?: number; +} + +export interface UseTransaction { + makeRequest: () => void; + result: Transaction[]; + reset: () => void; +} + +const randomDelay = (min = 2, max = 6) => { + const delay = Math.floor(Math.random() * (max - min + 1)) + min; + return delay * 1000; +}; + +export const useTransaction = ( + { stepsNumber = 3 }: TransactionConfig = { stepsNumber: 3 } +): UseTransaction => { + const baseRequest = Array.from({ length: stepsNumber }).map( + (_item, index) => ({ + title: `Transaction ${index + 1}`, + status: TransactionStatus.none, + }) + ); + + const [result, setResult] = useState(baseRequest); + + const reset = () => { + setResult(baseRequest); + }; + + const simulateApiCall = async ( + transaction: Transaction + ): Promise => { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ ...transaction, status: TransactionStatus.success }); + }, randomDelay()); + }); + }; + + const makeRequest = async () => { + for (let i = 0; i < result.length; i++) { + const response = await simulateApiCall(result[i]); + + setResult((prevState) => { + const newState = [...prevState]; + newState[i] = response; + return newState; + }); + } + }; + + const startRequest = () => { + setResult((prevState) => { + return [...prevState].map((item) => ({ + ...item, + status: TransactionStatus.pending, + })); + }); + + makeRequest(); + }; + + return { + makeRequest: startRequest, + result, + reset, + }; +}; diff --git a/examples/ui-demo/src/app/page.tsx b/examples/ui-demo/src/app/page.tsx index 0a8b799485..c7fa485360 100644 --- a/examples/ui-demo/src/app/page.tsx +++ b/examples/ui-demo/src/app/page.tsx @@ -20,6 +20,7 @@ import { AuthCardWrapper } from "../components/preview/AuthCardWrapper"; import { CodePreview } from "../components/preview/CodePreview"; import { CodePreviewSwitch } from "../components/shared/CodePreviewSwitch"; import { TopNav } from "../components/topnav/TopNav"; +import { Configuration } from "@/components/configuration/Configuration"; const publicSans = Public_Sans({ subsets: ["latin"], @@ -47,6 +48,7 @@ export default function Home() { >
+
diff --git a/examples/ui-demo/src/components/configuration/Configuration.tsx b/examples/ui-demo/src/components/configuration/Configuration.tsx new file mode 100644 index 0000000000..d5e8dff5da --- /dev/null +++ b/examples/ui-demo/src/components/configuration/Configuration.tsx @@ -0,0 +1,51 @@ +import { useState } from "react"; +import { cn } from "@/lib/utils"; + +import { SettingsIcon } from "../icons/settings"; +// import { HelpTooltip } from "../shared/HelpTooltip"; +import { WalletTypeSwitch } from "../shared/WalletTypeSwitch"; +import ExternalLink from "../shared/ExternalLink"; + +enum WalletTypes { + smart = "smart", + smart7702 = "7702", +} +export const Configuration = ({ className }: { className?: string }) => { + const [walletType, setWalletType] = useState(WalletTypes.smart); + + const onSwitchWalletType = () => { + setWalletType( + walletType === WalletTypes.smart + ? WalletTypes.smart7702 + : WalletTypes.smart + ); + }; + + return ( +
+
+ + Configuration +
+
+

+ Embedded Wallet Type{" "} +

+ {/* */} +
+ +

+ On sign up, create a smart account or create an EOA that delegates to a + smart account.{" "} + + Learn more. + +

+
+
+ ); +}; diff --git a/examples/ui-demo/src/components/icons/settings.tsx b/examples/ui-demo/src/components/icons/settings.tsx new file mode 100644 index 0000000000..53a4a45855 --- /dev/null +++ b/examples/ui-demo/src/components/icons/settings.tsx @@ -0,0 +1,21 @@ +import { SVGProps } from "react"; + +export const SettingsIcon = ({ + stroke = "currentColor", + ...props +}: JSX.IntrinsicAttributes & SVGProps) => ( + + + +); diff --git a/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx b/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx new file mode 100644 index 0000000000..6535332a83 --- /dev/null +++ b/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { Root, Thumb } from "@radix-ui/react-switch"; +import { forwardRef, ElementRef, ComponentPropsWithoutRef } from "react"; + +import { cn } from "@/lib/utils"; + +const selectedStyles = "text-[#475569]"; +const unselectedStyles = "text-[#020617]"; + +const WalletTypeSwitch = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, checked, ...props }, ref) => ( + + +
+
+

+ Smart account +

+
+
+

+ 7702 Smart Account +

+
+
+
+)); +WalletTypeSwitch.displayName = Root.displayName; + +export { WalletTypeSwitch }; From a5173186868f0547e0a4bbd2d7a098ba07054f17 Mon Sep 17 00:00:00 2001 From: rob chang Date: Thu, 19 Dec 2024 14:35:38 -0500 Subject: [PATCH 3/8] feat: mocked transactions --- examples/ui-demo/src/components/icons/key.tsx | 24 ++++ .../components/shared/7702/MintCard7702.tsx | 117 ++---------------- .../src/components/shared/7702/MintStages.tsx | 5 +- .../components/shared/7702/Transactions.tsx | 54 ++++++++ .../shared/7702/TransactionsCard.tsx | 54 ++++++++ .../src/components/shared/7702/Wrapper.tsx | 35 ++++++ .../src/components/shared/7702/useMint.tsx | 116 +++++++++++++++++ .../components/shared/7702/useTransaction.ts | 83 +++++++++++++ 8 files changed, 382 insertions(+), 106 deletions(-) create mode 100644 examples/ui-demo/src/components/icons/key.tsx create mode 100644 examples/ui-demo/src/components/shared/7702/Transactions.tsx create mode 100644 examples/ui-demo/src/components/shared/7702/TransactionsCard.tsx create mode 100644 examples/ui-demo/src/components/shared/7702/Wrapper.tsx create mode 100644 examples/ui-demo/src/components/shared/7702/useMint.tsx create mode 100644 examples/ui-demo/src/components/shared/7702/useTransaction.ts diff --git a/examples/ui-demo/src/components/icons/key.tsx b/examples/ui-demo/src/components/icons/key.tsx new file mode 100644 index 0000000000..4fc423192e --- /dev/null +++ b/examples/ui-demo/src/components/icons/key.tsx @@ -0,0 +1,24 @@ +export const Key = ({ className }: { className?: string }) => ( + + + + +); diff --git a/examples/ui-demo/src/components/shared/7702/MintCard7702.tsx b/examples/ui-demo/src/components/shared/7702/MintCard7702.tsx index 6db2037f84..1957b041e1 100644 --- a/examples/ui-demo/src/components/shared/7702/MintCard7702.tsx +++ b/examples/ui-demo/src/components/shared/7702/MintCard7702.tsx @@ -1,21 +1,8 @@ -import { useSmartAccountClient } from "@account-kit/react"; -import { useQuery } from "@tanstack/react-query"; -import { AccountKitNftMinterABI, nftContractAddress } from "@/utils/config"; -import { useCallback, useState } from "react"; import Image from "next/image"; import { LoadingIcon } from "@/components/icons/loading"; import { Button } from "./Button"; -import { useToast } from "@/hooks/useToast"; -import { useSendUserOperation } from "@account-kit/react"; -import { encodeFunctionData } from "viem"; import { MintStages } from "./MintStages"; -const initialValuePropState = { - signing: "initial", - gas: "initial", - batch: "initial", -} satisfies MintStatus; - type NFTLoadingState = "initial" | "loading" | "success"; export type MintStatus = { signing: NFTLoadingState; @@ -23,97 +10,19 @@ export type MintStatus = { batch: NFTLoadingState; }; -export const MintCard7702 = () => { - const [status, setStatus] = useState(initialValuePropState); - const [nftTransfered, setNftTransfered] = useState(false); - const isLoading = Object.values(status).some((x) => x === "loading"); - const { setToast } = useToast(); - const { client } = useSmartAccountClient({ type: "LightAccount" }); - const handleSuccess = () => { - setStatus(() => ({ - batch: "success", - gas: "success", - signing: "success", - })); - setToast({ - type: "success", - text: ( - <> - - {`You've collected your NFT!`} - - - {`You've successfully collected your NFT! Refresh to mint - again.`} - - - ), - open: true, - }); - }; - - const handleError = (error: Error) => { - console.error(error); - setStatus(initialValuePropState); - setNftTransfered(false); - setToast({ - type: "error", - text: "There was a problem with that action", - open: true, - }); - }; - - const { data: uri } = useQuery({ - queryKey: ["contractURI", nftContractAddress], - queryFn: async () => { - const uri = await client?.readContract({ - address: nftContractAddress, - abi: AccountKitNftMinterABI, - functionName: "baseURI", - }); - return uri; - }, - enabled: !!client && !!client?.readContract, - }); - const { sendUserOperationResult, sendUserOperation } = useSendUserOperation({ - client, - waitForTxn: true, - onError: handleError, - onSuccess: handleSuccess, - onMutate: () => { - setTimeout(() => { - setStatus((prev) => ({ ...prev, signing: "success" })); - }, 500); - setTimeout(() => { - setStatus((prev) => ({ ...prev, gas: "success" })); - }, 750); - }, - }); - - const handleCollectNFT = useCallback(async () => { - if (!client) { - console.error("no client"); - return; - } - setNftTransfered(true); - - setStatus({ - signing: "loading", - gas: "loading", - batch: "loading", - }); - sendUserOperation({ - uo: { - target: nftContractAddress, - data: encodeFunctionData({ - abi: AccountKitNftMinterABI, - functionName: "mintTo", - args: [client.getAddress()], - }), - }, - }); - }, [client, sendUserOperation]); - +export const MintCard7702 = ({ + isLoading, + status, + nftTransfered, + handleCollectNFT, + uri, +}: { + isLoading: boolean; + status: MintStatus; + nftTransfered: boolean; + handleCollectNFT: () => void; + uri?: string; +}) => { return (
{ return ( @@ -26,7 +27,7 @@ const Stage = ({ description, className, }: { - icon: "loading" | "success" | "initial"; + icon: loadingState; description: string | JSX.Element; className?: string; }) => { @@ -38,7 +39,7 @@ const Stage = ({ ); }; -const getMintIcon = (icon: "loading" | "success" | "initial") => { +export const getMintIcon = (icon: loadingState) => { switch (icon) { case "loading": case "initial": diff --git a/examples/ui-demo/src/components/shared/7702/Transactions.tsx b/examples/ui-demo/src/components/shared/7702/Transactions.tsx new file mode 100644 index 0000000000..bba5315d8f --- /dev/null +++ b/examples/ui-demo/src/components/shared/7702/Transactions.tsx @@ -0,0 +1,54 @@ +import { ExternalLinkIcon } from "@/components/icons/external-link"; +import { TransactionType } from "./useTransaction"; +import { CheckCircleFilledIcon } from "@/components/icons/check-circle-filled"; +import { LoadingIcon } from "@/components/icons/loading"; + +export type loadingState = "loading" | "success" | "initial"; + +export const Transactions = ({ + transactions, +}: { + transactions: TransactionType[]; +}) => { + return ( +
+ {transactions.map((transaction, i) => ( + + ))} +
+ ); +}; + +const Transaction = ({ + className, + externalLink, + description, + state, +}: TransactionType & { className?: string }) => { + const getText = () => { + if (state === "initial") { + return ""; + } + if (state === "initiating") { + return "Initiating buy..."; + } + if (state === "next") { + return "Next buy in 10 seconds..."; + } + return description; + }; + + return ( +
+
+ {state === "complete" ? ( + + ) : ( + + )} +
+

{getText()}

+ {externalLink && } +
+ ); +}; diff --git a/examples/ui-demo/src/components/shared/7702/TransactionsCard.tsx b/examples/ui-demo/src/components/shared/7702/TransactionsCard.tsx new file mode 100644 index 0000000000..a829bb0415 --- /dev/null +++ b/examples/ui-demo/src/components/shared/7702/TransactionsCard.tsx @@ -0,0 +1,54 @@ +import { Key } from "@/components/icons/key"; +import { Button } from "./Button"; +import { useState } from "react"; +import { Transactions } from "./Transactions"; +import { TransactionType } from "./useTransaction"; + +export const TransactionsCard = ({ + isLoading, + transactions, + handleTransactions, +}: { + isLoading: boolean; + transactions: TransactionType[]; + handleTransactions: () => void; +}) => { + const [isFiring, setIsFiring] = useState(false); + const handleClick = () => { + setIsFiring(true); + handleTransactions(); + }; + return ( +
+
+ +
+

+ Recurring transactions +

+ {!isFiring ? ( +

+ Set up a dollar-cost average order by creating a session key with + permission to buy ETH every 10 seconds. +

+ ) : ( + + )} + + +
+ ); +}; diff --git a/examples/ui-demo/src/components/shared/7702/Wrapper.tsx b/examples/ui-demo/src/components/shared/7702/Wrapper.tsx new file mode 100644 index 0000000000..d7267c19cb --- /dev/null +++ b/examples/ui-demo/src/components/shared/7702/Wrapper.tsx @@ -0,0 +1,35 @@ +import { MintCard7702 } from "./MintCard7702"; +import { TransactionsCard } from "./TransactionsCard"; +import { useMint } from "./useMint"; +import { useTransactions } from "./useTransaction"; + +export const Wrapper7702 = () => { + const { + isLoading: isLoadingTransactions, + transactions, + handleTransactions, + } = useTransactions(); + const { + isLoading: isLoadingMint, + status, + nftTransfered, + handleCollectNFT, + uri, + } = useMint(); + return ( +
+ + +
+ ); +}; diff --git a/examples/ui-demo/src/components/shared/7702/useMint.tsx b/examples/ui-demo/src/components/shared/7702/useMint.tsx new file mode 100644 index 0000000000..df7bdec51c --- /dev/null +++ b/examples/ui-demo/src/components/shared/7702/useMint.tsx @@ -0,0 +1,116 @@ +import { + useSendUserOperation, + useSmartAccountClient, +} from "@account-kit/react"; +import { useQuery } from "@tanstack/react-query"; +import { AccountKitNftMinterABI, nftContractAddress } from "@/utils/config"; +import { useCallback, useState } from "react"; +import { useToast } from "@/hooks/useToast"; +import { encodeFunctionData } from "viem"; +import { MintStatus } from "./MintCard7702"; + +const initialValuePropState = { + signing: "initial", + gas: "initial", + batch: "initial", +} satisfies MintStatus; + +export const useMint = () => { + const [status, setStatus] = useState(initialValuePropState); + const [nftTransfered, setNftTransfered] = useState(false); + const isLoading = Object.values(status).some((x) => x === "loading"); + const { setToast } = useToast(); + const { client } = useSmartAccountClient({ type: "LightAccount" }); + const handleSuccess = () => { + setStatus(() => ({ + batch: "success", + gas: "success", + signing: "success", + })); + setToast({ + type: "success", + text: ( + <> + + {`You've collected your NFT!`} + + + {`You've successfully collected your NFT! Refresh to mint + again.`} + + + ), + open: true, + }); + }; + + const handleError = (error: Error) => { + console.error(error); + setStatus(initialValuePropState); + setNftTransfered(false); + setToast({ + type: "error", + text: "There was a problem with that action", + open: true, + }); + }; + + const { data: uri } = useQuery({ + queryKey: ["contractURI", nftContractAddress], + queryFn: async () => { + const uri = await client?.readContract({ + address: nftContractAddress, + abi: AccountKitNftMinterABI, + functionName: "baseURI", + }); + return uri; + }, + enabled: !!client && !!client?.readContract, + }); + const { sendUserOperationResult, sendUserOperation } = useSendUserOperation({ + client, + waitForTxn: true, + onError: handleError, + onSuccess: handleSuccess, + onMutate: () => { + setTimeout(() => { + setStatus((prev) => ({ ...prev, signing: "success" })); + }, 500); + setTimeout(() => { + setStatus((prev) => ({ ...prev, gas: "success" })); + }, 750); + }, + }); + + const handleCollectNFT = useCallback(async () => { + if (!client) { + console.error("no client"); + return; + } + setNftTransfered(true); + + setStatus({ + signing: "loading", + gas: "loading", + batch: "loading", + }); + sendUserOperation({ + uo: { + target: nftContractAddress, + data: encodeFunctionData({ + abi: AccountKitNftMinterABI, + functionName: "mintTo", + args: [client.getAddress()], + }), + }, + }); + }, [client, sendUserOperation]); + + return { + isLoading, + status, + nftTransfered, + handleCollectNFT, + uri, + }; +}; diff --git a/examples/ui-demo/src/components/shared/7702/useTransaction.ts b/examples/ui-demo/src/components/shared/7702/useTransaction.ts new file mode 100644 index 0000000000..eabe14c93d --- /dev/null +++ b/examples/ui-demo/src/components/shared/7702/useTransaction.ts @@ -0,0 +1,83 @@ +import { useState } from "react"; + +export type TransactionStages = "initial" | "initiating" | "next" | "complete"; +export type TransactionType = { + state: TransactionStages; + description: string; + externalLink: string; +}; + +const initialState: TransactionType[] = [ + { + state: "initial", + description: "Bought 1 ETH for 4,000 USDC", + externalLink: "", + }, + { + state: "initial", + description: "Bought 1 ETH for 3,500 USDC", + externalLink: "", + }, + { + state: "initial", + description: "Bought 1 ETH for 4,200 USDC", + externalLink: "", + }, +]; + +export const useTransactions = () => { + const [transactions, setTransactions] = + useState(initialState); + + const [isLoading, setIsLoading] = useState(false); + + const handleTransaction = async (transactionIndex: number) => { + setTransactions((prev) => { + const newState = [...prev]; + newState[transactionIndex].state = "initiating"; + if (transactionIndex + 1 < newState.length) { + newState[transactionIndex + 1].state = "next"; + } + return newState; + }); + await new Promise((resolve) => setTimeout(resolve, 3000)); + setTransactions((prev) => { + const newState = [...prev]; + newState[transactionIndex].state = "complete"; + return newState; + }); + }; + // Mock method to fire transactions for 7702 + const handleTransactions = async () => { + console.log({ initialState }); + // initial state is mutated + setIsLoading(true); + setTransactions([ + { + state: "initial", + description: "Bought 1 ETH for 4,000 USDC", + externalLink: "", + }, + { + state: "initial", + description: "Bought 1 ETH for 3,500 USDC", + externalLink: "", + }, + { + state: "initial", + description: "Bought 1 ETH for 4,200 USDC", + externalLink: "", + }, + ]); + for (let i = 0; i < transactions.length; i++) { + await handleTransaction(i); + } + setIsLoading(false); + }; + + return { + transactions, + handleTransactions, + isLoading, + }; +}; From 26bc1474d9e634ff72aaa19fd7155baf8e363722 Mon Sep 17 00:00:00 2001 From: rob chang Date: Thu, 19 Dec 2024 14:59:34 -0500 Subject: [PATCH 4/8] feat: disabled buttons, lift hooks --- .../components/preview/AuthCardWrapper.tsx | 6 +-- .../src/components/shared/7702/Button.tsx | 2 +- .../components/shared/7702/MintCard7702.tsx | 51 ++++++++++--------- .../components/shared/7702/Transactions.tsx | 2 +- .../shared/7702/TransactionsCard.tsx | 20 ++++++-- .../src/components/shared/7702/Wrapper.tsx | 2 + .../components/shared/7702/useTransaction.ts | 12 ++--- 7 files changed, 56 insertions(+), 39 deletions(-) diff --git a/examples/ui-demo/src/components/preview/AuthCardWrapper.tsx b/examples/ui-demo/src/components/preview/AuthCardWrapper.tsx index 9b94e30120..cfa7e00ef5 100644 --- a/examples/ui-demo/src/components/preview/AuthCardWrapper.tsx +++ b/examples/ui-demo/src/components/preview/AuthCardWrapper.tsx @@ -4,7 +4,7 @@ import { cn } from "@/lib/utils"; import { useTheme } from "@/state/useTheme"; import { AuthCard, useUser } from "@account-kit/react"; import { EOAPostLogin } from "../shared/eoa-post-login/EOAPostLogin"; -import { MintCard } from "../shared/mint-card/MintCard"; +import { Wrapper7702 } from "../shared/7702/Wrapper"; export function AuthCardWrapper({ className }: { className?: string }) { const theme = useTheme(); @@ -53,6 +53,6 @@ const RenderContent = () => {
); } - - return ; + return ; + // return ; }; diff --git a/examples/ui-demo/src/components/shared/7702/Button.tsx b/examples/ui-demo/src/components/shared/7702/Button.tsx index a610ed388f..6d19275a90 100644 --- a/examples/ui-demo/src/components/shared/7702/Button.tsx +++ b/examples/ui-demo/src/components/shared/7702/Button.tsx @@ -9,7 +9,7 @@ export const Button = ({ >) => { return (
{!nftTransfered ? ( -
-

Gas Fee

-

- - $0.02 - - - Free - + <> +

+ Sponsor gas and sign in the background for a one-click transaction + experience.

-
+
+

Gas Fee

+

+ + $0.02 + + + Free + +

+
+ ) : ( )}
); diff --git a/examples/ui-demo/src/components/shared/7702/Wrapper.tsx b/examples/ui-demo/src/components/shared/7702/Wrapper.tsx index d7267c19cb..d09d9281bc 100644 --- a/examples/ui-demo/src/components/shared/7702/Wrapper.tsx +++ b/examples/ui-demo/src/components/shared/7702/Wrapper.tsx @@ -20,12 +20,14 @@ export const Wrapper7702 = () => {
{ { state: "initial", description: "Bought 1 ETH for 4,000 USDC", - externalLink: "", + externalLink: "www.alchemy.com", }, { state: "initial", description: "Bought 1 ETH for 3,500 USDC", - externalLink: "", + externalLink: "www.alchemy.com", }, { state: "initial", description: "Bought 1 ETH for 4,200 USDC", - externalLink: "", + externalLink: "www.alchemy.com", }, ]); for (let i = 0; i < transactions.length; i++) { From 93082a86b8ea1fc0b9bdb616debaa6cdf17b4d9f Mon Sep 17 00:00:00 2001 From: Caleb Briancesco Date: Thu, 19 Dec 2024 14:58:08 -0600 Subject: [PATCH 5/8] feat: add wallet type store and toggle cards --- .../components/auth/hooks/useTransaction.ts | 84 ------------------- examples/ui-demo/src/app/config.tsx | 7 ++ .../configuration/Configuration.tsx | 28 ++++--- .../components/preview/AuthCardWrapper.tsx | 8 +- .../components/shared/WalletTypeSwitch.tsx | 2 +- examples/ui-demo/src/state/store.tsx | 9 +- 6 files changed, 39 insertions(+), 99 deletions(-) delete mode 100644 account-kit/react/src/components/auth/hooks/useTransaction.ts diff --git a/account-kit/react/src/components/auth/hooks/useTransaction.ts b/account-kit/react/src/components/auth/hooks/useTransaction.ts deleted file mode 100644 index f180fcaf7f..0000000000 --- a/account-kit/react/src/components/auth/hooks/useTransaction.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { useState } from "react"; - -export enum TransactionStatus { - none = "none", - pending = "pending", - success = "success", - error = "error", -} - -export interface Transaction { - title: string; - status: TransactionStatus; -} - -export interface TransactionConfig { - stepsNumber?: number; -} - -export interface UseTransaction { - makeRequest: () => void; - result: Transaction[]; - reset: () => void; -} - -const randomDelay = (min = 2, max = 6) => { - const delay = Math.floor(Math.random() * (max - min + 1)) + min; - return delay * 1000; -}; - -export const useTransaction = ( - { stepsNumber = 3 }: TransactionConfig = { stepsNumber: 3 } -): UseTransaction => { - const baseRequest = Array.from({ length: stepsNumber }).map( - (_item, index) => ({ - title: `Transaction ${index + 1}`, - status: TransactionStatus.none, - }) - ); - - const [result, setResult] = useState(baseRequest); - - const reset = () => { - setResult(baseRequest); - }; - - const simulateApiCall = async ( - transaction: Transaction - ): Promise => { - return new Promise((resolve) => { - setTimeout(() => { - resolve({ ...transaction, status: TransactionStatus.success }); - }, randomDelay()); - }); - }; - - const makeRequest = async () => { - for (let i = 0; i < result.length; i++) { - const response = await simulateApiCall(result[i]); - - setResult((prevState) => { - const newState = [...prevState]; - newState[i] = response; - return newState; - }); - } - }; - - const startRequest = () => { - setResult((prevState) => { - return [...prevState].map((item) => ({ - ...item, - status: TransactionStatus.pending, - })); - }); - - makeRequest(); - }; - - return { - makeRequest: startRequest, - result, - reset, - }; -}; diff --git a/examples/ui-demo/src/app/config.tsx b/examples/ui-demo/src/app/config.tsx index d827762cf8..176447f23e 100644 --- a/examples/ui-demo/src/app/config.tsx +++ b/examples/ui-demo/src/app/config.tsx @@ -36,9 +36,15 @@ export type Config = { } | undefined; }; + walletType: WalletTypes; supportUrl?: string; }; +export enum WalletTypes { + smart = "smart", + hybrid7702 = "7702", +} + export const DEFAULT_CONFIG: Config = { auth: { showEmail: true, @@ -65,6 +71,7 @@ export const DEFAULT_CONFIG: Config = { logoLight: undefined, logoDark: undefined, }, + walletType: WalletTypes.smart, }; export const queryClient = new QueryClient(); diff --git a/examples/ui-demo/src/components/configuration/Configuration.tsx b/examples/ui-demo/src/components/configuration/Configuration.tsx index d5e8dff5da..3a590a3c36 100644 --- a/examples/ui-demo/src/components/configuration/Configuration.tsx +++ b/examples/ui-demo/src/components/configuration/Configuration.tsx @@ -1,22 +1,28 @@ -import { useState } from "react"; +// import { useState } from "react"; import { cn } from "@/lib/utils"; import { SettingsIcon } from "../icons/settings"; // import { HelpTooltip } from "../shared/HelpTooltip"; import { WalletTypeSwitch } from "../shared/WalletTypeSwitch"; import ExternalLink from "../shared/ExternalLink"; +import { useConfigStore } from "@/state"; +import { WalletTypes } from "@/app/config"; -enum WalletTypes { - smart = "smart", - smart7702 = "7702", -} export const Configuration = ({ className }: { className?: string }) => { - const [walletType, setWalletType] = useState(WalletTypes.smart); + const { setWalletType, walletType } = useConfigStore( + ({ walletType, setWalletType }) => { + return { + walletType, + setWalletType, + }; + } + ); + // const [walletType, setWalletType] = useState(WalletTypes.smart); const onSwitchWalletType = () => { setWalletType( walletType === WalletTypes.smart - ? WalletTypes.smart7702 + ? WalletTypes.hybrid7702 : WalletTypes.smart ); }; @@ -29,18 +35,18 @@ export const Configuration = ({ className }: { className?: string }) => {

- Embedded Wallet Type{" "} + Embedded Wallet Type

{/* */}

- On sign up, create a smart account or create an EOA that delegates to a - smart account.{" "} + Sentence describing all of the value props fo 7702 and educating the + user. Curious about what this means? Learn more. diff --git a/examples/ui-demo/src/components/preview/AuthCardWrapper.tsx b/examples/ui-demo/src/components/preview/AuthCardWrapper.tsx index cfa7e00ef5..38d6962f23 100644 --- a/examples/ui-demo/src/components/preview/AuthCardWrapper.tsx +++ b/examples/ui-demo/src/components/preview/AuthCardWrapper.tsx @@ -5,6 +5,9 @@ import { useTheme } from "@/state/useTheme"; import { AuthCard, useUser } from "@account-kit/react"; import { EOAPostLogin } from "../shared/eoa-post-login/EOAPostLogin"; import { Wrapper7702 } from "../shared/7702/Wrapper"; +import { useConfigStore } from "@/state"; +import { WalletTypes } from "@/app/config"; +import { MintCard } from "../shared/mint-card/MintCard"; export function AuthCardWrapper({ className }: { className?: string }) { const theme = useTheme(); @@ -25,6 +28,7 @@ export function AuthCardWrapper({ className }: { className?: string }) { } const RenderContent = () => { + const { walletType } = useConfigStore(); const user = useUser(); const hasUser = !!user; @@ -53,6 +57,6 @@ const RenderContent = () => { ); } - return ; - // return ; + + return walletType === WalletTypes.smart ? : ; }; diff --git a/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx b/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx index 6535332a83..1dc0416401 100644 --- a/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx +++ b/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx @@ -42,7 +42,7 @@ const WalletTypeSwitch = forwardRef< checked ? unselectedStyles : selectedStyles } font-semibold`} > - 7702 Smart Account + Hybrid account (7702)

diff --git a/examples/ui-demo/src/state/store.tsx b/examples/ui-demo/src/state/store.tsx index 6e588ed3cc..e99db24681 100644 --- a/examples/ui-demo/src/state/store.tsx +++ b/examples/ui-demo/src/state/store.tsx @@ -1,4 +1,4 @@ -import { Config, DEFAULT_CONFIG } from "@/app/config"; +import { Config, DEFAULT_CONFIG, WalletTypes } from "@/app/config"; import { getSectionsForConfig } from "@/app/sections"; import { AuthCardHeader } from "@/components/shared/AuthCardHeader"; import { cookieStorage, parseCookie } from "@account-kit/core"; @@ -49,6 +49,7 @@ export type DemoState = Config & { ) => void; setTheme: (theme: Config["ui"]["theme"]) => void; setSupportUrl: (url: string) => void; + setWalletType: (walletType: WalletTypes) => void; }; export const createDemoStore = (initialConfig: Config = DEFAULT_CONFIG) => { @@ -68,6 +69,7 @@ export const createDemoStore = (initialConfig: Config = DEFAULT_CONFIG) => { setTheme, setNftTransferred, nftTransferred, + setWalletType, ...config }) => config, skipHydration: true, @@ -141,6 +143,11 @@ function createInitialState( }, })); }, + setWalletType: (walletType: WalletTypes) => { + set(() => ({ + walletType, + })); + }, }); } From f57b486097a9cc760197a9714a6a27577e085f07 Mon Sep 17 00:00:00 2001 From: Caleb Briancesco Date: Thu, 19 Dec 2024 15:15:20 -0600 Subject: [PATCH 6/8] feat: updating user details dropdown --- .../configuration/Configuration.tsx | 11 +-- .../UserConnectionDetails.tsx | 87 ++++++++++++------- 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/examples/ui-demo/src/components/configuration/Configuration.tsx b/examples/ui-demo/src/components/configuration/Configuration.tsx index 3a590a3c36..78a4663931 100644 --- a/examples/ui-demo/src/components/configuration/Configuration.tsx +++ b/examples/ui-demo/src/components/configuration/Configuration.tsx @@ -9,14 +9,7 @@ import { useConfigStore } from "@/state"; import { WalletTypes } from "@/app/config"; export const Configuration = ({ className }: { className?: string }) => { - const { setWalletType, walletType } = useConfigStore( - ({ walletType, setWalletType }) => { - return { - walletType, - setWalletType, - }; - } - ); + const { setWalletType, walletType } = useConfigStore(); // const [walletType, setWalletType] = useState(WalletTypes.smart); const onSwitchWalletType = () => { @@ -46,7 +39,7 @@ export const Configuration = ({ className }: { className?: string }) => { />

Sentence describing all of the value props fo 7702 and educating the - user. Curious about what this means? + user. Curious about what this means?{" "} Learn more. diff --git a/examples/ui-demo/src/components/shared/user-connection-avatar/UserConnectionDetails.tsx b/examples/ui-demo/src/components/shared/user-connection-avatar/UserConnectionDetails.tsx index 4384f19854..255161a128 100644 --- a/examples/ui-demo/src/components/shared/user-connection-avatar/UserConnectionDetails.tsx +++ b/examples/ui-demo/src/components/shared/user-connection-avatar/UserConnectionDetails.tsx @@ -1,3 +1,4 @@ +import { WalletTypes } from "@/app/config"; import { ExternalLinkIcon } from "@/components/icons/external-link"; import { LogoutIcon } from "@/components/icons/logout"; import { DeploymentStatusIndicator } from "@/components/shared/DeploymentStatusIndicator"; @@ -15,8 +16,12 @@ export function UserConnectionDetails({ const user = useUser(); const signer = useSigner(); const { logout } = useLogout(); - const { theme, primaryColor } = useConfigStore( - ({ ui: { theme, primaryColor } }) => ({ theme, primaryColor }) + const { theme, primaryColor, walletType } = useConfigStore( + ({ ui: { theme, primaryColor }, walletType }) => ({ + theme, + primaryColor, + walletType, + }) ); const scaAccount = useAccount({ type: "LightAccount" }); @@ -69,40 +74,60 @@ export function UserConnectionDetails({ {/* Smart Account */}

- Smart account + {walletType === WalletTypes.smart ? "Smart account" : "Address"}
- {/* Status */} -
- Status -
- - - {deploymentStatus ? "Deployed" : "Not deployed"} - -
-
- {/* Signer */} -
- - - Signer - - + + ) : ( +
+ + Delegated to + +
+ + + None + +
+
+ )} {/* Logout */} From 2193ee2ad61c123217d9e50766e30ba48b84c23c Mon Sep 17 00:00:00 2001 From: rob chang Date: Thu, 19 Dec 2024 16:26:17 -0500 Subject: [PATCH 7/8] feat: styling polish --- examples/ui-demo/src/components/icons/key.tsx | 8 ++--- .../components/shared/7702/MintCard7702.tsx | 21 ++++++------ .../src/components/shared/7702/MintStages.tsx | 2 +- .../components/shared/7702/Transactions.tsx | 26 ++++++++++----- .../shared/7702/TransactionsCard.tsx | 9 +++-- .../src/components/shared/7702/useMint.tsx | 33 ++++++++++--------- 6 files changed, 55 insertions(+), 44 deletions(-) diff --git a/examples/ui-demo/src/components/icons/key.tsx b/examples/ui-demo/src/components/icons/key.tsx index 4fc423192e..366380ad26 100644 --- a/examples/ui-demo/src/components/icons/key.tsx +++ b/examples/ui-demo/src/components/icons/key.tsx @@ -9,14 +9,14 @@ export const Key = ({ className }: { className?: string }) => ( > diff --git a/examples/ui-demo/src/components/shared/7702/MintCard7702.tsx b/examples/ui-demo/src/components/shared/7702/MintCard7702.tsx index 4c387d7c91..100f871b0a 100644 --- a/examples/ui-demo/src/components/shared/7702/MintCard7702.tsx +++ b/examples/ui-demo/src/components/shared/7702/MintCard7702.tsx @@ -27,7 +27,7 @@ export const MintCard7702 = ({ }) => { return (
{uri ? (
-
- An NFT -
+ An NFT
) : (
@@ -57,7 +56,7 @@ export const MintCard7702 = ({
{!nftTransfered ? ( <> -

+

Sponsor gas and sign in the background for a one-click transaction experience.

diff --git a/examples/ui-demo/src/components/shared/7702/MintStages.tsx b/examples/ui-demo/src/components/shared/7702/MintStages.tsx index dbad8ae397..70708d7ec0 100644 --- a/examples/ui-demo/src/components/shared/7702/MintStages.tsx +++ b/examples/ui-demo/src/components/shared/7702/MintStages.tsx @@ -32,7 +32,7 @@ const Stage = ({ className?: string; }) => { return ( -
+
{getMintIcon(icon)}

{description}

diff --git a/examples/ui-demo/src/components/shared/7702/Transactions.tsx b/examples/ui-demo/src/components/shared/7702/Transactions.tsx index 0d397d2259..c4ea547ea3 100644 --- a/examples/ui-demo/src/components/shared/7702/Transactions.tsx +++ b/examples/ui-demo/src/components/shared/7702/Transactions.tsx @@ -39,16 +39,24 @@ const Transaction = ({ }; return ( -
-
- {state === "complete" ? ( - - ) : ( - - )} +
+
+
+ {state === "complete" ? ( + + ) : ( + + )} +
+

{getText()}

-

{getText()}

- {externalLink && } + {externalLink && state === "complete" && ( + +
+ +
+
+ )}
); }; diff --git a/examples/ui-demo/src/components/shared/7702/TransactionsCard.tsx b/examples/ui-demo/src/components/shared/7702/TransactionsCard.tsx index 2f8eb744f7..a1c0836a3c 100644 --- a/examples/ui-demo/src/components/shared/7702/TransactionsCard.tsx +++ b/examples/ui-demo/src/components/shared/7702/TransactionsCard.tsx @@ -22,21 +22,24 @@ export const TransactionsCard = ({ }; return (
+

+ New! +

-

+

Recurring transactions

{!hasClicked ? ( diff --git a/examples/ui-demo/src/components/shared/7702/useMint.tsx b/examples/ui-demo/src/components/shared/7702/useMint.tsx index df7bdec51c..be0c0072ef 100644 --- a/examples/ui-demo/src/components/shared/7702/useMint.tsx +++ b/examples/ui-demo/src/components/shared/7702/useMint.tsx @@ -27,21 +27,22 @@ export const useMint = () => { gas: "success", signing: "success", })); - setToast({ - type: "success", - text: ( - <> - - {`You've collected your NFT!`} - - - {`You've successfully collected your NFT! Refresh to mint - again.`} - - - ), - open: true, - }); + // Current design does not have a success toast, leaving commented to implement later. + // setToast({ + // type: "success", + // text: ( + // <> + // + // {`You've collected your NFT!`} + // + // + // {`You've successfully collected your NFT! Refresh to mint + // again.`} + // + // + // ), + // open: true, + // }); }; const handleError = (error: Error) => { @@ -67,7 +68,7 @@ export const useMint = () => { }, enabled: !!client && !!client?.readContract, }); - const { sendUserOperationResult, sendUserOperation } = useSendUserOperation({ + const { sendUserOperation } = useSendUserOperation({ client, waitForTxn: true, onError: handleError, From a2ab5994f0267f6c885097ca38307a3496be9867 Mon Sep 17 00:00:00 2001 From: Caleb Briancesco Date: Fri, 20 Dec 2024 13:16:57 -0600 Subject: [PATCH 8/8] feat: updating wallet type switch styles --- .../src/components/configuration/Configuration.tsx | 9 ++++++--- .../ui-demo/src/components/shared/WalletTypeSwitch.tsx | 10 +++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/ui-demo/src/components/configuration/Configuration.tsx b/examples/ui-demo/src/components/configuration/Configuration.tsx index 78a4663931..1b471a08e4 100644 --- a/examples/ui-demo/src/components/configuration/Configuration.tsx +++ b/examples/ui-demo/src/components/configuration/Configuration.tsx @@ -37,10 +37,13 @@ export const Configuration = ({ className }: { className?: string }) => { checked={walletType === WalletTypes.hybrid7702} onCheckedChange={onSwitchWalletType} /> -

+

Sentence describing all of the value props fo 7702 and educating the - user. Curious about what this means?{" "} - + user. Curious about what this means? + Learn more.

diff --git a/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx b/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx index 1dc0416401..9b43192654 100644 --- a/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx +++ b/examples/ui-demo/src/components/shared/WalletTypeSwitch.tsx @@ -26,21 +26,21 @@ const WalletTypeSwitch = forwardRef< "pointer-events-none block h-full w-2/4 rounded-[7px] bg-background ring-0 transition-transform data-[state=checked]:translate-x-[100%] data-[state=unchecked]:translate-x-[0%] border border-active" )} > -
-
+
+

Smart account

-
+

Hybrid account (7702)