3
3
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
4
4
*/
5
5
6
- import { Alert , Tag } from "antd" ;
6
+ import { Alert , Modal , Space , Tag } from "antd" ;
7
7
import humanizeList from "humanize-list" ;
8
8
import { join } from "path" ;
9
9
10
10
import {
11
11
CSS ,
12
12
React ,
13
+ redux ,
14
+ useEffect ,
15
+ useForceUpdate ,
13
16
useMemo ,
17
+ useRef ,
14
18
useState ,
15
- redux ,
19
+ useTypedRedux ,
16
20
} from "@cocalc/frontend/app-framework" ;
17
- import { A , Icon , Paragraph } from "@cocalc/frontend/components" ;
21
+ import { A , Icon , Paragraph , Text } from "@cocalc/frontend/components" ;
22
+ import { SiteName } from "@cocalc/frontend/customize" ;
18
23
import { appBasePath } from "@cocalc/frontend/customize/app-base-path" ;
24
+ import { TimeAmount } from "@cocalc/frontend/editors/stopwatch/time" ;
25
+ import { open_new_tab } from "@cocalc/frontend/misc" ;
19
26
import {
20
27
SiteLicenseInput ,
21
28
useManagedLicenses ,
22
29
} from "@cocalc/frontend/site-licenses/input" ;
23
30
import { BuyLicenseForProject } from "@cocalc/frontend/site-licenses/purchase/buy-license-for-project" ;
31
+ import track from "@cocalc/frontend/user-tracking" ;
24
32
import {
25
33
BANNER_NON_DISMISSABLE_DAYS ,
26
34
EVALUATION_PERIOD_DAYS ,
27
35
LICENSE_MIN_PRICE ,
28
36
} from "@cocalc/util/consts/billing" ;
29
- import { server_time } from "@cocalc/util/relative-time " ;
37
+ import { server_time } from "@cocalc/util/misc " ;
30
38
import { COLORS , DOC_URL } from "@cocalc/util/theme" ;
31
39
import { useAllowedFreeProjectToRun } from "./client-side-throttle" ;
40
+ import { useProjectContext } from "./context" ;
32
41
import { applyLicense } from "./settings/site-license" ;
33
42
34
43
export const DOC_TRIAL = "https://doc.cocalc.com/trial.html" ;
@@ -133,22 +142,22 @@ export const TrialBanner: React.FC<BannerProps> = React.memo(
133
142
< strong > No upgrades</ strong >
134
143
) ;
135
144
136
- function renderComputeServer ( ) {
137
- return (
138
- < a
139
- style = { a_style }
140
- onClick = { ( ) => {
141
- const actions = redux . getProjectActions ( project_id ) ;
142
- actions . setState ( { create_compute_server : true } ) ;
143
- actions . set_active_tab ( "servers" , {
144
- change_history : true ,
145
- } ) ;
146
- } }
147
- >
148
- using a compute server
149
- </ a >
150
- ) ;
151
- }
145
+ // function renderComputeServer() {
146
+ // return (
147
+ // <a
148
+ // style={a_style}
149
+ // onClick={() => {
150
+ // const actions = redux.getProjectActions(project_id);
151
+ // actions.setState({ create_compute_server: true });
152
+ // actions.set_active_tab("servers", {
153
+ // change_history: true,
154
+ // });
155
+ // }}
156
+ // >
157
+ // using a compute server
158
+ // </a>
159
+ // );
160
+ // }
152
161
153
162
function renderBuyAndUpgrade ( text : string = "with a license" ) : JSX . Element {
154
163
return (
@@ -160,9 +169,10 @@ export const TrialBanner: React.FC<BannerProps> = React.memo(
160
169
asLink = { true }
161
170
style = { { padding : 0 , fontSize : style . fontSize , ...a_style } }
162
171
/>
163
- . Price starts at { LICENSE_MIN_PRICE } .{ " " }
172
+ .< br />
173
+ Price starts at { LICENSE_MIN_PRICE } .{ " " }
164
174
< a style = { a_style } onClick = { ( ) => setShowAddLicense ( true ) } >
165
- Apply your license to this project.
175
+ Apply your license to this project
166
176
</ a >
167
177
</ >
168
178
) ;
@@ -183,7 +193,7 @@ export const TrialBanner: React.FC<BannerProps> = React.memo(
183
193
return (
184
194
< span >
185
195
{ trial_project } You can improve hosting quality and get internet
186
- access { renderComputeServer ( ) } or { renderBuyAndUpgrade ( ) } .
196
+ access { /* { renderComputeServer()} */ } or { renderBuyAndUpgrade ( ) } .
187
197
< br />
188
198
Otherwise, { humanizeList ( [ ...NO_HOST , NO_INTERNET ] ) }
189
199
{ "." }
@@ -251,20 +261,28 @@ export const TrialBanner: React.FC<BannerProps> = React.memo(
251
261
return null ;
252
262
}
253
263
264
+ function renderClose ( ) {
265
+ return (
266
+ < Tag
267
+ style = { { marginTop : "10px" , fontSize : style . fontSize } }
268
+ color = "#faad14"
269
+ >
270
+ < Icon name = "times" /> Dismiss
271
+ </ Tag >
272
+ ) ;
273
+ }
274
+
275
+ function renderCountDown ( ) {
276
+ if ( closable ) return ;
277
+
278
+ return < CountdownProject fontSize = { style . fontSize } /> ;
279
+ }
280
+
254
281
return (
255
282
< Alert
256
283
type = "warning"
257
284
closable = { closable }
258
- closeIcon = {
259
- closable ? (
260
- < Tag
261
- style = { { marginTop : "10px" , fontSize : style . fontSize } }
262
- color = "#faad14"
263
- >
264
- < Icon name = "times" /> Dismiss
265
- </ Tag >
266
- ) : undefined
267
- }
285
+ closeIcon = { renderClose ( ) }
268
286
style = { style }
269
287
banner = { true }
270
288
showIcon = { ! closable || ( internet && host ) }
@@ -286,15 +304,16 @@ export const TrialBanner: React.FC<BannerProps> = React.memo(
286
304
padding : 0 ,
287
305
} }
288
306
>
307
+ { renderCountDown ( ) }
289
308
{ renderMessage ( ) } { renderLearnMore ( style . color ) }
290
309
</ Paragraph >
291
- { showAddLicense && (
310
+ { showAddLicense ? (
292
311
< BannerApplySiteLicense
293
312
project_id = { project_id }
294
313
projectSiteLicenses = { projectSiteLicenses }
295
314
setShowAddLicense = { setShowAddLicense }
296
315
/>
297
- ) }
316
+ ) : undefined }
298
317
</ >
299
318
}
300
319
/>
@@ -356,3 +375,137 @@ export const BannerApplySiteLicense: React.FC<ApplyLicenseProps> = (
356
375
</ >
357
376
) ;
358
377
} ;
378
+
379
+ interface CountdownProjectProps {
380
+ fontSize : CSS [ "fontSize" ] ;
381
+ }
382
+
383
+ function CountdownProject ( { fontSize } : CountdownProjectProps ) {
384
+ const { status, project, project_id, actions } = useProjectContext ( ) ;
385
+ const limit_min = useTypedRedux ( "customize" , "limit_free_project_uptime" ) ;
386
+ const [ showInfo , setShowInfo ] = useState < boolean > ( false ) ;
387
+ const openFiles = useTypedRedux ( { project_id } , "open_files_order" ) ;
388
+ const triggered = useRef < boolean > ( false ) ;
389
+ const update = useForceUpdate ( ) ;
390
+
391
+ useEffect ( ( ) => {
392
+ const interval = setInterval ( update , 1000 ) ;
393
+ return ( ) => clearInterval ( interval ) ;
394
+ } , [ ] ) ;
395
+
396
+ if (
397
+ status . get ( "state" ) !== "running" ||
398
+ project == null ||
399
+ limit_min == null ||
400
+ limit_min <= 0
401
+ ) {
402
+ return null ;
403
+ }
404
+
405
+ // start_ts is e.g. 1508576664416
406
+ const start_ts = project . getIn ( [ "status" , "start_ts" ] ) ;
407
+ if ( start_ts == undefined ) return null ;
408
+
409
+ const shutdown_ts = start_ts + 1000 * 60 * limit_min ;
410
+ const countdown = shutdown_ts - server_time ( ) . getTime ( ) ;
411
+ const countdwon0 = countdown > 0 ? countdown : 0 ;
412
+
413
+ if ( countdown < 0 && ! triggered . current ) {
414
+ triggered . current = true ;
415
+
416
+ // This closes all tabs and then stops the project.
417
+ openFiles . map ( ( path ) => actions ?. close_tab ( path ) ) ;
418
+ redux . getActions ( "projects" ) . stop_project ( project_id ) ;
419
+ }
420
+
421
+ function renderInfo ( ) {
422
+ return (
423
+ < Modal
424
+ title = {
425
+ < Space >
426
+ < Icon name = "hand-stop" /> Automatic Project Shutdown
427
+ </ Space >
428
+ }
429
+ open = { showInfo }
430
+ onOk = { ( ) => open_new_tab ( BUY_A_LICENSE_URL ) }
431
+ onCancel = { ( ) => setShowInfo ( false ) }
432
+ >
433
+ < Paragraph >
434
+ < A href = { "https://doc.cocalc.com/trial.html" } > Trial projects</ A > have
435
+ a maximum uptime of { limit_min } minutes. After that period, the
436
+ project will stop and interrupt your work.
437
+ </ Paragraph >
438
+ < Paragraph strong >
439
+ This shutdown timer only exists for projects without any upgrades!
440
+ </ Paragraph >
441
+ < Alert
442
+ banner
443
+ type = "info"
444
+ showIcon = { false }
445
+ message = {
446
+ < >
447
+ < Paragraph strong >
448
+ This is a call to support < SiteName /> by{ " " }
449
+ < A href = { BUY_A_LICENSE_URL } > purchasing a license</ A > .
450
+ </ Paragraph >
451
+ < Paragraph >
452
+ Behind this curtains,{ " " }
453
+ < A href = { "/about/team" } > humans are working hard</ A > to keep the
454
+ service running and improving it constantly. Your files and
455
+ computations < A href = { "/info/status" } > run in our cluster</ A > ,
456
+ which costs money as well.
457
+ </ Paragraph >
458
+ < Paragraph >
459
+ < SiteName /> receives no funding from large venture captital
460
+ organizations or charitable foundations. The site depends
461
+ entirely < Text strong > on your financial support</ Text > to
462
+ continue operating. Without your financial support this service
463
+ will not survive long-term!
464
+ </ Paragraph >
465
+ < Paragraph >
466
+ < A
467
+ href = {
468
+ "/support/new?hideExtra=true&type=purchase&subject=Support+CoCalc&title=Support+CoCalc"
469
+ }
470
+ >
471
+ Contact us
472
+ </ A > { " " }
473
+ if you can give support in other ways.
474
+ </ Paragraph >
475
+ </ >
476
+ }
477
+ />
478
+ </ Modal >
479
+ ) ;
480
+ }
481
+
482
+ return (
483
+ < >
484
+ { renderInfo ( ) }
485
+ < Tag
486
+ style = { {
487
+ marginTop : "5px" ,
488
+ fontSize,
489
+ float : "right" ,
490
+ fontWeight : "bold" ,
491
+ color : COLORS . ANTD_RED ,
492
+ cursor : "pointer" ,
493
+ } }
494
+ color = { COLORS . GRAY_LL }
495
+ onClick = { ( ) => {
496
+ setShowInfo ( true ) ;
497
+ track ( "trial-banner" , { what : "countdown" , project_id } ) ;
498
+ } }
499
+ >
500
+ < TimeAmount
501
+ key = { "time" }
502
+ amount = { countdwon0 }
503
+ compact = { true }
504
+ showIcon = { true }
505
+ countdown = { countdwon0 }
506
+ style = { { color : COLORS . ANTD_RED } }
507
+ />
508
+ </ Tag >
509
+ </ >
510
+ ) ;
511
+ }
0 commit comments