This commit is contained in:
Andras Schmelczer 2026-06-10 22:25:15 +01:00
parent 1241132095
commit 54a5c3ca9a
28 changed files with 826 additions and 422 deletions

View file

@ -8,6 +8,7 @@ import BottomIllustration from './BottomIllustration';
import { TickerValue } from '../ui/TickerValue';
import { ChevronIcon, LogoIcon, PlayIcon } from '../ui/icons';
import { trackEvent } from '../../lib/analytics';
import { apiUrl } from '../../lib/api';
const BRAND_NAME = 'Perfect Postcode';
const BRAND_TEXT_CLASS = 'text-teal-600 dark:text-teal-400';
@ -163,11 +164,78 @@ function ProductDemoVideo() {
);
}
interface PriceStripTier {
up_to: number | null;
price_pence: number;
}
/**
* Compact pricing teaser under the hero CTAs: surfaces the current lifetime
* price and tier scarcity that otherwise hide behind the Pricing nav link.
*/
function PriceStrip({
onOpenPricing,
hidePricing,
}: {
onOpenPricing: () => void;
hidePricing?: boolean;
}) {
const { t } = useTranslation();
const [pricing, setPricing] = useState<{
licensed_count: number;
current_price_pence: number;
tiers: PriceStripTier[];
} | null>(null);
useEffect(() => {
if (hidePricing) return;
const controller = new AbortController();
fetch(apiUrl('pricing'), { signal: controller.signal })
.then((res) => (res.ok ? res.json() : null))
.then(setPricing)
.catch(() => {});
return () => controller.abort();
}, [hidePricing]);
if (hidePricing || !pricing) return null;
const price = `£${pricing.current_price_pence / 100}`;
const currentTier = pricing.tiers.find(
(tier) => tier.up_to === null || pricing.licensed_count < tier.up_to
);
const spotsRemaining =
currentTier?.up_to != null ? currentTier.up_to - pricing.licensed_count : 0;
return (
<p className="text-sm text-warm-300 mb-2">
{pricing.current_price_pence === 0
? t('upgrade.freeForEarly')
: t('home.priceStrip', { price })}{' '}
{pricing.current_price_pence > 0 && spotsRemaining > 0 && (
<span className="font-semibold text-teal-300">
{spotsRemaining === 1
? t('home.priceStripSpots', { count: spotsRemaining })
: t('home.priceStripSpotsPlural', { count: spotsRemaining })}
</span>
)}{' '}
<button
onClick={() => {
trackEvent('CTA Click', { location: 'hero', label: 'price_strip' });
onOpenPricing();
}}
className="underline decoration-dotted underline-offset-2 text-teal-300 hover:text-teal-200"
>
{t('home.priceStripCta')}
</button>
</p>
);
}
export default function HomePage({
onOpenDashboard,
onOpenPricing: _onOpenPricing,
onOpenPricing,
theme = 'light',
hidePricing: _hidePricing,
hidePricing,
}: {
onOpenDashboard: () => void;
onOpenPricing: () => void;
@ -327,7 +395,7 @@ export default function HomePage({
<p className="text-base md:text-lg text-warm-200 mb-8 max-w-xl">
{highlightBrandText(t('home.heroDescription'), 'font-semibold text-teal-300')}
</p>
<div className="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4 mb-10">
<div className="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4 mb-5">
<button
onClick={() => {
trackEvent('CTA Click', { location: 'hero', label: 'explore_map' });
@ -347,6 +415,8 @@ export default function HomePage({
{t('home.seeTheDifference')}
</button>
</div>
<PriceStrip onOpenPricing={onOpenPricing} hidePricing={hidePricing} />
<p className="text-sm text-warm-400 mb-8">{t('home.coverageNote')}</p>
<div className="home-hero-stats flex flex-wrap pt-3 border-t border-white/10">
<div className="home-hero-stat">
<div className="home-hero-stat-value">
@ -356,7 +426,7 @@ export default function HomePage({
</div>
<div className="home-hero-stat">
<div className="home-hero-stat-value">
<TickerValue text="56" active={statsActive} />
<TickerValue text="40+" active={statsActive} />
</div>
<div className="home-hero-stat-label">{t('home.statFilters')}</div>
</div>