@stellar-expert/ui-framework 1.9.0

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.
Files changed (124) hide show
  1. package/.npmrc +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +3 -0
  4. package/account/account-address.js +127 -0
  5. package/account/account-address.scss +20 -0
  6. package/account/available-balance.js +21 -0
  7. package/account/identicon.js +91 -0
  8. package/account/identicon.scss +6 -0
  9. package/account/signer-key.js +65 -0
  10. package/api/api-cache.js +5 -0
  11. package/api/explorer-api-call.js +37 -0
  12. package/api/explorer-api-hooks.js +202 -0
  13. package/api/explorer-api-paginated-list-hooks.js +439 -0
  14. package/api/explorer-batch-info-loader.js +85 -0
  15. package/api/explorer-tx-api.js +29 -0
  16. package/asset/amount.js +53 -0
  17. package/asset/asset-icon.js +34 -0
  18. package/asset/asset-issuer.js +22 -0
  19. package/asset/asset-link.js +93 -0
  20. package/asset/asset-link.scss +26 -0
  21. package/asset/asset-list-hooks.js +60 -0
  22. package/asset/asset-meta-hooks.js +78 -0
  23. package/asset/asset-selector.js +72 -0
  24. package/asset/asset-selector.scss +41 -0
  25. package/basic-styles/base.scss +53 -0
  26. package/basic-styles/form.scss +90 -0
  27. package/basic-styles/grid.scss +388 -0
  28. package/basic-styles/link.scss +17 -0
  29. package/basic-styles/lists.scss +68 -0
  30. package/basic-styles/loader.scss +22 -0
  31. package/basic-styles/responsive.scss +29 -0
  32. package/basic-styles/scrollbars.scss +19 -0
  33. package/basic-styles/spacing.scss +50 -0
  34. package/basic-styles/table.scss +104 -0
  35. package/basic-styles/themes.scss +59 -0
  36. package/basic-styles/typography.scss +191 -0
  37. package/basic-styles/variables.scss +26 -0
  38. package/claimable-balance/claimable-balance-claimants.js +12 -0
  39. package/contract/sc-val.js +70 -0
  40. package/contract/wasm-section-parser.js +159 -0
  41. package/contract/wasm-section-reader.js +79 -0
  42. package/contract/xdr-reader.js +148 -0
  43. package/controls/active-icon.scss +23 -0
  44. package/controls/button-group.js +20 -0
  45. package/controls/button-group.scss +48 -0
  46. package/controls/button.js +74 -0
  47. package/controls/button.scss +148 -0
  48. package/controls/code-block.js +35 -0
  49. package/controls/code-block.scss +74 -0
  50. package/controls/dropdown.js +281 -0
  51. package/controls/dropdown.scss +161 -0
  52. package/controls/external-link.js +5 -0
  53. package/controls/info-tooltip.js +17 -0
  54. package/controls/info-tooltip.scss +4 -0
  55. package/controls/slider.js +20 -0
  56. package/controls/slider.scss +22 -0
  57. package/controls/tabs.js +94 -0
  58. package/controls/tabs.scss +73 -0
  59. package/controls/tooltip.js +214 -0
  60. package/controls/tooltip.scss +97 -0
  61. package/controls/update-highlighter.js +28 -0
  62. package/controls/update-highlighter.scss +10 -0
  63. package/date/date-selector.js +44 -0
  64. package/date/elapsed-time.js +22 -0
  65. package/date/utc-timestamp.js +23 -0
  66. package/dex/price-dynamic.js +45 -0
  67. package/dex/price-dynamic.scss +36 -0
  68. package/directory/directory-hooks.js +89 -0
  69. package/effect/effect-description.js +289 -0
  70. package/effect/op-effects-view.js +11 -0
  71. package/effect/op-effects.scss +8 -0
  72. package/errors/error-boundary.js +98 -0
  73. package/fonts/font.scss +23 -0
  74. package/fonts/icons/config.json +1858 -0
  75. package/fonts/icons/icons-embedded.scss +644 -0
  76. package/horizon/horizon-account-helpers.js +104 -0
  77. package/horizon/horizon-client-helpers.js +52 -0
  78. package/horizon/horizon-ledger-helpers.js +36 -0
  79. package/horizon/horizon-orderbook-helpers.js +14 -0
  80. package/horizon/horizon-trades-helper.js +89 -0
  81. package/horizon/horizon-transaction-helpers.js +36 -0
  82. package/index.js +85 -0
  83. package/index.scss +16 -0
  84. package/interaction/autofocus.js +9 -0
  85. package/interaction/block-select.js +54 -0
  86. package/interaction/block-select.scss +24 -0
  87. package/interaction/copy-to-clipboard.js +19 -0
  88. package/interaction/inline-progress.js +16 -0
  89. package/interaction/qr-code.js +45 -0
  90. package/interaction/responsive.js +21 -0
  91. package/interaction/spoiler.js +40 -0
  92. package/interaction/spoiler.scss +9 -0
  93. package/interaction/theme-selector.js +11 -0
  94. package/ledger/ledger-entry-href-formatter.js +22 -0
  95. package/ledger/ledger-entry-link.js +59 -0
  96. package/meta/page-meta-tags.js +229 -0
  97. package/module/dynamic-module.js +48 -0
  98. package/package.json +41 -0
  99. package/state/on-screen-hooks.js +23 -0
  100. package/state/page-visibility-helpers.js +17 -0
  101. package/state/page-visibility-hooks.js +12 -0
  102. package/state/screen-orientation-hooks.js +16 -0
  103. package/state/state-hooks.js +77 -0
  104. package/state/stellar-network-hooks.js +45 -0
  105. package/state/theme.js +29 -0
  106. package/stellar/key-type.js +92 -0
  107. package/stellar/ledger-generic-id.js +40 -0
  108. package/stellar/signature-hint-utils.js +65 -0
  109. package/toast/toast-notification-instance.js +43 -0
  110. package/toast/toast-notification.js +46 -0
  111. package/toast/toast-notifications-block.js +55 -0
  112. package/toast/toast-notifications.scss +166 -0
  113. package/tx/op-accounting-changes.js +27 -0
  114. package/tx/op-description-view.js +925 -0
  115. package/tx/op-description.scss +98 -0
  116. package/tx/op-icon.js +93 -0
  117. package/tx/parser/op-balance-changes.js +62 -0
  118. package/tx/parser/op-descriptor.js +49 -0
  119. package/tx/parser/tx-details-parser.js +75 -0
  120. package/tx/parser/tx-matcher.js +372 -0
  121. package/tx/parser/type-filter-matcher.js +127 -0
  122. package/tx/tx-fee-effect.js +14 -0
  123. package/tx/tx-list-hooks.js +19 -0
  124. package/tx/tx-operations-list.js +105 -0
