Browse Source

Feature/trial variation of subscription interstitials (#8258)

* Extend variations

* Update changelog
pull/8275/head
Thomas Kaul 2 days ago
committed by GitHub
parent
commit
44bcbf6147
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 16
      apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts
  3. 42
      apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html
  4. 10
      apps/client/src/app/components/user-account-membership/user-account-membership.component.ts
  5. 5
      apps/client/src/app/components/user-account-membership/user-account-membership.html
  6. 14
      libs/common/src/lib/config.ts

1
CHANGELOG.md

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Extended the variations of the interstitials for the subscription by a trial
- Deprecated the `api/v1/auth-device` endpoint in favor of the `api/v1/auth-devices` endpoint - Deprecated the `api/v1/auth-device` endpoint in favor of the `api/v1/auth-devices` endpoint
- Deprecated the `api/v1/exchange-rate` endpoint in favor of the `api/v1/exchange-rates` endpoint - Deprecated the `api/v1/exchange-rate` endpoint in favor of the `api/v1/exchange-rates` endpoint
- Upgraded `@modelcontextprotocol/core` and `@modelcontextprotocol/server` from version `2.0.0` to `2.3.1` - Upgraded `@modelcontextprotocol/core` and `@modelcontextprotocol/server` from version `2.0.0` to `2.3.1`

16
apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts

@ -1,3 +1,5 @@
import { TRY_SUBSCRIPTION_MAIL_HREF } from '@ghostfolio/common/config';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfMembershipCardComponent } from '@ghostfolio/ui/membership-card'; import { GfMembershipCardComponent } from '@ghostfolio/ui/membership-card';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
@ -51,6 +53,7 @@ export class GfSubscriptionInterstitialDialogComponent implements OnInit {
public remainingSkipButtonDelay = public remainingSkipButtonDelay =
GfSubscriptionInterstitialDialogComponent.SKIP_BUTTON_DELAY_IN_SECONDS; GfSubscriptionInterstitialDialogComponent.SKIP_BUTTON_DELAY_IN_SECONDS;
public routerLinkPricing = publicRoutes.pricing.routerLink; public routerLinkPricing = publicRoutes.pricing.routerLink;
public trySubscriptionMailHref = TRY_SUBSCRIPTION_MAIL_HREF;
public variantIndex: number; public variantIndex: number;
public constructor( public constructor(
@ -59,10 +62,19 @@ export class GfSubscriptionInterstitialDialogComponent implements OnInit {
private destroyRef: DestroyRef, private destroyRef: DestroyRef,
public dialogRef: MatDialogRef<GfSubscriptionInterstitialDialogComponent> public dialogRef: MatDialogRef<GfSubscriptionInterstitialDialogComponent>
) { ) {
this.variantIndex = Math.floor( const isEligibleForTrial =
Math.random() * GfSubscriptionInterstitialDialogComponent.VARIANTS_COUNT !this.data.user?.subscription?.subscribedAt &&
hasPermission(
this.data.user?.permissions,
permissions.updateUserSettings
); );
const variantsCount = isEligibleForTrial
? GfSubscriptionInterstitialDialogComponent.VARIANTS_COUNT + 1
: GfSubscriptionInterstitialDialogComponent.VARIANTS_COUNT;
this.variantIndex = Math.floor(Math.random() * variantsCount);
addIcons({ arrowForwardOutline, checkmarkCircleOutline }); addIcons({ arrowForwardOutline, checkmarkCircleOutline });
} }

42
apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html

@ -195,4 +195,46 @@
</a> </a>
</div> </div>
} }
@case (4) {
<div class="flex-grow-1 pt-0" mat-dialog-content>
<div class="intro-container">
<div class="intro-inner-container mx-auto">
<div class="h-100 intro w-100"></div>
</div>
</div>
<div>
<h5 class="align-items-center d-flex justify-content-center mb-3">
<span>Ghostfolio Premium</span>
<gf-premium-indicator class="ms-1" [enableLink]="false" />
</h5>
<div class="fw-normal h5 mb-3 text-center" i18n>
Request a free trial
</div>
<p class="m-0 text-center" i18n>
No credit card required. The trial ends automatically.
</p>
</div>
</div>
<div class="justify-content-end" mat-dialog-actions>
<button
mat-button
[disabled]="remainingSkipButtonDelay > 0"
(click)="closeDialog()"
>
<ng-container i18n>Skip</ng-container>
@if (remainingSkipButtonDelay > 0) {
({{ remainingSkipButtonDelay }})
}
</button>
<a
color="primary"
mat-flat-button
[href]="trySubscriptionMailHref"
(click)="closeDialog()"
>
<span i18n>Try Premium</span>
<ion-icon class="ms-1" name="arrow-forward-outline" />
</a>
</div>
}
} }

