Skip to content

Commit ecf4d96

Browse files
jankeromnesroboquat
authored andcommitted
[dashboard] Add proper form submission and error validation to usage limit modal
1 parent 345971a commit ecf4d96

File tree

1 file changed

+58
-39
lines changed

1 file changed

+58
-39
lines changed

components/dashboard/src/components/UsageBasedBillingConfig.tsx

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import DropDown from "../components/DropDown";
2020
import Modal from "../components/Modal";
2121
import Alert from "./Alert";
2222

23+
const BASE_USAGE_LIMIT_FOR_STRIPE_USERS = 1000;
24+
2325
type PendingStripeSubscription = { pendingSince: number };
2426

2527
interface Props {
@@ -92,13 +94,13 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
9294
setPendingStripeSubscription(pendingSubscription);
9395
window.localStorage.setItem(localStorageKey, JSON.stringify(pendingSubscription));
9496
try {
95-
// Pick a good initial value for the Stripe usage limit (1000 * team_size)
97+
// Pick a good initial value for the Stripe usage limit (base_limit * team_size)
9698
// FIXME: Should we ask the customer to confirm or edit this default limit?
97-
let limit = 1000;
99+
let limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS;
98100
const attrId = AttributionId.parse(attributionId);
99101
if (attrId?.kind === "team") {
100102
const members = await getGitpodService().server.getTeamMembers(attrId.teamId);
101-
limit = 1000 * members.length;
103+
limit = BASE_USAGE_LIMIT_FOR_STRIPE_USERS * members.length;
102104
}
103105
await getGitpodService().server.subscribeToStripe(attributionId, setupIntentId, limit);
104106
const newLimit = await getGitpodService().server.getUsageLimit(attributionId);
@@ -409,7 +411,11 @@ export default function UsageBasedBillingConfig({ attributionId }: Props) {
409411
)}
410412
{showUpdateLimitModal && (
411413
<UpdateLimitModal
412-
minValue={AttributionId.parse(attributionId || "")?.kind === "user" ? 1000 : 0}
414+
minValue={
415+
AttributionId.parse(attributionId || "")?.kind === "user"
416+
? BASE_USAGE_LIMIT_FOR_STRIPE_USERS
417+
: 0
418+
}
413419
currentValue={usageLimit}
414420
onClose={() => setShowUpdateLimitModal(false)}
415421
onUpdate={(newLimit) => updateUsageLimit(newLimit)}
@@ -545,46 +551,59 @@ function UpdateLimitModal(props: {
545551
onClose: () => void;
546552
onUpdate: (newLimit: number) => {};
547553
}) {
554+
const [error, setError] = useState<string>("");
548555
const [newLimit, setNewLimit] = useState<string | undefined>(
549556
typeof props.currentValue === "number" ? String(props.currentValue) : undefined,
550557
);
551558

552-
return (
553-
<Modal visible={true} onClose={props.onClose}>
554-
<h3 className="flex">Usage Limit</h3>
555-
<div className="border-t border-b border-gray-200 dark:border-gray-700 -mx-6 px-6 py-4 flex flex-col">
556-
<p className="pb-4 text-gray-500 text-base">Set usage limit in total credits per month.</p>
559+
function onSubmit(event: React.FormEvent) {
560+
event.preventDefault();
561+
if (!newLimit) {
562+
setError("Please specify a limit");
563+
return;
564+
}
565+
const n = parseInt(newLimit, 10);
566+
if (typeof n !== "number") {
567+
setError("Please specify a limit that is a valid number");
568+
return;
569+
}
570+
if (typeof props.minValue === "number" && n < props.minValue) {
571+
setError(`Please specify a limit that is >= ${props.minValue}`);
572+
return;
573+
}
574+
props.onUpdate(n);
575+
}
557576

558-
<label className="font-medium">
559-
Credits
560-
<div className="w-full">
561-
<input
562-
type="number"
563-
min={props.minValue || 0}
564-
value={newLimit}
565-
className="rounded-md w-full truncate overflow-x-scroll pr-8"
566-
onChange={(e) => setNewLimit(e.target.value)}
567-
/>
568-
</div>
569-
</label>
570-
</div>
571-
<div className="flex justify-end mt-6 space-x-2">
572-
<button
573-
className="secondary"
574-
onClick={() => {
575-
if (!newLimit) {
576-
return;
577-
}
578-
const n = parseInt(newLimit, 10);
579-
if (typeof n !== "number") {
580-
return;
581-
}
582-
props.onUpdate(n);
583-
}}
584-
>
585-
Update
586-
</button>
587-
</div>
577+
return (
578+
<Modal visible={true} onClose={props.onClose} onEnter={() => false}>
579+
<h3 className="mb-4">Usage Limit</h3>
580+
<form onSubmit={onSubmit}>
581+
<div className="border-t border-b border-gray-200 dark:border-gray-700 -mx-6 px-6 py-4 flex flex-col">
582+
<p className="pb-4 text-gray-500 text-base">Set usage limit in total credits per month.</p>
583+
{error && (
584+
<Alert type="error" className="-mt-2 mb-2">
585+
{error}
586+
</Alert>
587+
)}
588+
<label className="font-medium">
589+
Credits
590+
<div className="w-full">
591+
<input
592+
type="text"
593+
value={newLimit}
594+
className={`rounded-md w-full truncate overflow-x-scroll pr-8 ${error ? "error" : ""}`}
595+
onChange={(e) => {
596+
setError("");
597+
setNewLimit(e.target.value);
598+
}}
599+
/>
600+
</div>
601+
</label>
602+
</div>
603+
<div className="flex justify-end mt-6 space-x-2">
604+
<button className="secondary">Update</button>
605+
</div>
606+
</form>
588607
</Modal>
589608
);
590609
}

0 commit comments

Comments
 (0)