Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build-dev/index.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '5b3157c19fc96b39abe8');
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '74bff5ce417ac6b489ec');
198 changes: 117 additions & 81 deletions build-dev/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build-dev/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/index.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '19b6c7224bd2514d0bd2');
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => 'b0a5f1b2e11da3750a5f');
5 changes: 3 additions & 2 deletions build/index.js

Large diffs are not rendered by default.

27 changes: 12 additions & 15 deletions resources/js/components/atoms/PurchaseLink.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { __, sprintf } from '@wordpress/i18n';
import { ExternalLink } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { buttonVariants } from '@/components/ui/button';
import { cn } from '@/lib/utils';

interface PurchaseLinkProps {
/** Tier name to upgrade to (e.g. "Agency") */
Expand All @@ -9,32 +10,28 @@ interface PurchaseLinkProps {
upgradeUrl: string;
/** "upgrade" shows "Upgrade to Tier", "learn-more" shows "Learn more" */
mode?: 'upgrade' | 'learn-more';
className?: string;
}

/**
* @since 1.0.0
*/
export function PurchaseLink( { tierName, upgradeUrl, mode = 'upgrade' }: PurchaseLinkProps ) {
export function PurchaseLink( { tierName, upgradeUrl, mode = 'upgrade', className }: PurchaseLinkProps ) {
const label =
mode === 'upgrade'
? /* translators: %s is the name of the license tier to upgrade to */
sprintf( __( 'Upgrade to %s', '%TEXTDOMAIN%' ), tierName )
: __( 'Learn more', '%TEXTDOMAIN%' );

return (
<Button
variant="outline"
size="xs"
asChild
<a
href={ upgradeUrl }
target="_blank"
rel="noopener noreferrer"
className={ cn( buttonVariants( { variant: 'outline', size: 'sm' } ), 'h-7 gap-1 text-xs', className ) }
>
<a
href={ upgradeUrl }
target="_blank"
rel="noopener noreferrer"
>
{ label }
<ExternalLink className="w-3 h-3" />
</a>
</Button>
{ label }
<ExternalLink className="w-3 h-3 -translate-y-px" />
</a>
);
}
18 changes: 7 additions & 11 deletions resources/js/components/molecules/TierGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
*/
import { useState } from 'react';
import { __ } from '@wordpress/i18n';
import { ChevronRight, ChevronDown, Lock, ExternalLink } from 'lucide-react';
import { ChevronRight, ChevronDown, Lock } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { LicenseBadge } from '@/components/atoms/LicenseBadge';
import { PurchaseLink } from '@/components/atoms/PurchaseLink';
import { FeatureRow } from '@/components/molecules/FeatureRow';
import type { CatalogTier, Feature } from '@/types/api';

Expand Down Expand Up @@ -60,15 +60,11 @@ export function TierGroup( { tier, features, forceOpen = false, showUpgrade = tr
<Lock className="w-3.5 h-3.5 text-muted-foreground ml-1" />
</div>
{ showUpgrade && buttonHref && (
<Button
variant="outline"
size="sm"
className="gap-1 text-xs h-7 ml-auto shrink-0"
onClick={ () => window.open( buttonHref, '_blank', 'noopener,noreferrer' ) }
>
<ExternalLink className="w-3 h-3" />
{ __( 'Upgrade to', '%TEXTDOMAIN%' ) }{ ' ' }{ tier.name }
</Button>
<PurchaseLink
tierName={ tier.name }
upgradeUrl={ buttonHref }
className="ml-auto shrink-0"
/>
) }
{ showUnactivated && (
<LicenseBadge type="unactivated" className="ml-auto shrink-0 text-xs" />
Expand Down
16 changes: 6 additions & 10 deletions resources/js/components/organisms/ProductSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ import { TierGroup } from '@/components/molecules/TierGroup';
import { store as harborStore } from '@/store';
import { useFilter } from '@/context/filter-context';
import { useProductFeatureGroups } from '@/hooks/useProductFeatureGroups';
import { buildChangePlanUrl } from '@/lib/change-plan-url';
import { buildUpgradeUrl } from '@/lib/upgrade-url';
import type { Product } from '@/types/api';

interface ProductSectionProps {
product: Product;
}

/**
* @since 1.0.1 Show Unactivated badge on tier groups and product header for unactivated licenses; route upgrade button to change-plan URL for existing subscribers.
* @since 1.0.2 Route upgrade CTA to catalog upgrade_url for existing subscribers, purchase_url for new subscribers.
* @since 1.0.1 Show Unactivated badge on tier groups and product header for unactivated licenses.
* @since 1.0.0
*/
export function ProductSection( { product }: ProductSectionProps ) {
Expand Down Expand Up @@ -137,15 +138,10 @@ export function ProductSection( { product }: ProductSectionProps ) {
const locked = lockedByTier[ tier.tier_slug ] ?? [];
if ( locked.length === 0 ) return null;

// Any user with an existing subscription — activated or not — is
// routed to the portal's change-plan flow so the upgrade modifies
// their existing subscription. Truly unlicensed visitors fall back
// to the catalog's purchase_url so they can buy fresh.
const subscriptionsUrl = window.harborData?.subscriptionsUrl;
const effectiveLicenseProduct = licenseProduct ?? unactivatedLicenseProduct;
const buttonHref = effectiveLicenseProduct && subscriptionsUrl
? buildChangePlanUrl( subscriptionsUrl, product.slug, tier.tier_slug )
: tier.purchase_url;
const buttonHref = effectiveLicenseProduct
? ( tier.upgrade_url ? buildUpgradeUrl( tier.upgrade_url, window.harborData?.domain ) : undefined )
: ( tier.purchase_url || undefined );

return (
<TierGroup
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const buttonVariants = cva(
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
"border bg-background text-foreground shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
Expand Down
37 changes: 0 additions & 37 deletions resources/js/lib/change-plan-url.ts

This file was deleted.

33 changes: 33 additions & 0 deletions resources/js/lib/upgrade-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Appends portal tracking parameters to a catalog upgrade URL.
*
* The upgrade_url comes from the catalog tier and is a base URL without
* site context. This function appends domain and portal-referral so the
* portal can identify the originating site and entry point.
*
* Example:
* baseUrl = https://my.liquidweb.com/upgrade/kadence/pro/
* domain = example.com
* → https://my.liquidweb.com/upgrade/kadence/pro/?domain=example.com&portal-referral=plugin
*
* @param baseUrl The upgrade_url string from the catalog tier.
* @param domain The site domain from window.harborData.domain.
*
* @since 1.0.2
*/
export function buildUpgradeUrl(
baseUrl: string,
domain: string | undefined,
): string {
if ( ! domain ) {
return baseUrl;
}
try {
const url = new URL( baseUrl );
url.searchParams.set( 'domain', domain );
url.searchParams.set( 'portal-referral', 'plugin' );
return url.toString();
} catch {
return baseUrl;
}
}
7 changes: 6 additions & 1 deletion resources/js/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,14 @@ export interface CatalogTier {
*/
herald_slugs: string[];
/**
* Checkout URL to purchase or upgrade to this tier.
* Checkout URL to purchase this tier for new subscribers.
*/
purchase_url: string;
/**
* URL for existing subscribers to upgrade to this tier.
* The site domain is appended as a query parameter before use.
*/
upgrade_url: string;
}

/**
Expand Down
1 change: 1 addition & 0 deletions resources/js/types/harbor-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export interface HarborData {
pluginsUrl: string;
activationUrl: string;
subscriptionsUrl: string;
domain: string;
version: string;
}
6 changes: 6 additions & 0 deletions src/Harbor/API/REST/V1/Catalog_Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ public function get_item_schema(): array {
'type' => 'string',
],
],
'purchase_url' => [
'type' => 'string',
],
'upgrade_url' => [
'type' => 'string',
],
],
],
],
Expand Down
1 change: 1 addition & 0 deletions src/Harbor/Admin/Feature_Manager_Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ private function enqueue_assets(): void {
PHP_QUERY_RFC3986
),
'subscriptionsUrl' => Config::get_portal_base_url() . '/subscriptions/',
'domain' => $this->site_data->get_domain(),
'version' => Harbor::VERSION,
]
);
Expand Down
14 changes: 14 additions & 0 deletions src/Harbor/Portal/Results/Catalog_Tier.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* currency: string,
* herald_slugs: list<string>,
* purchase_url: string,
* upgrade_url: string,
* }
*/
final class Catalog_Tier {
Expand All @@ -38,6 +39,7 @@ final class Catalog_Tier {
'currency' => '',
'herald_slugs' => [],
'purchase_url' => '',
'upgrade_url' => '',
];

/**
Expand Down Expand Up @@ -76,6 +78,7 @@ public static function from_array( array $data ): self {
? array_map( [ Cast::class, 'to_string' ], array_values( $data['herald_slugs'] ) )
: [],
'purchase_url' => Cast::to_string( $data['purchase_url'] ?? '' ),
'upgrade_url' => Cast::to_string( $data['upgrade_url'] ?? '' ),
]
);
}
Expand Down Expand Up @@ -167,4 +170,15 @@ public function get_herald_slugs(): array {
public function get_purchase_url(): string {
return $this->attributes['purchase_url'];
}

/**
* Gets the upgrade URL for this tier.
*
* @since 1.0.0
*
* @return string
*/
public function get_upgrade_url(): string {
return $this->attributes['upgrade_url'];
}
}
Loading
Loading