Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(billing): unify deployment modals #804

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ 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 { 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 { LeaseSpecDetail } from "../shared/LeaseSpecDetail";
import { LinkTo } from "../shared/LinkTo";
import { GranteeDepositMenuItem } from "./GranteeDepositMenuItem";

Expand All @@ -45,6 +48,8 @@ export type DeploymentDepositModalProps = {
onDeploymentDeposit: (deposit: number, depositorAddress: string) => void;
handleCancel: () => void;
children?: ReactNode;
title?: string;
services?: ServiceType[];
};

const formSchema = z
Expand Down Expand Up @@ -73,7 +78,9 @@ export const DeploymentDepositModal: React.FunctionComponent<DeploymentDepositMo
onDeploymentDeposit,
disableMin,
denom,
infoText = null
title = "Deployment Deposit",
infoText = null,
services = []
}) => {
const formRef = useRef<HTMLFormElement>(null);
const { settings } = useSettings();
Expand All @@ -96,6 +103,17 @@ export const DeploymentDepositModal: React.FunctionComponent<DeploymentDepositMo
const { handleSubmit, control, watch, setValue, clearErrors, unregister } = form;
const { amount, useDepositor, depositorAddress } = watch();
const validGrants = granteeGrants?.filter(x => compareAsc(new Date(), new Date(x.expiration)) !== 1 && x.authorization.spend_limit.denom === denom) || [];
const whenLoggedInAndVerified = useAddFundsVerifiedLoginRequiredEventHandler();

const closePopupAndGoToCheckoutIfPossible = (event: React.MouseEvent) => {
handleCancel();

whenLoggedInAndVerified(goToCheckout)(event);
};

const goToCheckout = () => {
window.location.href = "/api/proxy/v1/checkout";
};

useEffect(() => {
if (depositData && amount === 0 && !disableMin) {
Expand Down Expand Up @@ -225,10 +243,29 @@ export const DeploymentDepositModal: React.FunctionComponent<DeploymentDepositMo
}
]}
onClose={onClose}
maxWidth="xs"
enableCloseOnBackdropClick
title="Deployment Deposit"
title={title}
>
{services.length > 0 && (
<div className="mb-3 max-h-[300px] overflow-scroll">
{services.map(service => {
return (
<Alert key={service.title} className="mb-1">
<div className="mb-2 text-sm">
<span className="font-bold">{service.title}</span>:{service.image}
</div>
<div className="flex items-center space-x-4 whitespace-nowrap">
<LeaseSpecDetail type="cpu" className="flex-shrink-0" value={service.profile?.cpu} />
{!!service.profile?.gpu && <LeaseSpecDetail type="gpu" className="flex-shrink-0" value={service.profile?.gpu} />}
<LeaseSpecDetail type="ram" className="flex-shrink-0" value={`${service.profile?.ram} ${service.profile?.ramUnit}`} />
<LeaseSpecDetail type="storage" className="flex-shrink-0" value={`${service.profile?.storage} ${service.profile?.storageUnit}`} />
</div>
</Alert>
);
})}
</div>
)}

<Form {...form}>
<form onSubmit={handleSubmit(onSubmit)} ref={formRef}>
{infoText}
Expand Down Expand Up @@ -259,6 +296,13 @@ export const DeploymentDepositModal: React.FunctionComponent<DeploymentDepositMo
);
}}
/>
{isManaged && (
<div className="mt-1 flex justify-end text-xs">
<LinkTo onClick={closePopupAndGoToCheckoutIfPossible} className="text-primary">
Buy more credits
</LinkTo>
</div>
)}
</div>

