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