feat(web): add plan decision helper + trial value hook
The brand-count plan model leaves users unsure whether they need 1/2/3
brands. Added a PlanHelperHint above the grid ("Hangi planı seçmeliyim? …
4+ marka için Full Paket marka başına daha avantajlı"). Also added an
outcome-focused hook line to the trial card ("Şase numarasını gir, doğru
parçayı saniyede bul …") above its feature list, selling the result rather
than only listing features.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -175,6 +175,7 @@
|
||||
"featureMatrix": "Feature Comparison",
|
||||
"matrixBrandCount": "Brand count",
|
||||
"brandCountAll": "All",
|
||||
"planHelper": "Which plan should I pick? Choose the number of brands you work with. For 4+ brands, the Full Package is cheaper per brand.",
|
||||
"selectBrands": "Select Brands",
|
||||
"selectBrandsDescription": "Choose the brands you want to include in your plan.",
|
||||
"brandsSelected": "brands selected",
|
||||
@@ -224,6 +225,7 @@
|
||||
"orderSummaryPrice": "Price",
|
||||
"trialTitle": "30-Day Full Package Trial",
|
||||
"trialDescription": "Free access to all brands for 30 days. No credit card required.",
|
||||
"trialHook": "Enter the VIN, find the right part in seconds. All catalogs unlocked for 30 days.",
|
||||
"startTrial": "Start Free Trial",
|
||||
"trialStarted": "Your 30-day Full Package trial has started!",
|
||||
"onboarding": {
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
"featureMatrix": "Özellik Karşılaştırması",
|
||||
"matrixBrandCount": "Marka sayısı",
|
||||
"brandCountAll": "Tümü",
|
||||
"planHelper": "Hangi planı seçmeliyim? İşine giren marka sayısını seç. 4+ marka için Full Paket marka başına daha avantajlı.",
|
||||
"selectBrands": "Marka Seçin",
|
||||
"selectBrandsDescription": "Planınıza dahil etmek istediğiniz markaları seçin.",
|
||||
"brandsSelected": "marka seçildi",
|
||||
@@ -224,6 +225,7 @@
|
||||
"orderSummaryPrice": "Tutar",
|
||||
"trialTitle": "30 Gün Full Paket Denemesi",
|
||||
"trialDescription": "Tüm markalara 30 gün boyunca ücretsiz erişim. Kredi kartı gerekmez.",
|
||||
"trialHook": "Şase numarasını gir, doğru parçayı saniyede bul. 30 gün boyunca tüm kataloglar açık.",
|
||||
"startTrial": "Ücretsiz Denemeyi Başlat",
|
||||
"trialStarted": "30 günlük Full Paket denemeniz başlatıldı!",
|
||||
"onboarding": {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Tests for the plan decision helper and the trial value hook.
|
||||
*
|
||||
* The brand-count plan model is ambiguous ("do I need 1, 2 or 3?"), so a
|
||||
* helper hint sits above the grid. The trial card gained an outcome-focused
|
||||
* hook line above its feature list. Both are exported pure components.
|
||||
*/
|
||||
|
||||
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(),
|
||||
}),
|
||||
}));
|
||||
|
||||
import { PlanHelperHint, TrialCtaCard } from "@/routes/dashboard/subscription/index";
|
||||
|
||||
describe("PlanHelperHint", () => {
|
||||
test("renders the plan-helper guidance text", () => {
|
||||
const { container } = render(<PlanHelperHint />);
|
||||
expect(container.textContent).toContain("subscription.planHelper");
|
||||
});
|
||||
});
|
||||
|
||||
describe("TrialCtaCard value hook", () => {
|
||||
test("renders the outcome hook line alongside title and CTA", () => {
|
||||
const { container } = render(<TrialCtaCard onStartTrial={vi.fn()} pending={false} />);
|
||||
const text = container.textContent ?? "";
|
||||
expect(text).toContain("subscription.trialHook");
|
||||
expect(text).toContain("subscription.trialTitle");
|
||||
expect(text).toContain("subscription.startTrial");
|
||||
});
|
||||
|
||||
test("clicking the CTA invokes onStartTrial", () => {
|
||||
const onStartTrial = vi.fn();
|
||||
const { getByText } = render(<TrialCtaCard onStartTrial={onStartTrial} pending={false} />);
|
||||
fireEvent.click(getByText("subscription.startTrial"));
|
||||
expect(onStartTrial).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("pending state disables the CTA and shows loading label", () => {
|
||||
const { getByText, container } = render(
|
||||
<TrialCtaCard onStartTrial={vi.fn()} pending={true} />,
|
||||
);
|
||||
expect(getByText("common.loading")).toBeTruthy();
|
||||
expect(container.querySelector("button")?.hasAttribute("disabled")).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
Clock,
|
||||
CreditCard,
|
||||
Crown,
|
||||
Info,
|
||||
Loader2,
|
||||
Minus,
|
||||
Pencil,
|
||||
@@ -624,6 +625,7 @@ export function SubscriptionPage() {
|
||||
}
|
||||
onEdit={() => setStep("plan")}
|
||||
>
|
||||
<PlanHelperHint />
|
||||
<BillingPeriodToggle period={billingPeriod} onChange={setBillingPeriod} />
|
||||
<PlanGrid
|
||||
billingPeriod={billingPeriod}
|
||||
@@ -939,6 +941,16 @@ function StepCard({
|
||||
);
|
||||
}
|
||||
|
||||
export function PlanHelperHint() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex items-start gap-2 rounded-lg border border-border bg-muted/30 px-3 py-2 text-xs leading-5 text-muted-foreground">
|
||||
<Info className="mt-0.5 h-3.5 w-3.5 shrink-0 text-primary" />
|
||||
<span>{t("subscription.planHelper")}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BillingPeriodToggle({
|
||||
period,
|
||||
onChange,
|
||||
@@ -1411,7 +1423,10 @@ function ActiveSubscriptionCard({
|
||||
);
|
||||
}
|
||||
|
||||
function TrialCtaCard({ onStartTrial, pending }: { onStartTrial: () => void; pending: boolean }) {
|
||||
export function TrialCtaCard({
|
||||
onStartTrial,
|
||||
pending,
|
||||
}: { onStartTrial: () => void; pending: boolean }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Card className="relative overflow-hidden border-brand/25 bg-brand/5">
|
||||
@@ -1428,6 +1443,9 @@ function TrialCtaCard({ onStartTrial, pending }: { onStartTrial: () => void; pen
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="relative space-y-4">
|
||||
<p className="text-sm font-medium leading-6 text-foreground">
|
||||
{t("subscription.trialHook")}
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm leading-relaxed">
|
||||
{["allBrands", "vinSearch", "partsCatalog", "schemaViewer"].map((f) => (
|
||||
<li key={f} className="flex items-center gap-2 text-foreground/85">
|
||||
|
||||
Reference in New Issue
Block a user