Skip to content

Commit ca626ec

Browse files
committed
Improvements to the modal
1 parent b7de654 commit ca626ec

File tree

2 files changed

+81
-25
lines changed

2 files changed

+81
-25
lines changed

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.concurrency/route.tsx

Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { concurrencyPath, EnvironmentParamSchema, v3BillingPath } from "~/utils/
5353
import { SetConcurrencyAddOnService } from "~/v3/services/setConcurrencyAddOn.server";
5454
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
5555
import { SpinnerWhite } from "~/components/primitives/Spinner";
56+
import { cn } from "~/utils/cn";
5657

5758
export const meta: MetaFunction = () => {
5859
return [
@@ -235,6 +236,7 @@ function Upgradable({
235236
<PurchaseConcurrencyModal
236237
concurrencyPricing={concurrencyPricing}
237238
extraConcurrency={extraConcurrency}
239+
extraUnallocatedConcurrency={extraUnallocatedConcurrency}
238240
maxQuota={maxQuota}
239241
/>
240242
</div>
@@ -365,13 +367,15 @@ function NotUpgradable({ environments }: { environments: EnvironmentWithConcurre
365367
function PurchaseConcurrencyModal({
366368
concurrencyPricing,
367369
extraConcurrency,
370+
extraUnallocatedConcurrency,
368371
maxQuota,
369372
}: {
370373
concurrencyPricing: {
371374
stepSize: number;
372375
centsPerStep: number;
373376
};
374377
extraConcurrency: number;
378+
extraUnallocatedConcurrency: number;
375379
maxQuota: number;
376380
}) {
377381
const lastSubmission = useActionData();
@@ -385,33 +389,40 @@ function PurchaseConcurrencyModal({
385389
shouldRevalidate: "onSubmit",
386390
});
387391

388-
const [amountValue, setAmountValue] = useState(0);
392+
const [amountValue, setAmountValue] = useState(extraConcurrency);
389393
const navigation = useNavigation();
390394
const isLoading = navigation.state !== "idle" && navigation.formMethod === "POST";
391395

392-
const maximum = maxQuota - extraConcurrency;
393-
const isAboveMaxQuota = amountValue > maximum;
396+
const state = updateState({
397+
value: amountValue,
398+
existingValue: extraConcurrency,
399+
quota: maxQuota,
400+
extraUnallocatedConcurrency,
401+
});
402+
const changeClassName =
403+
state === "decrease" ? "text-error" : state === "increase" ? "text-success" : undefined;
404+
405+
const title = extraConcurrency === 0 ? "Purchase extra concurrency" : "Add/remove concurrency";
394406

395407
return (
396408
<Dialog>
397409
<DialogTrigger asChild>
398-
<Button variant="primary/small" LeadingIcon={PlusIcon}>
399-
Purchase extra concurrency
400-
</Button>
410+
<Button variant="primary/small">{title}</Button>
401411
</DialogTrigger>
402412
<DialogContent>
403-
<DialogHeader>Purchase extra concurrency</DialogHeader>
413+
<DialogHeader>{title}</DialogHeader>
404414
<Form method="post" {...form.props}>
405415
<div className="flex flex-col gap-4 pt-2">
406416
<Paragraph variant="base/bright" spacing>
407417
You can purchase bundles of {concurrencyPricing.stepSize} concurrency for{" "}
408-
{formatCurrency(concurrencyPricing.centsPerStep / 100, false)}/month. You’ll be billed
409-
monthly, with changes available after a full billing cycle.
418+
{formatCurrency(concurrencyPricing.centsPerStep / 100, false)}/month. Or you can
419+
remove any extra concurrency after you have unallocated it from your environments
420+
first.
410421
</Paragraph>
411422
<Fieldset>
412423
<InputGroup fullWidth>
413424
<Label htmlFor="amount" className="text-text-dimmed">
414-
Extra concurrency to purchase
425+
Total extra concurrency
415426
</Label>
416427
<InputNumberStepper
417428
{...conform.input(amount, { type: "number" })}
@@ -425,12 +436,20 @@ function PurchaseConcurrencyModal({
425436
<FormError>{form.error}</FormError>
426437
</InputGroup>
427438
</Fieldset>
428-
{isAboveMaxQuota ? (
439+
{state === "need_to_increase_unallocated" ? (
440+
<div className="flex flex-col pb-3">
441+
<Paragraph variant="small" className="text-warning" spacing>
442+
You need to unallocate{" "}
443+
{formatNumber(extraConcurrency - amountValue - extraUnallocatedConcurrency)} more
444+
concurrency from your environments in order to remove{" "}
445+
{formatNumber(extraConcurrency - amountValue)} concurrency from your account.
446+
</Paragraph>
447+
</div>
448+
) : state === "above_quota" ? (
429449
<div className="flex flex-col pb-3">
430450
<Paragraph variant="small" className="text-warning" spacing>
431-
Currently you can only have up to {maxQuota} extra concurrency. This request for{" "}
432-
{formatNumber(amountValue)} takes you to{" "}
433-
{formatNumber(extraConcurrency + amountValue)} extra concurrency.
451+
Currently you can only have up to {maxQuota} extra concurrency. This is a request
452+
for {formatNumber(amountValue)}.
434453
</Paragraph>
435454
<Paragraph variant="small" className="text-warning">
436455
Send a request below to lift your current limit. We'll get back to you soon.
@@ -439,14 +458,16 @@ function PurchaseConcurrencyModal({
439458
) : (
440459
<div className="flex flex-col pb-3">
441460
<div className="grid grid-cols-2 border-b border-grid-dimmed pb-1">
442-
<Header3 className="font-normal text-text-dimmed">Summary</Header3>
443-
<Header3 className="justify-self-end font-normal text-text-dimmed">Total</Header3>
461+
<Header3 className="font-normal text-text-dimmed">Purchase</Header3>
462+
<Header3 className="justify-self-end font-normal text-text-dimmed">Cost</Header3>
444463
</div>
445464
<div className="grid grid-cols-2 pt-2">
446-
<Header3 className="pb-0 font-normal">{amountValue}</Header3>
447-
<Header3 className="justify-self-end font-normal">
465+
<Header3 className={cn("pb-0 font-normal", changeClassName)}>
466+
{formatNumber(amountValue - extraConcurrency)}
467+
</Header3>
468+
<Header3 className={cn("justify-self-end font-normal", changeClassName)}>
448469
{formatCurrency(
449-
(amountValue * concurrencyPricing.centsPerStep) /
470+
((amountValue - extraConcurrency) * concurrencyPricing.centsPerStep) /
450471
concurrencyPricing.stepSize /
451472
100,
452473
false
@@ -455,7 +476,7 @@ function PurchaseConcurrencyModal({
455476
</div>
456477
<div className="grid grid-cols-2 text-xs">
457478
<span className="text-text-dimmed">
458-
({amountValue / concurrencyPricing.stepSize} bundles @{" "}
479+
({(amountValue - extraConcurrency) / concurrencyPricing.stepSize} bundles @{" "}
459480
{formatCurrency(concurrencyPricing.centsPerStep / 100, false)}/mth)
460481
</span>
461482
<span className="justify-self-end text-text-dimmed">/mth</span>
@@ -465,7 +486,7 @@ function PurchaseConcurrencyModal({
465486
</div>
466487
<FormButtons
467488
confirmButton={
468-
isAboveMaxQuota ? (
489+
state === "above_quota" ? (
469490
<>
470491
<input type="hidden" name="action" value="quota-increase" />
471492
<Button
@@ -474,7 +495,19 @@ function PurchaseConcurrencyModal({
474495
type="submit"
475496
disabled={isLoading}
476497
>
477-
{`Send request for ${formatNumber(extraConcurrency + amountValue)}`}
498+
{`Send request for ${formatNumber(amountValue)}`}
499+
</Button>
500+
</>
501+
) : state === "decrease" || state === "need_to_increase_unallocated" ? (
502+
<>
503+
<input type="hidden" name="action" value="purchase" />
504+
<Button
505+
variant="danger/medium"
506+
type="submit"
507+
disabled={isLoading || state === "need_to_increase_unallocated"}
508+
LeadingIcon={isLoading ? SpinnerWhite : undefined}
509+
>
510+
{`Remove ${formatNumber(extraConcurrency - amountValue)} concurrency`}
478511
</Button>
479512
</>
480513
) : (
@@ -483,10 +516,10 @@ function PurchaseConcurrencyModal({
483516
<Button
484517
variant="primary/medium"
485518
type="submit"
486-
disabled={isLoading}
519+
disabled={isLoading || state === "no_change"}
487520
LeadingIcon={isLoading ? SpinnerWhite : undefined}
488521
>
489-
Purchase
522+
{`Purchase ${formatNumber(amountValue - extraConcurrency)}`}
490523
</Button>
491524
</>
492525
)
@@ -504,3 +537,26 @@ function PurchaseConcurrencyModal({
504537
</Dialog>
505538
);
506539
}
540+
541+
function updateState({
542+
value,
543+
existingValue,
544+
quota,
545+
extraUnallocatedConcurrency,
546+
}: {
547+
value: number;
548+
existingValue: number;
549+
quota: number;
550+
extraUnallocatedConcurrency: number;
551+
}): "no_change" | "increase" | "decrease" | "above_quota" | "need_to_increase_unallocated" {
552+
if (value === existingValue) return "no_change";
553+
if (value < existingValue) {
554+
const difference = existingValue - value;
555+
if (difference > extraUnallocatedConcurrency) {
556+
return "need_to_increase_unallocated";
557+
}
558+
return "decrease";
559+
}
560+
if (value > quota) return "above_quota";
561+
return "increase";
562+
}

apps/webapp/app/v3/services/setConcurrencyAddOn.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class SetConcurrencyAddOnService extends BaseService {
4343
}
4444

4545
const currentConcurrency = result.extraConcurrency;
46-
const totalExtraConcurrency = currentConcurrency + amount;
46+
const totalExtraConcurrency = amount;
4747

4848
switch (action) {
4949
case "purchase": {

0 commit comments

Comments
 (0)