Skip to content

Commit 7b786d1

Browse files
committed
add cta in accounts to unlock kactus
1 parent 0202dd6 commit 7b786d1

File tree

9 files changed

+222
-79
lines changed

9 files changed

+222
-79
lines changed

app/src/lib/app-state.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ export enum PopupType {
195195
ExternalEditorFailed,
196196
}
197197

198+
export type PremiumType = 'premium' | 'enterprise'
199+
198200
export type Popup =
199201
| { type: PopupType.RenameBranch; repository: Repository; branch: Branch }
200202
| { type: PopupType.DeleteBranch; repository: Repository; branch: Branch }
@@ -226,7 +228,7 @@ export type Popup =
226228
| { type: PopupType.CreateSketchFile; repository: Repository }
227229
| {
228230
type: PopupType.PremiumUpsell
229-
enterprise: boolean
231+
kind: PremiumType | 'choice'
230232
user?: Account
231233
retryAction?: RetryAction
232234
}

app/src/lib/dispatcher/app-store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2986,7 +2986,7 @@ export class AppStore {
29862986
if (premiumType) {
29872987
await this._showPopup({
29882988
type: PopupType.PremiumUpsell,
2989-
enterprise: premiumType.enterprise,
2989+
kind: premiumType.enterprise ? 'enterprise' : 'premium',
29902990
user: premiumType.user,
29912991
retryAction,
29922992
})

app/src/ui/app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1090,7 +1090,7 @@ export class App extends React.Component<IAppProps, IAppState> {
10901090
case PopupType.PremiumUpsell:
10911091
return (
10921092
<PremiumUpsell
1093-
enterprise={popup.enterprise}
1093+
kind={popup.kind}
10941094
retryAction={popup.retryAction}
10951095
user={popup.user}
10961096
dispatcher={this.props.dispatcher}

app/src/ui/preferences/accounts.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface IAccountsProps {
1616
readonly onDotComSignIn: () => void
1717
readonly onEnterpriseSignIn: () => void
1818
readonly onLogout: (account: Account) => void
19+
readonly onShowUnlockKactusPopup: (account: Account) => void
1920
}
2021

2122
enum SignInType {
@@ -61,9 +62,19 @@ export class Accounts extends React.Component<IAccountsProps, {}> {
6162
@{account.login}
6263
</div>
6364
</div>
64-
<Button onClick={this.logout(account)}>
65-
{__DARWIN__ ? 'Sign Out' : 'Sign out'}
66-
</Button>
65+
<div className="actions-wrapper">
66+
{account.unlockedEnterpriseKactus || account.unlockedEnterpriseKactus
67+
? <span className="kactus-unlocked">✅ Kactus unlocked</span>
68+
: <Button
69+
className="action-button"
70+
onClick={this.unlockKactus(account)}
71+
>
72+
Unlock Kactus
73+
</Button>}
74+
<Button onClick={this.logout(account)}>
75+
{__DARWIN__ ? 'Sign Out' : 'Sign out'}
76+
</Button>
77+
</div>
6778
</Row>
6879
)
6980
}
@@ -113,4 +124,10 @@ export class Accounts extends React.Component<IAccountsProps, {}> {
113124
this.props.onLogout(account)
114125
}
115126
}
127+
128+
private unlockKactus = (account: Account) => {
129+
return () => {
130+
this.props.onShowUnlockKactusPopup(account)
131+
}
132+
}
116133
}

app/src/ui/preferences/preferences.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
setGlobalConfigValue,
2020
} from '../../lib/git/config'
2121
import { lookupPreferredEmail } from '../../lib/email'
22+
import { PopupType } from '../../lib/app-state'
2223

2324
interface IPreferencesProps {
2425
readonly dispatcher: Dispatcher
@@ -132,6 +133,14 @@ export class Preferences extends React.Component<
132133
this.props.dispatcher.removeAccount(account)
133134
}
134135

136+
private onShowUnlockKactusPopup = (account: Account) => {
137+
this.props.dispatcher.showPopup({
138+
type: PopupType.PremiumUpsell,
139+
kind: 'choice',
140+
user: account,
141+
})
142+
}
143+
135144
private renderActiveTab() {
136145
const index = this.state.selectedIndex
137146
switch (index) {
@@ -143,6 +152,7 @@ export class Preferences extends React.Component<
143152
onDotComSignIn={this.onDotComSignIn}
144153
onEnterpriseSignIn={this.onEnterpriseSignIn}
145154
onLogout={this.onLogout}
155+
onShowUnlockKactusPopup={this.onShowUnlockKactusPopup}
146156
/>
147157
)
148158
case PreferencesTab.Git: {

app/src/ui/premium-upsell/premium-upsell.tsx

Lines changed: 97 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ import { shell } from '../../lib/dispatcher/app-shell'
1212
import { CouponInput } from './coupon-input'
1313
import { LinkButton } from '../lib/link-button'
1414
import { CallToAction } from '../lib/call-to-action'
15+
import { PremiumType } from '../../lib/app-state'
1516

1617
interface IPremiumUpsellProps {
1718
/** A function called when the dialog is dismissed. */
1819
readonly onDismissed: () => void
1920
readonly dispatcher: Dispatcher
2021
readonly user?: Account
2122
readonly isUnlockingKactusFullAccess: boolean
22-
readonly enterprise: boolean
23+
readonly kind: PremiumType | 'choice'
2324
readonly retryAction?: RetryAction
2425
}
2526

@@ -28,10 +29,42 @@ interface IPremiumUpsellState {
2829
readonly showingCheckout: boolean
2930
readonly loadingCheckout: boolean
3031
readonly coupon: string
31-
readonly plan: string
3232
readonly couponState: IAPICoupon | 'loading' | null
33+
readonly choice: PremiumType
3334
}
3435

36+
const EnterpriseCopy = () =>
37+
<ul>
38+
<li>Unlimited public repositories</li>
39+
<li>
40+
No locked-in commitment: you can always generate the sketch files to
41+
switch back
42+
</li>
43+
<li>
44+
<strong>Unlimited private repositories</strong>
45+
</li>
46+
<li>
47+
<strong>Support single sign-on and on-premises deployment</strong>
48+
</li>
49+
<li>
50+
<strong>
51+
Support any git server (BitBucket, Gitlab, self-hosted, etc.)
52+
</strong>
53+
</li>
54+
</ul>
55+
56+
const PremiumCopy = () =>
57+
<ul>
58+
<li>Unlimited public repositories</li>
59+
<li>
60+
No locked-in commitment: you can always generate the sketch files to
61+
switch back
62+
</li>
63+
<li>
64+
<strong>Unlimited private repositories</strong>
65+
</li>
66+
</ul>
67+
3568
export class PremiumUpsell extends React.Component<
3669
IPremiumUpsellProps,
3770
IPremiumUpsellState
@@ -45,8 +78,8 @@ export class PremiumUpsell extends React.Component<
4578
this.state = {
4679
showingCheckout: false,
4780
loadingCheckout: false,
81+
choice: this.props.kind !== 'choice' ? this.props.kind : 'premium',
4882
coupon: '',
49-
plan: 'kactus-1-month',
5083
couponState: null,
5184
}
5285
}
@@ -83,7 +116,7 @@ export class PremiumUpsell extends React.Component<
83116
}
84117
await this.props.dispatcher.unlockKactus(this.props.user, token.id, {
85118
email: token.email,
86-
enterprise: this.props.enterprise,
119+
enterprise: this.state.choice === 'enterprise',
87120
coupon: this.state.coupon !== '' ? this.state.coupon : undefined,
88121
})
89122

@@ -152,8 +185,10 @@ export class PremiumUpsell extends React.Component<
152185
<div>
153186
<p>
154187
Hey! This feature is only available in the{' '}
155-
{this.props.enterprise ? 'enterprise' : 'full access'} version
156-
of Kactus.
188+
{this.state.choice === 'enterprise'
189+
? 'enterprise'
190+
: 'full access'}{' '}
191+
version of Kactus.
157192
</p>
158193
<p>
159194
You need to login to Kactus using GitHub before unlocking it.
@@ -172,8 +207,9 @@ export class PremiumUpsell extends React.Component<
172207
}
173208

