@@ -16,6 +16,9 @@ import Alert from "./components/Alert";
16
16
import { useLocalStorage } from "./hooks/use-local-storage" ;
17
17
import { Subscription } from "@gitpod/gitpod-protocol/lib/accounting-protocol" ;
18
18
import { TeamSubscription , TeamSubscription2 } from "@gitpod/gitpod-protocol/lib/team-subscription-protocol" ;
19
+ import { useConfetti } from "./contexts/ConfettiContext" ;
20
+ import { resetAllNotifications } from "./AppNotifications" ;
21
+ import { Plans } from "@gitpod/gitpod-protocol/lib/plans" ;
19
22
20
23
/**
21
24
* Keys of known page params
@@ -37,7 +40,11 @@ type PageParams = {
37
40
type PageState = {
38
41
phase : "call-to-action" | "trigger-signup" | "wait-for-signup" | "cleanup" | "done" ;
39
42
attributionId ?: string ;
40
- oldSubscriptionId ?: string ;
43
+ old ?: {
44
+ planName : string ;
45
+ planDetails : string ;
46
+ subscriptionId : string ;
47
+ } ;
41
48
} ;
42
49
43
50
function SwitchToPAYG ( ) {
@@ -52,9 +59,11 @@ function SwitchToPAYG() {
52
59
const [ errorMessage , setErrorMessage ] = useState < string | undefined > ( ) ;
53
60
const [ showBillingSetupModal , setShowBillingSetupModal ] = useState < boolean > ( false ) ;
54
61
const [ pendingStripeSubscription , setPendingStripeSubscription ] = useState < boolean > ( false ) ;
62
+ const [ droppedConfetti , setDroppedConfetti ] = useState < boolean > ( false ) ;
63
+ const { dropConfetti } = useConfetti ( ) ;
55
64
56
65
useEffect ( ( ) => {
57
- const { phase, attributionId, oldSubscriptionId } = pageState ;
66
+ const { phase, attributionId, old } = pageState ;
58
67
const { setupIntentId, type, oldSubscriptionOrTeamId } = pageParams || { } ;
59
68
60
69
if ( ! type ) {
@@ -74,10 +83,10 @@ function SwitchToPAYG() {
74
83
( async ( ) => {
75
84
// Old Subscription still active?
76
85
let derivedAttributionId : string | undefined = undefined ;
77
- let oldSubscriptionId : string ;
86
+ let old : PageState [ "old" ] ;
78
87
switch ( type ) {
79
88
case "personalSubscription" : {
80
- oldSubscriptionId = oldSubscriptionOrTeamId ;
89
+ const oldSubscriptionId = oldSubscriptionOrTeamId ;
81
90
const statement = await getGitpodService ( ) . server . getAccountStatement ( { } ) ;
82
91
if ( ! statement ) {
83
92
console . error ( "No AccountStatement!" ) ;
@@ -96,12 +105,17 @@ function SwitchToPAYG() {
96
105
}
97
106
return ;
98
107
}
108
+ old = {
109
+ subscriptionId : sub . uid ,
110
+ planName : Plans . getById ( sub . planId ! ) ! . name ,
111
+ planDetails : "personal" ,
112
+ } ;
99
113
derivedAttributionId = AttributionId . render ( { kind : "user" , userId : sub . userId } ) ;
100
114
break ;
101
115
}
102
116
103
117
case "teamSubscription" : {
104
- oldSubscriptionId = oldSubscriptionOrTeamId ;
118
+ const oldSubscriptionId = oldSubscriptionOrTeamId ;
105
119
const tss = await getGitpodService ( ) . server . tsGet ( ) ;
106
120
const ts = tss . find ( ( s ) => s . id === oldSubscriptionId ) ;
107
121
if ( ! ts ) {
@@ -116,6 +130,11 @@ function SwitchToPAYG() {
116
130
}
117
131
return ;
118
132
}
133
+ old = {
134
+ subscriptionId : ts . id ,
135
+ planName : Plans . getById ( ts . planId ! ) ! . name ,
136
+ planDetails : `${ ts . quantity } Members` ,
137
+ } ;
119
138
// no derivedAttributionId: user has to select/create new org
120
139
break ;
121
140
}
@@ -127,7 +146,6 @@ function SwitchToPAYG() {
127
146
console . error ( `No TeamSubscription2 for team ${ teamId } !` ) ;
128
147
break ;
129
148
}
130
- oldSubscriptionId = ts2 . id ;
131
149
const now = new Date ( ) . toISOString ( ) ;
132
150
if ( TeamSubscription2 . isCancelled ( ts2 , now ) || ! TeamSubscription2 . isActive ( ts2 , now ) ) {
133
151
// We're happy!
@@ -136,14 +154,19 @@ function SwitchToPAYG() {
136
154
}
137
155
return ;
138
156
}
157
+ old = {
158
+ subscriptionId : ts2 . id ,
159
+ planName : Plans . getById ( ts2 . planId ! ) ! . name ,
160
+ planDetails : `${ ts2 . quantity } Members` ,
161
+ } ;
139
162
derivedAttributionId = AttributionId . render ( { kind : "team" , teamId } ) ;
140
163
break ;
141
164
}
142
165
}
143
- if ( ! cancelled ) {
166
+ if ( ! cancelled && ! attributionId ) {
144
167
setPageState ( ( s ) => {
145
168
const attributionId = s . attributionId || derivedAttributionId ;
146
- return { ...s , attributionId, oldSubscriptionId } ;
169
+ return { ...s , attributionId, old } ;
147
170
} ) ;
148
171
}
149
172
} ) ( ) . catch ( console . error ) ;
@@ -248,8 +271,9 @@ function SwitchToPAYG() {
248
271
}
249
272
250
273
case "cleanup" : {
274
+ const oldSubscriptionId = old ?. subscriptionId ;
251
275
if ( ! oldSubscriptionId ) {
252
- setErrorMessage ( "Error during params parsing: oldSubscriptionOrTeamId not set!" ) ;
276
+ setErrorMessage ( "Error during cleanup: old.oldSubscriptionId not set!" ) ;
253
277
return ;
254
278
}
255
279
switch ( type ) {
@@ -291,10 +315,15 @@ function SwitchToPAYG() {
291
315
}
292
316
293
317
case "done" :
294
- // Render hooray and confetti!
318
+ // Hooray and confetti!
319
+ resetAllNotifications ( ) ;
320
+ if ( ! droppedConfetti ) {
321
+ setDroppedConfetti ( true ) ;
322
+ dropConfetti ( ) ;
323
+ }
295
324
return ;
296
325
}
297
- } , [ location . search , pageParams , pageState , setPageState ] ) ;
326
+ } , [ location . search , pageParams , pageState , setPageState , dropConfetti , droppedConfetti ] ) ;
298
327
299
328
const onUpgradePlan = useCallback ( async ( ) => {
300
329
if ( pageState . phase !== "call-to-action" || ! pageState . attributionId ) {
@@ -334,8 +363,11 @@ function SwitchToPAYG() {
334
363
} else if ( pageParams ?. type === "teamSubscription2" ) {
335
364
titleModifier = "organization's plan" ;
336
365
}
366
+
367
+ const planName = pageState . old ?. planName || "Legacy Plan" ;
368
+ const planDescription = pageState . old ?. planDetails || "" ;
337
369
return (
338
- < div className = "flex flex-col max-h-screen max-w-2xl mx-auto items-center w-full mt-32" >
370
+ < div className = "flex flex-col max-h-screen max-w-3xl mx-auto items-center w-full mt-32" >
339
371
< h1 > { `Update your ${ titleModifier } ` } </ h1 >
340
372
< div className = "w-full text-gray-500 text-center" >
341
373
Switch to the new pricing model to keep uninterrupted access and get < strong > large workspaces</ strong > { " " }
@@ -347,24 +379,39 @@ function SwitchToPAYG() {
347
379
Learn more →
348
380
</ a >
349
381
</ div >
350
- < div className = "w-96 mt-6 text-sm" >
351
- < div className = "w-full h-h96 rounded-xl text-center text-gray-500 bg-gray-50 dark:bg-gray-800" > </ div >
352
- < div className = "mt-6 w-full text-center" >
353
- { renderCard ( {
354
- headline : "LEGACY SUBSCRIPTION" ,
355
- title : "Team Professional" ,
356
- description : "29 Members" ,
357
- selected : false ,
358
- action : < > Discontinued</ > ,
359
- additionalStyles : "w-80" ,
360
- } ) }
361
- </ div >
382
+ < div className = "mt-6 space-x-3 flex" >
383
+ { renderCard ( {
384
+ headline : "LEGACY PLAN" ,
385
+ title : planName ,
386
+ description : planDescription ,
387
+ selected : false ,
388
+ action : (
389
+ < Alert type = "error" >
390
+ Discontinued on < strong > March 31st</ strong >
391
+ </ Alert >
392
+ ) ,
393
+ additionalStyles : "" ,
394
+ } ) }
395
+ { renderCard ( {
396
+ headline : "NEW PLAN" ,
397
+ title : "$9 / month (1,000 credits)" ,
398
+ description : "Pay-as-you-go after that for $0.036 per credit." ,
399
+ selected : true ,
400
+ action : (
401
+ < a className = "gp-link" href = "https://www.gitpod.io/pricing#cost-estimator" >
402
+ Estimate costs
403
+ </ a >
404
+ ) ,
405
+ additionalStyles : "" ,
406
+ } ) }
407
+ </ div >
408
+ < div className = "w-full mt-6 grid justify-items-center" >
362
409
{ pendingStripeSubscription && (
363
410
< div className = "w-full mt-6 text-center" >
364
411
< SpinnerLoader small = { false } content = "Creating subscription with Stripe" />
365
412
</ div >
366
413
) }
367
- < div className = "w-full mt-10 text-center" >
414
+ < div className = "w-96 mt-10 text-center" >
368
415
< button
369
416
className = "w-full"
370
417
onClick = { onUpgradePlan }
@@ -435,7 +482,7 @@ function renderCard(props: {
435
482
>
436
483
{ props . description }
437
484
</ div >
438
- < div className = "text-xl my-1 flex-row flex align-middle" >
485
+ < div className = "text-xl my-1 flex-row flex align-middle items-end " >
439
486
< div
440
487
className = { `text-sm font-normal truncate w-full ${
441
488
props . selected ? "text-gray-300 dark:text-gray-500" : "text-gray-500 dark:text-gray-400"
0 commit comments