From c7fad3084413abb80d6114fbc25228b05e201d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20Kozma?= Date: Thu, 6 Feb 2025 12:41:48 +0100 Subject: [PATCH 1/4] feat(billing): unify deployment modals refs #628 --- .../deployments/DeploymentDepositModal.tsx | 53 ++++++++++++- .../new-deployment/ManifestEdit.tsx | 26 ++++--- .../src/components/sdl/RentGpusForm.tsx | 24 +++--- .../src/hooks/useManagedDeploymentConfirm.tsx | 74 +------------------ 4 files changed, 81 insertions(+), 96 deletions(-) diff --git a/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx b/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx index 66e9a6001..e303b4f9d 100644 --- a/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx +++ b/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx @@ -30,11 +30,15 @@ import { UAKT_DENOM } from "@src/config/denom.config"; import { usePricing } from "@src/context/PricingProvider"; import { useSettings } from "@src/context/SettingsProvider"; import { useWallet } from "@src/context/WalletProvider"; +import { useUser } from "@src/hooks/useUser"; import { useDenomData, useWalletBalance } from "@src/hooks/useWalletBalance"; import { useGranteeGrants } from "@src/queries/useGrantsQuery"; +import { ServiceType } from "@src/types"; import { AnalyticsCategory, AnalyticsEvents } from "@src/types/analytics"; import { denomToUdenom, udenomToDenom } from "@src/utils/mathHelpers"; import { coinToUDenom } from "@src/utils/priceUtils"; +import { UrlService } from "@src/utils/urlUtils"; +import { LeaseSpecDetail } from "../shared/LeaseSpecDetail"; import { LinkTo } from "../shared/LinkTo"; import { GranteeDepositMenuItem } from "./GranteeDepositMenuItem"; @@ -45,6 +49,8 @@ export type DeploymentDepositModalProps = { onDeploymentDeposit: (deposit: number, depositorAddress: string) => void; handleCancel: () => void; children?: ReactNode; + title?: string; + services?: ServiceType[]; }; const formSchema = z @@ -73,7 +79,9 @@ export const DeploymentDepositModal: React.FunctionComponent { const formRef = useRef(null); const { settings } = useSettings(); @@ -96,6 +104,15 @@ export const DeploymentDepositModal: React.FunctionComponent compareAsc(new Date(), new Date(x.expiration)) !== 1 && x.authorization.spend_limit.denom === denom) || []; + const user = useUser(); + + const goToSignIn = () => { + window.location.href = UrlService.login(); + }; + + const goToCheckout = () => { + window.location.href = "/api/proxy/v1/checkout"; + }; useEffect(() => { if (depositData && amount === 0 && !disableMin) { @@ -225,10 +242,29 @@ export const DeploymentDepositModal: React.FunctionComponent + {services.length > 0 && ( +
+ {services.map(service => { + return ( + +
+ {service.title}:{service.image} +
+
+ + {service.profile?.hasGpu && } + + +
+
+ ); + })} +
+ )} +
{infoText} @@ -259,6 +295,17 @@ export const DeploymentDepositModal: React.FunctionComponent +
+ {user?.userId ? ( + goToCheckout()} className="text-primary"> + Buy more credits + + ) : ( + goToSignIn()} className="text-primary"> + Sign in to buy more credits + + )} +
{isCustodial && ( diff --git a/apps/deploy-web/src/components/new-deployment/ManifestEdit.tsx b/apps/deploy-web/src/components/new-deployment/ManifestEdit.tsx index e50d98e3b..d19fca8ee 100644 --- a/apps/deploy-web/src/components/new-deployment/ManifestEdit.tsx +++ b/apps/deploy-web/src/components/new-deployment/ManifestEdit.tsx @@ -17,8 +17,8 @@ import { useCertificate } from "@src/context/CertificateProvider"; import { useChainParam } from "@src/context/ChainParamProvider"; import { useSdlBuilder } from "@src/context/SdlBuilderProvider/SdlBuilderProvider"; import { useWallet } from "@src/context/WalletProvider"; -import { useManagedDeploymentConfirm } from "@src/hooks/useManagedDeploymentConfirm"; import { useManagedWalletDenom } from "@src/hooks/useManagedWalletDenom"; +import { useDenomData } from "@src/hooks/useWalletBalance"; import { useWhen } from "@src/hooks/useWhen"; import { useDepositParams } from "@src/queries/useSettings"; import sdlStore from "@src/store/sdlStore"; @@ -67,6 +67,8 @@ export const ManifestEdit: React.FunctionComponent = ({ const [selectedSdlEditMode, setSelectedSdlEditMode] = useAtom(sdlStore.selectedSdlEditMode); const [isRepoInputValid, setIsRepoInputValid] = useState(false); const [sdlDenom, setSdlDenom] = useState("uakt"); + const depositData = useDenomData(sdlDenom); + const { settings } = useSettings(); const { address, signAndBroadcastTx, isManaged, isTrialing } = useWallet(); const router = useRouter(); @@ -83,8 +85,8 @@ export const ManifestEdit: React.FunctionComponent = ({ const defaultDeposit = depositParams || browserEnvConfig.NEXT_PUBLIC_DEFAULT_INITIAL_DEPOSIT; const wallet = useWallet(); const managedDenom = useManagedWalletDenom(); - const { createDeploymentConfirm } = useManagedDeploymentConfirm(); const { enqueueSnackbar } = useSnackbar(); + const services = importSimpleSdl(editedManifest as string); useWhen( wallet.isManaged && sdlDenom === "uakt" && editedManifest, @@ -192,18 +194,12 @@ export const ManifestEdit: React.FunctionComponent = ({ } if (isManaged) { - const services = importSimpleSdl(editedManifest as string); - if (!services) { setParsingError("Error while parsing SDL file"); return; } - const isConfirmed = await createDeploymentConfirm(services); - - if (isConfirmed) { - await handleCreateClick(defaultDeposit, browserEnvConfig.NEXT_PUBLIC_MASTER_WALLET_ADDRESS); - } + setIsDepositingDeployment(true); } else { setIsCheckingPrerequisites(true); } @@ -429,14 +425,24 @@ export const ManifestEdit: React.FunctionComponent = ({ handleCancel={() => setIsDepositingDeployment(false)} onDeploymentDeposit={onDeploymentDeposit} denom={sdlDenom} + title="Confirm deployment creation?" infoText={ - To create a deployment, you need to have at least {minDeposit.akt} AKT or {minDeposit.usdc} USDC in an escrow account.{" "} + {isManaged ? ( + <> + To create a deployment, you need to have at least ${depositData?.min} in an escrow account.{" "} + + ) : ( + <> + To create a deployment, you need to have at least {minDeposit.akt} AKT or {minDeposit.usdc} USDC in an escrow account.{" "} + + )} handleDocClick(ev, "https://akash.network/docs/other-resources/payments/")}> Learn more. } + services={services} /> )} {isCheckingPrerequisites && setIsCheckingPrerequisites(false)} onContinue={onPrerequisiteContinue} />} diff --git a/apps/deploy-web/src/components/sdl/RentGpusForm.tsx b/apps/deploy-web/src/components/sdl/RentGpusForm.tsx index a54a1e43f..f91551199 100644 --- a/apps/deploy-web/src/components/sdl/RentGpusForm.tsx +++ b/apps/deploy-web/src/components/sdl/RentGpusForm.tsx @@ -15,8 +15,8 @@ import { useCertificate } from "@src/context/CertificateProvider"; import { useChainParam } from "@src/context/ChainParamProvider"; import { useSettings } from "@src/context/SettingsProvider"; import { useWallet } from "@src/context/WalletProvider"; -import { useManagedDeploymentConfirm } from "@src/hooks/useManagedDeploymentConfirm"; import { useManagedWalletDenom } from "@src/hooks/useManagedWalletDenom"; +import { useDenomData } from "@src/hooks/useWalletBalance"; import { useWhen } from "@src/hooks/useWhen"; import { useGpuModels } from "@src/queries/useGpuQuery"; import { useDepositParams } from "@src/queries/useSettings"; @@ -77,10 +77,10 @@ export const RentGpusForm: React.FunctionComponent = () => { const [sdlDenom, setSdlDenom] = useState("uakt"); const { minDeposit } = useChainParam(); const router = useRouter(); - const { createDeploymentConfirm } = useManagedDeploymentConfirm(); const managedDenom = useManagedWalletDenom(); const { data: depositParams } = useDepositParams(); const defaultDeposit = depositParams || browserEnvConfig.NEXT_PUBLIC_DEFAULT_INITIAL_DEPOSIT; + const depositData = useDenomData(sdlDenom); useWhen(isManaged && sdlDenom === "uakt", () => { setSdlDenom(managedDenom); @@ -207,13 +207,7 @@ export const RentGpusForm: React.FunctionComponent = () => { setRentGpuSdl(data); if (isManaged) { - const isConfirmed = await createDeploymentConfirm(rentGpuSdl?.services as ServiceType[]); - - if (!isConfirmed) { - return; - } - - await handleCreateClick(defaultDeposit, browserEnvConfig.NEXT_PUBLIC_MASTER_WALLET_ADDRESS); + setIsDepositingDeployment(true); } else { setIsCheckingPrerequisites(true); } @@ -298,13 +292,23 @@ export const RentGpusForm: React.FunctionComponent = () => { infoText={

- To create a deployment, you need to have at least {minDeposit.akt} AKT or {minDeposit.usdc} USDC in an escrow account.{" "} + {isManaged ? ( + <> + To create a deployment, you need to have at least ${depositData?.min} in an escrow account.{" "} + + ) : ( + <> + To create a deployment, you need to have at least {minDeposit.akt} AKT or {minDeposit.usdc} USDC in an escrow account.{" "} + + )} handleDocClick(ev, "https://akash.network/docs/getting-started/intro-to-akash/bids-and-leases/#escrow-accounts")}> Learn more.

} + title="Confirm deployment creation?" + services={rentGpuSdl?.services} /> )} {isCheckingPrerequisites && setIsCheckingPrerequisites(false)} onContinue={onPrerequisiteContinue} />} diff --git a/apps/deploy-web/src/hooks/useManagedDeploymentConfirm.tsx b/apps/deploy-web/src/hooks/useManagedDeploymentConfirm.tsx index 366bdca64..2581fa119 100644 --- a/apps/deploy-web/src/hooks/useManagedDeploymentConfirm.tsx +++ b/apps/deploy-web/src/hooks/useManagedDeploymentConfirm.tsx @@ -1,16 +1,8 @@ -import { FormattedNumber } from "react-intl"; -import { Alert, AlertDescription, AlertTitle } from "@akashnetwork/ui/components"; import { usePopup } from "@akashnetwork/ui/context"; -import { LeaseSpecDetail } from "@src/components/shared/LeaseSpecDetail"; -import { useChainParam } from "@src/context/ChainParamProvider"; import { useWallet } from "@src/context/WalletProvider"; -import { ServiceType } from "@src/types"; -import { useWalletBalance } from "./useWalletBalance"; export const useManagedDeploymentConfirm = () => { - const { minDeposit } = useChainParam(); - const { balance: walletBalance } = useWalletBalance(); const { isManaged } = useWallet(); const { confirm } = usePopup(); @@ -36,69 +28,5 @@ export const useManagedDeploymentConfirm = () => { return true; }; - const createDeploymentConfirm = async (services: ServiceType[]) => { - if (isManaged) { - const hasEnoughForDeposit = (walletBalance?.totalDeploymentGrantsUSD || 0) >= minDeposit.usdc; - - const isConfirmed = await confirm({ - title: "Confirm deployment creation?", - message: ( -
- {!hasEnoughForDeposit && ( - - Insufficient funds - -

- You need more than{" "} - {" "} - available to create a deployment. -

-

- Current available balance:{" "} - - - -

-
-
- )} - - {services.map(service => { - return ( - -
- {service.title}:{service.image} -
-
- - {service.profile?.hasGpu && } - - -
-
- ); - })} -
- ) - }); - - if (!isConfirmed) { - return false; - } - } - - return true; - }; - - return { closeDeploymentConfirm, createDeploymentConfirm }; + return { closeDeploymentConfirm }; }; From 5c69e62d9450b46e366b534089a7160d04bfa2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20Kozma?= Date: Mon, 10 Feb 2025 21:44:00 +0100 Subject: [PATCH 2/4] fix(billing): make it work with no type casting refs #628 --- .../src/components/deployments/DeploymentDepositModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx b/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx index e303b4f9d..30814e9e2 100644 --- a/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx +++ b/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx @@ -254,8 +254,8 @@ export const DeploymentDepositModal: React.FunctionComponent{service.title}:{service.image}
- - {service.profile?.hasGpu && } + + {!!service.profile?.gpu && }
From c12562d697e508272de208377f318fa9d537ceb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20Kozma?= Date: Mon, 10 Feb 2025 21:50:08 +0100 Subject: [PATCH 3/4] fix(billing): pop verify/signin modal if needed when clicking to top up refs #628 --- .../deployments/DeploymentDepositModal.tsx | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx b/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx index 30814e9e2..f83a59f08 100644 --- a/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx +++ b/apps/deploy-web/src/components/deployments/DeploymentDepositModal.tsx @@ -30,14 +30,13 @@ import { UAKT_DENOM } from "@src/config/denom.config"; import { usePricing } from "@src/context/PricingProvider"; import { useSettings } from "@src/context/SettingsProvider"; import { useWallet } from "@src/context/WalletProvider"; -import { useUser } from "@src/hooks/useUser"; +import { useAddFundsVerifiedLoginRequiredEventHandler } from "@src/hooks/useAddFundsVerifiedLoginRequiredEventHandler"; import { useDenomData, useWalletBalance } from "@src/hooks/useWalletBalance"; import { useGranteeGrants } from "@src/queries/useGrantsQuery"; import { ServiceType } from "@src/types"; import { AnalyticsCategory, AnalyticsEvents } from "@src/types/analytics"; import { denomToUdenom, udenomToDenom } from "@src/utils/mathHelpers"; import { coinToUDenom } from "@src/utils/priceUtils"; -import { UrlService } from "@src/utils/urlUtils"; import { LeaseSpecDetail } from "../shared/LeaseSpecDetail"; import { LinkTo } from "../shared/LinkTo"; import { GranteeDepositMenuItem } from "./GranteeDepositMenuItem"; @@ -104,10 +103,12 @@ export const DeploymentDepositModal: React.FunctionComponent compareAsc(new Date(), new Date(x.expiration)) !== 1 && x.authorization.spend_limit.denom === denom) || []; - const user = useUser(); + const whenLoggedInAndVerified = useAddFundsVerifiedLoginRequiredEventHandler(); - const goToSignIn = () => { - window.location.href = UrlService.login(); + const closePopupAndGoToCheckoutIfPossible = (event: React.MouseEvent) => { + handleCancel(); + + whenLoggedInAndVerified(goToCheckout)(event); }; const goToCheckout = () => { @@ -295,17 +296,13 @@ export const DeploymentDepositModal: React.FunctionComponent -
- {user?.userId ? ( - goToCheckout()} className="text-primary"> + {isManaged && ( +
+ Buy more credits - ) : ( - goToSignIn()} className="text-primary"> - Sign in to buy more credits - - )} -
+
+ )} {isCustodial && ( From d8946652ca46c933e23784cf01212381697a4a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20Kozma?= Date: Wed, 12 Feb 2025 02:00:29 +0100 Subject: [PATCH 4/4] fix(billing): move duplicated code to a component refs #628 --- .../new-deployment/ManifestEdit.tsx | 15 ++----------- .../sdl/DeploymentMinimumEscrowAlertText.tsx | 22 +++++++++++++++++++ .../src/components/sdl/RentGpusForm.tsx | 15 ++----------- 3 files changed, 26 insertions(+), 26 deletions(-) create mode 100644 apps/deploy-web/src/components/sdl/DeploymentMinimumEscrowAlertText.tsx diff --git a/apps/deploy-web/src/components/new-deployment/ManifestEdit.tsx b/apps/deploy-web/src/components/new-deployment/ManifestEdit.tsx index d19fca8ee..46552504b 100644 --- a/apps/deploy-web/src/components/new-deployment/ManifestEdit.tsx +++ b/apps/deploy-web/src/components/new-deployment/ManifestEdit.tsx @@ -14,11 +14,9 @@ import { useSnackbar } from "notistack"; import { browserEnvConfig } from "@src/config/browser-env.config"; import { useCertificate } from "@src/context/CertificateProvider"; -import { useChainParam } from "@src/context/ChainParamProvider"; import { useSdlBuilder } from "@src/context/SdlBuilderProvider/SdlBuilderProvider"; import { useWallet } from "@src/context/WalletProvider"; import { useManagedWalletDenom } from "@src/hooks/useManagedWalletDenom"; -import { useDenomData } from "@src/hooks/useWalletBalance"; import { useWhen } from "@src/hooks/useWhen"; import { useDepositParams } from "@src/queries/useSettings"; import sdlStore from "@src/store/sdlStore"; @@ -37,6 +35,7 @@ import { domainName, handleDocClick, UrlService } from "@src/utils/urlUtils"; import { updateWallet } from "@src/utils/walletUtils"; import { useSettings } from "../../context/SettingsProvider"; import { DeploymentDepositModal } from "../deployments/DeploymentDepositModal"; +import { DeploymentMinimumEscrowAlertText } from "../sdl/DeploymentMinimumEscrowAlertText"; import { CustomNextSeo } from "../shared/CustomNextSeo"; import { DynamicMonacoEditor } from "../shared/DynamicMonacoEditor"; import { LinkTo } from "../shared/LinkTo"; @@ -67,7 +66,6 @@ export const ManifestEdit: React.FunctionComponent = ({ const [selectedSdlEditMode, setSelectedSdlEditMode] = useAtom(sdlStore.selectedSdlEditMode); const [isRepoInputValid, setIsRepoInputValid] = useState(false); const [sdlDenom, setSdlDenom] = useState("uakt"); - const depositData = useDenomData(sdlDenom); const { settings } = useSettings(); const { address, signAndBroadcastTx, isManaged, isTrialing } = useWallet(); @@ -77,7 +75,6 @@ export const ManifestEdit: React.FunctionComponent = ({ const muiTheme = useMuiTheme(); const smallScreen = useMediaQuery(muiTheme.breakpoints.down("md")); const sdlBuilderRef = useRef(null); - const { minDeposit } = useChainParam(); const { hasComponent } = useSdlBuilder(); const searchParams = useSearchParams(); const templateId = searchParams.get("templateId"); @@ -428,15 +425,7 @@ export const ManifestEdit: React.FunctionComponent = ({ title="Confirm deployment creation?" infoText={ - {isManaged ? ( - <> - To create a deployment, you need to have at least ${depositData?.min} in an escrow account.{" "} - - ) : ( - <> - To create a deployment, you need to have at least {minDeposit.akt} AKT or {minDeposit.usdc} USDC in an escrow account.{" "} - - )} + handleDocClick(ev, "https://akash.network/docs/other-resources/payments/")}> Learn more. diff --git a/apps/deploy-web/src/components/sdl/DeploymentMinimumEscrowAlertText.tsx b/apps/deploy-web/src/components/sdl/DeploymentMinimumEscrowAlertText.tsx new file mode 100644 index 000000000..15fd6a63c --- /dev/null +++ b/apps/deploy-web/src/components/sdl/DeploymentMinimumEscrowAlertText.tsx @@ -0,0 +1,22 @@ +import { FC, useState } from "react"; + +import { useChainParam } from "@src/context/ChainParamProvider"; +import { useWallet } from "@src/context/WalletProvider"; +import { useDenomData } from "@src/hooks/useWalletBalance"; + +export const DeploymentMinimumEscrowAlertText: FC = () => { + const { isManaged } = useWallet(); + const [sdlDenom] = useState("uakt"); + const depositData = useDenomData(sdlDenom); + const { minDeposit } = useChainParam(); + + return isManaged ? ( + <> + To create a deployment, you need to have at least ${depositData?.min} in an escrow account.{" "} + + ) : ( + <> + To create a deployment, you need to have at least {minDeposit.akt} AKT or {minDeposit.usdc} USDC in an escrow account.{" "} + + ); +}; diff --git a/apps/deploy-web/src/components/sdl/RentGpusForm.tsx b/apps/deploy-web/src/components/sdl/RentGpusForm.tsx index f91551199..34f3f12c9 100644 --- a/apps/deploy-web/src/components/sdl/RentGpusForm.tsx +++ b/apps/deploy-web/src/components/sdl/RentGpusForm.tsx @@ -12,11 +12,9 @@ import { event } from "nextjs-google-analytics"; import { browserEnvConfig } from "@src/config/browser-env.config"; import { useCertificate } from "@src/context/CertificateProvider"; -import { useChainParam } from "@src/context/ChainParamProvider"; import { useSettings } from "@src/context/SettingsProvider"; import { useWallet } from "@src/context/WalletProvider"; import { useManagedWalletDenom } from "@src/hooks/useManagedWalletDenom"; -import { useDenomData } from "@src/hooks/useWalletBalance"; import { useWhen } from "@src/hooks/useWhen"; import { useGpuModels } from "@src/queries/useGpuQuery"; import { useDepositParams } from "@src/queries/useSettings"; @@ -41,6 +39,7 @@ import { LinkTo } from "../shared/LinkTo"; import { PrerequisiteList } from "../shared/PrerequisiteList"; import { AdvancedConfig } from "./AdvancedConfig"; import { CpuFormControl } from "./CpuFormControl"; +import { DeploymentMinimumEscrowAlertText } from "./DeploymentMinimumEscrowAlertText"; import { FormPaper } from "./FormPaper"; import { GpuFormControl } from "./GpuFormControl"; import { ImageSelect } from "./ImageSelect"; @@ -75,12 +74,10 @@ export const RentGpusForm: React.FunctionComponent = () => { const { address, signAndBroadcastTx, isManaged } = useWallet(); const { loadValidCertificates, localCert, isLocalCertMatching, loadLocalCert, setSelectedCertificate } = useCertificate(); const [sdlDenom, setSdlDenom] = useState("uakt"); - const { minDeposit } = useChainParam(); const router = useRouter(); const managedDenom = useManagedWalletDenom(); const { data: depositParams } = useDepositParams(); const defaultDeposit = depositParams || browserEnvConfig.NEXT_PUBLIC_DEFAULT_INITIAL_DEPOSIT; - const depositData = useDenomData(sdlDenom); useWhen(isManaged && sdlDenom === "uakt", () => { setSdlDenom(managedDenom); @@ -292,15 +289,7 @@ export const RentGpusForm: React.FunctionComponent = () => { infoText={

- {isManaged ? ( - <> - To create a deployment, you need to have at least ${depositData?.min} in an escrow account.{" "} - - ) : ( - <> - To create a deployment, you need to have at least {minDeposit.akt} AKT or {minDeposit.usdc} USDC in an escrow account.{" "} - - )} + handleDocClick(ev, "https://akash.network/docs/getting-started/intro-to-akash/bids-and-leases/#escrow-accounts")}> Learn more.