174209
if (
175-
(!this.props.enterprise && this.props.user.unlockedKactus) ||
176-
(this.props.enterprise && this.props.user.unlockedEnterpriseKactus)
210+
(this.state.choice === 'premium' && this.props.user.unlockedKactus) ||
211+
(this.state.choice === 'enterprise' &&
212+
this.props.user.unlockedEnterpriseKactus)
177213
) {
178214
return (
179215
<Dialog
@@ -186,67 +222,37 @@ export class PremiumUpsell extends React.Component<
186222
)
187223
}
188224

189-
const copy = this.props.enterprise
190-
? <div>
191-
<p>
192-
Hey! This feature is only available in the enterprise version of
193-
Kactus.
194-
</p>
195-
<ul>
196-
<li>Unlimited public repositories</li>
197-
<li>
198-
No locked-in commitment: you can always generate the sketch files
199-
to switch back
200-
</li>
201-
<li>
202-
<strong>Unlimited private repositories</strong>
203-
</li>
204-
<li>
205-
<strong>Support single sign-on and on-premises deployment</strong>
206-
</li>
207-
<li>
208-
<strong>
209-
Support any git server (BitBucket, Gitlab, self-hosted, etc.)
210-
</strong>
211-
</li>
212-
</ul>
213-
<p>
214-
More information available{' '}
215-
<LinkButton onClick={this.onExternalLink}>here</LinkButton>.
216-
</p>
217-
<CouponInput
218-
couponState={couponState}
219-
coupon={coupon}
220-
onValueChanged={this.onCouponChange}
221-
/>
222-
</div>
223-
: <div>
224-
<p>
225-
Hey! This feature is only available in the full access version of
226-
Kactus.
227-
</p>
228-
<ul>
229-
<li>Unlimited public repositories</li>
230-
<li>
231-
No locked-in commitment: you can always generate the sketch files
232-
to switch back
233-
</li>
234-
<li>
235-
<strong>Unlimited private repositories</strong>
236-
</li>
237-
</ul>
238-
<p>
239-
More information available{' '}
240-
<LinkButton onClick={this.onExternalLink}>here</LinkButton>.
241-
</p>
242-
<CouponInput
243-
couponState={couponState}
244-
coupon={coupon}
245-
onValueChanged={this.onCouponChange}
246-
/>
247-
</div>
225+
const copy =
226+
this.props.kind === 'choice'
227+
? <div className="choices">
228+
<div
229+
className={
230+
'plan' + (this.state.choice === 'premium' ? ' selected' : '')
231+
}
232+
onClick={this.selectPremiumPlan}
233+
>
234+
<h2>Premium</h2>
235+
<h3>$4.99</h3>
236+
<h4>per month</h4>
237+
<PremiumCopy />
238+
</div>
239+
<div
240+
className={
241+
'plan' + (this.state.choice === 'enterprise' ? ' selected' : '')
242+
}
243+
onClick={this.selectEnterprisePlan}
244+
>
245+
<h2>Enterprise</h2>
246+
<h3>$11.99</h3>
247+
<h4>per month</h4>
248+
<EnterpriseCopy />
249+
</div>
250+
</div>
251+
: this.state.choice === 'enterprise'
252+
? <EnterpriseCopy />
253+
: <PremiumCopy />
248254

