@blocklet/launcher-workflow 2.3.80 → 2.3.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/es/purchase.js DELETED
@@ -1,675 +0,0 @@
1
- /* eslint-disable react-hooks/exhaustive-deps */
2
- import Button from '@arcblock/ux/lib/Button';
3
- import Center from '@arcblock/ux/lib/Center';
4
- import useConnect from '@arcblock/did-connect/lib/Connect/use-connect';
5
- import CompactLayout from '@blocklet/launcher-layout/lib/compact-layout';
6
- import { NFT_TYPE_SERVERLESS } from '@blocklet/launcher-util/es/constant';
7
- import useMobile from '@blocklet/launcher-ux/lib/use-mobile';
8
- import styled from '@emotion/styled';
9
- import CircularProgress from '@mui/material/CircularProgress';
10
- import Tooltip from '@mui/material/Tooltip';
11
- import { Splide, SplideSlide, SplideTrack } from '@splidejs/react-splide';
12
- import { Grid } from '@splidejs/splide-extension-grid';
13
- import get from 'lodash.get';
14
- import PropTypes from 'prop-types';
15
- import React, { useEffect } from 'react';
16
- import { Link, useNavigate, useSearchParams } from 'react-router-dom';
17
- import useMeasure from 'react-use/lib/useMeasure';
18
- import useSetState from 'react-use/lib/useSetState';
19
- import joinURL from 'url-join';
20
- import { API_DID_PREFIX } from '@arcblock/did-connect/lib/constant';
21
- import ResultMessage from '@blocklet/launcher-layout/lib/launch-result-message';
22
- import formatError from '@blocklet/launcher-util/es/format-error';
23
- import useBrowser from '@arcblock/react-hooks/lib/useBrowser';
24
- import '@splidejs/splide/dist/css/splide.min.css';
25
- var NextIcon = function NextIcon(props) {
26
- return /*#__PURE__*/_jsx("svg", {
27
- ...props,
28
- children: /*#__PURE__*/_jsx("path", {
29
- d: "m15.5.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4L15.5.912z"
30
- })
31
- });
32
- };
33
- NextIcon.defaultProps = {
34
- xmlns: "http://www.w3.org/2000/svg",
35
- width: "40",
36
- height: "40"
37
- };
38
- var PrevIcon = function PrevIcon(props) {
39
- return /*#__PURE__*/_jsx("svg", {
40
- ...props,
41
- children: /*#__PURE__*/_jsx("path", {
42
- d: "m15.5.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4L15.5.912z"
43
- })
44
- });
45
- };
46
- PrevIcon.defaultProps = {
47
- xmlns: "http://www.w3.org/2000/svg",
48
- width: "40",
49
- height: "40"
50
- };
51
- import InProgressSession from './components/in-progress-session';
52
- import Layout from './components/layout';
53
- import Body from './components/layout/body';
54
- import Footer from './components/layout/footer';
55
- import Header from './components/layout/header';
56
- import Plan from './components/plan';
57
- import { useLocaleContext } from './contexts/locale';
58
- import useRequest from './contexts/request';
59
- import { useSessionContext } from './contexts/session';
60
- import { useWorkflowContext } from './contexts/workflow';
61
- import { launchSession } from './util';
62
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
63
- const SLIDE_WIDTH = 300;
64
- const SLIDE_MIN_WIDTH = 280;
65
- const getSlideConfig = (maxLength, planLength) => {
66
- const factor = Math.min(maxLength, planLength);
67
- const width = factor < 3 ? SLIDE_MIN_WIDTH : SLIDE_WIDTH;
68
- return {
69
- perPage: factor,
70
- width: `${factor * width}px`,
71
- gap: '24px'
72
- };
73
- };
74
- function PurchasePage({
75
- disableBack
76
- }) {
77
- const {
78
- t,
79
- locale
80
- } = useLocaleContext();
81
- const {
82
- create,
83
- api: launchSessionAPI,
84
- baseURL
85
- } = useRequest();
86
- const {
87
- isMobile
88
- } = useMobile();
89
- const navigate = useNavigate();
90
- const {
91
- session,
92
- storage,
93
- events
94
- } = useSessionContext();
95
- const {
96
- connectApi,
97
- connectHolder
98
- } = useConnect();
99
- const [params, setParams] = useSearchParams();
100
- const {
101
- routerPrefix,
102
- embed,
103
- didPayPrefix,
104
- isPurchaseOnly
105
- } = useWorkflowContext();
106
- const [planListRef, {
107
- width
108
- }] = useMeasure();
109
- const [state, setState] = useSetState({
110
- loading: true,
111
- error: '',
112
- plans: [],
113
- paymentMethods: [],
114
- paymentMethod: params.get('paymentMethod'),
115
- sessionId: params.get('sessionId'),
116
- planId: params.get('planId'),
117
- showMorePrompt: false,
118
- arrows: false,
119
- perPage: 100,
120
- width: `${SLIDE_WIDTH}px`,
121
- gap: '16px'
122
- });
123
- const api = create({
124
- baseURL: didPayPrefix
125
- });
126
- const browser = useBrowser();
127
- const selectPlan = launch => {
128
- if (!state.plans.length) {
129
- return;
130
- }
131
- if (launch && launch.planId && state.plans.find(x => x._id === launch.planId)) {
132
- setState({
133
- planId: launch.planId
134
- });
135
- }
136
- const recommended = state.plans.find(x => !!x.isRecommended);
137
- if (recommended) {
138
- setState({
139
- planId: recommended._id
140
- });
141
- } else {
142
- setState({
143
- planId: state.plans[0]._id
144
- });
145
- }
146
- };
147
-
148
- // 用户退出的时候自动重新创建启动会话
149
- useEffect(() => {
150
- if (isPurchaseOnly) {
151
- return;
152
- }
153
- events.on('logout', () => {
154
- const metaUrl = params.get('blocklet_meta_url');
155
- launchSession.create(launchSessionAPI, routerPrefix, metaUrl, (err, launch) => {
156
- setState({
157
- sessionId: launch._id
158
- });
159
- selectPlan(launch);
160
- });
161
- });
162
- }, []);
163
-
164
- // 创建启动会话或者加载现有的会话
165
- useEffect(() => {
166
- Promise.all([api.get('/plans'), api.get('/payment-methods')]).then(([{
167
- data
168
- }, {
169
- data: {
170
- data: paymentMethods
171
- }
172
- }]) => {
173
- if (data.length === 0 || paymentMethods.length === 0) {
174
- setState({
175
- loading: false,
176
- error: t('purchase.noProducts')
177
- });
178
- return;
179
- }
180
- let plans = (data || []).sort((a, b) => b.weight - a.weight);
181
- const {
182
- components = []
183
- } = get(window, 'blockletMeta', {}) || {};
184
- const supported = components.every(x => get(x, 'meta.capabilities.serverless', true));
185
- plans.forEach(x => {
186
- if (get(x, 'extra.type') === 'serverless' && !supported) {
187
- x.available = false;
188
- x.reason = t('purchase.serverlessNotSupported');
189
- } else {
190
- x.available = true;
191
- }
192
- });
193
- plans = plans.sort((a, b) => {
194
- if (b.available && !a.available) {
195
- return 1;
196
- }
197
- if (!b.available && a.available) {
198
- return -1;
199
- }
200
- return 0;
201
- });
202
- setState({
203
- plans,
204
- paymentMethods,
205
- paymentMethod: paymentMethods[0]._id,
206
- loading: false
207
- });
208
- if (isPurchaseOnly) {
209
- selectPlan();
210
- return;
211
- }
212
- const metaUrl = params.get('blocklet_meta_url');
213
- if (params.get('sessionId')) {
214
- launchSession.load(launchSessionAPI, routerPrefix, params.get('sessionId'), metaUrl, (err, launch) => {
215
- selectPlan(launch);
216
- });
217
- } else if (metaUrl) {
218
- launchSession.create(launchSessionAPI, routerPrefix, metaUrl, (err, launch) => {
219
- setState({
220
- sessionId: launch._id
221
- });
222
- selectPlan(launch);
223
- });
224
- }
225
- }).catch(err => {
226
- setState({
227
- error: formatError(err)
228
- });
229
- });
230
- }, []);
231
- useEffect(() => {
232
- if (!state.sessionId) {
233
- return;
234
- }
235
- if (state.sessionId !== params.get('sessionId')) {
236
- params.set('sessionId', state.sessionId);
237
- setParams(params, {
238
- replace: true
239
- });
240
- }
241
- }, [state.sessionId]);
242
- useEffect(() => {
243
- if (!state.plans?.length || typeof state.perPage === 'undefined') {
244
- return;
245
- }
246
- const index = state.plans.findIndex(x => !!x.isRecommended && x.available);
247
- const pageIndex = Math.floor(index / state.perPage);
248
-
249
- // 如果推荐 Plan 不在第一页,则将推荐 Plan 放在第一个
250
- if (pageIndex >= 1) {
251
- const recommend = state.plans[index];
252
- const copy = [...state.plans];
253
- copy.splice(index, 1);
254
- copy.unshift(recommend);
255
- setState({
256
- plans: copy
257
- });
258
- }
259
- }, [state.perPage, state.plans]);
260
- useEffect(() => {
261
- if (!state.plans?.length) {
262
- return;
263
- }
264
- if (Number.isNaN(width)) {
265
- return;
266
- }
267
-
268
- // 最小得有1个
269
- const tmp = Math.max(Math.floor(width / SLIDE_WIDTH) - 1, 1);
270
- setState(getSlideConfig(tmp, state.plans.length));
271
- }, [width, state.plans, isMobile]);
272
- const handleSelect = planId => {
273
- if (state.planId === planId) {
274
- return;
275
- }
276
- if (state.plans.find(x => x._id === planId)?.available === false) {
277
- return;
278
- }
279
- setState({
280
- planId
281
- });
282
- };
283
- useEffect(() => {
284
- if (!state.planId) {
285
- return;
286
- }
287
- if (state.planId !== params.get('planId')) {
288
- params.set('planId', state.planId);
289
- setParams(params, {
290
- replace: true
291
- });
292
- }
293
- }, [state.planId]);
294
- useEffect(() => {
295
- if (!state.paymentMethod) {
296
- return;
297
- }
298
- if (state.paymentMethod !== params.get('paymentMethod')) {
299
- params.set('paymentMethod', state.paymentMethod);
300
- setParams(params, {
301
- replace: true
302
- });
303
- }
304
- }, [state.paymentMethod]);
305
- useEffect(() => {
306
- if (isPurchaseOnly) {
307
- return;
308
- }
309
- if (!state.planId || !state.sessionId) {
310
- return;
311
- }
312
- const plan = state.plans.find(x => x._id === state.planId);
313
- const type = get(plan, 'extra.type') || 'serverless';
314
- launchSession.select(launchSessionAPI, routerPrefix, state.sessionId, type, state.planId);
315
- }, [state.planId, state.sessionId]);
316
- const productFeatures = [];
317
- if (!state.loading && state.plans?.length > 0) {
318
- // TODO: Pricing Table: 临时做法
319
- try {
320
- const {
321
- features
322
- } = state.plans[0];
323
- if (features && features.length > 0) {
324
- const tmp = JSON.parse(features[0].en);
325
- tmp.forEach(item => {
326
- productFeatures.push({
327
- _id: item.featureId,
328
- name: item.name,
329
- type: item.type
330
- });
331
- });
332
- }
333
- } catch (error) {
334
- console.error('parse product feature failed:', error);
335
- }
336
- }
337
- const ensureLogin = () => {
338
- return new Promise(resolve => {
339
- if (!session.user) {
340
- session.login(() => {
341
- resolve();
342
- });
343
- } else {
344
- resolve();
345
- }
346
- });
347
- };
348
- const handleRedeem = () => {
349
- if (browser.wallet) {
350
- // HACK: 钱包客户端中因为打开浏览器 Tab 的限制,所以在钱包客户端浏览器中使用 “登录 -> 兑换(购买)" 两步的流程
351
- ensureLogin().then(() => {
352
- connectApi.open({
353
- useSocket: false,
354
- locale,
355
- action: 'redeem',
356
- prefix: `${window.location.origin}${joinURL(baseURL || '', API_DID_PREFIX)}`,
357
- onSuccess: handleRedeemed,
358
- checkTimeout: 60 * 5000,
359
- showDownload: false,
360
- extraParams: {
361
- planId: state.planId || state.plans[0]._id,
362
- sessionId: state.sessionId,
363
- forceSwitch: !session?.user,
364
- // HACK: 在 openPopup 阶段需要手动添加 sourceAppPid 字段
365
- sourceAppPid: session?.user?.sourceAppPid,
366
- provider: 'wallet'
367
- },
368
- messages: {
369
- title: t('redeem.dialog.title'),
370
- scan: t('redeem.dialog.scan'),
371
- confirm: t('redeem.dialog.confirm'),
372
- success: t('redeem.dialog.success')
373
- }
374
- }, {
375
- locale
376
- });
377
- });
378
- } else {
379
- connectApi.openPopup({
380
- useSocket: false,
381
- locale,
382
- action: 'redeem',
383
- prefix: `${window.location.origin}${joinURL(baseURL || '', API_DID_PREFIX)}`,
384
- onSuccess: handleRedeemed,
385
- checkTimeout: 60 * 5000,
386
- showDownload: false,
387
- extraParams: {
388
- planId: state.planId || state.plans[0]._id,
389
- sessionId: state.sessionId,
390
- forceSwitch: !session?.user,
391
- // HACK: 在 openPopup 阶段需要手动添加 sourceAppPid 字段
392
- sourceAppPid: session?.user?.sourceAppPid,
393
- provider: 'wallet'
394
- },
395
- messages: {
396
- title: t('redeem.dialog.title'),
397
- scan: t('redeem.dialog.scan'),
398
- confirm: t('redeem.dialog.confirm'),
399
- success: t('redeem.dialog.success')
400
- },
401
- baseUrl: window.location.origin
402
- }, {
403
- locale
404
- });
405
- }
406
- };
407
- const handleRedeemed = ({
408
- sessionToken,
409
- nftId,
410
- type,
411
- sessionId
412
- }) => {
413
- if (type === NFT_TYPE_SERVERLESS) {
414
- params.set('launchType', 'serverless');
415
- }
416
- if (sessionToken) {
417
- storage.setToken(sessionToken);
418
- session.refresh();
419
- }
420
- if (sessionId) {
421
- params.set('sessionId', sessionId);
422
- }
423
- navigate({
424
- pathname: joinURL(routerPrefix, `/launch/${nftId}`),
425
- search: params.toString()
426
- });
427
- };
428
- const plan = state.planId && state.plans ? state.plans.find(x => x._id === state.planId) : null;
429
- return /*#__PURE__*/_jsxs(Container, {
430
- embed: embed,
431
- children: [/*#__PURE__*/_jsx(Header, {
432
- title: t('purchase.pageTitle'),
433
- disableBack: disableBack
434
- }), /*#__PURE__*/_jsx(Body, {
435
- style: {
436
- justifyContent: 'center'
437
- },
438
- children: /*#__PURE__*/_jsxs("div", {
439
- style: {
440
- flex: 1,
441
- position: 'relative'
442
- },
443
- children: [state.error && /*#__PURE__*/_jsx(ResultMessage, {
444
- variant: "error",
445
- title: t('common.error'),
446
- subTitle: state.error
447
- }), !state.error && /*#__PURE__*/_jsx("div", {
448
- className: "container-inner",
449
- children: /*#__PURE__*/_jsx(CompactLayout, {
450
- bottom: /*#__PURE__*/_jsxs(Footer, {
451
- className: "footer",
452
- children: [embed && isPurchaseOnly === false && /*#__PURE__*/_jsxs("div", {
453
- className: "footer-left",
454
- children: [/*#__PURE__*/_jsx(Button, {
455
- onClick: handleRedeem,
456
- className: "redeem-button",
457
- variant: "outlined",
458
- children: t('purchase.redeem')
459
- }), /*#__PURE__*/_jsx(Link, {
460
- className: "select-space",
461
- to: joinURL(routerPrefix, `/purchase/select${window.location.search}`),
462
- children: t('purchase.selectSpaceHint')
463
- })]
464
- }), /*#__PURE__*/_jsx(Button, {
465
- component: Link,
466
- disabled: state.loading || !state.planId,
467
- to: joinURL(routerPrefix, `/purchase/checkout?${params.toString()}`),
468
- className: "button-next",
469
- variant: "contained",
470
- children: plan ? t('purchase.hasPlan', {
471
- name: plan.name[locale]
472
- }) : t('purchase.noPlan')
473
- })]
474
- }),
475
- children: /*#__PURE__*/_jsx("div", {
476
- ref: planListRef,
477
- className: "plan-list",
478
- children: state.loading ? /*#__PURE__*/_jsx(Center, {
479
- relative: "parent",
480
- children: /*#__PURE__*/_jsx(CircularProgress, {})
481
- }) : /*#__PURE__*/_jsxs(_Fragment, {
482
- children: [!isMobile && /*#__PURE__*/_jsx(Plan, {
483
- toc: true,
484
- productFeatures: productFeatures,
485
- paymentMethods: state.paymentMethods,
486
- paymentMethod: state.paymentMethod,
487
- onChangeMethod: paymentMethod => setState({
488
- paymentMethod
489
- }),
490
- className: "toc"
491
- }), /*#__PURE__*/_jsxs(Splide, {
492
- extensions: {
493
- Grid
494
- },
495
- onPaginationMounted: splide => {
496
- const pageCount = Math.ceil(state.plans.length / splide.options.perPage);
497
- const showArrows = pageCount > 1;
498
- setState({
499
- arrows: showArrows
500
- });
501
- if (showArrows) {
502
- setTimeout(() => {
503
- setState({
504
- showMorePrompt: true
505
- });
506
- // 3s 后隐藏
507
- setTimeout(() => setState({
508
- showMorePrompt: false
509
- }), 3000);
510
- }, 1000);
511
- }
512
- },
513
- hasTrack: false,
514
- options: {
515
- rewind: true,
516
- pagination: true,
517
- arrows: state.arrows,
518
- classes: {
519
- pagination: 'splide__pagination splide__pagination-custom'
520
- },
521
- trimSpace: true,
522
- width: state.width,
523
- perPage: state.perPage,
524
- gap: state.gap
525
- },
526
- children: [/*#__PURE__*/_jsxs("div", {
527
- className: "splide__arrows",
528
- children: [/*#__PURE__*/_jsx("button", {
529
- className: "splide__arrow splide__arrow--prev",
530
- type: "button",
531
- disabled: "",
532
- "aria-label": "Previous slide",
533
- children: /*#__PURE__*/_jsx(PrevIcon, {
534
- viewBox: "0 0 40 40"
535
- })
536
- }), /*#__PURE__*/_jsx(Tooltip, {
537
- open: state.showMorePrompt,
538
- title: t('purchase.morePlanPrompt'),
539
- placement: "top",
540
- children: /*#__PURE__*/_jsx("button", {
541
- className: "splide__arrow splide__arrow--next",
542
- type: "button",
543
- "aria-label": "Next slide",
544
- children: /*#__PURE__*/_jsx(NextIcon, {
545
- viewBox: "0 0 40 40"
546
- })
547
- })
548
- })]
549
- }), /*#__PURE__*/_jsx(SplideTrack, {
550
- children: state.plans.map(x => /*#__PURE__*/_jsx(SplideSlide, {
551
- style: {
552
- width: 'auto'
553
- },
554
- children: /*#__PURE__*/_jsx(Plan, {
555
- recommend: !!x.isRecommended,
556
- checked: x._id === state.planId,
557
- plan: x,
558
- paymentMethod: state.paymentMethod,
559
- paymentMethods: state.paymentMethods,
560
- onClick: () => handleSelect(x._id)
561
- })
562
- }, x._id))
563
- })]
564
- })]
565
- })
566
- })
567
- })
568
- })]
569
- })
570
- }), session.user && /*#__PURE__*/_jsx(InProgressSession, {}), connectHolder]
571
- });
572
- }
573
- const Container = styled(Layout)`
574
- .container-inner {
575
- position: absolute;
576
- left: 0;
577
- top: 0;
578
- width: 100%;
579
- height: 100%;
580
- }
581
-
582
- .plan-list {
583
- display: flex;
584
- justify-content: center;
585
- align-items: center;
586
-
587
- @media (min-width: 900px) {
588
- margin-top: 20px;
589
- }
590
-
591
- @media (min-width: 680px) and (max-width: 899px) {
592
- justify-content: center;
593
- }
594
-
595
- .toc {
596
- min-width: 180px;
597
- margin-right: 20px;
598
- }
599
-
600
- button.splide__pagination__page.is-active {
601
- background: #9397a1;
602
- }
603
- }
604
-
605
- .splide__pagination-custom {
606
- bottom: -24px;
607
- }
608
-
609
- .footer {
610
- display: flex;
611
- justify-content: ${props => props.embed ? 'space-between' : 'flex-end'};
612
- align-item: center;
613
- margin-top: auto;
614
- padding-top: 16px;
615
- padding-bottom: ${props => props.embed ? '0' : '16px'};
616
-
617
- > * {
618
- margin: 0 8px;
619
- ${props => props.theme.breakpoints.up('md')} {
620
- margin: 0 16px;
621
- }
622
- }
623
-
624
- @media (max-width: ${props => props.theme.breakpoints.values.sm}px) {
625
- flex-direction: column;
626
- align-items: center;
627
- }
628
-
629
- .footer-left {
630
- display: flex;
631
- gap: 16px;
632
- align-items: center;
633
- justify-content: space-between;
634
-
635
- .select-space {
636
- order: 1;
637
- color: #000;
638
-
639
- &:hover {
640
- text-decoration: underline !important;
641
- }
642
- }
643
-
644
- .redeem-button {
645
- order: 1;
646
- }
647
-
648
- @media (max-width: ${props => props.theme.breakpoints.values.sm}px) {
649
- width: 100%;
650
- flex-direction: column;
651
-
652
- .redeem-button {
653
- width: 100%;
654
- order: 2;
655
- }
656
- }
657
- }
658
-
659
- .button-next {
660
- min-width: 200px;
661
-
662
- @media (max-width: ${props => props.theme.breakpoints.values.sm}px) {
663
- width: 100%;
664
- margin-top: 20px;
665
- }
666
- }
667
- }
668
- `;
669
- PurchasePage.propTypes = {
670
- disableBack: PropTypes.bool
671
- };
672
- PurchasePage.defaultProps = {
673
- disableBack: false
674
- };
675
- export default PurchasePage;