{isCustodial && (
Expand Down
19 changes: 7 additions & 12 deletions apps/deploy-web/src/components/new-deployment/ManifestEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ 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 { useManagedDeploymentConfirm } from "@src/hooks/useManagedDeploymentConfirm";
import { useManagedWalletDenom } from "@src/hooks/useManagedWalletDenom";
import { useWhen } from "@src/hooks/useWhen";
import { useDepositParams } from "@src/queries/useSettings";
Expand All @@ -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";
Expand Down Expand Up @@ -67,6 +66,7 @@ export const ManifestEdit: React.FunctionComponent<Props> = ({
const [selectedSdlEditMode, setSelectedSdlEditMode] = useAtom(sdlStore.selectedSdlEditMode);
const [isRepoInputValid, setIsRepoInputValid] = useState(false);
const [sdlDenom, setSdlDenom] = useState("uakt");

const { settings } = useSettings();
const { address, signAndBroadcastTx, isManaged, isTrialing } = useWallet();
const router = useRouter();
Expand All @@ -75,16 +75,15 @@ export const ManifestEdit: React.FunctionComponent<Props> = ({
const muiTheme = useMuiTheme();
const smallScreen = useMediaQuery(muiTheme.breakpoints.down("md"));
const sdlBuilderRef = useRef<SdlBuilderRefType>(null);
const { minDeposit } = useChainParam();
const { hasComponent } = useSdlBuilder();
const searchParams = useSearchParams();
const templateId = searchParams.get("templateId");
const { data: depositParams } = useDepositParams();
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,
Expand Down Expand Up @@ -192,18 +191,12 @@ export const ManifestEdit: React.FunctionComponent<Props> = ({
}

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);
}
Expand Down Expand Up @@ -429,14 +422,16 @@ export const ManifestEdit: React.FunctionComponent<Props> = ({
handleCancel={() => setIsDepositingDeployment(false)}
onDeploymentDeposit={onDeploymentDeposit}
denom={sdlDenom}
title="Confirm deployment creation?"
infoText={
<Alert className="mb-4 text-xs" variant="default">
To create a deployment, you need to have at least <b>{minDeposit.akt} AKT</b> or <b>{minDeposit.usdc} USDC</b> in an escrow account.{" "}
<DeploymentMinimumEscrowAlertText />
<LinkTo onClick={ev => handleDocClick(ev, "https://akash.network/docs/other-resources/payments/")}>
<strong>Learn more.</strong>
</LinkTo>
</Alert>
}
services={services}
/>
)}
{isCheckingPrerequisites && <PrerequisiteList onClose={() => setIsCheckingPrerequisites(false)} onContinue={onPrerequisiteContinue} />}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <b>${depositData?.min}</b> in an escrow account.{" "}
</>
) : (
<>
To create a deployment, you need to have at least <b>{minDeposit.akt} AKT</b> or <b>{minDeposit.usdc} USDC</b> in an escrow account.{" "}
</>
);
};
17 changes: 5 additions & 12 deletions apps/deploy-web/src/components/sdl/RentGpusForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ 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 { useManagedDeploymentConfirm } from "@src/hooks/useManagedDeploymentConfirm";
import { useManagedWalletDenom } from "@src/hooks/useManagedWalletDenom";
import { useWhen } from "@src/hooks/useWhen";
import { useGpuModels } from "@src/queries/useGpuQuery";
Expand All @@ -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";
Expand Down Expand Up @@ -75,9 +74,7 @@ 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 { createDeploymentConfirm } = useManagedDeploymentConfirm();
const managedDenom = useManagedWalletDenom();
const { data: depositParams } = useDepositParams();
const defaultDeposit = depositParams || browserEnvConfig.NEXT_PUBLIC_DEFAULT_INITIAL_DEPOSIT;
Expand Down Expand Up @@ -207,13 +204,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);
}
Expand Down Expand Up @@ -298,13 +289,15 @@ export const RentGpusForm: React.FunctionComponent = () => {
infoText={
<Alert className="mb-4" variant="default">
<p className="text-sm text-muted-foreground">
To create a deployment, you need to have at least <b>{minDeposit.akt} AKT</b> or <b>{minDeposit.usdc} USDC</b> in an escrow account.{" "}
<DeploymentMinimumEscrowAlertText />
<LinkTo onClick={ev => handleDocClick(ev, "https://akash.network/docs/getting-started/intro-to-akash/bids-and-leases/#escrow-accounts")}>
<strong>Learn more.</strong>
</LinkTo>
</p>
</Alert>
}
title="Confirm deployment creation?"
services={rentGpuSdl?.services}
/>
)}
{isCheckingPrerequisites && <PrerequisiteList onClose={() => setIsCheckingPrerequisites(false)} onContinue={onPrerequisiteContinue} />}
Expand Down
74 changes: 1 addition & 73 deletions apps/deploy-web/src/hooks/useManagedDeploymentConfirm.tsx
Original file line number Diff line number Diff line change
@@ -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();

Expand All @@ -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: (
<div className="space-y-2">
{!hasEnoughForDeposit && (
<Alert variant="destructive" className="text-primary">
<AlertTitle className="font-bold">Insufficient funds</AlertTitle>
<AlertDescription>
<p>
You need more than{" "}
<FormattedNumber
value={minDeposit.usdc}
// eslint-disable-next-line react/style-prop-object
style="currency"
currency="USD"
/>{" "}
available to create a deployment.
</p>
<p>
Current available balance:{" "}
<span className="font-bold">
<FormattedNumber
value={walletBalance?.totalDeploymentGrantsUSD || 0}
// eslint-disable-next-line react/style-prop-object
style="currency"
currency="USD"
/>
</span>
</p>
</AlertDescription>
</Alert>
)}

{services.map(service => {
return (
<Alert key={service.image}>
<div className="mb-2 text-sm">
<span className="font-bold">{service.title}</span>:{service.image}
</div>
<div className="flex items-center space-x-4 whitespace-nowrap">
<LeaseSpecDetail type="cpu" className="flex-shrink-0" value={service.profile?.cpu as number} />
{service.profile?.hasGpu && <LeaseSpecDetail type="gpu" className="flex-shrink-0" value={service.profile?.gpu as number} />}
<LeaseSpecDetail type="ram" className="flex-shrink-0" value={`${service.profile?.ram} ${service.profile?.ramUnit}`} />
<LeaseSpecDetail type="storage" className="flex-shrink-0" value={`${service.profile?.storage} ${service.profile?.storageUnit}`} />
</div>
</Alert>
);
})}
</div>
)
});

if (!isConfirmed) {
return false;
}
}

return true;
};

return { closeDeploymentConfirm, createDeploymentConfirm };
return { closeDeploymentConfirm };
};