Skip to content
Merged
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
3 changes: 2 additions & 1 deletion apps/api/src/core/services/feature-flags/feature-flags.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export const FeatureFlags = {
NOTIFICATIONS_ALERT_CREATE: "notifications_general_alerts_create",
NOTIFICATIONS_ALERT_UPDATE: "notifications_general_alerts_update",
ANONYMOUS_FREE_TRIAL: "anonymous_free_trial"
ANONYMOUS_FREE_TRIAL: "anonymous_free_trial",
AUTO_CREDIT_RELOAD: "auto_credit_reload"
} as const;

export type FeatureFlagValue = (typeof FeatureFlags)[keyof typeof FeatureFlags];
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React, { useCallback, useMemo, useState } from "react";
import { FormattedNumber } from "react-intl";
import { Button, Card, CardContent, CardFooter, CardHeader, CardTitle, Snackbar, Switch } from "@akashnetwork/ui/components";
import { LinearProgress } from "@mui/material";
import { Plus } from "iconoir-react";
import { useSnackbar } from "notistack";

import { PaymentPopup } from "@src/components/billing-usage/PaymentPopup/PaymentPopup";
import Layout from "@src/components/layout/Layout";
import { Title } from "@src/components/shared/Title";
import { PaymentSuccessAnimation } from "@src/components/user/payment/PaymentSuccessAnimation";
import { useWalletBalance } from "@src/hooks/useWalletBalance";
import { useDefaultPaymentMethodQuery, useWalletSettingsMutations, useWalletSettingsQuery } from "@src/queries";

