From 9f7c977a92973d8e7290aafb7ebf6f2d1d8f4b99 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 29 Oct 2024 16:38:08 +0000 Subject: [PATCH] rework steps a bit for better dismissing modal --- packages/entrykit/src/AccountModalContent.tsx | 6 +- .../entrykit/src/onboarding/Allowance.tsx | 23 ++++-- .../src/onboarding/ConnectedSteps.tsx | 72 +++++++++++++++++-- packages/entrykit/src/onboarding/Session.tsx | 43 +++++------ packages/entrykit/src/onboarding/common.ts | 1 - .../src/onboarding/useClaimGasPass.ts | 5 +- .../src/onboarding/usePrerequisites.ts | 4 +- .../src/onboarding/useSetupSession.ts | 4 +- packages/entrykit/src/onboarding/useSteps.tsx | 55 -------------- 9 files changed, 122 insertions(+), 91 deletions(-) delete mode 100644 packages/entrykit/src/onboarding/useSteps.tsx diff --git a/packages/entrykit/src/AccountModalContent.tsx b/packages/entrykit/src/AccountModalContent.tsx index 67f6f4d9a0..7d839367d9 100644 --- a/packages/entrykit/src/AccountModalContent.tsx +++ b/packages/entrykit/src/AccountModalContent.tsx @@ -1,15 +1,17 @@ -import { useConnectorClient } from "wagmi"; +import { useAccount, useConnectorClient } from "wagmi"; import { ConnectWallet } from "./ConnectWallet"; import { ConnectedSteps } from "./onboarding/ConnectedSteps"; import { useEntryKitConfig } from "./EntryKitConfigProvider"; +import { useRef } from "react"; export function AccountModalContent() { const { chainId } = useEntryKitConfig(); const userClient = useConnectorClient({ chainId }); + const initialAddress = useRef(useAccount().address); if (userClient.status !== "success") { return ; } - return ; + return ; } diff --git a/packages/entrykit/src/onboarding/Allowance.tsx b/packages/entrykit/src/onboarding/Allowance.tsx index 9842b5dbaf..da25a5aa0b 100644 --- a/packages/entrykit/src/onboarding/Allowance.tsx +++ b/packages/entrykit/src/onboarding/Allowance.tsx @@ -4,7 +4,7 @@ import { PendingIcon } from "../icons/PendingIcon"; import { useClaimGasPass } from "./useClaimGasPass"; import { Button } from "../ui/Button"; import { Balance } from "../ui/Balance"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { minGasBalance } from "./common"; export type Props = { @@ -17,10 +17,25 @@ export function Allowance({ isActive, isExpanded, userAddress }: Props) { const allowance = useAllowance(userAddress); const claimGasPass = useClaimGasPass(); - // TODO: improve pending state since this is kicked off automatically and showing a pending button is weird + // I assumed `queryClient.isMutating` would be useful to avoid multiple mutations at once, + // but it seems like it's doing something else internally where kicking off a mutation + // twice immediately (i.e. two renders) results in both returning 2 pending mutations. + // + // I also tried moving this into `useSetupSession` with `onMutate`, etc, but that seems + // to just mimick what I am seeing with the behavior of `useMutation`. + // + // Working around this with a ref :( + const isMutatingRef = useRef(false); useEffect(() => { - if (isActive && claimGasPass.status === "idle" && allowance.isSuccess && allowance.data < minGasBalance) { - claimGasPass.mutate(userAddress); + if ( + isActive && + claimGasPass.status === "idle" && + allowance.isSuccess && + allowance.data < minGasBalance && + !isMutatingRef.current + ) { + isMutatingRef.current = true; + claimGasPass.mutate(userAddress, { onSettled: () => (isMutatingRef.current = false) }); } }, [allowance.data, allowance.isSuccess, claimGasPass, isActive, userAddress]); diff --git a/packages/entrykit/src/onboarding/ConnectedSteps.tsx b/packages/entrykit/src/onboarding/ConnectedSteps.tsx index 5e3a923a20..92a7cb6e1f 100644 --- a/packages/entrykit/src/onboarding/ConnectedSteps.tsx +++ b/packages/entrykit/src/onboarding/ConnectedSteps.tsx @@ -1,16 +1,78 @@ -import { useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { ConnectedClient } from "../common"; -import { useSteps } from "./useSteps"; import { twMerge } from "tailwind-merge"; +import { usePrerequisites } from "./usePrerequisites"; +import { Wallet } from "./Wallet"; +import { Allowance } from "./Allowance"; +import { Session } from "./Session"; +import { Step } from "./common"; +import { Address } from "viem"; +import { useAccountModal } from "../useAccountModal"; export type Props = { userClient: ConnectedClient; + initialAddress: Address | undefined; }; -export function ConnectedSteps({ userClient }: Props) { - const steps = useSteps(userClient); +export function ConnectedSteps({ userClient, initialAddress }: Props) { + const userAddress = userClient.account.address; + const { data: prerequisites } = usePrerequisites(userAddress); - // TODO: detect if just connected and, if so, dismiss + const { closeAccountModal } = useAccountModal(); + const isNewConnection = userAddress !== initialAddress; + + const initialPrerequisites = useRef(prerequisites); + useEffect(() => { + if (prerequisites == null) return; + if (initialPrerequisites.current == null) { + initialPrerequisites.current = prerequisites; + } + + if (prerequisites.complete) { + if (isNewConnection || !initialPrerequisites.current.complete) { + closeAccountModal(); + } + } + }, [closeAccountModal, isNewConnection, prerequisites]); + + const { hasAllowance, isSpender, hasDelegation } = prerequisites ?? {}; + + const steps = useMemo((): readonly Step[] => { + if (!userAddress) { + return [ + { + id: "wallet", + isComplete: false, + content: () => null, + }, + ]; + } + + return [ + { + id: "wallet", + isComplete: true, + content: (props) => , + }, + { + id: "allowance", + isComplete: !!hasAllowance, + content: (props) => , + }, + { + id: "session", + isComplete: !!isSpender && !!hasDelegation, + content: (props) => ( + + ), + }, + ]; + }, [hasAllowance, hasDelegation, isSpender, userAddress, userClient]); const [selectedStepId] = useState(null); const nextStep = steps.find((step) => step.content != null && !step.isComplete); diff --git a/packages/entrykit/src/onboarding/Session.tsx b/packages/entrykit/src/onboarding/Session.tsx index 5b5eeeaf72..e1aa9d6bbf 100644 --- a/packages/entrykit/src/onboarding/Session.tsx +++ b/packages/entrykit/src/onboarding/Session.tsx @@ -1,8 +1,7 @@ import { Button } from "../ui/Button"; import { useSetupSession } from "./useSetupSession"; -import { useAccountModal } from "../useAccountModal"; import { ConnectedClient } from "../common"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { useSessionAccount } from "../useSessionAccount"; export type Props = { @@ -14,15 +13,24 @@ export type Props = { }; export function Session({ isActive, isExpanded, userClient, registerSpender, registerDelegation }: Props) { - const { closeAccountModal } = useAccountModal(); const sessionAccount = useSessionAccount(userClient.account.address); const sessionAddress = sessionAccount.data?.address; const setup = useSetupSession(); - const isReady = !registerDelegation && !registerDelegation; + const hasSession = !registerDelegation && !registerDelegation; + // I assumed `queryClient.isMutating` would be useful to avoid multiple mutations at once, + // but it seems like it's doing something else internally where kicking off a mutation + // twice immediately (i.e. two renders) results in both returning 2 pending mutations. + // + // I also tried moving this into `useSetupSession` with `onMutate`, etc, but that seems + // to just mimick what I am seeing with the behavior of `useMutation`. + // + // Working around this with a ref :( + const isMutatingRef = useRef(false); useEffect(() => { - if (isActive && setup.status === "idle" && sessionAddress && !isReady) { + if (isActive && setup.status === "idle" && sessionAddress && !hasSession && !isMutatingRef.current) { + isMutatingRef.current = true; setup.mutate( { userClient, @@ -30,19 +38,19 @@ export function Session({ isActive, isExpanded, userClient, registerSpender, reg registerSpender, registerDelegation, }, - { onSuccess: closeAccountModal }, + { onSettled: () => (isMutatingRef.current = false) }, ); } - }, [closeAccountModal, isActive, isReady, registerDelegation, registerSpender, sessionAddress, setup, userClient]); + }, [isActive, hasSession, registerDelegation, registerSpender, sessionAddress, setup, userClient]); return (
Session
-
{isReady ? "Enabled" : "Set up"}
+
{hasSession ? "Enabled" : "Set up"}
- {isReady ? ( + {hasSession ? (