import { forwardRef } from 'react'; import { __, sprintf, _n } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; import ButtonInput from '@/components/Inputs/ButtonInput'; import TextInput from '@/components/Inputs/TextInput'; import Icon from '@/utils/Icon'; import Hyperlink from '@/utils/Hyperlink'; import useLicenseData from '@/hooks/useLicenseData'; import type { LicenseUpgrade } from '@/hooks/useLicenseData'; import { clsx } from 'clsx'; import { burst_get_website_url } from '@/utils/lib'; /** * Props interface for the LicenseField component. */ interface LicenseFieldProps { /** Field object from react-hook-form's Controller. */ field: { name: string; value: string; onChange: ( value: string ) => void; onBlur: () => void; ref: React.Ref; }; /** Additional CSS class names. */ className?: string; /** Unique identifier for the field. */ id?: string; /** Additional props. */ [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any } /** * Props interface for the LicenseActivationForm component. */ interface LicenseActivationFormProps { licenseKey: string; onLicenseKeyChange: ( value: string ) => void; onActivate: () => void; isLicenseMutationPending: boolean; fieldName: string; errorMessage?: string; } /** * Props interface for the LicenseStatusCard component. */ interface LicenseStatusCardProps { tier: string; activationLimit: number; activationsLeft: number; expiresDate: string; isLifetime: boolean; isLicenseMutationPending: boolean; onDeactivate: () => void; upgrades: LicenseUpgrade[]; subscriptionStatus: string; hasSubscriptionInfo: boolean; isTrial: boolean; trialRemainingDays: number; licenseStatus: string; encryptedLicenseKey?: string; } /** * Interface for contextual action content. */ interface ContextualAction { headline: string; body: string; buttonText: string; buttonUrl: string; variant: 'default' | 'warning' | 'urgent'; icon?: string; } /** * Constants. */ const TIER_DISPLAY_MAP: Record = { creator: 'Creator', business: 'Business', agency: 'Agency' }; const VARIANT_STYLES = { urgent: { container: 'bg-red-100', button: 'primary' as const }, warning: { container: 'bg-yellow-50 border-t-2 border-yellow-500', button: 'primary' as const }, default: { container: 'bg-gray-50 border-t border-gray-200', button: 'secondary' as const } }; /** * LicenseActivationForm component displays the license key input and activation button. */ const LicenseActivationForm: React.FC = ({ licenseKey, onLicenseKeyChange, onActivate, isLicenseMutationPending, fieldName, errorMessage }) => { return (

{__( 'Activate Burst Pro', 'burst-statistics' )}

onLicenseKeyChange( e.target.value )} disabled={isLicenseMutationPending} className="w-full" /> {/* Inline error display. */} {errorMessage && (

{errorMessage}

)}
{__( 'Activate license', 'burst-statistics' )} {isLicenseMutationPending && }

{__( 'Activating your license gives you automatic updates and support.', 'burst-statistics' )}{' '} {' '} {' '}

); }; /** * LicenseStatusCard component displays the license details and status. */ const LicenseStatusCard: React.FC = ({ tier, activationLimit, activationsLeft, expiresDate, isLifetime, isLicenseMutationPending, onDeactivate, upgrades, subscriptionStatus, hasSubscriptionInfo, isTrial, trialRemainingDays, licenseStatus, encryptedLicenseKey }) => { const isActive = 'valid' === licenseStatus; /** * Format the tier name for display. */ const getTierDisplayName = ( tier: string ): string => { return TIER_DISPLAY_MAP[tier.toLowerCase()] || tier; }; /** * Check if an upgrade has more sites than a given limit. */ const hasMoreSites = ( upgrade: LicenseUpgrade, limit: number, orEqual = false ): boolean => { if ( 'unlimited' === upgrade.sites ) { return true; } return 'number' === typeof upgrade.sites && ( orEqual ? upgrade.sites >= limit : upgrade.sites > limit ); }; /** * Find an upgrade by tier name. */ const findUpgradeByTier = ( upgrades: LicenseUpgrade[], tierName: string ): LicenseUpgrade | undefined => { return upgrades.find( ( upgrade ) => upgrade.tier.toLowerCase() === tierName.toLowerCase() ); }; /** * Get the site activation display text. */ const getActivationDisplay = (): string => { if ( 0 === activationLimit ) { return __( 'Unlimited sites available', 'burst-statistics' ); } let usedActivations = activationLimit - activationsLeft; if ( 0 > usedActivations ) { usedActivations = activationLimit; } return sprintf( /* translators: 1: number of sites used, 2: total number of sites allowed */ __( '%1$d of %2$d sites used', 'burst-statistics' ), usedActivations, activationLimit ); }; /** * Get the license status display text. * This shows if the license is active on THIS website. */ const getLicenseStatusDisplay = (): string => { if ( 'valid' === licenseStatus ) { return __( 'Active on this site', 'burst-statistics' ); } return __( 'Inactive on this site', 'burst-statistics' ); }; /** * Get the subscription status display text. * This shows if auto-renewal is enabled. */ const getSubscriptionStatusDisplay = (): { text: string; color: string } => { const isExpired = new Date( expiresDate ) < new Date(); // Check for lifetime license first (highest priority). if ( isLifetime ) { return { text: __( 'Never', 'burst-statistics' ), color: 'text-text-black' }; } // Check for trial period. // Only show trial if: has trial remaining days AND either no subscription info OR subscription is not active. // This prevents showing "Trial" for paid subscriptions that still have trial data in the system. const isActuallyInTrial = isTrial && 0 < trialRemainingDays && ( ! hasSubscriptionInfo || 'active' !== subscriptionStatus ); if ( isActuallyInTrial ) { return { text: sprintf( /* translators: %d: number of days remaining */ __( 'Trial - %d days remaining', 'burst-statistics' ), trialRemainingDays ), color: 'text-green-700' }; } // No subscription info - show expiration. if ( ! hasSubscriptionInfo ) { return { text: sprintf( isExpired ? __( 'Expired on %s', 'burst-statistics' ) : __( 'Expires on %s', 'burst-statistics' ), expiresDate ), color: 'text-text-black' }; } // Handle subscription statuses. switch ( subscriptionStatus ) { case 'active': return { text: sprintf( /* translators: %s: renewal date */ __( 'Auto-renews on %s', 'burst-statistics' ), expiresDate ), color: 'text-text-black' }; case 'cancelled': return { text: sprintf( isExpired ? __( 'Cancelled - Expired on %s', 'burst-statistics' ) : __( 'Cancelled - Expires on %s', 'burst-statistics' ), expiresDate ), color: 'text-red font-semibold' }; case 'failing': return { text: __( 'Payment failed - Axtion required', 'burst-statistics' ), color: 'text-red' }; default: return { text: sprintf( isExpired ? __( 'Expired on %s', 'burst-statistics' ) : __( 'Expires on %s', 'burst-statistics' ), expiresDate ), color: 'text-text-black' }; } }; /** * Determine the contextual action to display based on user's specific context. * Only show critical warnings (subscription/payment issues). */ const getContextualAction = (): ContextualAction | null => { // Priority 1: Subscription cancelled or payment issue. if ( hasSubscriptionInfo && ( 'cancelled' === subscriptionStatus || 'failing' === subscriptionStatus ) ) { return { headline: __( 'Keep your Pro insights & lock in your price', 'burst-statistics' ), body: sprintf( __( 'Your subscription is set to expire on %s.', 'burst-statistics' ), expiresDate ) + ' ' + __( 'Renew your plan now to lock in your current rate. As long as your subscription stays active, your price won\'t increase and you\'ll keep access to all Pro data and features.', 'burst-statistics' ), buttonText: __( 'Renew subscription', 'burst-statistics' ), buttonUrl: burst_get_website_url( 'checkout', { edd_license_key: encryptedLicenseKey, download_id: 889, utm_source: 'license-settings', utm_content: 'resume-subscription' }), variant: 'urgent', icon: 'warning-triangle' }; } // No contextual action needed - let users choose upgrades themselves. return null; }; const contextualAction = getContextualAction(); /** * Determine which upgrade to recommend based on user's current plan. */ const getRecommendedUpgradeUrl = (): string | null => { // Site limit reached - recommend any upgrade with more sites. if ( 0 === activationsLeft && 0 < activationLimit ) { const validUpgrade = upgrades.find( ( upgrade ) => hasMoreSites( upgrade, activationLimit ) ); return validUpgrade?.url || null; } const currentTier = tier.toLowerCase(); // Creator tier recommendations. if ( 'creator' === currentTier ) { // Creator 1 site: recommend Creator 5 sites first (more sites, same tier). if ( 1 === activationLimit ) { const creator5Upgrade = upgrades.find( ( upgrade ) => 'creator' === upgrade.tier.toLowerCase() && 5 === upgrade.sites ); if ( creator5Upgrade ) { return creator5Upgrade.url; } } // Otherwise recommend Business tier upgrade (prefer matching or more sites). const validUpgrades = upgrades.filter( ( upgrade ) => hasMoreSites( upgrade, activationLimit, true ) ); const targetUpgrade = findUpgradeByTier( validUpgrades, 'business' ) || findUpgradeByTier( validUpgrades, 'agency' ); return targetUpgrade?.url || null; } // Business tier: recommend Agency. if ( 'business' === currentTier ) { return findUpgradeByTier( upgrades, 'agency' )?.url || null; } return null; }; const recommendedUpgradeUrl = getRecommendedUpgradeUrl(); const subscriptionDisplay = getSubscriptionStatusDisplay(); return (
{/* Card Header - Plan Name & Status. */}

{tier ? `Burst Pro — ${getTierDisplayName( tier )}` : 'Burst Pro' }

{isActive ? __( 'Active', 'burst-statistics' ) : __( 'Inactive', 'burst-statistics' ) }
{/* Status Details - Two-Column Layout. */}
{/* License Status - Active on THIS website. */}
{__( 'License status', 'burst-statistics' )} {getLicenseStatusDisplay()}
{/* Site Activations. */}
{__( 'Site activations', 'burst-statistics' )} {getActivationDisplay()}
{/* Subscription Status - Auto-renewal status. */}
{__( 'Subscription status', 'burst-statistics' )} {subscriptionDisplay.text}
{/* Primary Actions - Only shown when active. */} {isActive && (
window.open( burst_get_website_url( 'account', { utm_source: 'license-settings', utm_content: 'manage-subscription' }), '_blank' ) } > {__( 'Manage subscription', 'burst-statistics' )} {isLicenseMutationPending && }
)}
{/* Contextual Action Area - Smart, Priority-Based Messaging. Only shown when active. */} {isActive && contextualAction && (

{contextualAction.headline}

{contextualAction.body}

window.open( contextualAction.buttonUrl, '_blank' )} > {contextualAction.buttonText}
)} {/* Available Upgrades Section - Hide if there's an urgent action required. Only shown when active. */} {isActive && 0 < upgrades.length && ! contextualAction && (

{__( 'Available upgrades', 'burst-statistics' )}

{upgrades.map( ( upgrade, index ) => { // Add UTM parameters to upgrade URL. const upgradeUrlWithUTM = upgrade.url.includes( 'burst-statistics.com' ) ? addQueryArgs( upgrade.url, { utm_source: 'license-settings', utm_content: `upgrade-to-${upgrade.tier.toLowerCase()}` }) : upgrade.url; // Check if this is the recommended upgrade. const isRecommended = upgrade.url === recommendedUpgradeUrl; return (

{upgrade.tier}

{isRecommended && ( {__( 'Recommended for you', 'burst-statistics' )} )}

{'unlimited' === upgrade.sites ? __( 'Unlimited sites', 'burst-statistics' ) : sprintf( /* translators: %s: number of sites */ _n( '%s site', '%s sites', upgrade.sites, 'burst-statistics' ), upgrade.sites )}

window.open( upgradeUrlWithUTM, '_blank' )} > {__( 'Upgrade', 'burst-statistics' )}
); })}
)}
); }; /** * LicenseField component for managing license activation and status. * * This component renders based on the license status: * - Empty: Only shows LicenseActivationForm. * - Active: Only shows LicenseStatusCard. * - Inactive (deactivated/expired): Shows LicenseActivationForm + LicenseStatusCard. */ const LicenseField = forwardRef( ({ field, className, ...props }, ref ) => { // Use the custom license data hook. const { licenseNotices, licenseStatus, isFetching, isLicenseMutationPending, activateLicense, deactivateLicense, upgrades, tier, activationLimit, activationsLeft, licenseExpiresDate, licenseIsLifetime, subscriptionStatus, hasSubscriptionInfo, isTrial, trialRemainingDays, encryptedLicenseKey } = useLicenseData(); const inputId = props.id || field.name; const isActive = 'valid' === licenseStatus; const isEmpty = '' === licenseStatus || 'empty' === licenseStatus; const isInactive = ! isActive && ! isEmpty; // Extract error message from notices for inline display. const errorMessage = licenseNotices.find( ( notice ) => 'warning' === notice.icon )?.msg; /** * Handle license activation. */ const handleActivate = () => { if ( field.value ) { activateLicense( field.name, field.value ); } }; /** * Handle license deactivation. */ const handleDeactivate = () => { deactivateLicense(); }; return (
{/* Show activation form when not active. */} {! isActive && ( )} {/* Show status card when active OR when inactive (has license data but not active). */} {( isActive || isInactive ) && ( )}
); } ); LicenseField.displayName = 'LicenseField'; export default LicenseField;