Replace info box with toggle for granted balance in balance edit sheet

Swap the static "both will be updated" info box with a ConfigRow + Switch
toggle so users can control whether adding to balance also updates the
granted balance. Remove the overly restrictive included_grant validation
on the server and clean up dead params in computeGrantedBalanceInput.

Made-with: Cursor
This commit is contained in:
Ayush Rodrigues
2026-04-17 14:48:44 +01:00
parent 1073b0dd43
commit 1d9a1b96dd
6 changed files with 34 additions and 27 deletions

View File

@@ -1,12 +1,8 @@
import {
ErrCode,
findFeatureById,
notNullish,
nullish,
RecaseError,
UpdateBalanceParamsV0Schema,
} from "@autumn/shared";
import { StatusCodes } from "http-status-codes";
import { createRoute } from "@/honoMiddlewares/routeHandler";
import { runUpdateBalanceV2 } from "@/internal/balances/updateBalance/runUpdateBalanceV2";
import { runUpdateUsage } from "@/internal/balances/updateBalance/runUpdateUsage";
@@ -32,15 +28,6 @@ export const handleUpdateBalance = createRoute({
const targetBalance = params.remaining ?? params.current_balance;
if (notNullish(params.included_grant) && nullish(targetBalance)) {
throw new RecaseError({
message:
"'remaining' is required when updating granted balance",
code: ErrCode.InvalidRequest,
statusCode: StatusCodes.BAD_REQUEST,
});
}
let fullCustomer = await getOrSetCachedFullCustomer({
ctx,
customerId: params.customer_id,
@@ -59,7 +46,6 @@ export const handleUpdateBalance = createRoute({
}
if (notNullish(params.included_grant)) {
ctx.logger.info(
`updating granted balance for feature ${params.feature_id} to ${params.included_grant}`,
);

View File

@@ -96,8 +96,6 @@ test.concurrent(`${chalk.yellowBright("update-prepaid-granted1: granted_balance
const grantedBalanceInput = computeGrantedBalanceInput({
newGPB,
defaultGPB,
defaultBalance,
prepaidAllowance,
});

View File

@@ -11,8 +11,6 @@ export function computeGrantedBalanceInput({
prepaidAllowance,
}: {
newGPB: number;
defaultGPB: number;
defaultBalance: number;
prepaidAllowance: number;
}): number {
return newGPB - prepaidAllowance;

View File

@@ -13,7 +13,9 @@ import { ClockCountdownIcon } from "@phosphor-icons/react";
import { useStore } from "@tanstack/react-form";
import { useState } from "react";
import { toast } from "sonner";
import { ConfigRow } from "@/components/forms/shared/ConfigRow";
import { DateInputUnix } from "@/components/general/DateInputUnix";
import { Switch } from "@/components/ui/switch";
import { Button } from "@/components/v2/buttons/Button";
import { CopyButton } from "@/components/v2/buttons/CopyButton";
import { GroupedTabButton } from "@/components/v2/buttons/GroupedTabButton";
@@ -26,7 +28,6 @@ import { useAxiosInstance } from "@/services/useAxiosInstance";
import { formatUnixToDateTime } from "@/utils/formatUtils/formatDateUtils";
import { getBackendErr, notNullish } from "@/utils/genUtils";
import { useCusQuery } from "@/views/customers/customer/hooks/useCusQuery";
import { InfoBox } from "@/views/onboarding2/integrate/components/InfoBox";
import { useCustomerContext } from "../../customer/CustomerContext";
import { BalanceEditPreviews } from "./BalanceEditPreviews";
import { GrantedBalancePopover } from "./GrantedBalancePopover";
@@ -463,6 +464,11 @@ function SetBalanceFields({
/* ─── Add Balance Mode ─── */
function AddBalanceFields({ form }: { form: BalanceEditFormInstance }) {
const updateGrantedBalance = useStore(
form.store,
(s) => s.values.updateGrantedBalance,
);
return (
<div className="flex flex-col gap-3">
<form.Field name="addValue">
@@ -482,9 +488,18 @@ function AddBalanceFields({ form }: { form: BalanceEditFormInstance }) {
/>
)}
</form.Field>
<InfoBox variant="note">
Current and total granted balance will both be updated.
</InfoBox>
<ConfigRow
title="Also Update Granted Balance"
description="Increase the total granted balance by the same amount"
action={
<Switch
checked={updateGrantedBalance}
onCheckedChange={(checked) =>
form.setFieldValue("updateGrantedBalance", !!checked)
}
/>
}
/>
</div>
);
}
@@ -551,9 +566,6 @@ function SubmitButton({
if (values.mode === "set") {
const grantedBalanceInput = computeGrantedBalanceInput({
newGPB: values.grantedAndPurchasedBalance ?? 0,
defaultGPB:
form.options.defaultValues?.grantedAndPurchasedBalance ?? 0,
defaultBalance: form.options.defaultValues?.balance ?? 0,
prepaidAllowance: form.prepaidAllowance,
});
@@ -566,18 +578,29 @@ function SubmitButton({
feature_id: featureId,
current_balance: targetBalance,
included_grant: grantedBalanceInput ?? undefined,
granted_balance: grantedBalanceInput ?? undefined,
customer_entitlement_id: selectedCusEnt.id,
entity_id: entityId ?? undefined,
next_reset_at: values.nextResetAt ?? undefined,
}),
);
} else {
const addAmount = parseFloat(String(values.addValue));
const defaultGPB =
form.options.defaultValues?.grantedAndPurchasedBalance ?? 0;
const newGPB = defaultGPB + addAmount;
const grantedBalanceInput = values.updateGrantedBalance
? computeGrantedBalanceInput({
newGPB,
prepaidAllowance: form.prepaidAllowance,
})
: undefined;
promises.push(
axiosInstance.post("/v1/balances/update", {
customer_id: customer.id || customer.internal_id,
feature_id: featureId,
add_to_balance: parseFloat(String(values.addValue)),
add_to_balance: addAmount,
included_grant: grantedBalanceInput,
customer_entitlement_id: selectedCusEnt.id,
entity_id: entityId ?? undefined,
}),

View File

@@ -7,6 +7,7 @@ export const BalanceEditFormSchema = z
grantedAndPurchasedBalance: z.number().nullable(),
nextResetAt: z.number().nullable(),
addValue: z.number().nullable(),
updateGrantedBalance: z.boolean(),
})
.check((ctx) => {
const { mode, balance, addValue } = ctx.value;

View File

@@ -50,6 +50,7 @@ export function useBalanceEditForm({
grantedAndPurchasedBalance: grantedAndPurchasedBalance ?? null,
nextResetAt: selectedCusEnt.next_reset_at ?? null,
addValue: null,
updateGrantedBalance: true,
} as BalanceEditForm,
validators: {
onChange: BalanceEditFormSchema,