export const AccountOverview: React.FunctionComponent = () => {
const [showPaymentPopup, setShowPaymentPopup] = useState(false);
const [showPaymentSuccess, setShowPaymentSuccess] = useState<{ amount: string; show: boolean }>({ amount: "", show: false });
const { enqueueSnackbar } = useSnackbar();
const { data: defaultPaymentMethod, isLoading: isLoadingDefaultPaymentMethod } = useDefaultPaymentMethodQuery();
const { balance: walletBalance, isLoading: isWalletBalanceLoading } = useWalletBalance();
const { data: walletSettings } = useWalletSettingsQuery();
const { updateWalletSettings, createWalletSettings } = useWalletSettingsMutations();

const isLoading = isLoadingDefaultPaymentMethod;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incomplete loading state check.

The isLoading variable only checks isLoadingDefaultPaymentMethod, but the component also depends on wallet balance and wallet settings data. If these are still loading, the component might render with incomplete data.

Consider whether other loading states should be included:

-  const isLoading = isLoadingDefaultPaymentMethod;
+  const isLoading = isLoadingDefaultPaymentMethod || isWalletBalanceLoading;

Or if wallet settings loading should also block the initial render, though that data has a default fallback (walletSettings?.autoReloadEnabled ?? false).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isLoading = isLoadingDefaultPaymentMethod;
const isLoading = isLoadingDefaultPaymentMethod || isWalletBalanceLoading;
🤖 Prompt for AI Agents
In
apps/deploy-web/src/components/billing-usage/AccountOverview/AccountOverview.tsx
around line 24, the isLoading variable currently only uses
isLoadingDefaultPaymentMethod which can allow the component to render while
wallet balance or wallet settings are still loading; update isLoading to combine
the relevant loading flags (e.g., isLoadingDefaultPaymentMethod ||
isLoadingWalletBalance || isLoadingWalletSettings) so the component waits for
all required data before rendering, or if you intentionally want to allow wallet
settings to fallback, include only the flags you need (balance + default
payment) and keep the fallback for settings as is.


const defaultPaymentMethodId = useMemo(() => {
return defaultPaymentMethod?.id;
}, [defaultPaymentMethod]);

const toggleAutoReload = useCallback(
async (autoReloadEnabled: boolean) => {
try {
const settings = {
autoReloadEnabled
};

if (walletSettings) {
await updateWalletSettings.mutateAsync(settings);
} else {
await createWalletSettings.mutateAsync(settings);
}

enqueueSnackbar(<Snackbar title={`Auto Reload ${autoReloadEnabled ? "enabled" : "disabled"}`} iconVariant="success" />, {
variant: "success",
autoHideDuration: 3000
});
} catch (error: unknown) {
enqueueSnackbar(<Snackbar title="Failed to update Auto Reload settings" iconVariant="error" />, { variant: "error" });
}
},
[createWalletSettings, enqueueSnackbar, updateWalletSettings, walletSettings]
);

const isReloadChangeDisabled = useMemo(() => {
return !defaultPaymentMethod || updateWalletSettings.isPending || createWalletSettings.isPending;
}, [defaultPaymentMethod, updateWalletSettings.isPending, createWalletSettings.isPending]);

if (isLoading) {
return (
<Layout>
<div className="flex min-h-[50vh] items-center justify-center">
<div className="flex flex-col items-center gap-4">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<p className="text-muted-foreground">Loading payment information...</p>
</div>
</div>
</Layout>
);
}

return (
<div>
<Title subTitle>Account overview</Title>

<div className="pt-4">
<div className="flex w-full flex-col gap-4 lg:flex-row lg:gap-8">
<Card className="flex min-h-28 basis-1/2 flex-col">
<CardHeader className="flex flex-row items-center justify-between pb-0">
<CardTitle className="text-base">Credits Remaining</CardTitle>
</CardHeader>
{!walletBalance || isWalletBalanceLoading ? (
<div className="flex flex-1 items-center">
<LinearProgress color="primary" className="mx-auto w-11/12" />
</div>
) : (
<>
<CardContent className="pb-0">
<div className="mt-4 text-3xl font-bold">
<FormattedNumber value={walletBalance.totalDeploymentGrantsUSD} style="currency" currency="USD" />
</div>
</CardContent>
<CardFooter className="justify-end">
<Button
variant="default"
size="icon"
className="h-8 w-8 text-xs"
onClick={() => setShowPaymentPopup(true)}
disabled={isWalletBalanceLoading || !defaultPaymentMethod}
>
<Plus />
</Button>
</CardFooter>
</>
)}
</Card>
<Card className="flex min-h-28 basis-1/2 flex-col">
<CardHeader className="flex flex-row items-center justify-between pb-0">
<CardTitle className="text-base">Auto reload</CardTitle>
</CardHeader>
<CardContent className="pt-2">
<div>
<div className="pt-4">
<Switch checked={walletSettings?.autoReloadEnabled ?? false} onCheckedChange={toggleAutoReload} disabled={isReloadChangeDisabled} />
</div>
<div className="pt-2 text-sm">Add funds automatically</div>
</div>
</CardContent>
</Card>
</div>

<PaymentSuccessAnimation
show={showPaymentSuccess.show}
amount={showPaymentSuccess.amount}
onComplete={() => setShowPaymentSuccess({ amount: "", show: false })}
/>
</div>

{showPaymentPopup && (
<PaymentPopup
open={showPaymentPopup}
onClose={() => setShowPaymentPopup(false)}
selectedPaymentMethodId={defaultPaymentMethodId}
setShowPaymentSuccess={setShowPaymentSuccess}
/>
)}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import { BillingContainer } from "@src/components/billing-usage/BillingContainer
import { BillingUsageLayout, BillingUsageTabs } from "@src/components/billing-usage/BillingUsageLayout";
import { BillingView } from "@src/components/billing-usage/BillingView/BillingView";
import Layout from "@src/components/layout/Layout";
import { useFlag } from "@src/hooks/useFlag";
import { AccountOverview } from "./AccountOverview/AccountOverview";

export const BillingPage: FC = () => {
const isAutoCreditReloadEnabled = useFlag("auto_credit_reload");

return (
<Layout containerClassName="flex h-full flex-col justify-between">
<NextSeo title="Billing" />
<BillingUsageLayout page={BillingUsageTabs.BILLING}>
{isAutoCreditReloadEnabled && <AccountOverview />}
<BillingContainer>{props => <BillingView {...props} />}</BillingContainer>
</BillingUsageLayout>
</Layout>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { cn } from "@akashnetwork/ui/utils";
import { useRouter } from "next/navigation";

import { Title } from "@src/components/shared/Title";
import { useFlag } from "@src/hooks/useFlag";
import { UrlService } from "@src/utils/urlUtils";

export enum BillingUsageTabs {
BILLING = "BILLING",
PAYMENT_METHODS = "PAYMENT_METHODS",
USAGE = "USAGE"
}

Expand All @@ -22,12 +24,16 @@ type Props = {

export const BillingUsageLayout: React.FunctionComponent<Props> = ({ children, page }) => {
const router = useRouter();
const isAutoCreditReloadEnabled = useFlag("auto_credit_reload");

const changeTab = (newValue: string) => {
switch (newValue as BillingUsageTabs) {
case BillingUsageTabs.BILLING:
router.push(UrlService.billing());
break;
case BillingUsageTabs.PAYMENT_METHODS:
router.push(UrlService.paymentMethods());
break;
case BillingUsageTabs.USAGE:
default:
router.push(UrlService.usage());
Expand All @@ -39,10 +45,15 @@ export const BillingUsageLayout: React.FunctionComponent<Props> = ({ children, p
<Tabs value={page} onValueChange={changeTab} className="space-y-4 pb-6">
<Title>Billing & Usage</Title>

<TabsList className="grid w-full grid-cols-2">
<TabsList className={cn("grid w-full", isAutoCreditReloadEnabled ? "grid-cols-3" : "grid-cols-2")}>
<TabsTrigger value={BillingUsageTabs.BILLING} className={cn({ ["font-bold"]: page === BillingUsageTabs.BILLING })}>
Billing
</TabsTrigger>
{isAutoCreditReloadEnabled && (
<TabsTrigger value={BillingUsageTabs.PAYMENT_METHODS} className={cn({ ["font-bold"]: page === BillingUsageTabs.PAYMENT_METHODS })}>
Payment Methods
</TabsTrigger>
)}
<TabsTrigger value={BillingUsageTabs.USAGE} className={cn({ ["font-bold"]: page === BillingUsageTabs.USAGE })}>
Usage
</TabsTrigger>
Expand Down
Loading