10
apps/client/src/app/components/user-account-membership/user-account-membership.component.ts

@ -1,5 +1,5 @@
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { E_MAIL_LINE_BREAK } from '@ghostfolio/common/config'; import { TRY_SUBSCRIPTION_MAIL_HREF } from '@ghostfolio/common/config';
import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { getDateFormatString } from '@ghostfolio/common/helper'; import { getDateFormatString } from '@ghostfolio/common/helper';
import { User } from '@ghostfolio/common/interfaces'; import { User } from '@ghostfolio/common/interfaces';
@ -49,13 +49,7 @@ export class GfUserAccountMembershipComponent {
protected hasPermissionToCreateApiKey: boolean; protected hasPermissionToCreateApiKey: boolean;
protected hasPermissionToUpdateUserSettings: boolean; protected hasPermissionToUpdateUserSettings: boolean;
protected price: number; protected price: number;
protected readonly trySubscriptionMailHref = `mailto:hi@ghostfol.io?subject=Ghostfolio Premium Trial&body=${[ protected readonly trySubscriptionMailHref = TRY_SUBSCRIPTION_MAIL_HREF;
'Hello',
'',
'I am interested in Ghostfolio Premium. Can you please send me a coupon code to try it for some time?',
'',
'Kind regards'
].join(E_MAIL_LINE_BREAK)}`;
protected user: User; protected user: User;
private couponId: string | undefined; private couponId: string | undefined;

5
apps/client/src/app/components/user-account-membership/user-account-membership.html

@ -50,7 +50,10 @@
} }
} }
<div class="align-items-center d-flex justify-content-center mt-4"> <div class="align-items-center d-flex justify-content-center mt-4">
@if (!user?.subscription?.subscribedAt) { @if (
!user?.subscription?.subscribedAt &&
hasPermissionToUpdateUserSettings
) {
<a <a
class="mx-1" class="mx-1"
mat-stroked-button mat-stroked-button

14
libs/common/src/lib/config.ts

@ -422,4 +422,18 @@ export const THROTTLE_DEFAULT_TTL = ms('1 minute');
export const THROTTLE_SIGNUP_LIMIT = 5; export const THROTTLE_SIGNUP_LIMIT = 5;
export const THROTTLE_SIGNUP_TTL = ms('1 hour'); export const THROTTLE_SIGNUP_TTL = ms('1 hour');
export const TRY_SUBSCRIPTION_MAIL_HREF = `mailto:hi@ghostfol.io?subject=${encodeURIComponent(
'Ghostfolio Premium Trial'
)}&body=${[
'Hello',
'',
'I am interested in Ghostfolio Premium. Can you please send me a coupon code to try it for some time?',
'',
'Kind regards'
]
.map((line) => {
return encodeURIComponent(line);
})
.join(E_MAIL_LINE_BREAK)}`;
export const UNKNOWN_KEY = 'UNKNOWN'; export const UNKNOWN_KEY = 'UNKNOWN';

Loading…
Cancel
Save