mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 19:10:47 +02:00
# Description of Changes - Stripe SDK (`@stripe/*` + `js.stripe.com/v3`) was loading on every page; now it only loads when an upgrade/checkout modal actually opens. - Converted every import site to `React.lazy()` + `<Suspense>`, gated by the existing `opened` state. - Adds a Playwright spec that asserts no Stripe requests on landing or settings. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details.
137 lines
3.8 KiB
TypeScript
137 lines
3.8 KiB
TypeScript
import { lazy, Suspense, useEffect, useState, useCallback } from "react";
|
|
import { useBanner } from "@app/contexts/BannerContext";
|
|
import { useAuth } from "@app/auth/UseSession";
|
|
import { useTranslation } from "react-i18next";
|
|
import { InfoBanner } from "@app/components/shared/InfoBanner";
|
|
import { BASE_PATH } from "@app/constants/app";
|
|
|
|
const StripeCheckout = lazy(
|
|
() => import("@app/components/shared/StripeCheckoutSaas"),
|
|
);
|
|
|
|
const SESSION_STORAGE_KEY = "trialBannerDismissed";
|
|
|
|
export function TrialStatusBanner() {
|
|
const { setBanner } = useBanner();
|
|
const { t } = useTranslation();
|
|
const { trialStatus } = useAuth();
|
|
const [dismissed, setDismissed] = useState(() => {
|
|
return sessionStorage.getItem(SESSION_STORAGE_KEY) === "true";
|
|
});
|
|
const [checkoutOpen, setCheckoutOpen] = useState(false);
|
|
|
|
// Only show banner during ACTIVE trial (not after expiration - modal handles that)
|
|
// Don't show if payment method already added (user has scheduled subscription)
|
|
const shouldShowBanner =
|
|
trialStatus &&
|
|
trialStatus.isTrialing && // Only show during active trial
|
|
trialStatus.daysRemaining > 0 && // Trial hasn't expired yet
|
|
!trialStatus.hasPaymentMethod &&
|
|
!trialStatus.hasScheduledSub &&
|
|
!dismissed;
|
|
|
|
if (trialStatus?.hasPaymentMethod || trialStatus?.hasScheduledSub) {
|
|
console.log("Subscription scheduled - hiding trial banner");
|
|
}
|
|
|
|
const handleOpenCheckout = useCallback(() => {
|
|
setCheckoutOpen(true);
|
|
}, []);
|
|
|
|
const handleDismiss = useCallback(() => {
|
|
setDismissed(true);
|
|
sessionStorage.setItem(SESSION_STORAGE_KEY, "true");
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!shouldShowBanner) {
|
|
setBanner(null);
|
|
return;
|
|
}
|
|
|
|
const trialEndDate = new Date(trialStatus.trialEnd).toLocaleDateString(
|
|
"en-GB",
|
|
{
|
|
month: "short",
|
|
day: "numeric",
|
|
},
|
|
);
|
|
|
|
const message = t(
|
|
"plan.trial.message",
|
|
`Your trial ends in ${trialStatus.daysRemaining} day${trialStatus.daysRemaining !== 1 ? "s" : ""} (${trialEndDate}). Subscribe to continue Pro access.`,
|
|
{ days: trialStatus.daysRemaining, date: trialEndDate },
|
|
);
|
|
|
|
const logoIcon = (
|
|
<img
|
|
src={`${BASE_PATH}/modern-logo/logo512.png`}
|
|
alt="Stirling PDF"
|
|
style={{
|
|
width: "1.5rem",
|
|
height: "1.5rem",
|
|
objectFit: "contain",
|
|
}}
|
|
/>
|
|
);
|
|
|
|
setBanner(
|
|
<InfoBanner
|
|
icon={logoIcon}
|
|
tone="info"
|
|
message={message}
|
|
buttonText={t("plan.trial.subscribe", "Subscribe to Pro")}
|
|
buttonIcon="credit-card-rounded"
|
|
onButtonClick={handleOpenCheckout}
|
|
onDismiss={handleDismiss}
|
|
dismissible={true}
|
|
show={true}
|
|
background="var(--mantine-color-dark-7)"
|
|
borderColor="var(--mantine-color-dark-5)"
|
|
textColor="rgba(255, 255, 255, 0.95)"
|
|
iconColor="rgba(255, 255, 255, 0.95)"
|
|
buttonColor="gray"
|
|
buttonVariant="white"
|
|
buttonTextColor="var(--mantine-color-dark-9)"
|
|
closeIconColor="rgba(255, 255, 255, 0.7)"
|
|
/>,
|
|
);
|
|
|
|
return () => {
|
|
setBanner(null);
|
|
};
|
|
}, [
|
|
shouldShowBanner,
|
|
trialStatus,
|
|
setBanner,
|
|
t,
|
|
handleOpenCheckout,
|
|
handleDismiss,
|
|
]);
|
|
|
|
const handleCheckoutSuccess = () => {
|
|
// Refresh to hide banner and show updated plan
|
|
window.location.reload();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{trialStatus && checkoutOpen && (
|
|
<Suspense fallback={null}>
|
|
<StripeCheckout
|
|
opened={checkoutOpen}
|
|
onClose={() => setCheckoutOpen(false)}
|
|
purchaseType="subscription"
|
|
planId="pro"
|
|
creditsPack={null}
|
|
planName="Pro"
|
|
onSuccess={handleCheckoutSuccess}
|
|
onError={(error) => console.error("Checkout error:", error)}
|
|
isTrialConversion={true}
|
|
/>
|
|
</Suspense>
|
|
)}
|
|
</>
|
|
);
|
|
}
|