249-
let price = this.props.enterprise ? 11.99 : 4.99
255+
let price = this.state.choice === 'enterprise' ? 11.99 : 4.99
250256
if (couponState && couponState !== 'loading') {
251257
if (couponState.percent_off) {
252258
price = price * (100 - couponState.percent_off) / 100
@@ -263,7 +269,7 @@ export class PremiumUpsell extends React.Component<
263269
onLoaded={this.finishedLoadingCheckout}
264270
onToken={this.onToken}
265271
user={this.props.user}
266-
enterprise={this.props.enterprise}
272+
enterprise={this.state.choice === 'enterprise'}
267273
price={price}
268274
/>}
269275
{!showingCheckout &&
@@ -276,6 +282,16 @@ export class PremiumUpsell extends React.Component<
276282
>
277283
<DialogContent>
278284
{copy}
285+
<p>
286+
More information available{' '}
287+
<LinkButton onClick={this.onExternalLink}>here</LinkButton>.
288+
</p>
289+
{this.props.kind === 'choice' &&
290+
<CouponInput
291+
couponState={couponState}
292+
coupon={coupon}
293+
onValueChanged={this.onCouponChange}
294+
/>}
279295
</DialogContent>
280296

281297
<DialogFooter>
@@ -331,4 +347,16 @@ export class PremiumUpsell extends React.Component<
331347
private signInEnterprise = () => {
332348
this.props.dispatcher.showEnterpriseSignInDialog(this.props.retryAction)
333349
}
350+
351+
private selectPremiumPlan = () => {
352+
this.setState({
353+
choice: 'premium',
354+
})
355+
}
356+
357+
private selectEnterprisePlan = () => {
358+
this.setState({
359+
choice: 'enterprise',
360+
})
361+
}
334362
}

app/styles/ui/_button.scss

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,17 @@
2626
box-shadow: 0 0 0 1px var(--secondary-button-focus-shadow-color);
2727
}
2828

29-
&:disabled { opacity: 0.6; }
29+
&:disabled {
30+
opacity: 0.6;
31+
}
3032

3133
.octicon {
3234
vertical-align: middle;
3335
}
3436
}
3537

36-
.button-component[type='submit'] {
38+
.button-component[type='submit'],
39+
.button-component.action-button {
3740
background-color: var(--button-background);
3841
color: var(--button-text-color);
3942
border: 1px solid var(--button-background);

0 commit comments

Comments
 (0)