diff --git a/apps/deploy-web/src/components/deployments/DeploymentList.tsx b/apps/deploy-web/src/components/deployments/DeploymentList.tsx index 95b0eb1b36..23a5ad1e55 100644 --- a/apps/deploy-web/src/components/deployments/DeploymentList.tsx +++ b/apps/deploy-web/src/components/deployments/DeploymentList.tsx @@ -23,13 +23,11 @@ import { LinkTo } from "@src/components/shared/LinkTo"; import { useLocalNotes } from "@src/context/LocalNoteProvider"; import { useSettings } from "@src/context/SettingsProvider"; import { useWallet } from "@src/context/WalletProvider"; -import { useCustomUser } from "@src/hooks/useCustomUser"; import { useListSelection } from "@src/hooks/useListSelection/useListSelection"; import { useManagedDeploymentConfirm } from "@src/hooks/useManagedDeploymentConfirm"; import { useDeploymentList } from "@src/queries/useDeploymentQuery"; import { useProviderList } from "@src/queries/useProvidersQuery"; import sdlStore from "@src/store/sdlStore"; -import walletStore from "@src/store/walletStore"; import type { DeploymentDto, NamedDeploymentDto } from "@src/types/deployment"; import { TransactionMessageData } from "@src/utils/TransactionMessageData"; import { UrlService } from "@src/utils/urlUtils"; @@ -59,8 +57,6 @@ export const DeploymentList: React.FunctionComponent = () => { const pageCount = Math.ceil(orderedDeployments.length / pageSize); const [, setDeploySdl] = useAtom(sdlStore.deploySdl); const { closeDeploymentConfirm } = useManagedDeploymentConfirm(); - const [isSignedInWithTrial] = useAtom(walletStore.isSignedInWithTrial); - const { user } = useCustomUser(); const { selectedItemIds, selectItem, clearSelection } = useListSelection({ ids: currentPageDeployments.map(deployment => deployment.dseq) @@ -217,8 +213,6 @@ export const DeploymentList: React.FunctionComponent = () => { onDeployClick={onDeployClick} hasDeployments={Boolean(deployments && deployments.length > 0)} isWalletConnected={isWalletConnected} - isSignedInWithTrial={isSignedInWithTrial} - hasUser={Boolean(user)} showTemplatesButton /> )} diff --git a/apps/deploy-web/src/components/get-started/GetStartedStepper.tsx b/apps/deploy-web/src/components/get-started/GetStartedStepper.tsx index 505e6204e1..8aca81612c 100644 --- a/apps/deploy-web/src/components/get-started/GetStartedStepper.tsx +++ b/apps/deploy-web/src/components/get-started/GetStartedStepper.tsx @@ -8,23 +8,19 @@ import StepContent from "@mui/material/StepContent"; import StepLabel from "@mui/material/StepLabel"; import Stepper from "@mui/material/Stepper"; import { Check, HandCard, Rocket, WarningCircle, XmarkCircleSolid } from "iconoir-react"; -import { useAtom } from "jotai"; import Link from "next/link"; import { AddFundsLink } from "@src/components/user/AddFundsLink"; -import { ConnectManagedWalletButton } from "@src/components/wallet/ConnectManagedWalletButton"; import { useWallet } from "@src/context/WalletProvider"; import { useChainParam } from "@src/hooks/useChainParam/useChainParam"; -import { useCustomUser } from "@src/hooks/useCustomUser"; import { useWalletBalance } from "@src/hooks/useWalletBalance"; -import walletStore from "@src/store/walletStore"; import { RouteStep } from "@src/types/route-steps.type"; import { udenomToDenom } from "@src/utils/mathHelpers"; import { uaktToAKT } from "@src/utils/priceUtils"; import { UrlService } from "@src/utils/urlUtils"; import LiquidityModal from "../liquidity-modal"; import { ExternalLink } from "../shared/ExternalLink"; -import { ConnectWalletButton } from "../wallet/ConnectWalletButton"; +import { WalletConnectionButtons } from "../wallet/WalletConnectionButtons"; import { QontoConnector, QontoStepIcon } from "./Stepper"; export const GetStartedStepper: React.FunctionComponent = () => { @@ -34,8 +30,6 @@ export const GetStartedStepper: React.FunctionComponent = () => { const { minDeposit } = useChainParam(); const aktBalance = walletBalance ? uaktToAKT(walletBalance.balanceUAKT) : 0; const usdcBalance = walletBalance ? udenomToDenom(walletBalance.balanceUUSDC) : 0; - const [isSignedInWithTrial] = useAtom(walletStore.isSignedInWithTrial); - const { user } = useCustomUser(); useEffect(() => { const getStartedStep = localStorage.getItem("getStartedStep"); @@ -141,16 +135,7 @@ export const GetStartedStepper: React.FunctionComponent = () => { Billing is not set up -
- {!isSignedInWithTrial && } - - - {isSignedInWithTrial && !user && ( - - Sign in - - )} -
+ )} diff --git a/apps/deploy-web/src/components/home/NoDeploymentsState.tsx b/apps/deploy-web/src/components/home/NoDeploymentsState.tsx index dd5f70d6fb..8dc692e61a 100644 --- a/apps/deploy-web/src/components/home/NoDeploymentsState.tsx +++ b/apps/deploy-web/src/components/home/NoDeploymentsState.tsx @@ -1,32 +1,26 @@ "use client"; import React from "react"; -import { Button, buttonVariants, Card, CardContent } from "@akashnetwork/ui/components"; -import { cn } from "@akashnetwork/ui/utils"; +import { Button, Card, CardContent } from "@akashnetwork/ui/components"; import { MultiplePages, Rocket } from "iconoir-react"; +import { useAtom } from "jotai"; import Link from "next/link"; -import { ConnectWalletButton } from "@src/components/wallet/ConnectWalletButton"; +import { WalletConnectionButtons } from "@src/components/wallet/WalletConnectionButtons"; import { useServices } from "@src/context/ServicesProvider"; -import { UrlService } from "@src/utils/urlUtils"; +import { useCustomUser } from "@src/hooks/useCustomUser"; +import walletStore from "@src/store/walletStore"; type Props = { onDeployClick: () => void; hasDeployments?: boolean; isWalletConnected?: boolean; - isSignedInWithTrial?: boolean; - hasUser?: boolean; showTemplatesButton?: boolean; }; -export const NoDeploymentsState: React.FC = ({ - onDeployClick, - hasDeployments = false, - isWalletConnected = true, - isSignedInWithTrial = false, - hasUser = true, - showTemplatesButton = true -}) => { +export const NoDeploymentsState: React.FC = ({ onDeployClick, hasDeployments = false, isWalletConnected = true, showTemplatesButton = true }) => { const { urlService } = useServices(); + const [isSignedInWithTrial] = useAtom(walletStore.isSignedInWithTrial); + const { user } = useCustomUser(); const title = hasDeployments ? "No active deployments." : "No deployments yet."; @@ -38,8 +32,8 @@ export const NoDeploymentsState: React.FC = ({

{title}

- {isSignedInWithTrial && !hasUser && ( -

If you are expecting to see some, you may need to sign-in or connect a wallet

+ {isSignedInWithTrial && !user && ( +

If you are expecting to see some, you may need to sign in or connect a wallet

)} {showTemplatesButton && ( @@ -66,12 +60,7 @@ export const NoDeploymentsState: React.FC = ({ )} ) : ( -
- - - Sign in - -
+ )} diff --git a/apps/deploy-web/src/components/layout/Nav.tsx b/apps/deploy-web/src/components/layout/Nav.tsx index 99cc7349ab..012334cc2a 100644 --- a/apps/deploy-web/src/components/layout/Nav.tsx +++ b/apps/deploy-web/src/components/layout/Nav.tsx @@ -1,16 +1,12 @@ "use client"; -import { Button, buttonVariants } from "@akashnetwork/ui/components"; +import { Button } from "@akashnetwork/ui/components"; import { cn } from "@akashnetwork/ui/utils"; import type { ClassValue } from "clsx"; import { Menu, Xmark } from "iconoir-react"; -import { useAtom } from "jotai"; import Link from "next/link"; import { ACCOUNT_BAR_HEIGHT } from "@src/config/ui.config"; -import { useCustomUser } from "@src/hooks/useCustomUser"; import useCookieTheme from "@src/hooks/useTheme"; -import walletStore from "@src/store/walletStore"; -import { UrlService } from "@src/utils/urlUtils"; import { AccountMenu } from "./AccountMenu"; import { AkashLogo } from "./AkashLogo"; import { WalletStatus } from "./WalletStatus"; @@ -25,8 +21,6 @@ export const Nav = ({ className?: ClassValue; }>) => { const theme = useCookieTheme(); - const [isSignedInWithTrial] = useAtom(walletStore.isSignedInWithTrial); - const { user } = useCustomUser(); return (
@@ -47,12 +41,6 @@ export const Nav = ({
- - {isSignedInWithTrial && !user && ( - - Sign in - - )}
diff --git a/apps/deploy-web/src/components/layout/WalletStatus.tsx b/apps/deploy-web/src/components/layout/WalletStatus.tsx index 77fba23330..2b11c6713f 100644 --- a/apps/deploy-web/src/components/layout/WalletStatus.tsx +++ b/apps/deploy-web/src/components/layout/WalletStatus.tsx @@ -5,21 +5,17 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, Spinner } from import { cn } from "@akashnetwork/ui/utils"; import ClickAwayListener from "@mui/material/ClickAwayListener"; import { NavArrowDown, Wallet } from "iconoir-react"; -import { useAtom } from "jotai"; -import { ConnectManagedWalletButton } from "@src/components/wallet/ConnectManagedWalletButton"; import { useWallet } from "@src/context/WalletProvider"; import { getSplitText } from "@src/hooks/useShortText"; import { useWalletBalance } from "@src/hooks/useWalletBalance"; -import walletStore from "@src/store/walletStore"; -import { ConnectWalletButton } from "../wallet/ConnectWalletButton"; import { CustodialWalletPopup } from "../wallet/CustodialWalletPopup"; import { ManagedWalletPopup } from "../wallet/ManagedWalletPopup"; +import { WalletConnectionButtons } from "../wallet/WalletConnectionButtons"; export function WalletStatus() { const { walletName, isWalletLoaded, isWalletConnected, isManaged, isWalletLoading, isTrialing } = useWallet(); const { balance: walletBalance, isLoading: isWalletBalanceLoading } = useWalletBalance(); - const [isSignedInWithTrial] = useAtom(walletStore.isSignedInWithTrial); const [open, setOpen] = useState(false); const isLoadingBalance = isWalletBalanceLoading && !walletBalance; const isInit = isWalletLoaded && !isWalletLoading && !isLoadingBalance; @@ -89,10 +85,7 @@ export function WalletStatus() {
) : ( -
- {!isSignedInWithTrial && } - -
+ ) ) : (
diff --git a/apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.tsx b/apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.tsx index c157f503e1..d8f0aed411 100644 --- a/apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.tsx +++ b/apps/deploy-web/src/components/new-deployment/CreateLease/CreateLease.tsx @@ -37,7 +37,6 @@ import { useWallet } from "@src/context/WalletProvider"; import { useManagedDeploymentConfirm } from "@src/hooks/useManagedDeploymentConfirm"; import { useWhen } from "@src/hooks/useWhen"; import { useBidList } from "@src/queries/useBidQuery"; -import { useBlock } from "@src/queries/useBlocksQuery"; import { useDeploymentDetail } from "@src/queries/useDeploymentQuery"; import { useProviderList } from "@src/queries/useProvidersQuery"; import type { SendManifestToProviderOptions } from "@src/services/provider-proxy/provider-proxy.service"; @@ -99,7 +98,6 @@ export const DEPENDENCIES = { useSnackbar, useManagedDeploymentConfirm, useRouter, - useBlock, useSettings }; @@ -109,7 +107,6 @@ const REFRESH_BIDS_INTERVAL = 7000; const MAX_NUM_OF_BID_REQUESTS = Math.floor((5.5 * 60 * 1000) / REFRESH_BIDS_INTERVAL); // Show a warning after 1 minute const WARNING_NUM_OF_BID_REQUESTS = Math.round((60 * 1000) / REFRESH_BIDS_INTERVAL); -const TRIAL_SIGNUP_WARNING_TIMEOUT = 33_000; export const CreateLease: React.FunctionComponent = ({ dseq, dependencies: d = DEPENDENCIES }) => { const { settings } = d.useSettings(); @@ -297,25 +294,6 @@ export const CreateLease: React.FunctionComponent = ({ dseq, dependencies // eslint-disable-next-line react-hooks/exhaustive-deps }, [search, bids, providers, isFilteringFavorites, isFilteringAudited, favoriteProviders]); - const [zeroBidsForTrialWarningDisplayed, setZeroBidsForTrialWarningDisplayed] = useState(false); - const { data: block } = d.useBlock(dseq); - - useEffect(() => { - if (!isTrialing || numberOfRequests === 0 || (bids && bids.length > 0)) { - setZeroBidsForTrialWarningDisplayed(false); - return; - } - - const timerId = setTimeout(() => { - if (block) { - const blockTime = new Date(block.block.header.time).getTime(); - setZeroBidsForTrialWarningDisplayed(Date.now() - blockTime > TRIAL_SIGNUP_WARNING_TIMEOUT); - } - }, 1000); - - return () => clearTimeout(timerId); - }, [block, bids, isTrialing, numberOfRequests]); - const selectBid = (bid: BidDto) => { setSelectedBids(prev => ({ ...prev, [bid.gseq]: bid })); }; @@ -463,7 +441,7 @@ export const CreateLease: React.FunctionComponent = ({ dseq, dependencies )} - {!settings.isBlockchainDown && !zeroBidsForTrialWarningDisplayed && warningRequestsReached && !maxRequestsReached && (bids?.length || 0) === 0 && ( + {!settings.isBlockchainDown && warningRequestsReached && !maxRequestsReached && (bids?.length || 0) === 0 && (
There should be bids by now... You can wait longer in case a bid shows up or close the deployment and try again with a different configuration. @@ -476,18 +454,19 @@ export const CreateLease: React.FunctionComponent = ({ dseq, dependencies
)} - {!settings.isBlockchainDown && - (isLoadingBids || (bids?.length || 0) === 0) && - !maxRequestsReached && - !isSendingManifest && - !zeroBidsForTrialWarningDisplayed && ( -
- -
Waiting for bids...
+ {!settings.isBlockchainDown && (isLoadingBids || (bids?.length || 0) === 0) && !maxRequestsReached && !isSendingManifest && ( +
+ +
Waiting for bids...
+
+ + Close Deployment +
- )} +
+ )} - {!settings.isBlockchainDown && !zeroBidsForTrialWarningDisplayed && maxRequestsReached && (bids?.length || 0) === 0 && ( + {!settings.isBlockchainDown && maxRequestsReached && (bids?.length || 0) === 0 && (
There's no bid for the current deployment. You can close the deployment and try again with a different configuration. diff --git a/apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx b/apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx index a5f77311b5..e79c164fa7 100644 --- a/apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx +++ b/apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx @@ -280,6 +280,9 @@ describe("OnboardingContainer", () => { const mockUseChainParam = jest.fn().mockReturnValue({ minDeposit: { akt: 0.5, usdc: 5 } }); const mockDenomToUdenom = jest.fn().mockImplementation((amount: number) => amount * 1_000_000); const mockErrorHandler = mock(); + const mockTemplateService = { + findById: jest.fn().mockResolvedValue({ deploy: "mock-template-sdl" }) + }; const mockUseServices = jest.fn().mockReturnValue({ analyticsService: mockAnalyticsService, @@ -290,7 +293,8 @@ describe("OnboardingContainer", () => { publicConfig: mockAppConfig, errorHandler: mockErrorHandler, windowLocation, - windowHistory + windowHistory, + template: mockTemplateService }); const mockUseRouter = jest.fn().mockReturnValue(mockRouter); const mockUseWallet = jest.fn().mockReturnValue({ @@ -300,7 +304,6 @@ describe("OnboardingContainer", () => { address: "akash1test", signAndBroadcastTx: mockSignAndBroadcastTx }); - const mockUseTemplates = jest.fn().mockReturnValue({ templates: [] }); const mockUseCertificate = jest.fn().mockReturnValue({ genNewCertificateIfLocalIsInvalid: mockGenNewCertificateIfLocalIsInvalid, updateSelectedCertificate: mockUpdateSelectedCertificate @@ -387,7 +390,6 @@ describe("OnboardingContainer", () => { useServices: mockUseServices, useRouter: mockUseRouter, useWallet: mockUseWallet, - useTemplates: mockUseTemplates, useCertificate: mockUseCertificate, useSnackbar: mockUseSnackbar, useManagedWalletDenom: mockUseManagedWalletDenom, diff --git a/apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx b/apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx index 325822ef80..1bdeeda408 100644 --- a/apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx +++ b/apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx @@ -14,7 +14,6 @@ import { useManagedWalletDenom } from "@src/hooks/useManagedWalletDenom"; import { useReturnTo } from "@src/hooks/useReturnTo"; import { useUser } from "@src/hooks/useUser"; import { usePaymentMethodsQuery } from "@src/queries/usePaymentQueries"; -import { useTemplates } from "@src/queries/useTemplateQuery"; import { ONBOARDING_STEP_KEY } from "@src/services/storage/keys"; import { RouteStep } from "@src/types/route-steps.type"; import { deploymentData } from "@src/utils/deploymentData"; @@ -53,7 +52,6 @@ const DEPENDENCIES = { useServices, useRouter, useWallet, - useTemplates, useCertificate, useSnackbar, useManagedWalletDenom, @@ -78,9 +76,17 @@ export const OnboardingContainer: React.FunctionComponent t.id === templateConfig.id); + const template = await templateService.findById(templateConfig.id); if (!template || !template.deploy) { const error = new Error(`Template ${templateName} SDL not found`); errorHandler.reportError({ @@ -305,7 +311,7 @@ export const OnboardingContainer: React.FunctionComponent { - const { analyticsService } = useServices(); - const { isDeploymentReturnTo } = useReturnTo(); - - const handleBackToConsole = () => { - analyticsService.track("onboarding_back_to_console", { - category: "onboarding" - }); - }; - return ( -
+
-
+
{props => } - - {!isDeploymentReturnTo && ( -
- - - Back to Console - -
- )}
); diff --git a/apps/deploy-web/src/components/onboarding/OnboardingRedirect/OnboardingRedirect.tsx b/apps/deploy-web/src/components/onboarding/OnboardingRedirect/OnboardingRedirect.tsx new file mode 100644 index 0000000000..c90b519ee5 --- /dev/null +++ b/apps/deploy-web/src/components/onboarding/OnboardingRedirect/OnboardingRedirect.tsx @@ -0,0 +1,17 @@ +import { useEffect } from "react"; +import { useRouter } from "next/router"; + +import { Loading } from "@src/components/layout/Layout"; +import { UrlService } from "@src/utils/urlUtils"; + +const OnboardingRedirect = () => { + const router = useRouter(); + + useEffect(() => { + router.replace(UrlService.onboarding({ returnTo: router.asPath })); + }, [router]); + + return ; +}; + +export default OnboardingRedirect; diff --git a/apps/deploy-web/src/components/onboarding/OnboardingRedirectEffect/OnboardingRedirectEffect.tsx b/apps/deploy-web/src/components/onboarding/OnboardingRedirectEffect/OnboardingRedirectEffect.tsx new file mode 100644 index 0000000000..abf353453e --- /dev/null +++ b/apps/deploy-web/src/components/onboarding/OnboardingRedirectEffect/OnboardingRedirectEffect.tsx @@ -0,0 +1,28 @@ +import { useEffect } from "react"; +import { useRouter } from "next/router"; + +import { useWallet } from "@src/context/WalletProvider"; +import { useUser } from "@src/hooks/useUser"; +import { UrlService } from "@src/utils/urlUtils"; + +const EXCLUDED_PREFIXES = ["/signup", "/login", "/api/"]; + +export const OnboardingRedirectEffect = () => { + const { user, isLoading: isUserLoading } = useUser(); + const { hasManagedWallet, isWalletConnected, isWalletLoading } = useWallet(); + const router = useRouter(); + + useEffect(() => { + const isExcluded = EXCLUDED_PREFIXES.some(prefix => router.pathname.startsWith(prefix)); + + if (isExcluded || isUserLoading || isWalletLoading) { + return; + } + + if (user?.userId && !hasManagedWallet && !isWalletConnected) { + router.replace(UrlService.onboarding({ returnTo: router.asPath })); + } + }, [isUserLoading, isWalletLoading, user?.userId, hasManagedWallet, isWalletConnected, router]); + + return null; +}; diff --git a/apps/deploy-web/src/components/onboarding/OnboardingView/OnboardingView.tsx b/apps/deploy-web/src/components/onboarding/OnboardingView/OnboardingView.tsx index e1b7d78faf..a28d422ec4 100644 --- a/apps/deploy-web/src/components/onboarding/OnboardingView/OnboardingView.tsx +++ b/apps/deploy-web/src/components/onboarding/OnboardingView/OnboardingView.tsx @@ -1,6 +1,11 @@ import type { FC } from "react"; import React from "react"; +import { buttonVariants } from "@akashnetwork/ui/components"; +import { cn } from "@akashnetwork/ui/utils"; +import { LogOut } from "iconoir-react"; +import { useServices } from "@src/context/ServicesProvider"; +import { useUser } from "@src/hooks/useUser"; import { OnboardingStepIndex } from "../OnboardingContainer/OnboardingContainer"; import { type OnboardingStep, OnboardingStepper } from "../OnboardingStepper/OnboardingStepper"; import { EmailVerificationContainer } from "../steps/EmailVerificationContainer/EmailVerificationContainer"; @@ -66,5 +71,30 @@ export const OnboardingView: FC = ({ } ]; - return ; + const { analyticsService, authService } = useServices(); + const { user } = useUser(); + + const handleLogout = () => { + analyticsService.track("onboarding_logout", { + category: "onboarding" + }); + authService.logout(); + }; + + return ( + <> + + {user && currentStep !== OnboardingStepIndex.WELCOME && ( +
+ +
+ )} + + ); }; diff --git a/apps/deploy-web/src/components/onboarding/steps/WelcomeStep/TrialStatusBar.tsx b/apps/deploy-web/src/components/onboarding/steps/WelcomeStep/TrialStatusBar.tsx index 11621aa72d..c25679628c 100644 --- a/apps/deploy-web/src/components/onboarding/steps/WelcomeStep/TrialStatusBar.tsx +++ b/apps/deploy-web/src/components/onboarding/steps/WelcomeStep/TrialStatusBar.tsx @@ -1,13 +1,13 @@ "use client"; import React from "react"; import { FormattedDate } from "react-intl"; -import { Card, Progress, Skeleton } from "@akashnetwork/ui/components"; -import { InfoCircle } from "iconoir-react"; +import { Card, Skeleton } from "@akashnetwork/ui/components"; +import { Check, InfoCircle } from "iconoir-react"; import { useTrialBalance } from "@src/hooks/useTrialBalance"; export const TrialStatusBar: React.FC = () => { - const { total: TRIAL_TOTAL, remaining: creditsRemaining, used: creditsUsed, remainingPercentage, isLoading, trialEndDate, daysRemaining } = useTrialBalance(); + const { remaining: creditsRemaining, isLoading, trialEndDate, daysRemaining } = useTrialBalance(); if (isLoading) { return ( @@ -24,14 +24,6 @@ export const TrialStatusBar: React.FC = () => {
- -
- -
- - -
-
); @@ -41,8 +33,11 @@ export const TrialStatusBar: React.FC = () => {
-
- Trial Active +
+ + + Trial Active +
Free Trial Credits: ${creditsRemaining.toFixed(2)}
@@ -57,16 +52,6 @@ export const TrialStatusBar: React.FC = () => { Deployments last for maximum 1 day during trial
- -
- -
- ${creditsUsed.toFixed(2)} used - - ${creditsRemaining.toFixed(2)} remaining of ${TRIAL_TOTAL.toFixed(2)} - -
-
); diff --git a/apps/deploy-web/src/components/onboarding/steps/WelcomeStep/WelcomeStep.tsx b/apps/deploy-web/src/components/onboarding/steps/WelcomeStep/WelcomeStep.tsx index ca411d2ae3..a0697b9581 100644 --- a/apps/deploy-web/src/components/onboarding/steps/WelcomeStep/WelcomeStep.tsx +++ b/apps/deploy-web/src/components/onboarding/steps/WelcomeStep/WelcomeStep.tsx @@ -1,6 +1,7 @@ "use client"; import React, { useState } from "react"; import { Button, Spinner } from "@akashnetwork/ui/components"; +import { ArrowRight } from "iconoir-react"; import Image from "next/image"; import { useServices } from "@src/context/ServicesProvider"; @@ -55,56 +56,68 @@ export const WelcomeStep: React.FunctionComponent = ({ onCompl
-
-

Welcome to Akash Console

- {isDeploymentReturnTo ? ( -

You're all set! Continue to complete your deployment.

- ) : ( -

Choose a template below to launch your first app in seconds.

- )} -
- {isDeploymentReturnTo ? ( -
- -
- ) : ( -
- } - title="Hello Akash!" - description={ +
+
+

Welcome to Akash Console

+

You're all set! Continue to complete your deployment.

+
+
+ ) : ( + <> +
+

Welcome to Akash Console

+

Choose a template below to launch your first app in seconds.

+
+
+ } + title="Hello Akash!" + description={ + <> + A simple web app powered by Next.js, perfect for your first deployment on Akash. View and explore the full source code{" "} + + here + + . + + } + onDeploy={() => deployTemplate("hello-akash")} + disabled={isDeploying} + isLoading={deployingTemplate === "hello-akash"} + /> + } + title="ComfyUI" + description="A powerful, modular Stable Diffusion tool that lets you build and run advanced image workflows using a visual, node-based interface." + onDeploy={() => deployTemplate("comfyui")} + disabled={isDeploying} + isLoading={deployingTemplate === "comfyui"} + /> + } + title="Llama-3.1-8b" + description="A cutting-edge language model built for fast, context-aware text generation. Access a wide range of advanced language tasks." + onDeploy={() => deployTemplate("llama-3.1-8b")} + disabled={isDeploying} + isLoading={deployingTemplate === "llama-3.1-8b"} + /> +
+ )}
); diff --git a/apps/deploy-web/src/components/shared/ConnectWallet.tsx b/apps/deploy-web/src/components/shared/ConnectWallet.tsx index 2409d77c12..715c3acd66 100644 --- a/apps/deploy-web/src/components/shared/ConnectWallet.tsx +++ b/apps/deploy-web/src/components/shared/ConnectWallet.tsx @@ -1,13 +1,7 @@ "use client"; import type { ReactNode } from "react"; import React from "react"; -import { buttonVariants } from "@akashnetwork/ui/components"; -import { cn } from "@akashnetwork/ui/utils"; -import { useAtom } from "jotai"; -import Link from "next/link"; -import walletStore from "@src/store/walletStore"; -import { UrlService } from "@src/utils/urlUtils"; import { WalletStatus } from "../layout/WalletStatus"; import { Title } from "./Title"; @@ -17,8 +11,6 @@ type Props = { }; export const ConnectWallet: React.FunctionComponent = ({ text }) => { - const [isSignedInWithTrial] = useAtom(walletStore.isSignedInWithTrial); - return (
@@ -26,11 +18,6 @@ export const ConnectWallet: React.FunctionComponent<Props> = ({ text }) => {
- {isSignedInWithTrial && ( - - Sign in - - )}
); diff --git a/apps/deploy-web/src/components/shared/PaymentMethodForm/PaymentMethodForm.tsx b/apps/deploy-web/src/components/shared/PaymentMethodForm/PaymentMethodForm.tsx index a2f85057a2..7cd603bbb6 100644 --- a/apps/deploy-web/src/components/shared/PaymentMethodForm/PaymentMethodForm.tsx +++ b/apps/deploy-web/src/components/shared/PaymentMethodForm/PaymentMethodForm.tsx @@ -7,6 +7,7 @@ import { StripeInput } from "../StripeInput"; interface PaymentMethodFormProps { onSuccess: (organization?: string) => void; + onReady?: React.ComponentProps["onReady"]; buttonText?: string; processingText?: string; className?: string; @@ -14,6 +15,7 @@ interface PaymentMethodFormProps { export const PaymentMethodForm: React.FC = ({ onSuccess, + onReady, buttonText = "Add Card", processingText = "Processing...", className = "" @@ -83,6 +85,7 @@ export const PaymentMethodForm: React.FC = ({ options={{ layout: "tabs" }} + onReady={onReady} />
diff --git a/apps/deploy-web/src/components/wallet/ConnectWalletButton.tsx b/apps/deploy-web/src/components/wallet/ConnectWalletButton.tsx index ecdae19b3e..591a9bca74 100644 --- a/apps/deploy-web/src/components/wallet/ConnectWalletButton.tsx +++ b/apps/deploy-web/src/components/wallet/ConnectWalletButton.tsx @@ -3,6 +3,7 @@ import type { ReactNode } from "react"; import React from "react"; import type { ButtonProps } from "@akashnetwork/ui/components"; import { Button } from "@akashnetwork/ui/components"; +import { cn } from "@akashnetwork/ui/utils"; import { Wallet } from "iconoir-react"; import { useSelectedChain } from "@src/context/CustomChainProvider"; @@ -16,9 +17,15 @@ export const ConnectWalletButton: React.FunctionComponent = ({ className const { connect } = useSelectedChain(); return ( - ); }; diff --git a/apps/deploy-web/src/components/wallet/WalletConnectionButtons.tsx b/apps/deploy-web/src/components/wallet/WalletConnectionButtons.tsx new file mode 100644 index 0000000000..e637df7e32 --- /dev/null +++ b/apps/deploy-web/src/components/wallet/WalletConnectionButtons.tsx @@ -0,0 +1,41 @@ +"use client"; +import React from "react"; +import { buttonVariants } from "@akashnetwork/ui/components"; +import { cn } from "@akashnetwork/ui/utils"; +import { LogIn } from "iconoir-react"; +import { useAtom } from "jotai"; +import Link from "next/link"; + +import { useCustomUser } from "@src/hooks/useCustomUser"; +import walletStore from "@src/store/walletStore"; +import { UrlService } from "@src/utils/urlUtils"; +import { ConnectManagedWalletButton } from "./ConnectManagedWalletButton"; +import { ConnectWalletButton } from "./ConnectWalletButton"; + +interface WalletConnectionButtonsProps { + className?: string; + connectManagedWalletButtonClassName?: string; + connectWalletButtonClassName?: string; +} + +export const WalletConnectionButtons: React.FC = ({ + className, + connectManagedWalletButtonClassName, + connectWalletButtonClassName +}) => { + const [isSignedInWithTrial] = useAtom(walletStore.isSignedInWithTrial); + const { user } = useCustomUser(); + + return ( +
+ {!isSignedInWithTrial && } + {isSignedInWithTrial && !user && ( + + + Sign in + + )} + +
+ ); +}; diff --git a/apps/deploy-web/src/hooks/useIsOnboarded.ts b/apps/deploy-web/src/hooks/useIsOnboarded.ts new file mode 100644 index 0000000000..38b30ee82c --- /dev/null +++ b/apps/deploy-web/src/hooks/useIsOnboarded.ts @@ -0,0 +1,12 @@ +import { useWallet } from "@src/context/WalletProvider"; +import { useUser } from "@src/hooks/useUser"; + +export const useIsOnboarded = () => { + const { user, isLoading: isUserLoading } = useUser(); + const { hasManagedWallet, isWalletConnected, isWalletLoading } = useWallet(); + + return { + isLoading: isUserLoading || isWalletLoading, + canVisit: !user?.userId || hasManagedWallet || isWalletConnected + }; +}; diff --git a/apps/deploy-web/src/hooks/useManagedWallet.ts b/apps/deploy-web/src/hooks/useManagedWallet.ts index 440b374728..af18d761f6 100644 --- a/apps/deploy-web/src/hooks/useManagedWallet.ts +++ b/apps/deploy-web/src/hooks/useManagedWallet.ts @@ -1,8 +1,7 @@ -import { useEffect, useMemo } from "react"; +import { useEffect, useMemo, useRef } from "react"; import type { ApiManagedWalletOutput } from "@akashnetwork/http-sdk"; import { useAtom } from "jotai"; -import { useSelectedChain } from "@src/context/CustomChainProvider"; import { useUser } from "@src/hooks/useUser"; import { useCreateManagedWalletMutation, useManagedWalletQuery } from "@src/queries/useManagedWalletQuery"; import walletStore from "@src/store/walletStore"; @@ -12,7 +11,6 @@ import { useCustomUser } from "./useCustomUser"; export const useManagedWallet = () => { const { user } = useUser(); const { user: signedInUser } = useCustomUser(); - const userWallet = useSelectedChain(); const [selectedWalletType, setSelectedWalletType] = useAtom(walletStore.selectedWalletType); const { data: queried, isLoading: isInitialLoading, isFetching, refetch } = useManagedWalletQuery(user?.id); const { mutate: create, data: created, isPending: isCreating, isSuccess: isCreated, error: createError } = useCreateManagedWalletMutation(); @@ -20,12 +18,16 @@ export const useManagedWallet = () => { const isLoading = isInitialLoading || isCreating; const [, setIsSignedInWithTrial] = useAtom(walletStore.isSignedInWithTrial); const selected = getSelectedStorageWallet(); + const hasAutoSwitched = useRef(false); useEffect(() => { - if (selectedWalletType === "custodial" && queried && !userWallet.isWalletConnected && !userWallet.isWalletConnecting) { - setSelectedWalletType("managed"); + if (!hasAutoSwitched.current && queried) { + hasAutoSwitched.current = true; + if (selectedWalletType === "custodial") { + setSelectedWalletType("managed"); + } } - }, [queried, selectedWalletType, setSelectedWalletType, userWallet.isWalletConnected, userWallet.isWalletConnecting]); + }, [queried, selectedWalletType, setSelectedWalletType]); useEffect(() => { if (signedInUser?.id && (!!queried || !!created)) { diff --git a/apps/deploy-web/src/pages/_app.tsx b/apps/deploy-web/src/pages/_app.tsx index cb4bf9cbbd..ae2bd93050 100644 --- a/apps/deploy-web/src/pages/_app.tsx +++ b/apps/deploy-web/src/pages/_app.tsx @@ -20,6 +20,7 @@ import NProgress from "nprogress"; import GoogleAnalytics from "@src/components/layout/CustomGoogleAnalytics"; import { CustomIntlProvider } from "@src/components/layout/CustomIntlProvider"; import { PageHead } from "@src/components/layout/PageHead"; +import { OnboardingRedirectEffect } from "@src/components/onboarding/OnboardingRedirectEffect/OnboardingRedirectEffect"; import { UserProviders } from "@src/components/user/UserProviders/UserProviders"; import { CertificateProvider } from "@src/context/CertificateProvider"; import { CustomChainProvider } from "@src/context/CustomChainProvider"; @@ -60,6 +61,7 @@ const App: React.FunctionComponent = props => { + diff --git a/apps/deploy-web/src/pages/deployments/[dseq]/index.tsx b/apps/deploy-web/src/pages/deployments/[dseq]/index.tsx index 746d200f3b..53ffbb1771 100644 --- a/apps/deploy-web/src/pages/deployments/[dseq]/index.tsx +++ b/apps/deploy-web/src/pages/deployments/[dseq]/index.tsx @@ -1,9 +1,12 @@ import { z } from "zod"; import { DeploymentDetail } from "@src/components/deployments/DeploymentDetail"; +import OnboardingRedirect from "@src/components/onboarding/OnboardingRedirect/OnboardingRedirect"; +import { Guard } from "@src/hoc/guard/guard.hoc"; +import { useIsOnboarded } from "@src/hooks/useIsOnboarded"; import { defineServerSideProps } from "@src/lib/nextjs/defineServerSideProps/defineServerSideProps"; -export default DeploymentDetail; +export default Guard(DeploymentDetail, useIsOnboarded, OnboardingRedirect); export const getServerSideProps = defineServerSideProps({ route: "/deployments/[dseq]", diff --git a/apps/deploy-web/src/pages/deployments/index.tsx b/apps/deploy-web/src/pages/deployments/index.tsx index fa1c751139..8f5b1d4503 100644 --- a/apps/deploy-web/src/pages/deployments/index.tsx +++ b/apps/deploy-web/src/pages/deployments/index.tsx @@ -1,9 +1,12 @@ import React from "react"; import { DeploymentList } from "@src/components/deployments/DeploymentList"; +import OnboardingRedirect from "@src/components/onboarding/OnboardingRedirect/OnboardingRedirect"; +import { Guard } from "@src/hoc/guard/guard.hoc"; +import { useIsOnboarded } from "@src/hooks/useIsOnboarded"; function DeploymentsPage() { return ; } -export default DeploymentsPage; +export default Guard(DeploymentsPage, useIsOnboarded, OnboardingRedirect); diff --git a/apps/deploy-web/src/pages/login/index.tsx b/apps/deploy-web/src/pages/login/index.tsx index 87e10c9138..2d12a6d4e3 100644 --- a/apps/deploy-web/src/pages/login/index.tsx +++ b/apps/deploy-web/src/pages/login/index.tsx @@ -21,6 +21,7 @@ export default () => { const destination = getAuthRedirectDestination({ currentLocation: windowLocation.href, tab: searchParams.get("tab"), + fromSignup: searchParams.has("fromSignup"), services: { urlService, urlReturnToStack } }); windowLocation.assign(destination); @@ -57,6 +58,7 @@ export const getServerSideProps = defineServerSideProps({ const destination = getAuthRedirectDestination({ currentLocation: ctx.resolvedUrl, tab: ctx.query.tab, + fromSignup: ctx.resolvedUrl.includes("fromSignup"), services: { urlService: ctx.services.urlService, urlReturnToStack: ctx.services.urlReturnToStack @@ -74,8 +76,13 @@ export const getServerSideProps = defineServerSideProps({ function getAuthRedirectDestination(options: { currentLocation: string; tab: string | null; + fromSignup?: boolean; services: { urlService: typeof UrlService; urlReturnToStack: typeof UrlReturnToStack }; }): string { + if (options.tab === "signup" && !options.fromSignup) { + return options.services.urlService.onboarding({ returnTo: "/" }); + } + const redirectUrl = options.tab === "signup" ? options.services.urlService.signup() : options.services.urlService.login(); const returnTo = options.services.urlReturnToStack.getReturnTo(options.currentLocation); const separator = redirectUrl.includes("?") ? "&" : "?"; diff --git a/apps/deploy-web/src/pages/new-deployment/index.tsx b/apps/deploy-web/src/pages/new-deployment/index.tsx index 8a5ce223d3..e3490074cc 100644 --- a/apps/deploy-web/src/pages/new-deployment/index.tsx +++ b/apps/deploy-web/src/pages/new-deployment/index.tsx @@ -1,7 +1,10 @@ import { NewDeploymentContainer } from "@src/components/new-deployment/NewDeploymentContainer"; import { createServerSideProps } from "@src/components/new-deployment/NewDeploymentPage/createServerSideProps"; +import OnboardingRedirect from "@src/components/onboarding/OnboardingRedirect/OnboardingRedirect"; import { withSdlBuilder } from "@src/context/SdlBuilderProvider/SdlBuilderProvider"; +import { Guard } from "@src/hoc/guard/guard.hoc"; +import { useIsOnboarded } from "@src/hooks/useIsOnboarded"; -export default withSdlBuilder()(NewDeploymentContainer); +export default Guard(withSdlBuilder()(NewDeploymentContainer), useIsOnboarded, OnboardingRedirect); export const getServerSideProps = createServerSideProps("/new-deployment"); diff --git a/apps/deploy-web/src/pages/payment.tsx b/apps/deploy-web/src/pages/payment.tsx index 0219ca40d5..d40cfd52f9 100644 --- a/apps/deploy-web/src/pages/payment.tsx +++ b/apps/deploy-web/src/pages/payment.tsx @@ -5,6 +5,7 @@ import { useTheme } from "next-themes"; import { useSnackbar } from "notistack"; import Layout from "@src/components/layout/Layout"; +import OnboardingRedirect from "@src/components/onboarding/OnboardingRedirect/OnboardingRedirect"; import { ThreeDSecurePopup } from "@src/components/shared/PaymentMethodForm/ThreeDSecurePopup"; import { PaymentMethodsList } from "@src/components/shared/PaymentMethodsList"; import { Title } from "@src/components/shared/Title"; @@ -13,7 +14,9 @@ import { PaymentSuccessAnimation } from "@src/components/user/payment/PaymentSuc import { usePaymentPolling } from "@src/context/PaymentPollingProvider"; import { useSettings } from "@src/context/SettingsProvider"; import { useWallet } from "@src/context/WalletProvider"; +import { Guard } from "@src/hoc/guard/guard.hoc"; import { use3DSecure } from "@src/hooks/use3DSecure"; +import { useIsOnboarded } from "@src/hooks/useIsOnboarded"; import { useUser } from "@src/hooks/useUser"; import { defineServerSideProps } from "@src/lib/nextjs/defineServerSideProps/defineServerSideProps"; import { redirectIfAccessTokenExpired } from "@src/lib/nextjs/pageGuards/pageGuards"; @@ -340,7 +343,7 @@ const PayPage: React.FunctionComponent = () => { ); }; -export default PayPage; +export default Guard(PayPage, useIsOnboarded, OnboardingRedirect); export const getServerSideProps = defineServerSideProps({ if: redirectIfAccessTokenExpired, diff --git a/apps/deploy-web/src/services/analytics/analytics.service.ts b/apps/deploy-web/src/services/analytics/analytics.service.ts index b5d502a874..62991ffcc6 100644 --- a/apps/deploy-web/src/services/analytics/analytics.service.ts +++ b/apps/deploy-web/src/services/analytics/analytics.service.ts @@ -103,7 +103,7 @@ export type AnalyticsEvent = | "onboarding_email_verified" | "onboarding_payment_method_added" | "onboarding_completed" - | "onboarding_back_to_console"; + | "onboarding_logout"; export type AnalyticsCategory = | "user"