mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-18 23:28:32 -05:00
chore: A/B test different upgrade banner in trial (#7953)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Johannes <johannes@formbricks.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -41,6 +41,7 @@ import { getAccessFlags } from "@/lib/membership/utils";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { useSignOut } from "@/modules/auth/hooks/use-sign-out";
|
||||
import { TrialAlert } from "@/modules/ee/billing/components/trial-alert";
|
||||
import { TRIAL_BASE_RESPONSE_LIMIT, TrialBannerNew } from "@/modules/ee/billing/components/trial-banner-new";
|
||||
import { CreateOrganizationModal } from "@/modules/organization/components/CreateOrganizationModal";
|
||||
import { ProfileAvatar } from "@/modules/ui/components/avatars";
|
||||
import { Badge } from "@/modules/ui/components/badge";
|
||||
@@ -73,6 +74,8 @@ interface NavigationProps {
|
||||
organizationWorkspacesLimit: number;
|
||||
isLicenseActive: boolean;
|
||||
isAccessControlAllowed: boolean;
|
||||
responseCount: number;
|
||||
newTrialBannerVariant: string | boolean;
|
||||
}
|
||||
|
||||
export const MainNavigation = ({
|
||||
@@ -87,6 +90,8 @@ export const MainNavigation = ({
|
||||
organizationWorkspacesLimit,
|
||||
isLicenseActive,
|
||||
isAccessControlAllowed,
|
||||
responseCount,
|
||||
newTrialBannerVariant,
|
||||
}: NavigationProps) => {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
@@ -582,13 +587,26 @@ export const MainNavigation = ({
|
||||
)}
|
||||
|
||||
{/* Trial Days Remaining */}
|
||||
{!isCollapsed && isFormbricksCloud && trialDaysRemaining !== null && (
|
||||
<Link
|
||||
href={`/workspaces/${workspace.id}/settings/organization/billing`}
|
||||
className="m-2 block">
|
||||
<TrialAlert trialDaysRemaining={trialDaysRemaining} size="small" />
|
||||
</Link>
|
||||
)}
|
||||
{!isCollapsed &&
|
||||
isFormbricksCloud &&
|
||||
trialDaysRemaining !== null &&
|
||||
(newTrialBannerVariant === "new-trial-banner" ? (
|
||||
<TrialBannerNew
|
||||
trialDaysRemaining={trialDaysRemaining}
|
||||
planName={organization.billing.stripe?.plan ?? "pro"}
|
||||
responseCount={responseCount}
|
||||
responseLimit={organization.billing.limits.monthly.responses}
|
||||
baseResponseLimit={TRIAL_BASE_RESPONSE_LIMIT}
|
||||
billingHref={`/workspaces/${workspace.id}/settings/organization/billing`}
|
||||
hasPaymentMethod={organization.billing.stripe?.hasPaymentMethod}
|
||||
/>
|
||||
) : (
|
||||
<Link
|
||||
href={`/workspaces/${workspace.id}/settings/organization/billing`}
|
||||
className="m-2 block">
|
||||
<TrialAlert trialDaysRemaining={trialDaysRemaining} size="small" />
|
||||
</Link>
|
||||
))}
|
||||
|
||||
<div className="flex flex-col">
|
||||
<DropdownMenu onOpenChange={setIsWorkspaceDropdownOpen}>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TopControlBar } from "@/app/(app)/workspaces/[workspaceId]/components/T
|
||||
import { IS_DEVELOPMENT, IS_FORMBRICKS_CLOUD } from "@/lib/constants";
|
||||
import { getPublicDomain } from "@/lib/getPublicUrl";
|
||||
import { getAccessFlags } from "@/lib/membership/utils";
|
||||
import { getPostHogFeatureFlag } from "@/lib/posthog/get-feature-flag";
|
||||
import { getTranslate } from "@/lingodotdev/server";
|
||||
import { getOrganizationWorkspacesLimit } from "@/modules/ee/license-check/lib/utils";
|
||||
import { LimitsReachedBanner } from "@/modules/ui/components/limits-reached-banner";
|
||||
@@ -37,6 +38,7 @@ export const WorkspaceLayout = async ({ layoutData, children }: WorkspaceLayoutP
|
||||
const { features, lastChecked, isPendingDowngrade, active, status } = license;
|
||||
const isMultiOrgEnabled = features?.isMultiOrgEnabled ?? false;
|
||||
const organizationWorkspacesLimit = await getOrganizationWorkspacesLimit(organization.id);
|
||||
const newTrialBannerVariant = await getPostHogFeatureFlag(user.id, "a-b_navigation_rich-trial-banner");
|
||||
const isOwnerOrManager = isOwner || isManager;
|
||||
|
||||
// Validate that workspace permission exists for members
|
||||
@@ -71,6 +73,8 @@ export const WorkspaceLayout = async ({ layoutData, children }: WorkspaceLayoutP
|
||||
organizationWorkspacesLimit={organizationWorkspacesLimit}
|
||||
isLicenseActive={active}
|
||||
isAccessControlAllowed={isAccessControlAllowed}
|
||||
responseCount={responseCount}
|
||||
newTrialBannerVariant={newTrialBannerVariant}
|
||||
/>
|
||||
<div id="mainContent" className="flex flex-1 flex-col overflow-hidden bg-slate-50">
|
||||
<TopControlBar
|
||||
|
||||
@@ -449,6 +449,7 @@ checksums:
|
||||
common/trial_days_remaining: 914ff3132895e410bf0f862433ccb49e
|
||||
common/trial_expired: ca9f0532ac40ca427ca1ba4c86454e07
|
||||
common/trial_one_day_remaining: 2d64d39fca9589c4865357817bcc24d5
|
||||
common/trial_plan_badge: b7928bd1938c56199e7d8aada43e587e
|
||||
common/try_again: 33dd8820e743e35a66e6977f69e9d3b5
|
||||
common/type: f04471a7ddac844b9ad145eb9911ef75
|
||||
common/unknown_survey: dd8f6985e17ccf19fac1776e18b2c498
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "{count} Tage verbleibend in deiner Testphase",
|
||||
"trial_expired": "Deine Testphase ist abgelaufen",
|
||||
"trial_one_day_remaining": "1 Tag verbleibend in deiner Testphase",
|
||||
"trial_plan_badge": "{plan}-Testversion",
|
||||
"try_again": "Erneut versuchen",
|
||||
"type": "Typ",
|
||||
"unknown_survey": "Unbekannte Umfrage",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "{count} days left in your trial",
|
||||
"trial_expired": "Your trial has expired",
|
||||
"trial_one_day_remaining": "1 day left in your trial",
|
||||
"trial_plan_badge": "{plan} Trial",
|
||||
"try_again": "Try again",
|
||||
"type": "Type",
|
||||
"unknown_survey": "Unknown survey",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "{count} días restantes en tu prueba",
|
||||
"trial_expired": "Tu prueba ha expirado",
|
||||
"trial_one_day_remaining": "1 día restante en tu prueba",
|
||||
"trial_plan_badge": "Prueba de {plan}",
|
||||
"try_again": "Intentar de nuevo",
|
||||
"type": "Tipo",
|
||||
"unknown_survey": "Encuesta desconocida",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "{count} jours restants dans votre période d'essai",
|
||||
"trial_expired": "Votre période d'essai a expiré",
|
||||
"trial_one_day_remaining": "1 jour restant dans votre période d'essai",
|
||||
"trial_plan_badge": "Essai {plan}",
|
||||
"try_again": "Réessayer",
|
||||
"type": "Type",
|
||||
"unknown_survey": "Enquête inconnue",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "{count} nap van hátra a próbaidőszakából",
|
||||
"trial_expired": "A próbaidőszaka lejárt",
|
||||
"trial_one_day_remaining": "1 nap van hátra a próbaidőszakából",
|
||||
"trial_plan_badge": "{plan} próbaverzió",
|
||||
"try_again": "Próbálja újra",
|
||||
"type": "Típus",
|
||||
"unknown_survey": "Ismeretlen kérdőív",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "トライアル期間の残り{count}日",
|
||||
"trial_expired": "トライアル期間が終了しました",
|
||||
"trial_one_day_remaining": "トライアル期間の残り1日",
|
||||
"trial_plan_badge": "{plan}トライアル",
|
||||
"try_again": "もう一度お試しください",
|
||||
"type": "種類",
|
||||
"unknown_survey": "不明なフォーム",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "{count} dagen over in je proefperiode",
|
||||
"trial_expired": "Je proefperiode is verlopen",
|
||||
"trial_one_day_remaining": "1 dag over in je proefperiode",
|
||||
"trial_plan_badge": "{plan} Proefperiode",
|
||||
"try_again": "Probeer het opnieuw",
|
||||
"type": "Type",
|
||||
"unknown_survey": "Onbekende enquête",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "{count} dias restantes no seu período de teste",
|
||||
"trial_expired": "Seu período de teste expirou",
|
||||
"trial_one_day_remaining": "1 dia restante no seu período de teste",
|
||||
"trial_plan_badge": "Teste {plan}",
|
||||
"try_again": "Tenta de novo",
|
||||
"type": "Tipo",
|
||||
"unknown_survey": "Pesquisa desconhecida",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "{count} dias restantes no teu período de teste",
|
||||
"trial_expired": "O teu período de teste expirou",
|
||||
"trial_one_day_remaining": "1 dia restante no teu período de teste",
|
||||
"trial_plan_badge": "Teste {plan}",
|
||||
"try_again": "Tente novamente",
|
||||
"type": "Tipo",
|
||||
"unknown_survey": "Inquérito desconhecido",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "{count} zile rămase în perioada ta de probă",
|
||||
"trial_expired": "Perioada ta de probă a expirat",
|
||||
"trial_one_day_remaining": "1 zi rămasă în perioada ta de probă",
|
||||
"trial_plan_badge": "Perioadă de probă {plan}",
|
||||
"try_again": "Încearcă din nou",
|
||||
"type": "Tip",
|
||||
"unknown_survey": "Chestionar necunoscut",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "{count, plural, one {Остался # день пробного периода} few {Осталось # дня пробного периода} many {Осталось # дней пробного периода} other {Осталось # дней пробного периода}}",
|
||||
"trial_expired": "Пробный период истёк",
|
||||
"trial_one_day_remaining": "Остался 1 день пробного периода",
|
||||
"trial_plan_badge": "Пробная версия {plan}",
|
||||
"try_again": "Попробуйте ещё раз",
|
||||
"type": "Тип",
|
||||
"unknown_survey": "Неизвестный опрос",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "{count} dagar kvar av din provperiod",
|
||||
"trial_expired": "Din provperiod har gått ut",
|
||||
"trial_one_day_remaining": "1 dag kvar av din provperiod",
|
||||
"trial_plan_badge": "{plan} provperiod",
|
||||
"try_again": "Försök igen",
|
||||
"type": "Typ",
|
||||
"unknown_survey": "Okänd enkät",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "Deneme sürenizde {count} gün kaldı",
|
||||
"trial_expired": "Deneme süreniz doldu",
|
||||
"trial_one_day_remaining": "Deneme sürenizde 1 gün kaldı",
|
||||
"trial_plan_badge": "{plan} Deneme",
|
||||
"try_again": "Tekrar dene",
|
||||
"type": "Tür",
|
||||
"unknown_survey": "Bilinmeyen anket",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "试用期还剩 {count} 天",
|
||||
"trial_expired": "您的试用期已过期",
|
||||
"trial_one_day_remaining": "试用期还剩 1 天",
|
||||
"trial_plan_badge": "{plan} 试用版",
|
||||
"try_again": "再试一次",
|
||||
"type": "类型",
|
||||
"unknown_survey": "未知调查",
|
||||
|
||||
@@ -476,6 +476,7 @@
|
||||
"trial_days_remaining": "試用期剩餘 {count} 天",
|
||||
"trial_expired": "您的試用期已結束",
|
||||
"trial_one_day_remaining": "試用期剩餘 1 天",
|
||||
"trial_plan_badge": "{plan} 試用版",
|
||||
"try_again": "再試一次",
|
||||
"type": "類型",
|
||||
"unknown_survey": "未知問卷",
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import posthog from "posthog-js";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
|
||||
export const TRIAL_BASE_RESPONSE_LIMIT = 250;
|
||||
|
||||
interface TrialBannerNewProps {
|
||||
trialDaysRemaining: number;
|
||||
planName: string;
|
||||
responseCount: number;
|
||||
responseLimit: number | null;
|
||||
baseResponseLimit: number;
|
||||
billingHref: string;
|
||||
hasPaymentMethod?: boolean;
|
||||
}
|
||||
|
||||
export const TrialBannerNew = ({
|
||||
trialDaysRemaining,
|
||||
planName,
|
||||
responseCount,
|
||||
responseLimit,
|
||||
baseResponseLimit,
|
||||
billingHref,
|
||||
hasPaymentMethod = false,
|
||||
}: TrialBannerNewProps) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const locale = i18n.resolvedLanguage ?? i18n.language ?? "en-US";
|
||||
|
||||
const effectiveLimit = responseLimit ?? baseResponseLimit;
|
||||
const progressPercent = Math.min((responseCount / effectiveLimit) * 100, 100);
|
||||
const planLabel = planName.charAt(0).toUpperCase() + planName.slice(1);
|
||||
|
||||
return (
|
||||
<div className="m-2 rounded-lg border border-slate-200 bg-white p-3 text-sm shadow-sm">
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<span className="font-semibold text-slate-800">
|
||||
{trialDaysRemaining > 0
|
||||
? t("common.trial_days_remaining", { count: trialDaysRemaining })
|
||||
: t("common.trial_expired")}
|
||||
</span>
|
||||
<span className="whitespace-nowrap rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-600">
|
||||
{t("common.trial_plan_badge", { plan: planLabel })}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="mb-2 text-xs text-slate-500">
|
||||
{responseCount.toLocaleString(locale)} /{" "}
|
||||
<span className="line-through">{baseResponseLimit.toLocaleString(locale)}</span>{" "}
|
||||
{effectiveLimit.toLocaleString(locale)} {t("common.responses")}
|
||||
</p>
|
||||
|
||||
<div className="mb-3 h-1.5 w-full overflow-hidden rounded-full bg-slate-100">
|
||||
<div
|
||||
className="h-full rounded-full bg-slate-600 transition-all"
|
||||
style={{ width: `${progressPercent}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!hasPaymentMethod && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => posthog.capture("main_nav_add_payment_clicked")}>
|
||||
<Link href={billingHref}>{t("workspace.settings.billing.add_payment_method")}</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user