diff --git a/src/commands/gitCommands.ts b/src/commands/gitCommands.ts index 9c6581a..50f41ec 100644 --- a/src/commands/gitCommands.ts +++ b/src/commands/gitCommands.ts @@ -712,7 +712,7 @@ export class GitCommandsCommand extends Command { resolve(undefined); return; - case Directive.RequiresFreeSubscription: + case Directive.ExtendTrial: void Container.instance.subscription.loginOrSignUp(); resolve(undefined); return; diff --git a/src/commands/quickCommand.steps.ts b/src/commands/quickCommand.steps.ts index 085d420..90625e7 100644 --- a/src/commands/quickCommand.steps.ts +++ b/src/commands/quickCommand.steps.ts @@ -2167,26 +2167,20 @@ export async function* ensureAccessStep< let placeholder: string; if (access.subscription.current.account?.verified === false) { directives.push(DirectiveQuickPickItem.create(Directive.RequiresVerification, true)); - placeholder = 'You must verify your GitLens+ account email address before you can continue'; + placeholder = 'You must verify your email address before you can continue'; } else { if (access.subscription.required == null) return undefined; + placeholder = 'You need GitLens Pro to access GitLens+ features on this repo'; if (isSubscriptionPaidPlan(access.subscription.required) && access.subscription.current.account != null) { directives.push(DirectiveQuickPickItem.create(Directive.RequiresPaidSubscription, true)); - placeholder = 'GitLens+ features require an upgraded account'; + } else if ( + access.subscription.current.account == null && + !isSubscriptionPreviewTrialExpired(access.subscription.current) + ) { + directives.push(DirectiveQuickPickItem.create(Directive.StartPreviewTrial, true)); } else { - if ( - access.subscription.current.account == null && - !isSubscriptionPreviewTrialExpired(access.subscription.current) - ) { - directives.push( - DirectiveQuickPickItem.create(Directive.StartPreviewTrial, true), - DirectiveQuickPickItem.create(Directive.RequiresFreeSubscription), - ); - } else { - directives.push(DirectiveQuickPickItem.create(Directive.RequiresFreeSubscription)); - } - placeholder = 'GitLens+ features require a free account'; + directives.push(DirectiveQuickPickItem.create(Directive.ExtendTrial)); } } diff --git a/src/git/remotes/github.ts b/src/git/remotes/github.ts index ce8f4be..6112b74 100644 --- a/src/git/remotes/github.ts +++ b/src/git/remotes/github.ts @@ -7,7 +7,6 @@ import type { IntegrationAuthenticationProvider, IntegrationAuthenticationSessionDescriptor, } from '../../plus/integrationAuthentication'; -import { isSubscriptionPaidPlan, isSubscriptionPreviewTrialExpired } from '../../subscription'; import { log } from '../../system/decorators/log'; import { memoize } from '../../system/decorators/memoize'; import { encodeUrl } from '../../system/encoding'; @@ -18,7 +17,7 @@ import type { IssueOrPullRequest } from '../models/issue'; import type { PullRequest, PullRequestState } from '../models/pullRequest'; import { GitRevision } from '../models/reference'; import type { Repository } from '../models/repository'; -import { RichRemoteProvider } from './richRemoteProvider'; +import { ensurePaidPlan, RichRemoteProvider } from './richRemoteProvider'; const autolinkFullIssuesRegex = /\b(?[^/\s]+\/[^/\s]+)#(?[0-9]+)\b(?!]\()/g; const fileRegex = /^\/([^/]+)\/([^/]+?)\/blob(.+)$/i; @@ -141,77 +140,7 @@ export class GitHubRemote extends RichRemoteProvider { @log() override async connect(): Promise { if (!isGitHubDotCom(this.domain)) { - const title = - 'Connecting to a GitHub Enterprise instance for rich integration features requires a paid GitLens+ account.'; - - while (true) { - const subscription = await this.container.subscription.getSubscription(); - if (subscription.account?.verified === false) { - const resend = { title: 'Resend Verification' }; - const cancel = { title: 'Cancel', isCloseAffordance: true }; - const result = await window.showWarningMessage( - `${title}\n\nYou must verify your GitLens+ account email address before you can continue.`, - { modal: true }, - resend, - cancel, - ); - - if (result === resend) { - if (await this.container.subscription.resendVerification()) { - continue; - } - } - - return false; - } - - const plan = subscription.plan.effective.id; - if (isSubscriptionPaidPlan(plan)) break; - - if (subscription.account == null && !isSubscriptionPreviewTrialExpired(subscription)) { - const startTrial = { title: 'Try GitLens+' }; - const cancel = { title: 'Cancel', isCloseAffordance: true }; - const result = await window.showWarningMessage( - `${title}\n\nDo you want to try GitLens+ for free for 3 days?`, - { modal: true }, - startTrial, - cancel, - ); - - if (result !== startTrial) return false; - - void this.container.subscription.startPreviewTrial(); - break; - } else if (subscription.account == null) { - const signIn = { title: 'Sign In to GitLens+' }; - const cancel = { title: 'Cancel', isCloseAffordance: true }; - const result = await window.showWarningMessage( - `${title}\n\nDo you want to sign in to GitLens+?`, - { modal: true }, - signIn, - cancel, - ); - - if (result === signIn) { - if (await this.container.subscription.loginOrSignUp()) { - continue; - } - } - } else { - const upgrade = { title: 'Upgrade Account' }; - const cancel = { title: 'Cancel', isCloseAffordance: true }; - const result = await window.showWarningMessage( - `${title}\n\nDo you want to upgrade your account?`, - { modal: true }, - upgrade, - cancel, - ); - - if (result === upgrade) { - void this.container.subscription.purchase(); - } - } - + if (!(await ensurePaidPlan('GitHub Enterprise instance', this.container))) { return false; } } diff --git a/src/git/remotes/gitlab.ts b/src/git/remotes/gitlab.ts index 9fab660..cd568e5 100644 --- a/src/git/remotes/gitlab.ts +++ b/src/git/remotes/gitlab.ts @@ -8,7 +8,6 @@ import type { IntegrationAuthenticationProvider, IntegrationAuthenticationSessionDescriptor, } from '../../plus/integrationAuthentication'; -import { isSubscriptionPaidPlan, isSubscriptionPreviewTrialExpired } from '../../subscription'; import { log } from '../../system/decorators/log'; import { encodeUrl } from '../../system/encoding'; import { equalsIgnoreCase } from '../../system/string'; @@ -18,7 +17,7 @@ import type { IssueOrPullRequest } from '../models/issue'; import type { PullRequest, PullRequestState } from '../models/pullRequest'; import { GitRevision } from '../models/reference'; import type { Repository } from '../models/repository'; -import { RichRemoteProvider } from './richRemoteProvider'; +import { ensurePaidPlan, RichRemoteProvider } from './richRemoteProvider'; const autolinkFullIssuesRegex = /\b(?[^/\s]+\/[^/\s]+)#(?[0-9]+)\b(?!]\()/g; const autolinkFullMergeRequestsRegex = /\b(?[^/\s]+\/[^/\s]+)!(?[0-9]+)\b(?!]\()/g; @@ -184,77 +183,7 @@ export class GitLabRemote extends RichRemoteProvider { @log() override async connect(): Promise { if (!equalsIgnoreCase(this.domain, 'gitlab.com')) { - const title = - 'Connecting to a GitLab self-managed instance for rich integration features requires a paid GitLens+ account.'; - - while (true) { - const subscription = await this.container.subscription.getSubscription(); - if (subscription.account?.verified === false) { - const resend = { title: 'Resend Verification' }; - const cancel = { title: 'Cancel', isCloseAffordance: true }; - const result = await window.showWarningMessage( - `${title}\n\nYou must verify your GitLens+ account email address before you can continue.`, - { modal: true }, - resend, - cancel, - ); - - if (result === resend) { - if (await this.container.subscription.resendVerification()) { - continue; - } - } - - return false; - } - - const plan = subscription.plan.effective.id; - if (isSubscriptionPaidPlan(plan)) break; - - if (subscription.account == null && !isSubscriptionPreviewTrialExpired(subscription)) { - const startTrial = { title: 'Try GitLens+' }; - const cancel = { title: 'Cancel', isCloseAffordance: true }; - const result = await window.showWarningMessage( - `${title}\n\nDo you want to try GitLens+ for free for 3 days?`, - { modal: true }, - startTrial, - cancel, - ); - - if (result !== startTrial) return false; - - void this.container.subscription.startPreviewTrial(); - break; - } else if (subscription.account == null) { - const signIn = { title: 'Sign In to GitLens+' }; - const cancel = { title: 'Cancel', isCloseAffordance: true }; - const result = await window.showWarningMessage( - `${title}\n\nDo you want to sign in to GitLens+?`, - { modal: true }, - signIn, - cancel, - ); - - if (result === signIn) { - if (await this.container.subscription.loginOrSignUp()) { - continue; - } - } - } else { - const upgrade = { title: 'Upgrade Account' }; - const cancel = { title: 'Cancel', isCloseAffordance: true }; - const result = await window.showWarningMessage( - `${title}\n\nDo you want to upgrade your account?`, - { modal: true }, - upgrade, - cancel, - ); - - if (result === upgrade) { - void this.container.subscription.purchase(); - } - } - + if (!(await ensurePaidPlan('GitLab self-managed instance', this.container))) { return false; } } diff --git a/src/git/remotes/richRemoteProvider.ts b/src/git/remotes/richRemoteProvider.ts index 97c9535..d8b455e 100644 --- a/src/git/remotes/richRemoteProvider.ts +++ b/src/git/remotes/richRemoteProvider.ts @@ -8,6 +8,7 @@ import { AuthenticationError, ProviderRequestClientError } from '../../errors'; import { Logger } from '../../logger'; import { showIntegrationDisconnectedTooManyFailedRequestsWarningMessage } from '../../messages'; import type { IntegrationAuthenticationSessionDescriptor } from '../../plus/integrationAuthentication'; +import { isSubscriptionPaidPlan, isSubscriptionPreviewTrialExpired } from '../../subscription'; import { gate } from '../../system/decorators/gate'; import { debug, getLogScope, log } from '../../system/decorators/log'; import { isPromise } from '../../system/promise'; @@ -466,3 +467,80 @@ export abstract class RichRemoteProvider extends RemoteProvider { return session ?? undefined; } } + +export async function ensurePaidPlan(providerName: string, container: Container): Promise { + const title = `Connecting to a ${providerName} instance for rich integration features requires GitLens Pro.`; + + while (true) { + const subscription = await container.subscription.getSubscription(); + if (subscription.account?.verified === false) { + const resend = { title: 'Resend Verification' }; + const cancel = { title: 'Cancel', isCloseAffordance: true }; + const result = await window.showWarningMessage( + `${title}\n\nYou must verify your email address before you can continue.`, + { modal: true }, + resend, + cancel, + ); + + if (result === resend) { + if (await container.subscription.resendVerification()) { + continue; + } + } + + return false; + } + + const plan = subscription.plan.effective.id; + if (isSubscriptionPaidPlan(plan)) break; + + if (subscription.account == null && !isSubscriptionPreviewTrialExpired(subscription)) { + const startTrial = { title: 'Start a GitLens Pro Trial' }; + const cancel = { title: 'Cancel', isCloseAffordance: true }; + const result = await window.showWarningMessage( + `${title}\n\nDo you want to also try GitLens+ features on private repos, free for 3 days?`, + { modal: true }, + startTrial, + cancel, + ); + + if (result !== startTrial) return false; + + void container.subscription.startPreviewTrial(); + break; + } else if (subscription.account == null) { + const signIn = { title: 'Extend Your GitLens Pro Trial' }; + const cancel = { title: 'Cancel', isCloseAffordance: true }; + const result = await window.showWarningMessage( + `${title}\n\nDo you want to continue to use GitLens+ features on private repos, free for an additional 7-days?`, + { modal: true }, + signIn, + cancel, + ); + + if (result === signIn) { + if (await container.subscription.loginOrSignUp()) { + continue; + } + } + } else { + const upgrade = { title: 'Upgrade to Pro' }; + const cancel = { title: 'Cancel', isCloseAffordance: true }; + const result = await window.showWarningMessage( + `${title}\n\nDo you want to continue to use GitLens+ features on private repos?`, + { modal: true }, + upgrade, + cancel, + ); + + if (result === upgrade) { + void container.subscription.purchase(); + } + } + + return false; + } + + return true; +} diff --git a/src/plus/subscription/subscriptionService.ts b/src/plus/subscription/subscriptionService.ts index 314f9f2..289730f 100644 --- a/src/plus/subscription/subscriptionService.ts +++ b/src/plus/subscription/subscriptionService.ts @@ -242,7 +242,7 @@ export class SubscriptionService implements Disposable { const confirm: MessageItem = { title: 'Resend Verification', isCloseAffordance: true }; const cancel: MessageItem = { title: 'Cancel' }; const result = await window.showInformationMessage( - `Before you can access your ${actual.name} account, you must verify your email address.`, + `Before you can access ${effective.name}, you must verify your email address.`, confirm, cancel, ); @@ -256,11 +256,12 @@ export class SubscriptionService implements Disposable { const confirm: MessageItem = { title: 'OK', isCloseAffordance: true }; const learn: MessageItem = { title: 'Learn More' }; const result = await window.showInformationMessage( - `You are now signed in to your ${ - actual.name - } account which gives you access to GitLens+ features on public repos.\n\nYou were also granted a trial of ${ + `Welcome to ${ effective.name - } for both public and private repos for ${pluralize('more day', remaining ?? 0)}.`, + } (Trial). You now have additional access to GitLens+ features on private repos for ${pluralize( + 'more day', + remaining ?? 0, + )}.`, { modal: true }, confirm, learn, @@ -270,7 +271,10 @@ export class SubscriptionService implements Disposable { this.learn(); } } else { - void window.showInformationMessage(`You are now signed in to your ${actual.name} account.`, 'OK'); + void window.showInformationMessage( + `Welcome to ${actual.name}. You now have additional access to GitLens+ features on private repos.`, + 'OK', + ); } } return loggedIn; @@ -427,10 +431,10 @@ export class SubscriptionService implements Disposable { void this.showHomeView(); if (!silent && plan.effective.id === SubscriptionPlanId.Free) { - const confirm: MessageItem = { title: 'Sign in to GitLens+', isCloseAffordance: true }; + const confirm: MessageItem = { title: 'Extend Your Trial', isCloseAffordance: true }; const cancel: MessageItem = { title: 'Cancel' }; const result = await window.showInformationMessage( - 'Your GitLens+ features trial has ended.\nPlease sign in to use GitLens+ features on public repos and get a free 7-day trial for both public and private repos.', + 'Your 3-day trial has ended.\nExtend your GitLens Pro trial to continue to use GitLens+ features on private repos, free for an additional 7-days.', { modal: true }, confirm, cancel, @@ -476,7 +480,7 @@ export class SubscriptionService implements Disposable { const confirm: MessageItem = { title: 'OK', isCloseAffordance: true }; const learn: MessageItem = { title: 'Learn More' }; const result = await window.showInformationMessage( - `You have started a ${days} day trial of GitLens+ features for both public and private repos.`, + `You have started a ${days}-day GitLens Pro trial of GitLens+ features on private repos.`, { modal: true }, confirm, learn, @@ -920,8 +924,8 @@ export class SubscriptionService implements Disposable { this._statusBarSubscription.backgroundColor = new ThemeColor('statusBarItem.warningBackground'); this._statusBarSubscription.tooltip = new MarkdownString( trial - ? `**Please verify your email**\n\nBefore you can start your **${effective.name}** trial, please verify the email for the account you created.\n\nClick for details` - : `**Please verify your email**\n\nBefore you can use GitLens+ features, please verify the email for the account you created.\n\nClick for details`, + ? `**Please verify your email**\n\nBefore you can start your **${effective.name}** trial, please verify your email address.\n\nClick for details` + : `**Please verify your email**\n\nBefore you can also use GitLens+ features on private repos, please verify your email address.\n\nClick for details`, true, ); } else { diff --git a/src/quickpicks/items/directive.ts b/src/quickpicks/items/directive.ts index 3ac09ee..8bc3748 100644 --- a/src/quickpicks/items/directive.ts +++ b/src/quickpicks/items/directive.ts @@ -8,7 +8,7 @@ export enum Directive { Noop, RequiresVerification, - RequiresFreeSubscription, + ExtendTrial, RequiresPaidSubscription, StartPreviewTrial, } @@ -46,21 +46,20 @@ export namespace DirectiveQuickPickItem { label = 'Try again'; break; case Directive.StartPreviewTrial: - label = 'Try GitLens+ Features Now'; - detail = 'Try GitLens+ features now, without an account, for 3 days'; + label = 'Start a GitLens Pro Trial'; + detail = 'Try GitLens+ features on private repos, free for 3 days, without an account'; + break; + case Directive.ExtendTrial: + label = 'Extend Your GitLens Pro Trial'; + detail = 'To continue to use GitLens+ features on private repos, free for an additional 7-days'; break; case Directive.RequiresVerification: label = 'Resend Verification Email'; - detail = 'You must verify your GitLens+ account email address before you can continue'; - break; - case Directive.RequiresFreeSubscription: - label = 'Sign in to GitLens+'; - detail = - 'To use GitLens+ features on public repos and get a free 7-day trial for both public and private repos'; + detail = 'You must verify your email address before you can continue'; break; case Directive.RequiresPaidSubscription: - label = 'Upgrade your account'; - detail = 'To use GitLens+ features on both public and private repos'; + label = 'Upgrade to Pro'; + detail = 'To use GitLens+ features on private repos'; break; } } diff --git a/src/subscription.ts b/src/subscription.ts index f9e73b0..be8a116 100644 --- a/src/subscription.ts +++ b/src/subscription.ts @@ -114,13 +114,13 @@ export function getSubscriptionPlan(id: SubscriptionPlanId, startedOn?: Date, ex export function getSubscriptionPlanName(id: SubscriptionPlanId) { switch (id) { case SubscriptionPlanId.FreePlus: - return 'GitLens+'; + return 'GitLens'; case SubscriptionPlanId.Pro: - return 'GitLens+ Pro'; + return 'GitLens Pro'; case SubscriptionPlanId.Teams: - return 'GitLens+ Teams'; + return 'GitLens Teams'; case SubscriptionPlanId.Enterprise: - return 'GitLens+ Enterprise'; + return 'GitLens Enterprise'; case SubscriptionPlanId.Free: default: return 'GitLens';