feat(web): reinforce value on subscription plan selection
Three CRO additions at the point of decision:
- Outcome-focused page subtitle ("Doğru parçayı saniyede bul …") replacing
the purely procedural copy.
- Per-day price anchor on every plan card (≈ ₺X/gün), reframing the cost as
a small daily number. Computed from the displayed monthly/yearly price.
- "2 ay bedava" note under the billing toggle when yearly is selected,
making the otherwise-abstract -17% concrete (yearly = 10× monthly).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Tests for the value-reinforcement additions:
|
||||
* - per-day price anchor on every plan card
|
||||
* - "2 months free" note shown only for yearly billing
|
||||
*
|
||||
* These reframe the price at the moment of decision (small daily number) and
|
||||
* make the yearly discount concrete, per the CRO audit.
|
||||
*/
|
||||
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
vi.mock("@/lib/i18n", () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
locale: "tr",
|
||||
setLocale: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/posthog", () => ({
|
||||
capture: vi.fn(),
|
||||
setPeopleProperties: vi.fn(),
|
||||
}));
|
||||
|
||||
import {
|
||||
BillingPeriodToggle,
|
||||
PlanGrid,
|
||||
plans,
|
||||
} from "@/routes/dashboard/subscription/index";
|
||||
|
||||
describe("per-day price anchor (PlanGrid)", () => {
|
||||
test("renders a per-day line on every plan card", () => {
|
||||
const { container } = render(
|
||||
<PlanGrid
|
||||
billingPeriod="monthly"
|
||||
selectedPlanKey={null}
|
||||
currentPlanKey={null}
|
||||
activeSubscription={false}
|
||||
onSelect={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
const perDayNodes = Array.from(container.querySelectorAll("p")).filter((p) =>
|
||||
p.textContent?.includes("subscription.perDay"),
|
||||
);
|
||||
expect(perDayNodes).toHaveLength(plans.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe("yearly free-months note (BillingPeriodToggle)", () => {
|
||||
test("shows the free-months note when yearly is selected", () => {
|
||||
const { container } = render(<BillingPeriodToggle period="yearly" onChange={vi.fn()} />);
|
||||
expect(container.textContent).toContain("subscription.yearlyFreeMonths");
|
||||
});
|
||||
|
||||
test("hides the free-months note for monthly billing", () => {
|
||||
const { container } = render(<BillingPeriodToggle period="monthly" onChange={vi.fn()} />);
|
||||
expect(container.textContent).not.toContain("subscription.yearlyFreeMonths");
|
||||
});
|
||||
|
||||
test("toggling fires onChange with the chosen period", () => {
|
||||
const onChange = vi.fn();
|
||||
const { getByText } = render(<BillingPeriodToggle period="monthly" onChange={onChange} />);
|
||||
fireEvent.click(getByText("common.yearly"));
|
||||
expect(onChange).toHaveBeenCalledWith("yearly");
|
||||
});
|
||||
});
|
||||
@@ -939,7 +939,7 @@ function StepCard({
|
||||
);
|
||||
}
|
||||
|
||||
function BillingPeriodToggle({
|
||||
export function BillingPeriodToggle({
|
||||
period,
|
||||
onChange,
|
||||
}: {
|
||||
@@ -948,7 +948,7 @@ function BillingPeriodToggle({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="inline-flex rounded-full border border-border bg-muted/50 p-1">
|
||||
{(["monthly", "yearly"] as const).map((p) => (
|
||||
<button
|
||||
@@ -974,6 +974,11 @@ function BillingPeriodToggle({
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{period === "yearly" && (
|
||||
<p className="text-xs font-medium text-primary">
|
||||
{t("subscription.yearlyFreeMonths")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1043,6 +1048,13 @@ export function PlanGrid({
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{billingPeriod === "monthly" ? t("common.perMonth") : t("common.perYear")}
|
||||
</span>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
{t("subscription.perDay", {
|
||||
amount: formatTRY(
|
||||
Math.round(billingPeriod === "monthly" ? price / 30 : price / 365),
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="space-y-1.5 text-xs text-foreground/85">
|
||||
{plan.features.map((f) => (
|
||||
|
||||
Reference in New Issue
Block a user