@@ -0,0 +1,289 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import {AuthRequiredFlag, AuthRevocableFlag, AuthImmutableFlag, AuthClawbackEnabledFlag} from '@stellar/stellar-base'
4
+ import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
5
+ import {shortenString, formatWithAutoPrecision} from '@stellar-expert/formatter'
6
+ import {AccountAddress} from '../account/account-address'
7
+ import {SignerKey} from '../account/signer-key'
8
+ import {OfferLink, PoolLink} from '../ledger/ledger-entry-link'
9
+ import {AssetLink} from '../asset/asset-link'
10
+ import {Amount} from '../asset/amount'
11
+ import {CopyToClipboard} from '../interaction/copy-to-clipboard'
12
+ import {ScVal} from '../contract/sc-val'
13
+
14
+ export function EffectDescription({effect}) {
15
+ switch (effect.type) {
16
+ case 'accountCreated':
17
+ return <>Account <AccountAddress account={effect.account}/> created</>
18
+ case 'accountRemoved':
19
+ return <>Account <AccountAddress account={effect.source}/> removed</>
20
+ case 'accountCredited':
21
+ return <><Amount asset={effect.asset} amount={effect.amount} adjust/> credited to
22
+ account <AccountAddress account={effect.source}/></>
23
+ case 'accountDebited':
24
+ return <><Amount asset={effect.asset} amount={effect.amount} adjust/> debited from
25
+ account <AccountAddress account={effect.source}/></>
26
+ case 'accountThresholdsUpdated':
27
+ return <>Account <AccountAddress account={effect.source}/> set thresholds to {effect.thresholds.join('/')}</>
28
+ case 'accountHomeDomainUpdated':
29
+ return <>
30
+ Account <AccountAddress account={effect.source}/> set home domain to{' '}
31
+ <a href={'https://' + effect.home_domain} target="_blank" rel="noreferrer noopener">{effect.home_domain}</a>
32
+ </>
33
+ case 'accountFlagsUpdated':
34
+ return <>Account <AccountAddress account={effect.source}/> updated authorization
35
+ flags: {decodeAccountAuthFlags(effect.flags).join(', ')}</>
36
+ case 'accountInflationDestinationUpdated':
37
+ return <>Account <AccountAddress account={effect.source}/> set inflation destination
38
+ to <AccountAddress account={effect.inflationDestination}/></>
39
+ case 'accountSignerCreated':
40
+ return <>Account <AccountAddress account={effect.source}/> added
41
+ signer <AccountAddress account={effect.signer}/>(w:{effect.weight})</>
42
+ case 'accountSignerUpdated':
43
+ return <>Account <AccountAddress account={effect.source}/> updated
44
+ signer <AccountAddress account={effect.signer}/>(w:{effect.weight})</>
45
+ case 'accountSignerRemoved':
46
+ return <>Account <AccountAddress account={effect.source}/> removed
47
+ signer <AccountAddress account={effect.signer}/></>
48
+ case 'trustlineCreated':
49
+ return <>
50
+ Account <AccountAddress account={effect.source}/> established trustline to <AssetLink asset={effect.asset}/> with
51
+ limit <Amount asset={effect.asset} amount={effect.limit} issuer={false} icon={false} adjust/>
52
+ </>
53
+ case 'trustlineUpdated':
54
+ return <>
55
+ Account <AccountAddress account={effect.source}/> updated trustline to <AssetLink asset={effect.asset}/> with
56
+ limit <Amount asset={effect.asset} amount={effect.limit} issuer={false} icon={false} adjust/>
57
+ </>
58
+ case 'trustlineRemoved':
59
+ return <>Account <AccountAddress account={effect.source}/> removed trustline to <AssetLink asset={effect.asset}/></>
60
+ case 'trustlineAuthorizationUpdated':
61
+ return <>
62
+ Account <AccountAddress account={effect.source}/> updated <AssetLink asset={effect.asset}/> trustline authorization
63
+ flags ({decodeTrustlineFlags(effect.flags)}) for <AccountAddress account={effect.trustor}/>
64
+ </>
65
+ case 'assetMinted':
66
+ return <><Amount asset={effect.asset} amount={effect.amount} adjust/> minted</>
67
+ case 'assetBurned':
68
+ return <>Account <AccountAddress account={effect.source}/> burned <Amount asset={effect.asset} amount={effect.amount} adjust/></>
69
+ case 'offerCreated':
70
+ return <OfferChange effect={effect} action="created"/>
71
+ case 'offerUpdated':
72
+ return <OfferChange effect={effect} action="updated"/>
73
+ case 'offerRemoved':
74
+ return <>DEX offer <OfferLink offer={effect.offer}/> removed</>
75
+ case 'trade':
76
+ return <span>
77
+ Account <AccountAddress account={effect.source}/> exchanged{' '}
78
+ <span className="nowrap">
79
+ <Amount asset={effect.asset[0]} amount={effect.amount[0]} adjust/>{' '}
80
+ <span className="icon icon-shuffle color-primary"/>{' '}
81
+ <Amount asset={effect.asset[1]} amount={effect.amount[1]} adjust/>
82
+ </span>{' '}
83
+ {effect.pool ?
84
+ <>(pool <PoolLink pool={effect.pool}/>)</> :
85
+ <>(DEX offer <OfferLink offer={effect.offer}/> by <AccountAddress account={effect.seller}/>)</>}
86
+ </span>
87
+ case 'sequenceBumped':
88
+ return <>Account <AccountAddress account={effect.source}/> bumped sequence to {effect.sequence}</>
89
+ case 'dataEntryCreated':
90
+ case 'dataEntryUpdated':
91
+ return <>
92
+ Account <AccountAddress account={effect.source}/> {effect.type === 'dataEntryCreated' ? 'created' : 'updated'} data entry
93
+ <span className="word-break condensed text-small"><code>"{effect.name}"</code> = <code>"{effect.value}"</code></span>
94
+ </>
95
+ case 'dataEntryRemoved':
96
+ return <>
97
+ Account <AccountAddress account={effect.source}/> removed data entry
98
+ <span className="word-break condensed text-small"><code>"{effect.name}"</code></span>
99
+ </>
100
+ case 'claimableBalanceCreated':
101
+ return <>
102
+ Account <AccountAddress account={effect.source}/> created claimable
103
+ balance {shortenString(effect.balance)} <Amount asset={effect.asset} amount={effect.amount} adjust/>
104
+ </>
105
+ case 'claimableBalanceRemoved':
106
+ return <>
107
+ Account <AccountAddress account={effect.source}/> claimed claimable
108
+ balance {shortenString(effect.balance)} <Amount asset={effect.asset} amount={effect.amount} adjust/>
109
+ </>
110
+ case 'accountSponsorshipCreated':
111
+ case 'accountSponsorshipUpdated':
112
+ return <>
113
+ Account <AccountAddress account={effect.sponsor}/> sponsored account base reserve
114
+ for <AccountAddress account={effect.account}/>
115
+ </>
116
+ case 'accountSponsorshipRemoved':
117
+ return <>
118
+ Account <AccountAddress account={effect.prevSponsor}/> revoked account sponsorship
119
+ for <AccountAddress account={effect.account}/>
120
+ </>
121
+ case 'trustlineSponsorshipCreated':
122
+ case 'trustlineSponsorshipUpdated':
123
+ return <>
124
+ Account <AccountAddress account={effect.sponsor}/> sponsored <AssetLink asset={effect.asset}/> trustline reserve
125
+ for <AccountAddress account={effect.account}/>
126
+ </>
127
+ case 'trustlineSponsorshipRemoved':
128
+ return <>
129
+ Account <AccountAddress account={effect.prevSponsor}/> revoked <AssetLink asset={effect.asset}/> trustline sponsorship
130
+ for <AccountAddress account={effect.account}/>
131
+ </>
132
+ case 'offerSponsorshipCreated':
133
+ case 'offerSponsorshipUpdated':
134
+ return <>
135
+ Account <AccountAddress account={effect.sponsor}/> sponsored offer <OfferLink offer={effect.offer}/> reserve
136
+ for <AccountAddress account={effect.account}/>
137
+ </>
138
+ case 'offerSponsorshipRemoved':
139
+ return <>
140
+ Account <AccountAddress account={effect.prevSponsor}/> revoked offer <OfferLink offer={effect.offer}/> sponsorship
141
+ for <AccountAddress account={effect.account}/>
142
+ </>
143
+ case 'dataSponsorshipCreated':
144
+ case 'dataSponsorshipUpdated':
145
+ return <>
146
+ Account <AccountAddress account={effect.sponsor}/> sponsored "{shortenString(effect.name)}" data reserve
147
+ for <AccountAddress account={effect.account}/>
148
+ </>
149
+ case 'dataSponsorshipRemoved':
150
+ return <>
151
+ Account <AccountAddress account={effect.prevSponsor}/> revoked "{shortenString(effect.name)}" data sponsorship
152
+ for <AccountAddress account={effect.account}/>
153
+ </>
154
+ case 'claimableBalanceSponsorshipCreated':
155
+ case 'claimableBalanceSponsorshipUpdated':
156
+ return <>
157
+ Account <AccountAddress account={effect.sponsor}/> sponsored claimable balance {shortenString(effect.balance)}
158
+ {effect.source !== effect.sponsor && <> for account <AccountAddress account={effect.source}/></>}
159
+ </>
160
+ case 'claimableBalanceSponsorshipRemoved':
161
+ return <>
162
+ Account <AccountAddress account={effect.prevSponsor}/> revoked claimable balance {shortenString(effect.balance)} sponsorship
163
+ {effect.source !== effect.prevSponsor && <> for account <AccountAddress account={effect.source}/></>}
164
+ </>
165
+ case 'signerSponsorshipCreated':
166
+ case 'signerSponsorshipUpdated':
167
+ return <>
168
+ Account <AccountAddress account={effect.sponsor}/> sponsored signer <SignerKey account={effect.signer}/>{' '}
169
+ for account <AccountAddress account={effect.account}/>
170
+ </>
171
+ case 'signerSponsorshipRemoved':
172
+ return <>
173
+ Account <AccountAddress account={effect.prevSponsor}/> revoked signer <SignerKey account={effect.signer}/> sponsorship{' '}
174
+ for account <AccountAddress account={effect.account}/>
175
+ </>
176
+ case 'liquidityPoolDeposited':
177
+ return <>
178
+ <span className="nowrap">
179
+ <Amount asset={effect.assets[0].asset} amount={effect.assets[0].amount} adjust/>{' '}
180
+ and <Amount asset={effect.assets[1].asset} amount={effect.assets[1].amount} adjust/>
181
+ </span>{' '}
182
+ deposited to liquidity <span className="nowrap">pool <AssetLink asset={effect.pool}/></span>{' '}
183
+ <i className="icon icon-shuffle"/> <span className="nowrap">{formatWithAutoPrecision(effect.shares)} pool shares</span>
184
+ </>
185
+ case 'liquidityPoolWithdrew':
186
+ return <>
187
+ {formatWithAutoPrecision(effect.shares)} pool shares <i className="icon icon-shuffle"/>{' '}
188
+ <span className="nowrap">
189
+ <Amount asset={effect.assets[0].asset} amount={effect.assets[0].amount} adjust/>{' '}
190
+ and <Amount asset={effect.assets[1].asset} amount={effect.assets[1].amount} adjust/>
191
+ </span>{' '}
192
+ withdrawn from <span className="nowrap"><AssetLink asset={effect.pool}/> pool</span>
193
+ </>
194
+ case 'liquidityPoolCreated':
195
+ return <>Liquidity pool <AssetLink asset={effect.pool}/> created</>
196
+ case 'liquidityPoolUpdated':
197
+ return <>Liquidity pool <AssetLink asset={effect.pool}/> updated / {' '}
198
+ <span className="nowrap">
199
+ <Amount asset={effect.reserves[0].asset} amount={effect.reserves[0].amount} adjust/> <i className="icon-plus"/>{' '}
200
+ <Amount asset={effect.reserves[1].asset} amount={effect.reserves[1].amount} adjust/></span> /{' '}
201
+ <span className="nowrap">{formatWithAutoPrecision(effect.shares)} pool shares</span>,{' '}
202
+ <span className="nowrap">{effect.accounts} accounts</span></>
203
+ case 'liquidityPoolRemoved':
204
+ return <>Liquidity pool <AssetLink asset={effect.pool}/> removed</>
205
+ case 'inflation':
206
+ return <>Inflation distribution initialized</>
207
+ case 'contractCodeUploaded':
208
+ return <>Contract code <ContractCodeWasm wasm={effect.wasm}/> uploaded</>
209
+ case 'contractCreated':
210
+ return <>Contract <AccountAddress account={effect.contract}/> created <ContractDetails effect={effect}/></>
211
+ case 'contractInvoked':
212
+ return <>{effect.depth > 0 &&
213
+ <i className="icon-level-down text-tiny color-primary" style={{paddingLeft: (effect.depth - 1) + 'em'}}/>}
214
+ Contract <AccountAddress account={effect.contract}/>{' '} function <code>{effect.function}</code> invoked
215
+ {!!effect.rawArgs && <> with arguments <ScVal value={effect.rawArgs}/></>}</>
216
+ case 'contractEvent':
217
+ return <>Contract <AccountAddress account={effect.contract}/> raised event <ScVal value={effect.rawTopics}/>{' '}
218
+ with data <ScVal value={effect.rawData}/></>
219
+ case 'contractDataCreated':
220
+ case 'contractDataUpdated':
221
+ return <>Contract <AccountAddress account={effect.owner}/> {effect.type === 'contractDataCreated' ? 'created ' : 'updated '}
222
+ data <ScVal value={effect.key}/> with value <ScVal value={effect.value}/>
223
+ </>
224
+ case 'contractDataRemoved':
225
+ return <>Contract <AccountAddress account={effect.owner}/> removed data <ScVal value={effect.key}/></>
226
+ case 'feeCharged':
227
+ return <><Amount asset="XLM" amount={effect.charged} adjust/> fee charged from <AccountAddress account={effect.source}/> (
228
+ bid <Amount asset="XLM" amount={effect.bid} adjust/>)</>
229
+ }
230
+ return <><i className="icon-warning color-warning"/> Effect {effect.type} not supported</>
231
+ }
232
+
233
+ EffectDescription.propTypes = {
234
+ effect: PropTypes.object.isRequired
235
+ }
236
+
237
+ function decodeAccountAuthFlags(flags) {
238
+ const res = []
239
+ if ((flags & AuthRequiredFlag) === AuthRequiredFlag) {
240
+ res.push('REQUIRED')
241
+ }
242
+ if ((flags & AuthRevocableFlag) === AuthRevocableFlag) {
243
+ res.push('REVOCABLE')
244
+ }
245
+ if ((flags & AuthImmutableFlag) === AuthImmutableFlag) {
246
+ res.push('IMMUTABLE')
247
+ }
248
+ if ((flags & AuthClawbackEnabledFlag) === AuthClawbackEnabledFlag) {
249
+ res.push('CLAWBACK_ENABLED')
250
+ }
251
+ return res
252
+ }
253
+
254
+ function decodeTrustlineFlags(flags) {
255
+ if ((flags & 2) === 2)
256
+ return 'FROZEN'
257
+ if ((flags & 1) === 1)
258
+ return 'AUTHORIZED'
259
+ return 'UNAUTHORIZED'
260
+ }
261
+
262
+ function OfferChange({effect, action}) {
263
+ return <>DEX offer <OfferLink offer={effect.offer}/> {action} /{' '}
264
+ <span className="nowrap">
265
+ <Amount asset={effect.asset[0]} amount={effect.amount} adjust/> <i className="icon icon-shuffle color-primary"/>{' '}
266
+ <AssetLink asset={effect.asset[1]}/>
267
+ </span> at <span className="nowrap">
268
+ {formatWithAutoPrecision(parseFloat(effect.price))}{' '}
269
+ {AssetDescriptor.parse(effect.asset[1]).toCurrency()}/{AssetDescriptor.parse(effect.asset[0]).toCurrency()}
270
+ </span>
271
+ </>
272
+ }
273
+
274
+ function ContractDetails({effect}) {
275
+ switch (effect.kind) {
276
+ case 'wasm':
277
+ return <>from WASM <ContractCodeWasm wasm={effect.wasmHash}/></>
278
+ case 'fromAddress':
279
+ return <>from issuer <AccountAddress account={effect.issuer}/> with salt <ContractCodeWasm wasm={effect.salt}/></>
280
+ case 'fromAsset':
281
+ return <>from asset <AssetLink asset={effect.asset}/></>
282
+ default:
283
+ return <span className="dimme">(contract type not supported)</span>
284
+ }
285
+ }
286
+
287
+ function ContractCodeWasm({wasm}) {
288
+ return <><code title={wasm}>{shortenString(wasm, 16)}</code><CopyToClipboard text={wasm}/>)</>
289
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react'
2
+ import {EffectDescription} from './effect-description'
3
+ import './op-effects.scss'
4
+
5
+ export function OpEffectsView({effects}) {
6
+ return <div className="op-effects">
7
+ {!effects.length ?
8
+ <div className="dimmed">(no effects)</div> :
9
+ effects.map(e => <div><i className="icon-puzzle"/> <EffectDescription effect={e}/></div>)}
10
+ </div>
11
+ }
@@ -0,0 +1,8 @@
1
+ .op-effects {
2
+ padding: 0.2em 0 0 2.6em;
3
+ line-height: 1.8;
4
+
5
+ .icon-puzzle {
6
+ margin-left: -1.4em;
7
+ }
8
+ }
@@ -0,0 +1,98 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import {navigation as nav} from '@stellar-expert/navigation'
4
+ import {BlockSelect} from '../interaction/block-select'
5
+
6
+ export class ErrorBoundary extends React.Component {
7
+ constructor(props) {
8
+ super(props)
9
+ this.state = {lastError: null, url: null}
10
+ }
11
+
12
+ componentDidCatch(e, errorInfo) {
13
+ e.componentStack = errorInfo?.componentStack
14
+ console.error(e)
15
+ this.setState({lastError: e, url: window.location.href}, () => {
16
+ const stopListening = nav.history.listen((location) => {
17
+ if (this.state.url !== window.location.href) {
18
+ stopListening()
19
+ this.setState({lastError: null, url: null})
20
+ }
21
+ })
22
+ })
23
+ }
24
+
25
+ renderError() {
26
+ const {errorBoundarySendErrors = true} = this.props
27
+ return <>
28
+ {this.renderErrorDetails()}
29
+ {errorBoundarySendErrors && this.renderSendErrorBlock()}
30
+ </>
31
+ }
32
+
33
+ renderSendErrorBlock() {
34
+ const {lastError, url} = this.state
35
+ const {message, stack, componentStack} = lastError
36
+ const compiledText = `Error details:
37
+ "${message}" at ${url}
38
+ ${stack}
39
+ ${componentStack ? 'Components stack: ' + componentStack : ''}
40
+ ${navigator.userAgent}`
41
+ return <div className="space dimmed text-small text-right">
42
+ If this error persists please{' '}
43
+ <a href={'mailto:support@stellar.expert?subject=Exception&body=' + encodeURIComponent(compiledText)}
44
+ target="_blank">contact our support</a>.
45
+ </div>
46
+ }
47
+
48
+ renderErrorDetails() {
49
+ const {lastError, url} = this.state
50
+ const {message, stack, componentStack} = lastError
51
+ const text = `"${message}" at ${url}`
52
+ const {errorBoundaryErrorDetails = true} = this.props
53
+ if (errorBoundaryErrorDetails === false)
54
+ return null
55
+ if (errorBoundaryErrorDetails === true) {
56
+ return <div className="error space text-small" style={{overflow: 'auto', maxWidth: '100%', padding: '1rem 2rem'}}>
57
+ <BlockSelect as="div">
58
+ <div className="micro-space">{text}</div>
59
+ <div className="text-tiny">
60
+ <pre>{stack}</pre>
61
+ <div>{navigator.userAgent}</div>
62
+ </div>
63
+ </BlockSelect>
64
+ </div>
65
+ }
66
+ return <>{errorBoundaryErrorDetails}</>
67
+ }
68
+
69
+ render() {
70
+ if (!this.state.lastError)
71
+ return this.props.children
72
+ const {title = 'Unhandled error occurred', wrapper, ...otherProps} = this.props
73
+ if (wrapper)
74
+ return React.createElement(wrapper, {...otherProps}, this.renderError())
75
+ return <div className="segment blank">
76
+ <h3 className="color-danger">{title}</h3>
77
+ <hr className="flare"/>
78
+ {this.renderError()}
79
+ </div>
80
+ }
81
+
82
+ static propTypes = {
83
+ errorBoundaryTitle: PropTypes.string,
84
+ errorBoundarySendErrors: PropTypes.bool,
85
+ errorBoundaryErrorDetails: PropTypes.oneOfType([PropTypes.bool, PropTypes.any])
86
+ }
87
+ }
88
+
89
+ export function withErrorBoundary(wrapped, {errorBoundaryTitle, errorBoundarySendErrors, errorBoundaryErrorDetails} = {}) {
90
+ function ErrorBoundaryWrapper(props) {
91
+ const nested = /*#__PURE__*/React.createElement(wrapped, props)
92
+ return <ErrorBoundary {...{errorBoundaryTitle, errorBoundarySendErrors, errorBoundaryErrorDetails}}>{nested}</ErrorBoundary>
93
+ }
94
+
95
+ ErrorBoundaryWrapper.displayName = `withErrorBoundary(${wrapped.displayName || wrapped.name})`
96
+ ErrorBoundaryWrapper.WrappedComponent = wrapped
97
+ return ErrorBoundaryWrapper
98
+ }