mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-02 21:03:34 +03:00
fix(checkout): seed capacity to what the installation already needs
Screenshotting the stories showed an installation with 240 users arriving on the capacity stage at one server: below its own minimum, alert showing, button disabled. The first thing a paying customer saw was a blocked screen they had to click their way out of. Entering the stage now seeds the quantity from the current user count, so that installation lands on three servers with the total already correct. The alert and the disabled button stay as a guard for a quantity below the minimum arriving from anywhere else. Adds serversForUsers as the inverse of usersForServers so the seed and the stage's own minimum cannot disagree, with tests including the round trip.
This commit is contained in:
@@ -19,6 +19,7 @@ import { useCheckoutSession } from "@app/components/shared/stripeCheckout/hooks/
|
||||
import { EmailStage } from "@app/components/shared/stripeCheckout/stages/EmailStage";
|
||||
import { PlanSelectionStage } from "@app/components/shared/stripeCheckout/stages/PlanSelectionStage";
|
||||
import { CapacityStage } from "@app/components/shared/stripeCheckout/stages/CapacityStage";
|
||||
import { serversForUsers } from "@app/components/shared/stripeCheckout/utils/capacity";
|
||||
import { PaymentStage } from "@app/components/shared/stripeCheckout/stages/PaymentStage";
|
||||
import { SuccessStage } from "@app/components/shared/stripeCheckout/stages/SuccessStage";
|
||||
import { ErrorStage } from "@app/components/shared/stripeCheckout/stages/ErrorStage";
|
||||
@@ -115,7 +116,13 @@ const StripeCheckout: React.FC<StripeCheckoutProps> = ({
|
||||
// Plan selection handler
|
||||
const handlePlanSelect = (period: "monthly" | "yearly") => {
|
||||
checkoutState.setSelectedPeriod(period);
|
||||
navigation.goToStage(sellsCapacity ? "capacity" : "payment");
|
||||
if (sellsCapacity) {
|
||||
// Arrive on the capacity an installation already needs rather than on a blocked "1".
|
||||
checkoutState.setServerQuantity(serversForUsers(minimumSeats));
|
||||
navigation.goToStage("capacity");
|
||||
return;
|
||||
}
|
||||
navigation.goToStage("payment");
|
||||
};
|
||||
|
||||
// Close handler
|
||||
|
||||
+4
-2
@@ -8,6 +8,7 @@ import {
|
||||
USERS_PER_SERVER,
|
||||
SELF_SERVE_MAX_SERVERS,
|
||||
usersForServers,
|
||||
serversForUsers,
|
||||
shouldOfferEnterprise,
|
||||
} from "@app/components/shared/stripeCheckout/utils/capacity";
|
||||
|
||||
@@ -45,8 +46,9 @@ export const CapacityStage: React.FC<CapacityStageProps> = ({
|
||||
const total = unitPrice * serverQuantity;
|
||||
|
||||
// Never sell less capacity than is already in use; reducing capacity happens at renewal rather
|
||||
// than by stranding accounts that already exist.
|
||||
const minServers = Math.max(1, Math.ceil(currentUsers / USERS_PER_SERVER));
|
||||
// than by stranding accounts that already exist. The stage is entered pre-seeded to this minimum,
|
||||
// so the guard below only fires if a caller passes something lower.
|
||||
const minServers = serversForUsers(currentUsers);
|
||||
const belowCurrentUsage = serverQuantity < minServers;
|
||||
const offerEnterprise = shouldOfferEnterprise(serverQuantity);
|
||||
|
||||
|
||||
+20
@@ -4,6 +4,7 @@ import {
|
||||
SELF_SERVE_MAX_SERVERS,
|
||||
ENTERPRISE_ADVISORY_USERS,
|
||||
usersForServers,
|
||||
serversForUsers,
|
||||
shouldOfferEnterprise,
|
||||
} from "@app/components/shared/stripeCheckout/utils/capacity";
|
||||
|
||||
@@ -20,6 +21,25 @@ describe("usersForServers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("serversForUsers", () => {
|
||||
it("rounds part-full servers up", () => {
|
||||
expect(serversForUsers(1)).toBe(1);
|
||||
expect(serversForUsers(USERS_PER_SERVER)).toBe(1);
|
||||
expect(serversForUsers(USERS_PER_SERVER + 1)).toBe(2);
|
||||
expect(serversForUsers(240)).toBe(3);
|
||||
});
|
||||
|
||||
it("never returns zero servers", () => {
|
||||
// Checkout seeds the stepper from this, and a zero would render a blocked stage.
|
||||
expect(serversForUsers(0)).toBe(1);
|
||||
expect(serversForUsers(-5)).toBe(1);
|
||||
});
|
||||
|
||||
it("round-trips with usersForServers", () => {
|
||||
expect(serversForUsers(usersForServers(4))).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldOfferEnterprise", () => {
|
||||
it("stays quiet for a small purchase", () => {
|
||||
expect(shouldOfferEnterprise(1)).toBe(false);
|
||||
|
||||
@@ -18,6 +18,11 @@ export const SELF_SERVE_MAX_SERVERS = 5;
|
||||
/** Resulting capacity at which an enterprise quote is also worth offering. */
|
||||
export const ENTERPRISE_ADVISORY_USERS = 1000;
|
||||
|
||||
/** Servers needed to cover a given number of users. Always at least one. */
|
||||
export function serversForUsers(users: number): number {
|
||||
return Math.max(1, Math.ceil(Math.max(0, users) / USERS_PER_SERVER));
|
||||
}
|
||||
|
||||
/** Users covered by a given number of servers. */
|
||||
export function usersForServers(servers: number): number {
|
||||
return Math.max(1, servers) * USERS_PER_SERVER;
|
||||
|
||||
Reference in New Issue
Block a user