Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
frontend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
frontend
Commits
0a992acd
Commit
0a992acd
authored
Aug 11, 2023
by
tom
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor features config
parent
64981a9e
Changes
59
Show whitespace changes
Inline
Side-by-side
Showing
59 changed files
with
776 additions
and
325 deletions
+776
-325
account.ts
configs/app/features/account.ts
+25
-6
addressVerification.ts
configs/app/features/addressVerification.ts
+23
-8
adsBanner.ts
configs/app/features/adsBanner.ts
+46
-9
adsText.ts
configs/app/features/adsText.ts
+19
-5
beaconChain.ts
configs/app/features/beaconChain.ts
+25
-7
blockchainInteraction.ts
configs/app/features/blockchainInteraction.ts
+25
-9
csvExport.ts
configs/app/features/csvExport.ts
+21
-9
googleAnalytics.ts
configs/app/features/googleAnalytics.ts
+20
-5
graphqlApiDocs.ts
configs/app/features/graphqlApiDocs.ts
+13
-5
marketplace.ts
configs/app/features/marketplace.ts
+27
-8
mixpanel.ts
configs/app/features/mixpanel.ts
+20
-5
restApiDocs.ts
configs/app/features/restApiDocs.ts
+20
-5
rollup.ts
configs/app/features/rollup.ts
+28
-6
sentry.ts
configs/app/features/sentry.ts
+23
-9
sol2uml.ts
configs/app/features/sol2uml.ts
+23
-8
stats.ts
configs/app/features/stats.ts
+23
-8
types.ts
configs/app/features/types.ts
+10
-0
verifiedTokens.ts
configs/app/features/verifiedTokens.ts
+23
-8
web3Wallet.ts
configs/app/features/web3Wallet.ts
+23
-9
react.ts
configs/sentry/react.ts
+63
-52
resources.ts
lib/api/resources.ts
+19
-18
ad.ts
lib/csp/policies/ad.ts
+1
-1
app.ts
lib/csp/policies/app.ts
+19
-11
useConfigSentry.tsx
lib/hooks/useConfigSentry.tsx
+4
-0
useIsAccountActionAllowed.tsx
lib/hooks/useIsAccountActionAllowed.tsx
+4
-0
useLoginUrl.tsx
lib/hooks/useLoginUrl.tsx
+5
-1
useRedirectForInvalidAuthToken.tsx
lib/hooks/useRedirectForInvalidAuthToken.tsx
+1
-1
useInit.tsx
lib/mixpanel/useInit.tsx
+3
-2
account.ts
lib/next/middlewares/account.ts
+4
-13
useProvider.tsx
lib/web3/useProvider.tsx
+5
-3
buildApiUrl.ts
playwright/utils/buildApiUrl.ts
+1
-1
SwaggerUI.tsx
ui/apiDocs/SwaggerUI.tsx
+7
-1
CsvExportFormReCaptcha.tsx
ui/csvExport/CsvExportFormReCaptcha.tsx
+4
-2
GraphQL.tsx
ui/graphQL/GraphQL.tsx
+7
-1
LatestDepositsItem.tsx
ui/home/LatestDepositsItem.tsx
+8
-2
DepositsListItem.tsx
ui/l2Deposits/DepositsListItem.tsx
+9
-3
DepositsTableItem.tsx
ui/l2Deposits/DepositsTableItem.tsx
+9
-3
OutputRootsListItem.tsx
ui/l2OutputRoots/OutputRootsListItem.tsx
+7
-1
OutputRootsTableItem.tsx
ui/l2OutputRoots/OutputRootsTableItem.tsx
+7
-1
TxnBatchesListItem.tsx
ui/l2TxnBatches/TxnBatchesListItem.tsx
+8
-2
TxnBatchesTableItem.tsx
ui/l2TxnBatches/TxnBatchesTableItem.tsx
+8
-2
WithdrawalsListItem.tsx
ui/l2Withdrawals/WithdrawalsListItem.tsx
+8
-2
WithdrawalsTableItem.tsx
ui/l2Withdrawals/WithdrawalsTableItem.tsx
+8
-2
useMarketplaceApps.tsx
ui/marketplace/useMarketplaceApps.tsx
+5
-1
Marketplace.tsx
ui/pages/Marketplace.tsx
+24
-21
MarketplaceApp.tsx
ui/pages/MarketplaceApp.tsx
+7
-1
Withdrawals.tsx
ui/pages/Withdrawals.tsx
+4
-2
GoogleAnalytics.tsx
ui/shared/GoogleAnalytics.tsx
+4
-2
NetworkAddToWallet.tsx
ui/shared/NetworkAddToWallet.tsx
+4
-8
Web3ModalProvider.tsx
ui/shared/Web3ModalProvider.tsx
+7
-5
AdBanner.tsx
ui/shared/ad/AdBanner.tsx
+5
-3
AdbutlerBanner.tsx
ui/shared/ad/AdbutlerBanner.tsx
+11
-5
adbutlerScript.ts
ui/shared/ad/adbutlerScript.ts
+21
-13
AddressAddToWallet.tsx
ui/shared/address/AddressAddToWallet.tsx
+5
-5
ProfileMenuContent.tsx
ui/snippets/profileMenu/ProfileMenuContent.tsx
+7
-1
ProfileMenuDesktop.tsx
ui/snippets/profileMenu/ProfileMenuDesktop.tsx
+1
-1
ProfileMenuMobile.tsx
ui/snippets/profileMenu/ProfileMenuMobile.tsx
+1
-1
WithdrawalsListItem.tsx
ui/withdrawals/WithdrawalsListItem.tsx
+7
-1
WithdrawalsTable.tsx
ui/withdrawals/WithdrawalsTable.tsx
+7
-1
No files found.
configs/app/features/account.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
stripTrailingSlash
from
'
lib/stripTrailingSlash
'
;
import
app
from
'
../app
'
;
...
...
@@ -25,9 +27,26 @@ const logoutUrl = (() => {
}
})();
export
default
Object
.
freeze
({
title
:
'
My account
'
,
isEnabled
:
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED
)
===
'
true
'
,
const
title
=
'
My account
'
;
const
config
:
Feature
<
{
authUrl
:
string
;
logoutUrl
:
string
}
>
=
(()
=>
{
if
(
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED
)
===
'
true
'
&&
authUrl
&&
logoutUrl
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
authUrl
,
logoutUrl
,
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/addressVerification.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
import
account
from
'
./account
'
;
import
verifiedTokens
from
'
./verifiedTokens
'
;
const
adminServiceApiHost
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST
);
export
default
Object
.
freeze
({
const
title
=
'
Address verification in "My account"
'
;
const
config
:
Feature
<
{
api
:
{
endpoint
:
string
;
basePath
:
string
}
}
>
=
(()
=>
{
if
(
account
.
isEnabled
&&
verifiedTokens
.
isEnabled
&&
adminServiceApiHost
)
{
return
Object
.
freeze
({
title
:
'
Address verification in "My account"
'
,
isEnabled
:
account
.
isEnabled
&&
verifiedTokens
.
isEnabled
&&
Boolean
(
adminServiceApiHost
)
,
isEnabled
:
true
,
api
:
{
endpoint
:
adminServiceApiHost
,
basePath
:
''
,
},
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/adsBanner.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
type
{
AdButlerConfig
}
from
'
types/client/adButlerConfig
'
;
import
type
{
AdBannerProviders
}
from
'
types/client/adProviders
'
;
...
...
@@ -10,14 +11,50 @@ const provider: AdBannerProviders = (() => {
return
envValue
&&
SUPPORTED_AD_BANNER_PROVIDERS
.
includes
(
envValue
)
?
envValue
:
'
slise
'
;
})();
export
default
Object
.
freeze
({
title
:
'
Banner ads
'
,
isEnabled
:
provider
!==
'
none
'
,
const
title
=
'
Banner ads
'
;
type
AdsBannerFeaturePayload
=
{
provider
:
Exclude
<
AdBannerProviders
,
'
adbutler
'
|
'
none
'
>
;
}
|
{
provider
:
'
adbutler
'
;
adButler
:
{
config
:
{
desktop
:
AdButlerConfig
;
mobile
:
AdButlerConfig
;
};
};
}
const
config
:
Feature
<
AdsBannerFeaturePayload
>
=
(()
=>
{
if
(
provider
===
'
adbutler
'
)
{
const
desktopConfig
=
parseEnvJson
<
AdButlerConfig
>
(
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP
));
const
mobileConfig
=
parseEnvJson
<
AdButlerConfig
>
(
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE
));
if
(
desktopConfig
&&
mobileConfig
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
provider
,
adButler
:
{
config
:
{
desktop
:
parseEnvJson
<
AdButlerConfig
>
(
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP
))
??
undefined
,
mobile
:
parseEnvJson
<
AdButlerConfig
>
(
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE
))
??
undefined
,
desktop
:
desktopConfig
,
mobile
:
mobileConfig
,
},
},
});
});
}
}
else
if
(
provider
!==
'
none
'
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
provider
,
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/adsText.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
type
{
AdTextProviders
}
from
'
types/client/adProviders
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
...
...
@@ -9,8 +10,21 @@ const provider: AdTextProviders = (() => {
return
envValue
&&
SUPPORTED_AD_BANNER_PROVIDERS
.
includes
(
envValue
)
?
envValue
as
AdTextProviders
:
'
coinzilla
'
;
})();
export
default
Object
.
freeze
({
title
:
'
Text ads
'
,
isEnabled
:
provider
!==
'
none
'
,
const
title
=
'
Text ads
'
;
const
config
:
Feature
<
{
provider
:
AdTextProviders
}
>
=
(()
=>
{
if
(
provider
!==
'
none
'
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
provider
,
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/beaconChain.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
export
default
Object
.
freeze
({
title
:
'
Beacon chain
'
,
isEnabled
:
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_HAS_BEACON_CHAIN
)
===
'
true
'
,
const
title
=
'
Beacon chain
'
;
const
config
:
Feature
<
{
currency
:
{
symbol
:
string
}
}
>
=
(()
=>
{
if
(
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_HAS_BEACON_CHAIN
)
===
'
true
'
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
currency
:
{
symbol
:
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_BEACON_CHAIN_CURRENCY_SYMBOL
)
||
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL
),
symbol
:
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_BEACON_CHAIN_CURRENCY_SYMBOL
)
||
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL
)
||
''
,
// maybe we need some other default value here
},
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/blockchainInteraction.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
chain
from
'
../chain
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
const
walletConnectProjectId
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID
);
export
default
Object
.
freeze
({
title
:
'
Blockchain interaction (writing to contract, etc.)
'
,
isEnabled
:
Boolean
(
const
title
=
'
Blockchain interaction (writing to contract, etc.)
'
;
const
config
:
Feature
<
{
walletConnect
:
{
projectId
:
string
}
}
>
=
(()
=>
{
if
(
// all chain parameters are required for wagmi provider
// @wagmi/chains/dist/index.d.ts
chain
.
id
&&
...
...
@@ -14,9 +18,21 @@ export default Object.freeze({
chain
.
currency
.
symbol
&&
chain
.
currency
.
decimals
&&
chain
.
rpcUrl
&&
walletConnectProjectId
,
),
walletConnectProjectId
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
walletConnect
:
{
projectId
:
walletConnectProjectId
??
''
,
projectId
:
walletConnectProjectId
,
},
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/csvExport.ts
View file @
0a992acd
import
{
getEnvValue
}
from
'
../util
s
'
;
import
type
{
Feature
}
from
'
./type
s
'
;
const
reCaptchaSiteKey
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY
)
;
import
services
from
'
../services
'
;
export
default
Object
.
freeze
({
title
:
'
Export data to CSV file
'
,
isEnabled
:
Boolean
(
reCaptchaSiteKey
),
const
title
=
'
Export data to CSV file
'
;
const
config
:
Feature
<
{
reCaptcha
:
{
siteKey
:
string
}}
>
=
(()
=>
{
if
(
services
.
reCaptcha
.
siteKey
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
reCaptcha
:
{
siteKey
:
reCaptchaSiteKey
??
''
,
siteKey
:
services
.
reCaptcha
.
siteKey
,
},
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/googleAnalytics.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
const
propertyId
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID
);
export
default
Object
.
freeze
({
title
:
'
Google analytics
'
,
isEnabled
:
Boolean
(
propertyId
),
const
title
=
'
Google analytics
'
;
const
config
:
Feature
<
{
propertyId
:
string
}
>
=
(()
=>
{
if
(
propertyId
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
propertyId
,
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/graphqlApiDocs.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
const
defaultTxHash
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_GRAPHIQL_TRANSACTION
);
export
default
Object
.
freeze
({
title
:
'
GraphQL API documentation
'
,
const
title
=
'
GraphQL API documentation
'
;
const
config
:
Feature
<
{
defaultTxHash
:
string
|
undefined
}
>
=
(()
=>
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
defaultTxHash
,
});
});
})();
export
default
config
;
configs/app/features/marketplace.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
chain
from
'
../chain
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
const
configUrl
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL
);
const
submitForm
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM
);
export
default
Object
.
freeze
({
title
:
'
Marketplace
'
,
isEnabled
:
Boolean
(
chain
.
rpcUrl
&&
configUrl
&&
submitForm
),
configUrl
:
configUrl
??
''
,
submitFormUrl
:
submitForm
??
''
,
});
const
submitFormUrl
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM
);
const
title
=
'
Marketplace
'
;
const
config
:
Feature
<
{
configUrl
:
string
;
submitFormUrl
:
string
}
>
=
(()
=>
{
if
(
chain
.
rpcUrl
&&
configUrl
&&
submitFormUrl
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
configUrl
,
submitFormUrl
,
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/mixpanel.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
const
projectToken
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN
);
export
default
Object
.
freeze
({
title
:
'
Mixpanel analytics
'
,
isEnabled
:
Boolean
(
projectToken
),
projectToken
:
projectToken
??
''
,
});
const
title
=
'
Mixpanel analytics
'
;
const
config
:
Feature
<
{
projectToken
:
string
}
>
=
(()
=>
{
if
(
projectToken
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
projectToken
,
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/restApiDocs.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
const
specUrl
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_API_SPEC_URL
);
export
default
Object
.
freeze
({
title
:
'
REST API documentation
'
,
isEnabled
:
Boolean
(
specUrl
),
const
title
=
'
REST API documentation
'
;
const
config
:
Feature
<
{
specUrl
:
string
}
>
=
(()
=>
{
if
(
specUrl
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
specUrl
,
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/rollup.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
export
default
Object
.
freeze
({
title
:
'
Rollup (L2) chain
'
,
isEnabled
:
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_IS_L2_NETWORK
)
===
'
true
'
,
L1BaseUrl
:
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_L1_BASE_URL
)
??
''
,
withdrawalUrl
:
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_L2_WITHDRAWAL_URL
)
??
''
,
});
const
title
=
'
Rollup (L2) chain
'
;
const
config
:
Feature
<
{
L1BaseUrl
:
string
;
withdrawalUrl
:
string
}
>
=
(()
=>
{
const
L1BaseUrl
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_L1_BASE_URL
);
const
withdrawalUrl
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_L2_WITHDRAWAL_URL
);
if
(
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_IS_L2_NETWORK
)
===
'
true
'
&&
L1BaseUrl
&&
withdrawalUrl
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
L1BaseUrl
,
withdrawalUrl
,
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/sentry.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
const
dsn
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_SENTRY_DSN
);
// TODO @tom2drum check sentry setup
export
default
Object
.
freeze
({
title
:
'
Sentry error monitoring
'
,
isEnabled
:
Boolean
(
dsn
),
const
title
=
'
Sentry error monitoring
'
;
const
config
:
Feature
<
{
dsn
:
string
;
environment
:
string
|
undefined
;
cspReportUrl
:
string
|
undefined
;
instance
:
string
|
undefined
}
>
=
(()
=>
{
if
(
dsn
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
dsn
,
environment
:
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_APP_ENV
)
||
getEnvValue
(
process
.
env
.
NODE_ENV
),
cspReportUrl
:
getEnvValue
(
process
.
env
.
SENTRY_CSP_REPORT_URI
),
instance
:
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_APP_INSTANCE
),
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/sol2uml.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
const
apiEndpoint
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_VISUALIZE_API_HOST
);
export
default
Object
.
freeze
({
title
:
'
Solidity to UML diagrams
'
,
isEnabled
:
Boolean
(
apiEndpoint
),
const
title
=
'
Solidity to UML diagrams
'
;
const
config
:
Feature
<
{
api
:
{
endpoint
:
string
;
basePath
:
string
}
}
>
=
(()
=>
{
if
(
apiEndpoint
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
api
:
{
endpoint
:
apiEndpoint
,
basePath
:
''
,
},
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/stats.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
const
apiEndpoint
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_STATS_API_HOST
);
export
default
Object
.
freeze
({
title
:
'
Blockchain statistics
'
,
isEnabled
:
Boolean
(
apiEndpoint
),
const
title
=
'
Blockchain statistics
'
;
const
config
:
Feature
<
{
api
:
{
endpoint
:
string
;
basePath
:
string
}
}
>
=
(()
=>
{
if
(
apiEndpoint
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
api
:
{
endpoint
:
apiEndpoint
,
basePath
:
''
,
},
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/types.ts
0 → 100644
View file @
0a992acd
type
FeatureEnabled
<
Payload
extends
Record
<
string
,
unknown
>
=
Record
<
string
,
never
>>
=
{
title
:
string
;
isEnabled
:
true
}
&
Payload
;
type
FeatureDisabled
=
{
title
:
string
;
isEnabled
:
false
};
export
type
Feature
<
Payload
extends
Record
<
string
,
unknown
>
=
Record
<
string
,
never
>>
=
FeatureEnabled
<
Payload
>
|
FeatureDisabled
;
// typescript cannot properly resolve unions in nested objects - https://github.com/microsoft/TypeScript/issues/18758
// so we use this little helper where it is needed
export
const
getFeaturePayload
=
<
Payload
extends
Record
<
string
,
unknown
>>
(
feature
:
Feature
<
Payload
>
):
Payload
|
undefined
=>
{
return
feature
.
isEnabled
?
feature
:
undefined
;
};
configs/app/features/verifiedTokens.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
const
contractInfoApiHost
=
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_CONTRACT_INFO_API_HOST
);
export
default
Object
.
freeze
({
title
:
'
Verified tokens info
'
,
isEnabled
:
Boolean
(
contractInfoApiHost
),
const
title
=
'
Verified tokens info
'
;
const
config
:
Feature
<
{
api
:
{
endpoint
:
string
;
basePath
:
string
}
}
>
=
(()
=>
{
if
(
contractInfoApiHost
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
api
:
{
endpoint
:
contractInfoApiHost
,
basePath
:
''
,
},
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/app/features/web3Wallet.ts
View file @
0a992acd
import
type
{
Feature
}
from
'
./types
'
;
import
type
{
WalletType
}
from
'
types/client/wallets
'
;
import
{
getEnvValue
}
from
'
../utils
'
;
...
...
@@ -12,12 +13,25 @@ const defaultWallet = ((): WalletType => {
return
envValue
&&
SUPPORTED_WALLETS
.
includes
(
envValue
)
?
envValue
:
'
metamask
'
;
})();
export
default
Object
.
freeze
({
title
:
'
Web3 wallet integration (add token or network to the wallet)
'
,
isEnabled
:
defaultWallet
!==
'
none
'
,
const
title
=
'
Web3 wallet integration (add token or network to the wallet)
'
;
const
config
:
Feature
<
{
defaultWallet
:
Exclude
<
WalletType
,
'
none
'
>
;
addToken
:
{
isDisabled
:
boolean
}}
>
=
(()
=>
{
if
(
defaultWallet
!==
'
none
'
)
{
return
Object
.
freeze
({
title
,
isEnabled
:
true
,
defaultWallet
,
addToken
:
{
isDisabled
:
getEnvValue
(
process
.
env
.
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET
)
===
'
true
'
,
},
addNetwork
:
{},
});
});
}
return
Object
.
freeze
({
title
,
isEnabled
:
false
,
});
})();
export
default
config
;
configs/sentry/react.ts
View file @
0a992acd
...
...
@@ -3,9 +3,16 @@ import { BrowserTracing } from '@sentry/tracing';
import
appConfig
from
'
configs/app
'
;
export
const
config
:
Sentry
.
BrowserOptions
=
{
environment
:
appConfig
.
features
.
sentry
.
environment
,
dsn
:
appConfig
.
features
.
sentry
.
dsn
,
const
feature
=
appConfig
.
features
.
sentry
;
export
const
config
:
Sentry
.
BrowserOptions
|
undefined
=
(()
=>
{
if
(
!
feature
.
isEnabled
)
{
return
;
}
return
{
environment
:
feature
.
environment
,
dsn
:
feature
.
dsn
,
release
:
process
.
env
.
NEXT_PUBLIC_GIT_TAG
||
process
.
env
.
NEXT_PUBLIC_GIT_COMMIT_SHA
,
integrations
:
[
new
BrowserTracing
()
],
// We recommend adjusting this value in production, or using tracesSampler
...
...
@@ -54,8 +61,12 @@ export const config: Sentry.BrowserOptions = {
/webappstoolbarba
\.
texthelp
\.
com
\/
/i
,
/metrics
\.
itunes
\.
apple
\.
com
\.
edgesuite
\.
net
\/
/i
,
],
};
};
})();
export
function
configureScope
(
scope
:
Sentry
.
Scope
)
{
scope
.
setTag
(
'
app_instance
'
,
appConfig
.
features
.
sentry
.
instance
);
if
(
!
feature
.
isEnabled
)
{
return
;
}
scope
.
setTag
(
'
app_instance
'
,
feature
.
instance
);
}
lib/api/resources.ts
View file @
0a992acd
import
{
getFeaturePayload
}
from
'
configs/app/features/types
'
;
import
type
{
UserInfo
,
CustomAbis
,
...
...
@@ -111,58 +112,58 @@ export const RESOURCES = {
address_verification
:
{
path
:
'
/api/v1/chains/:chainId/verified-addresses:type
'
,
pathParams
:
[
'
chainId
'
as
const
,
'
type
'
as
const
],
endpoint
:
config
.
features
.
verifiedTokens
.
api
.
endpoint
,
basePath
:
config
.
features
.
verifiedTokens
.
api
.
basePath
,
endpoint
:
getFeaturePayload
(
config
.
features
.
verifiedTokens
)?
.
api
.
endpoint
,
basePath
:
getFeaturePayload
(
config
.
features
.
verifiedTokens
)?
.
api
.
basePath
,
needAuth
:
true
,
},
verified_addresses
:
{
path
:
'
/api/v1/chains/:chainId/verified-addresses
'
,
pathParams
:
[
'
chainId
'
as
const
],
endpoint
:
config
.
features
.
verifiedTokens
.
api
.
endpoint
,
basePath
:
config
.
features
.
verifiedTokens
.
api
.
basePath
,
endpoint
:
getFeaturePayload
(
config
.
features
.
verifiedTokens
)?
.
api
.
endpoint
,
basePath
:
getFeaturePayload
(
config
.
features
.
verifiedTokens
)?
.
api
.
basePath
,
needAuth
:
true
,
},
token_info_applications_config
:
{
path
:
'
/api/v1/chains/:chainId/token-info-submissions/selectors
'
,
pathParams
:
[
'
chainId
'
as
const
],
endpoint
:
config
.
features
.
addressVerification
.
api
.
endpoint
,
basePath
:
config
.
features
.
addressVerification
.
api
.
basePath
,
endpoint
:
getFeaturePayload
(
config
.
features
.
addressVerification
)?
.
api
.
endpoint
,
basePath
:
getFeaturePayload
(
config
.
features
.
addressVerification
)?
.
api
.
basePath
,
needAuth
:
true
,
},
token_info_applications
:
{
path
:
'
/api/v1/chains/:chainId/token-info-submissions/:id?
'
,
pathParams
:
[
'
chainId
'
as
const
,
'
id
'
as
const
],
endpoint
:
config
.
features
.
addressVerification
.
api
.
endpoint
,
basePath
:
config
.
features
.
addressVerification
.
api
.
basePath
,
endpoint
:
getFeaturePayload
(
config
.
features
.
addressVerification
)?
.
api
.
endpoint
,
basePath
:
getFeaturePayload
(
config
.
features
.
addressVerification
)?
.
api
.
basePath
,
needAuth
:
true
,
},
// STATS
stats_counters
:
{
path
:
'
/api/v1/counters
'
,
endpoint
:
config
.
features
.
stats
.
api
.
endpoint
,
basePath
:
config
.
features
.
stats
.
api
.
basePath
,
endpoint
:
getFeaturePayload
(
config
.
features
.
stats
)?
.
api
.
endpoint
,
basePath
:
getFeaturePayload
(
config
.
features
.
stats
)?
.
api
.
basePath
,
},
stats_lines
:
{
path
:
'
/api/v1/lines
'
,
endpoint
:
config
.
features
.
stats
.
api
.
endpoint
,
basePath
:
config
.
features
.
stats
.
api
.
basePath
,
endpoint
:
getFeaturePayload
(
config
.
features
.
stats
)?
.
api
.
endpoint
,
basePath
:
getFeaturePayload
(
config
.
features
.
stats
)?
.
api
.
basePath
,
},
stats_line
:
{
path
:
'
/api/v1/lines/:id
'
,
pathParams
:
[
'
id
'
as
const
],
endpoint
:
config
.
features
.
stats
.
api
.
endpoint
,
basePath
:
config
.
features
.
stats
.
api
.
basePath
,
endpoint
:
getFeaturePayload
(
config
.
features
.
stats
)?
.
api
.
endpoint
,
basePath
:
getFeaturePayload
(
config
.
features
.
stats
)?
.
api
.
basePath
,
},
// VISUALIZATION
visualize_sol2uml
:
{
path
:
'
/api/v1/solidity
\\
:visualize-contracts
'
,
endpoint
:
config
.
features
.
sol2uml
.
api
.
endpoint
,
basePath
:
config
.
features
.
sol2uml
.
api
.
basePath
,
endpoint
:
getFeaturePayload
(
config
.
features
.
sol2uml
)?
.
api
.
endpoint
,
basePath
:
getFeaturePayload
(
config
.
features
.
sol2uml
)?
.
api
.
basePath
,
},
// BLOCKS, TXS
...
...
@@ -345,8 +346,8 @@ export const RESOURCES = {
token_verified_info
:
{
path
:
'
/api/v1/chains/:chainId/token-infos/:hash
'
,
pathParams
:
[
'
chainId
'
as
const
,
'
hash
'
as
const
],
endpoint
:
config
.
features
.
verifiedTokens
.
api
.
endpoint
,
basePath
:
config
.
features
.
verifiedTokens
.
api
.
basePath
,
endpoint
:
getFeaturePayload
(
config
.
features
.
verifiedTokens
)?
.
api
.
endpoint
,
basePath
:
getFeaturePayload
(
config
.
features
.
verifiedTokens
)?
.
api
.
basePath
,
},
token_counters
:
{
path
:
'
/api/v2/tokens/:hash/counters
'
,
...
...
lib/csp/policies/ad.ts
View file @
0a992acd
...
...
@@ -19,7 +19,7 @@ export function ad(): CspDev.DirectiveDescriptor {
'
coinzillatag.com
'
,
'
servedbyadbutler.com
'
,
`'sha256-
${
Base64
.
stringify
(
sha256
(
connectAdbutler
))
}
'`
,
`'sha256-
${
Base64
.
stringify
(
sha256
(
placeAd
))
}
'`
,
`'sha256-
${
Base64
.
stringify
(
sha256
(
placeAd
??
''
))
}
'
`,
'
*
.
slise
.
xyz
'
,
],
'
img
-
src
'
: [
...
...
lib/csp/policies/app.ts
View file @
0a992acd
import
type
CspDev
from
'
csp-dev
'
;
import
{
getFeaturePayload
}
from
'
configs/app/features/types
'
;
import
config
from
'
configs/app
'
;
import
{
KEY_WORDS
}
from
'
../utils
'
;
...
...
@@ -7,9 +9,8 @@ import { KEY_WORDS } from '../utils';
const
MAIN_DOMAINS
=
[
`*.
${
config
.
app
.
host
}
`
,
config
.
app
.
host
,
config
.
features
.
sol2uml
.
api
.
endpoint
,
getFeaturePayload
(
config
.
features
.
sol2uml
)?
.
api
.
endpoint
,
].
filter
(
Boolean
);
// eslint-disable-next-line no-restricted-properties
export
function
app
():
CspDev
.
DirectiveDescriptor
{
return
{
...
...
@@ -30,10 +31,10 @@ export function app(): CspDev.DirectiveDescriptor {
// APIs
config
.
api
.
endpoint
,
config
.
api
.
socket
,
config
.
features
.
stats
.
api
.
endpoint
,
config
.
features
.
sol2uml
.
api
.
endpoint
,
config
.
features
.
verifiedTokens
.
api
.
endpoint
,
config
.
features
.
addressVerification
.
api
.
endpoint
,
getFeaturePayload
(
config
.
features
.
stats
)?
.
api
.
endpoint
,
getFeaturePayload
(
config
.
features
.
sol2uml
)?
.
api
.
endpoint
,
getFeaturePayload
(
config
.
features
.
verifiedTokens
)?
.
api
.
endpoint
,
getFeaturePayload
(
config
.
features
.
addressVerification
)?
.
api
.
endpoint
,
// chain RPC server
config
.
chain
.
rpcUrl
,
...
...
@@ -108,10 +109,17 @@ export function app(): CspDev.DirectiveDescriptor {
'
*
'
,
],
...(
config
.
features
.
sentry
.
isEnabled
&&
config
.
features
.
sentry
.
cspReportUrl
&&
!
config
.
app
.
isDev
?
{
...((()
=>
{
const
sentryFeature
=
config
.
features
.
sentry
;
if
(
!
sentryFeature
.
isEnabled
||
!
sentryFeature
.
cspReportUrl
||
config
.
app
.
isDev
)
{
return
{};
}
return
{
'
report-uri
'
:
[
config
.
features
.
sentry
.
cspReportUrl
,
sentryFeature
.
cspReportUrl
,
],
}
:
{}),
};
})()),
};
}
lib/hooks/useConfigSentry.tsx
View file @
0a992acd
...
...
@@ -5,6 +5,10 @@ import { config, configureScope } from 'configs/sentry/react';
export
default
function
useConfigSentry
()
{
React
.
useEffect
(()
=>
{
if
(
!
config
)
{
return
;
}
// gotta init sentry in browser
Sentry
.
init
(
config
);
Sentry
.
configureScope
(
configureScope
);
...
...
lib/hooks/useIsAccountActionAllowed.tsx
View file @
0a992acd
...
...
@@ -14,6 +14,10 @@ export default function useIsAccountActionAllowed() {
const
loginUrl
=
useLoginUrl
();
return
React
.
useCallback
(()
=>
{
if
(
!
loginUrl
)
{
return
false
;
}
if
(
!
isAuth
)
{
window
.
location
.
assign
(
loginUrl
);
return
false
;
...
...
lib/hooks/useLoginUrl.tsx
View file @
0a992acd
...
...
@@ -3,7 +3,11 @@ import { route } from 'nextjs-routes';
import
config
from
'
configs/app
'
;
const
feature
=
config
.
features
.
account
;
export
default
function
useLoginUrl
()
{
const
router
=
useRouter
();
return
config
.
features
.
account
.
authUrl
+
route
({
pathname
:
'
/auth/auth0
'
,
query
:
{
path
:
router
.
asPath
}
});
return
feature
.
isEnabled
?
feature
.
authUrl
+
route
({
pathname
:
'
/auth/auth0
'
,
query
:
{
path
:
router
.
asPath
}
})
:
undefined
;
}
lib/hooks/useRedirectForInvalidAuthToken.tsx
View file @
0a992acd
...
...
@@ -18,7 +18,7 @@ export default function useRedirectForInvalidAuthToken() {
if
(
errorStatus
===
401
)
{
const
apiToken
=
cookies
.
get
(
cookies
.
NAMES
.
API_TOKEN
);
if
(
apiToken
)
{
if
(
apiToken
&&
loginUrl
)
{
Sentry
.
captureException
(
new
Error
(
'
Invalid api token
'
),
{
tags
:
{
source
:
'
invalid_api_token
'
}
});
window
.
location
.
assign
(
loginUrl
);
}
...
...
lib/mixpanel/useInit.tsx
View file @
0a992acd
...
...
@@ -19,7 +19,8 @@ export default function useMixpanelInit() {
React
.
useEffect
(()
=>
{
isGoogleAnalyticsLoaded
().
then
((
isGALoaded
)
=>
{
if
(
!
config
.
features
.
mixpanel
.
isEnabled
)
{
const
feature
=
config
.
features
.
mixpanel
;
if
(
!
feature
.
isEnabled
)
{
return
;
}
...
...
@@ -30,7 +31,7 @@ export default function useMixpanelInit() {
};
const
isAuth
=
Boolean
(
cookies
.
get
(
cookies
.
NAMES
.
API_TOKEN
));
mixpanel
.
init
(
config
.
features
.
mixpanel
.
projectToken
,
mixpanelConfig
);
mixpanel
.
init
(
feature
.
projectToken
,
mixpanelConfig
);
mixpanel
.
register
({
'
Chain id
'
:
config
.
chain
.
id
,
Environment
:
config
.
app
.
isDev
?
'
Dev
'
:
'
Prod
'
,
...
...
lib/next/middlewares/account.ts
View file @
0a992acd
...
...
@@ -3,12 +3,12 @@ import { NextResponse } from 'next/server';
import
{
route
}
from
'
nextjs-routes
'
;
import
config
from
'
configs/app
'
;
import
{
httpLogger
}
from
'
lib/api/logger
'
;
import
{
DAY
}
from
'
lib/consts
'
;
import
*
as
cookies
from
'
lib/cookies
'
;
export
function
account
(
req
:
NextRequest
)
{
if
(
!
config
.
features
.
account
.
isEnabled
)
{
const
feature
=
config
.
features
.
account
;
if
(
!
feature
.
isEnabled
)
{
return
;
}
...
...
@@ -24,7 +24,7 @@ export function account(req: NextRequest) {
const
isProfileRoute
=
req
.
nextUrl
.
pathname
.
includes
(
'
/auth/profile
'
);
if
((
isAccountRoute
||
isProfileRoute
))
{
const
authUrl
=
config
.
features
.
account
.
authUrl
+
route
({
pathname
:
'
/auth/auth0
'
,
query
:
{
path
:
req
.
nextUrl
.
pathname
}
});
const
authUrl
=
feature
.
authUrl
+
route
({
pathname
:
'
/auth/auth0
'
,
query
:
{
path
:
req
.
nextUrl
.
pathname
}
});
return
NextResponse
.
redirect
(
authUrl
);
}
}
...
...
@@ -33,20 +33,11 @@ export function account(req: NextRequest) {
if
(
req
.
cookies
.
get
(
cookies
.
NAMES
.
INVALID_SESSION
))
{
// if user has both cookies, make redirect to logout
if
(
apiTokenCookie
)
{
// temporary solution
// TODO check app for integrity https://github.com/blockscout/frontend/issues/1028 and make typescript happy here
if
(
!
config
.
features
.
account
.
logoutUrl
)
{
httpLogger
.
logger
.
error
({
message
:
'
Logout URL is not configured
'
,
});
return
;
}
// yes, we could have checked that the current URL is not the logout URL, but we hadn't
// logout URL is always external URL in auth0.com sub-domain
// at least we hope so
const
res
=
NextResponse
.
redirect
(
config
.
features
.
account
.
logoutUrl
);
const
res
=
NextResponse
.
redirect
(
feature
.
logoutUrl
);
res
.
cookies
.
delete
(
cookies
.
NAMES
.
CONFIRM_EMAIL_PAGE_VIEWED
);
// reset cookie to show email verification page again
return
res
;
...
...
lib/web3/useProvider.tsx
View file @
0a992acd
...
...
@@ -5,11 +5,13 @@ import 'wagmi/window';
import
config
from
'
configs/app
'
;
const
feature
=
config
.
features
.
web3Wallet
;
export
default
function
useProvider
()
{
const
[
provider
,
setProvider
]
=
React
.
useState
<
WindowProvider
>
();
React
.
useEffect
(()
=>
{
if
(
!
(
'
ethereum
'
in
window
&&
window
.
ethereum
)
||
!
config
.
features
.
web3Wallet
.
isEnabled
)
{
if
(
!
(
'
ethereum
'
in
window
&&
window
.
ethereum
)
||
!
feature
.
isEnabled
)
{
return
;
}
...
...
@@ -18,11 +20,11 @@ export default function useProvider() {
const
providers
=
Array
.
isArray
(
window
.
ethereum
.
providers
)
?
window
.
ethereum
.
providers
:
[
window
.
ethereum
];
providers
.
forEach
(
async
(
provider
)
=>
{
if
(
config
.
features
.
web3Wallet
.
defaultWallet
===
'
coinbase
'
&&
provider
.
isCoinbaseWallet
)
{
if
(
feature
.
defaultWallet
===
'
coinbase
'
&&
provider
.
isCoinbaseWallet
)
{
return
setProvider
(
provider
);
}
if
(
config
.
features
.
web3Wallet
.
defaultWallet
===
'
metamask
'
&&
provider
.
isMetaMask
)
{
if
(
feature
.
defaultWallet
===
'
metamask
'
&&
provider
.
isMetaMask
)
{
return
setProvider
(
provider
);
}
});
...
...
playwright/utils/buildApiUrl.ts
View file @
0a992acd
...
...
@@ -6,6 +6,6 @@ import { RESOURCES } from 'lib/api/resources';
export
default
function
buildApiUrl
<
R
extends
ResourceName
>
(
resourceName
:
R
,
pathParams
?:
ResourcePathParams
<
R
>
)
{
const
resource
=
RESOURCES
[
resourceName
];
const
defaultApi
=
'
https://
'
+
process
.
env
.
NEXT_PUBLIC_API_HOST
+
'
:
'
+
process
.
env
.
NEXT_PUBLIC_API_PORT
;
const
origin
=
'
endpoint
'
in
resource
?
resource
.
endpoint
+
resource
.
basePath
:
defaultApi
;
const
origin
=
'
endpoint
'
in
resource
&&
resource
.
endpoint
?
resource
.
endpoint
+
(
resource
.
basePath
??
''
)
:
defaultApi
;
return
origin
+
compile
(
resource
.
path
)(
pathParams
);
}
ui/apiDocs/SwaggerUI.tsx
View file @
0a992acd
...
...
@@ -13,6 +13,8 @@ import ContentLoader from 'ui/shared/ContentLoader';
import
'
swagger-ui-react/swagger-ui.css
'
;
const
feature
=
config
.
features
.
restApiDocs
;
const
DEFAULT_SERVER
=
'
blockscout.com/poa/core
'
;
const
NeverShowInfoPlugin
=
()
=>
{
...
...
@@ -65,10 +67,14 @@ const SwaggerUI = () => {
return
req
;
},
[]);
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
Box
sx=
{
swaggerStyle
}
>
<
SwaggerUIReact
url=
{
config
.
features
.
restApiDocs
.
specUrl
}
url=
{
feature
.
specUrl
}
plugins=
{
[
NeverShowInfoPlugin
]
}
requestInterceptor=
{
reqInterceptor
}
/>
...
...
ui/csvExport/CsvExportFormReCaptcha.tsx
View file @
0a992acd
...
...
@@ -42,7 +42,9 @@ const CsvExportFormReCaptcha = ({ formApi }: Props) => {
formApi
.
setError
(
'
reCaptcha
'
,
{
type
:
'
required
'
});
},
[
formApi
]);
if
(
!
config
.
features
.
csvExport
.
isEnabled
)
{
const
feature
=
config
.
features
.
csvExport
;
if
(
!
feature
.
isEnabled
)
{
return
(
<
Alert
status=
"error"
>
CSV export is not available at the moment since reCaptcha is not configured for this application.
...
...
@@ -55,7 +57,7 @@ const CsvExportFormReCaptcha = ({ formApi }: Props) => {
<
ReCaptcha
className=
"recaptcha"
ref=
{
ref
}
sitekey=
{
config
.
features
.
csvExport
.
reCaptcha
.
siteKey
}
sitekey=
{
feature
.
reCaptcha
.
siteKey
}
onChange=
{
handleReCaptchaChange
}
onExpired=
{
handleReCaptchaExpire
}
/>
...
...
ui/graphQL/GraphQL.tsx
View file @
0a992acd
...
...
@@ -8,6 +8,8 @@ import buildUrl from 'lib/api/buildUrl';
import
'
graphiql/graphiql.css
'
;
import
isBrowser
from
'
lib/isBrowser
'
;
const
feature
=
config
.
features
.
graphqlApiDocs
;
const
graphQLStyle
=
{
'
.graphiql-container
'
:
{
backgroundColor
:
'
unset
'
,
...
...
@@ -31,9 +33,13 @@ const GraphQL = () => {
}
},
[
colorMode
]);
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
const
initialQuery
=
`{
transaction(
hash: "
${
config
.
features
.
graphqlApiDocs
.
defaultTxHash
}
"
hash: "
${
feature
.
defaultTxHash
}
"
) {
hash
blockNumber
...
...
ui/home/LatestDepositsItem.tsx
View file @
0a992acd
...
...
@@ -19,6 +19,8 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import
LinkExternal
from
'
ui/shared/LinkExternal
'
;
import
LinkInternal
from
'
ui/shared/LinkInternal
'
;
const
feature
=
config
.
features
.
rollup
;
type
Props
=
{
item
:
L2DepositsItem
;
isLoading
?:
boolean
;
...
...
@@ -28,9 +30,13 @@ const LatestTxsItem = ({ item, isLoading }: Props) => {
const
timeAgo
=
dayjs
(
item
.
l1_block_timestamp
).
fromNow
();
const
isMobile
=
useIsMobile
();
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
const
l1BlockLink
=
(
<
LinkExternal
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/block/[height_or_hash]
'
,
query
:
{
height_or_hash
:
item
.
l1_block_number
.
toString
()
}
})
}
fontWeight=
{
700
}
...
...
@@ -45,7 +51,7 @@ const LatestTxsItem = ({ item, isLoading }: Props) => {
const
l1TxLink
=
(
<
LinkExternal
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
maxW=
"100%"
display=
"inline-flex"
alignItems=
"center"
...
...
ui/l2Deposits/DepositsListItem.tsx
View file @
0a992acd
...
...
@@ -16,18 +16,24 @@ import LinkExternal from 'ui/shared/LinkExternal';
import
LinkInternal
from
'
ui/shared/LinkInternal
'
;
import
ListItemMobileGrid
from
'
ui/shared/ListItemMobile/ListItemMobileGrid
'
;
const
feature
=
config
.
features
.
rollup
;
type
Props
=
{
item
:
L2DepositsItem
;
isLoading
?:
boolean
};
const
DepositsListItem
=
({
item
,
isLoading
}:
Props
)
=>
{
const
timeAgo
=
dayjs
(
item
.
l1_block_timestamp
).
fromNow
();
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
ListItemMobileGrid
.
Container
>
<
ListItemMobileGrid
.
Label
isLoading=
{
isLoading
}
>
L1 block No
</
ListItemMobileGrid
.
Label
>
<
ListItemMobileGrid
.
Value
py=
"3px"
>
<
LinkExternal
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/block/[height_or_hash]
'
,
query
:
{
height_or_hash
:
item
.
l1_block_number
.
toString
()
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/block/[height_or_hash]
'
,
query
:
{
height_or_hash
:
item
.
l1_block_number
.
toString
()
}
})
}
fontWeight=
{
600
}
display=
"flex"
isLoading=
{
isLoading
}
...
...
@@ -63,7 +69,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => {
<
ListItemMobileGrid
.
Label
isLoading=
{
isLoading
}
>
L1 txn hash
</
ListItemMobileGrid
.
Label
>
<
ListItemMobileGrid
.
Value
py=
"3px"
>
<
LinkExternal
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
maxW=
"100%"
display=
"flex"
overflow=
"hidden"
...
...
@@ -79,7 +85,7 @@ const DepositsListItem = ({ item, isLoading }: Props) => {
<
ListItemMobileGrid
.
Label
isLoading=
{
isLoading
}
>
L1 txn origin
</
ListItemMobileGrid
.
Label
>
<
ListItemMobileGrid
.
Value
py=
"3px"
>
<
LinkExternal
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/address/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_origin
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/address/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_origin
}
})
}
maxW=
"100%"
display=
"flex"
overflow=
"hidden"
...
...
ui/l2Deposits/DepositsTableItem.tsx
View file @
0a992acd
...
...
@@ -15,16 +15,22 @@ import HashStringShorten from 'ui/shared/HashStringShorten';
import
LinkExternal
from
'
ui/shared/LinkExternal
'
;
import
LinkInternal
from
'
ui/shared/LinkInternal
'
;
const
feature
=
config
.
features
.
rollup
;
type
Props
=
{
item
:
L2DepositsItem
;
isLoading
?:
boolean
};
const
WithdrawalsTableItem
=
({
item
,
isLoading
}:
Props
)
=>
{
const
timeAgo
=
dayjs
(
item
.
l1_block_timestamp
).
fromNow
();
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
Tr
>
<
Td
verticalAlign=
"middle"
fontWeight=
{
600
}
>
<
LinkExternal
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/block/[height_or_hash]
'
,
query
:
{
height_or_hash
:
item
.
l1_block_number
.
toString
()
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/block/[height_or_hash]
'
,
query
:
{
height_or_hash
:
item
.
l1_block_number
.
toString
()
}
})
}
fontWeight=
{
600
}
display=
"inline-flex"
isLoading=
{
isLoading
}
...
...
@@ -56,7 +62,7 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => {
</
Td
>
<
Td
verticalAlign=
"middle"
>
<
LinkExternal
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
maxW=
"100%"
display=
"inline-flex"
overflow=
"hidden"
...
...
@@ -70,7 +76,7 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => {
</
Td
>
<
Td
verticalAlign=
"middle"
>
<
LinkExternal
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/address/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_origin
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/address/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_origin
}
})
}
maxW=
"100%"
display=
"inline-flex"
overflow=
"hidden"
...
...
ui/l2OutputRoots/OutputRootsListItem.tsx
View file @
0a992acd
...
...
@@ -14,11 +14,17 @@ import LinkExternal from 'ui/shared/LinkExternal';
import
LinkInternal
from
'
ui/shared/LinkInternal
'
;
import
ListItemMobileGrid
from
'
ui/shared/ListItemMobile/ListItemMobileGrid
'
;
const
feature
=
config
.
features
.
rollup
;
type
Props
=
{
item
:
L2OutputRootsItem
;
isLoading
?:
boolean
};
const
OutputRootsListItem
=
({
item
,
isLoading
}:
Props
)
=>
{
const
timeAgo
=
dayjs
(
item
.
l1_timestamp
).
fromNow
();
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
ListItemMobileGrid
.
Container
>
...
...
@@ -53,7 +59,7 @@ const OutputRootsListItem = ({ item, isLoading }: Props) => {
maxW=
"100%"
display=
"flex"
overflow=
"hidden"
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
isLoading=
{
isLoading
}
>
<
Icon
as=
{
txIcon
}
boxSize=
{
6
}
isLoading=
{
isLoading
}
/>
...
...
ui/l2OutputRoots/OutputRootsTableItem.tsx
View file @
0a992acd
...
...
@@ -14,11 +14,17 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import
LinkExternal
from
'
ui/shared/LinkExternal
'
;
import
LinkInternal
from
'
ui/shared/LinkInternal
'
;
const
feature
=
config
.
features
.
rollup
;
type
Props
=
{
item
:
L2OutputRootsItem
;
isLoading
?:
boolean
};
const
OutputRootsTableItem
=
({
item
,
isLoading
}:
Props
)
=>
{
const
timeAgo
=
dayjs
(
item
.
l1_timestamp
).
fromNow
();
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
Tr
>
<
Td
verticalAlign=
"middle"
>
...
...
@@ -47,7 +53,7 @@ const OutputRootsTableItem = ({ item, isLoading }: Props) => {
<
LinkExternal
maxW=
"100%"
display=
"inline-flex"
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
isLoading=
{
isLoading
}
>
<
Icon
as=
{
txIcon
}
boxSize=
{
6
}
isLoading=
{
isLoading
}
/>
...
...
ui/l2TxnBatches/TxnBatchesListItem.tsx
View file @
0a992acd
...
...
@@ -14,11 +14,17 @@ import LinkExternal from 'ui/shared/LinkExternal';
import
LinkInternal
from
'
ui/shared/LinkInternal
'
;
import
ListItemMobileGrid
from
'
ui/shared/ListItemMobile/ListItemMobileGrid
'
;
const
feature
=
config
.
features
.
rollup
;
type
Props
=
{
item
:
L2TxnBatchesItem
;
isLoading
?:
boolean
};
const
TxnBatchesListItem
=
({
item
,
isLoading
}:
Props
)
=>
{
const
timeAgo
=
dayjs
(
item
.
l1_timestamp
).
fromNow
();
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
ListItemMobileGrid
.
Container
gridTemplateColumns=
"100px auto"
>
...
...
@@ -56,7 +62,7 @@ const TxnBatchesListItem = ({ item, isLoading }: Props) => {
<
LinkExternal
fontWeight=
{
600
}
display=
"inline-flex"
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/block/[height_or_hash]
'
,
query
:
{
height_or_hash
:
item
.
epoch_number
.
toString
()
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/block/[height_or_hash]
'
,
query
:
{
height_or_hash
:
item
.
epoch_number
.
toString
()
}
})
}
isLoading=
{
isLoading
}
>
<
Skeleton
isLoaded=
{
!
isLoading
}
>
...
...
@@ -72,7 +78,7 @@ const TxnBatchesListItem = ({ item, isLoading }: Props) => {
<
LinkExternal
maxW=
"100%"
display=
"inline-flex"
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
hash
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
hash
}
})
}
key=
{
hash
}
isLoading=
{
isLoading
}
>
...
...
ui/l2TxnBatches/TxnBatchesTableItem.tsx
View file @
0a992acd
...
...
@@ -13,11 +13,17 @@ import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import
LinkExternal
from
'
ui/shared/LinkExternal
'
;
import
LinkInternal
from
'
ui/shared/LinkInternal
'
;
const
feature
=
config
.
features
.
rollup
;
type
Props
=
{
item
:
L2TxnBatchesItem
;
isLoading
?:
boolean
};
const
TxnBatchesTableItem
=
({
item
,
isLoading
}:
Props
)
=>
{
const
timeAgo
=
dayjs
(
item
.
l1_timestamp
).
fromNow
();
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
Tr
>
<
Td
>
...
...
@@ -47,7 +53,7 @@ const TxnBatchesTableItem = ({ item, isLoading }: Props) => {
</
Td
>
<
Td
>
<
LinkExternal
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/block/[height_or_hash]
'
,
query
:
{
height_or_hash
:
item
.
epoch_number
.
toString
()
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/block/[height_or_hash]
'
,
query
:
{
height_or_hash
:
item
.
epoch_number
.
toString
()
}
})
}
fontWeight=
{
600
}
display=
"inline-flex"
isLoading=
{
isLoading
}
...
...
@@ -65,7 +71,7 @@ const TxnBatchesTableItem = ({ item, isLoading }: Props) => {
maxW=
"100%"
display=
"inline-flex"
key=
{
hash
}
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
hash
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
hash
}
})
}
isLoading=
{
isLoading
}
>
<
Icon
as=
{
txIcon
}
boxSize=
{
6
}
isLoading=
{
isLoading
}
/>
...
...
ui/l2Withdrawals/WithdrawalsListItem.tsx
View file @
0a992acd
...
...
@@ -16,12 +16,18 @@ import LinkExternal from 'ui/shared/LinkExternal';
import
LinkInternal
from
'
ui/shared/LinkInternal
'
;
import
ListItemMobileGrid
from
'
ui/shared/ListItemMobile/ListItemMobileGrid
'
;
const
feature
=
config
.
features
.
rollup
;
type
Props
=
{
item
:
L2WithdrawalsItem
;
isLoading
?:
boolean
};
const
WithdrawalsListItem
=
({
item
,
isLoading
}:
Props
)
=>
{
const
timeAgo
=
item
.
l2_timestamp
?
dayjs
(
item
.
l2_timestamp
).
fromNow
()
:
null
;
const
timeToEnd
=
item
.
challenge_period_end
?
dayjs
(
item
.
challenge_period_end
).
fromNow
(
true
)
+
'
left
'
:
null
;
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
ListItemMobileGrid
.
Container
>
...
...
@@ -75,7 +81,7 @@ const WithdrawalsListItem = ({ item, isLoading }: Props) => {
<
ListItemMobileGrid
.
Label
isLoading=
{
isLoading
}
>
Status
</
ListItemMobileGrid
.
Label
>
<
ListItemMobileGrid
.
Value
>
{
item
.
status
===
'
Ready for relay
'
?
<
LinkExternal
href=
{
config
.
features
.
rollup
.
withdrawalUrl
}
>
{
item
.
status
}
</
LinkExternal
>
:
<
LinkExternal
href=
{
feature
.
withdrawalUrl
}
>
{
item
.
status
}
</
LinkExternal
>
:
<
Skeleton
isLoaded=
{
!
isLoading
}
display=
"inline-block"
>
{
item
.
status
}
</
Skeleton
>
}
</
ListItemMobileGrid
.
Value
>
...
...
@@ -84,7 +90,7 @@ const WithdrawalsListItem = ({ item, isLoading }: Props) => {
<
ListItemMobileGrid
.
Label
isLoading=
{
isLoading
}
>
L1 txn hash
</
ListItemMobileGrid
.
Label
>
<
ListItemMobileGrid
.
Value
py=
"3px"
>
<
LinkExternal
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
maxW=
"100%"
display=
"inline-flex"
overflow=
"hidden"
...
...
ui/l2Withdrawals/WithdrawalsTableItem.tsx
View file @
0a992acd
...
...
@@ -15,12 +15,18 @@ import HashStringShorten from 'ui/shared/HashStringShorten';
import
LinkExternal
from
'
ui/shared/LinkExternal
'
;
import
LinkInternal
from
'
ui/shared/LinkInternal
'
;
const
feature
=
config
.
features
.
rollup
;
type
Props
=
{
item
:
L2WithdrawalsItem
;
isLoading
?:
boolean
};
const
WithdrawalsTableItem
=
({
item
,
isLoading
}:
Props
)
=>
{
const
timeAgo
=
item
.
l2_timestamp
?
dayjs
(
item
.
l2_timestamp
).
fromNow
()
:
'
N/A
'
;
const
timeToEnd
=
item
.
challenge_period_end
?
dayjs
(
item
.
challenge_period_end
).
fromNow
(
true
)
+
'
left
'
:
''
;
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
Tr
>
<
Td
verticalAlign=
"middle"
fontWeight=
{
600
}
>
...
...
@@ -55,14 +61,14 @@ const WithdrawalsTableItem = ({ item, isLoading }: Props) => {
</
Td
>
<
Td
verticalAlign=
"middle"
>
{
item
.
status
===
'
Ready for relay
'
?
<
LinkExternal
href=
{
config
.
features
.
rollup
.
withdrawalUrl
}
>
{
item
.
status
}
</
LinkExternal
>
:
<
LinkExternal
href=
{
feature
.
withdrawalUrl
}
>
{
item
.
status
}
</
LinkExternal
>
:
<
Skeleton
isLoaded=
{
!
isLoading
}
display=
"inline-block"
>
{
item
.
status
}
</
Skeleton
>
}
</
Td
>
<
Td
verticalAlign=
"middle"
>
{
item
.
l1_tx_hash
?
(
<
LinkExternal
href=
{
config
.
features
.
rollup
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
href=
{
feature
.
L1BaseUrl
+
route
({
pathname
:
'
/tx/[hash]
'
,
query
:
{
hash
:
item
.
l1_tx_hash
}
})
}
isLoading=
{
isLoading
}
display=
"inline-flex"
>
...
...
ui/marketplace/useMarketplaceApps.tsx
View file @
0a992acd
...
...
@@ -9,6 +9,9 @@ import type { ResourceError } from 'lib/api/resources';
import
useApiFetch
from
'
lib/hooks/useFetch
'
;
import
{
MARKETPLACE_APP
}
from
'
stubs/marketplace
'
;
const
feature
=
config
.
features
.
marketplace
;
const
configUrl
=
feature
.
isEnabled
?
feature
.
configUrl
:
''
;
function
isAppNameMatches
(
q
:
string
,
app
:
MarketplaceAppOverview
)
{
return
app
.
title
.
toLowerCase
().
includes
(
q
.
toLowerCase
());
}
...
...
@@ -23,11 +26,12 @@ export default function useMarketplaceApps(filter: string, selectedCategoryId: s
const
apiFetch
=
useApiFetch
();
const
{
isPlaceholderData
,
isError
,
error
,
data
}
=
useQuery
<
unknown
,
ResourceError
<
unknown
>
,
Array
<
MarketplaceAppOverview
>>
(
[
'
marketplace-apps
'
],
async
()
=>
apiFetch
(
config
.
features
.
marketplace
.
configUrl
||
''
),
async
()
=>
apiFetch
(
config
Url
),
{
select
:
(
data
)
=>
(
data
as
Array
<
MarketplaceAppOverview
>
).
sort
((
a
,
b
)
=>
a
.
title
.
localeCompare
(
b
.
title
)),
placeholderData
:
Array
(
9
).
fill
(
MARKETPLACE_APP
),
staleTime
:
Infinity
,
enabled
:
feature
.
isEnabled
,
});
const
displayedApps
=
React
.
useMemo
(()
=>
{
...
...
ui/pages/Marketplace.tsx
View file @
0a992acd
...
...
@@ -9,6 +9,7 @@ import MarketplaceList from 'ui/marketplace/MarketplaceList';
import
FilterInput
from
'
ui/shared/filters/FilterInput
'
;
import
useMarketplace
from
'
../marketplace/useMarketplace
'
;
const
feature
=
config
.
features
.
marketplace
;
const
Marketplace
=
()
=>
{
const
{
...
...
@@ -32,6 +33,10 @@ const Marketplace = () => {
throw
new
Error
(
'
Unable to get apps list
'
,
{
cause
:
error
});
}
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
const
selectedApp
=
displayedApps
.
find
(
app
=>
app
.
id
===
selectedAppId
);
return
(
...
...
@@ -74,7 +79,6 @@ const Marketplace = () => {
/>
)
}
{
config
.
features
.
marketplace
.
isEnabled
&&
(
<
Skeleton
isLoaded=
{
!
isPlaceholderData
}
marginTop=
{
{
base
:
8
,
sm
:
16
}
}
...
...
@@ -84,7 +88,7 @@ const Marketplace = () => {
fontWeight=
"bold"
display=
"inline-flex"
alignItems=
"baseline"
href=
{
config
.
features
.
marketplac
e
.
submitFormUrl
}
href=
{
featur
e
.
submitFormUrl
}
isExternal
>
<
Icon
...
...
@@ -97,7 +101,6 @@ const Marketplace = () => {
Submit an app
</
Link
>
</
Skeleton
>
)
}
</>
);
};
...
...
ui/pages/MarketplaceApp.tsx
View file @
0a992acd
...
...
@@ -15,6 +15,9 @@ import getQueryParamString from 'lib/router/getQueryParamString';
import
ContentLoader
from
'
ui/shared/ContentLoader
'
;
import
PageTitle
from
'
ui/shared/Page/PageTitle
'
;
const
feature
=
config
.
features
.
marketplace
;
const
configUrl
=
feature
.
isEnabled
?
feature
.
configUrl
:
''
;
const
IFRAME_SANDBOX_ATTRIBUTE
=
'
allow-forms allow-orientation-lock
'
+
'
allow-pointer-lock allow-popups-to-escape-sandbox
'
+
'
allow-same-origin allow-scripts
'
+
...
...
@@ -33,7 +36,7 @@ const MarketplaceApp = () => {
const
{
isLoading
,
isError
,
error
,
data
}
=
useQuery
<
unknown
,
ResourceError
<
unknown
>
,
MarketplaceAppOverview
>
(
[
'
marketplace-apps
'
,
id
],
async
()
=>
{
const
result
=
await
apiFetch
<
Array
<
MarketplaceAppOverview
>
,
unknown
>
(
config
.
features
.
marketplace
.
config
Url
);
const
result
=
await
apiFetch
<
Array
<
MarketplaceAppOverview
>
,
unknown
>
(
configUrl
);
if
(
!
Array
.
isArray
(
result
))
{
throw
result
;
}
...
...
@@ -45,6 +48,9 @@ const MarketplaceApp = () => {
return
item
;
},
{
enabled
:
feature
.
isEnabled
,
},
);
const
[
isFrameLoading
,
setIsFrameLoading
]
=
useState
(
isLoading
);
...
...
ui/pages/Withdrawals.tsx
View file @
0a992acd
...
...
@@ -16,6 +16,8 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import
WithdrawalsListItem
from
'
ui/withdrawals/WithdrawalsListItem
'
;
import
WithdrawalsTable
from
'
ui/withdrawals/WithdrawalsTable
'
;
const
feature
=
config
.
features
.
beaconChain
;
const
Withdrawals
=
()
=>
{
const
isMobile
=
useIsMobile
();
...
...
@@ -61,7 +63,7 @@ const Withdrawals = () => {
);
}
if
(
countersQuery
.
isError
)
{
if
(
countersQuery
.
isError
||
!
feature
.
isEnabled
)
{
return
null
;
}
...
...
@@ -69,7 +71,7 @@ const Withdrawals = () => {
return
(
<
Text
mb=
{
{
base
:
6
,
lg
:
pagination
.
isVisible
?
0
:
6
}
}
lineHeight=
{
{
base
:
'
24px
'
,
lg
:
'
32px
'
}
}
>
{
BigNumber
(
countersQuery
.
data
.
withdrawal_count
).
toFormat
()
}
withdrawals processed
and
{
valueStr
}
{
config
.
features
.
beaconChain
.
currency
.
symbol
}
withdrawn
and
{
valueStr
}
{
feature
.
currency
.
symbol
}
withdrawn
</
Text
>
);
})();
...
...
ui/shared/GoogleAnalytics.tsx
View file @
0a992acd
...
...
@@ -3,12 +3,14 @@ import React from 'react';
import
config
from
'
configs/app
'
;
const
feature
=
config
.
features
.
googleAnalytics
;
const
GoogleAnalytics
=
()
=>
{
if
(
!
config
.
features
.
googleAnalytics
.
isEnabled
)
{
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
const
id
=
config
.
features
.
googleAnalytics
.
propertyId
;
const
id
=
feature
.
propertyId
;
return
(
<>
...
...
ui/shared/NetworkAddToWallet.tsx
View file @
0a992acd
...
...
@@ -6,6 +6,8 @@ import useToast from 'lib/hooks/useToast';
import
useProvider
from
'
lib/web3/useProvider
'
;
import
{
WALLETS_INFO
}
from
'
lib/web3/wallets
'
;
const
feature
=
config
.
features
.
web3Wallet
;
interface
Props
{
className
?:
string
;
}
...
...
@@ -54,19 +56,13 @@ const NetworkAddToWallet = ({ className }: Props) => {
}
},
[
provider
,
toast
]);
if
(
!
provider
||
!
config
.
chain
.
rpcUrl
)
{
return
null
;
}
const
defaultWallet
=
config
.
features
.
web3Wallet
.
defaultWallet
;
if
(
defaultWallet
===
'
none
'
)
{
if
(
!
provider
||
!
config
.
chain
.
rpcUrl
||
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
Button
variant=
"outline"
size=
"sm"
onClick=
{
handleClick
}
className=
{
className
}
>
<
Icon
as=
{
WALLETS_INFO
[
defaultWallet
].
icon
}
boxSize=
{
5
}
mr=
{
2
}
/>
<
Icon
as=
{
WALLETS_INFO
[
feature
.
defaultWallet
].
icon
}
boxSize=
{
5
}
mr=
{
2
}
/>
Add
{
config
.
chain
.
name
}
</
Button
>
);
...
...
ui/shared/Web3ModalProvider.tsx
View file @
0a992acd
...
...
@@ -8,10 +8,12 @@ import { configureChains, createConfig, WagmiConfig } from 'wagmi';
import
config
from
'
configs/app
'
;
const
feature
=
config
.
features
.
blockchainInteraction
;
const
getConfig
=
()
=>
{
try
{
if
(
!
config
.
features
.
blockchainInteraction
.
walletConnect
.
projectI
d
)
{
throw
new
Error
(
'
WalletConnect Project ID is not set
'
);
if
(
!
feature
.
isEnable
d
)
{
throw
new
Error
();
}
const
currentChain
:
Chain
=
{
...
...
@@ -50,7 +52,7 @@ const getConfig = () => {
]);
const
wagmiConfig
=
createConfig
({
autoConnect
:
true
,
connectors
:
w3mConnectors
({
projectId
:
config
.
features
.
blockchainInteraction
.
walletConnect
.
projectId
,
chains
}),
connectors
:
w3mConnectors
({
projectId
:
feature
.
walletConnect
.
projectId
,
chains
}),
publicClient
,
});
const
ethereumClient
=
new
EthereumClient
(
wagmiConfig
,
chains
);
...
...
@@ -72,7 +74,7 @@ const Web3ModalProvider = ({ children, fallback }: Props) => {
const
modalZIndex
=
useToken
<
string
>
(
'
zIndices
'
,
'
modal
'
);
const
web3ModalTheme
=
useColorModeValue
(
'
light
'
,
'
dark
'
);
if
(
!
wagmiConfig
||
!
ethereumClient
||
!
config
.
features
.
blockchainInteraction
.
isEnabled
)
{
if
(
!
wagmiConfig
||
!
ethereumClient
||
!
feature
.
isEnabled
)
{
return
typeof
fallback
===
'
function
'
?
fallback
()
:
(
fallback
||
null
);
}
...
...
@@ -82,7 +84,7 @@ const Web3ModalProvider = ({ children, fallback }: Props) => {
{
children
}
</
WagmiConfig
>
<
Web3Modal
projectId=
{
config
.
features
.
blockchainInteraction
.
walletConnect
.
projectId
}
projectId=
{
feature
.
walletConnect
.
projectId
}
ethereumClient=
{
ethereumClient
}
themeMode=
{
web3ModalTheme
}
themeVariables=
{
{
...
...
ui/shared/ad/AdBanner.tsx
View file @
0a992acd
...
...
@@ -9,15 +9,17 @@ import AdbutlerBanner from './AdbutlerBanner';
import
CoinzillaBanner
from
'
./CoinzillaBanner
'
;
import
SliseBanner
from
'
./SliseBanner
'
;
const
feature
=
config
.
features
.
adsBanner
;
const
AdBanner
=
({
className
,
isLoading
}:
{
className
?:
string
;
isLoading
?:
boolean
})
=>
{
const
hasAdblockCookie
=
cookies
.
get
(
cookies
.
NAMES
.
ADBLOCK_DETECTED
,
useAppContext
().
cookies
);
if
(
!
config
.
features
.
adsBanner
.
isEnabled
||
hasAdblockCookie
)
{
if
(
!
feature
.
isEnabled
||
hasAdblockCookie
)
{
return
null
;
}
const
content
=
(()
=>
{
switch
(
config
.
features
.
adsBanner
.
provider
)
{
switch
(
feature
.
provider
)
{
case
'
adbutler
'
:
return
<
AdbutlerBanner
/>;
case
'
coinzilla
'
:
...
...
@@ -32,7 +34,7 @@ const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: bo
className=
{
className
}
isLoaded=
{
!
isLoading
}
borderRadius=
"none"
maxW=
{
config
.
features
.
adsBanner
.
provider
===
'
adbutler
'
?
config
.
features
.
adsBanner
.
adButler
.
config
.
desktop
?
.
width
:
'
728px
'
}
maxW=
{
feature
.
provider
===
'
adbutler
'
?
feature
.
adButler
.
config
.
desktop
.
width
:
'
728px
'
}
w=
"100%"
>
{
content
}
...
...
ui/shared/ad/AdbutlerBanner.tsx
View file @
0a992acd
...
...
@@ -8,10 +8,16 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import
isBrowser
from
'
lib/isBrowser
'
;
import
{
connectAdbutler
,
placeAd
,
ADBUTLER_ACCOUNT
}
from
'
ui/shared/ad/adbutlerScript
'
;
const
feature
=
config
.
features
.
adsBanner
;
const
AdbutlerBanner
=
({
className
}:
{
className
?:
string
})
=>
{
const
router
=
useRouter
();
const
isMobile
=
useIsMobile
();
React
.
useEffect
(()
=>
{
if
(
!
feature
.
isEnabled
||
feature
.
provider
!==
'
adbutler
'
)
{
return
;
}
if
(
isBrowser
()
&&
window
.
AdButler
)
{
const
abkw
=
window
.
abkw
||
''
;
if
(
!
window
.
AdButler
.
ads
)
{
...
...
@@ -19,8 +25,8 @@ const AdbutlerBanner = ({ className }: { className?: string }) => {
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore:
let
plc
=
window
[
`plc
${
config
.
features
.
adsBanner
.
adButler
.
config
.
mobile
?
.
id
}
`] || 0;
const adButlerConfig = isMobile ?
config.features.adsBanner.adButler.config.mobile : config.features.adsBanner
.adButler.config.desktop;
let
plc
=
window
[
`plc
${
feature
.
adButler
.
config
.
mobile
.
id
}
`
]
||
0
;
const
adButlerConfig
=
isMobile
?
feature
.
adButler
.
config
.
mobile
:
feature
.
adButler
.
config
.
desktop
;
const
banner
=
document
.
getElementById
(
'
ad-banner
'
);
if
(
banner
)
{
banner
.
innerHTML
=
'
<
'
+
'
div id="placement_
'
+
adButlerConfig
?.
id
+
'
_
'
+
plc
+
'
"></
'
+
'
div>
'
;
...
...
@@ -30,9 +36,9 @@ const AdbutlerBanner = ({ className }: { className?: string }) => {
window
.
AdButler
.
ads
.
push
({
handler
:
function
(
opt
)
{
window
.
AdButler
.
register
(
ADBUTLER_ACCOUNT
,
adButlerConfig
?
.id,
[ adButlerConfig
?.width, adButlerConfig?
.height ],
`
placement_$
{
adButlerConfig
?
.
id
}
_
` + opt.place,
adButlerConfig
.
id
,
[
adButlerConfig
.
width
,
adButlerConfig
.
height
],
`placement_
${
adButlerConfig
.
id
}
_`
+
opt
.
place
,
opt
,
);
},
opt
:
{
place
:
plc
++
,
keywords
:
abkw
,
domain
:
'
servedbyadbutler.com
'
,
click
:
'
CLICK_MACRO_PLACEHOLDER
'
}
});
...
...
ui/shared/ad/adbutlerScript.ts
View file @
0a992acd
...
...
@@ -5,17 +5,25 @@ export const ADBUTLER_ACCOUNT = 182226;
export
const
connectAdbutler
=
`if (!window.AdButler){(function(){var s = document.createElement("script"); s.async = true; s.type = "text/javascript";s.src = 'https://servedbyadbutler.com/app.js';var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(s, n);}());}`
;
export
const
placeAd
=
`
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
export
const
placeAd
=
(()
=>
{
const
feature
=
config
.
features
.
adsBanner
;
if
(
!
feature
.
isEnabled
||
feature
.
provider
!==
'
adbutler
'
)
{
return
;
}
return
`
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
var abkw = window.abkw || '';
const isMobile = window.matchMedia("only screen and (max-width: 1000px)").matches;
if (isMobile) {
var plc
${
config
.
features
.
adsBanner
.
adButler
.
config
.
mobile
?.
id
}
=
window
.
plc$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
mobile
?
.
id
}
||
0
;
document
.
getElementById
(
'
ad-banner
'
).
innerHTML
=
'
<
'
+
'
div id="placement_${ config.features.adsBanner.adButler.config.mobile?.id }_
'
+
plc$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
mobile
?
.
id
}
+
'
"></
'
+
'
div>
'
;
AdButler
.
ads
.
push
({
handler
:
function
(
opt
){
AdButler
.
register
(
$
{
ADBUTLER_ACCOUNT
},
$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
mobile
?.
id
},
[
$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
mobile
?.
width
},
$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
mobile
?.
height
}],
'
placement_${ config.features.adsBanner.adButler.config.mobile?.id }_
'
+
opt
.
place
,
opt
);
},
opt
:
{
place
:
plc$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
mobile
?
.
id
}
++
,
keywords
:
abkw
,
domain
:
'
servedbyadbutler.com
'
,
click
:
'
CLICK_MACRO_PLACEHOLDER
'
}});
var plc
${
feature
.
adButler
.
config
.
mobile
.
id
}
= window.plc
${
feature
.
adButler
.
config
.
mobile
.
id
}
|| 0;
document.getElementById('ad-banner').innerHTML = '<'+'div id="placement_
${
feature
.
adButler
.
config
.
mobile
.
id
}
_'+plc
${
feature
.
adButler
.
config
.
mobile
.
id
}
+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(
${
ADBUTLER_ACCOUNT
}
,
${
feature
.
adButler
.
config
.
mobile
.
id
}
, [
${
feature
.
adButler
.
config
.
mobile
.
width
}
,
${
feature
.
adButler
.
config
.
mobile
.
height
}
], 'placement_
${
feature
.
adButler
.
config
.
mobile
.
id
}
_'+opt.place, opt); }, opt: { place: plc
${
feature
.
adButler
.
config
.
mobile
.
id
}
++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
} else {
var
plc$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
desktop
?.
id
}
=
window
.
plc$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
desktop
?
.
id
}
||
0
;
document
.
getElementById
(
'
ad-banner
'
).
innerHTML
=
'
<
'
+
'
div id="placement_${ config.features.adsBanner.adButler.config.desktop?.id }_
'
+
plc$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
desktop
?
.
id
}
+
'
"></
'
+
'
div>
'
;
AdButler
.
ads
.
push
({
handler
:
function
(
opt
){
AdButler
.
register
(
$
{
ADBUTLER_ACCOUNT
},
$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
desktop
?.
id
},
[
$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
desktop
?.
width
},
$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
desktop
?.
height
}],
'
placement_${ config.features.adsBanner.adButler.config.desktop?.id }_
'
+
opt
.
place
,
opt
);
},
opt
:
{
place
:
plc$
{
config
.
features
.
adsBanner
.
adButler
.
config
.
desktop
?
.
id
}
++
,
keywords
:
abkw
,
domain
:
'
servedbyadbutler.com
'
,
click
:
'
CLICK_MACRO_PLACEHOLDER
'
}});
var plc
${
feature
.
adButler
.
config
.
desktop
.
id
}
= window.plc
${
feature
.
adButler
.
config
.
desktop
.
id
}
|| 0;
document.getElementById('ad-banner').innerHTML = '<'+'div id="placement_
${
feature
.
adButler
.
config
.
desktop
.
id
}
_'+plc
${
feature
.
adButler
.
config
.
desktop
.
id
}
+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(
${
ADBUTLER_ACCOUNT
}
,
${
feature
.
adButler
.
config
.
desktop
.
id
}
, [
${
feature
.
adButler
.
config
.
desktop
.
width
}
,
${
feature
.
adButler
.
config
.
desktop
.
height
}
], 'placement_
${
feature
.
adButler
.
config
.
desktop
.
id
}
_'+opt.place, opt); }, opt: { place: plc
${
feature
.
adButler
.
config
.
desktop
.
id
}
++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
}
`;
`
;
})();
ui/shared/address/AddressAddToWallet.tsx
View file @
0a992acd
...
...
@@ -8,6 +8,8 @@ import useToast from 'lib/hooks/useToast';
import
useProvider
from
'
lib/web3/useProvider
'
;
import
{
WALLETS_INFO
}
from
'
lib/web3/wallets
'
;
const
feature
=
config
.
features
.
web3Wallet
;
interface
Props
{
className
?:
string
;
token
:
TokenInfo
;
...
...
@@ -64,16 +66,14 @@ const AddressAddToWallet = ({ className, token, isLoading }: Props) => {
return
<
Skeleton
className=
{
className
}
boxSize=
{
6
}
borderRadius=
"base"
/>;
}
const
defaultWallet
=
config
.
features
.
web3Wallet
.
defaultWallet
;
if
(
defaultWallet
===
'
none
'
)
{
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
Tooltip
label=
{
`Add token to ${ WALLETS_INFO[defaultWallet].name }`
}
>
<
Tooltip
label=
{
`Add token to ${ WALLETS_INFO[
feature.
defaultWallet].name }`
}
>
<
Box
className=
{
className
}
display=
"inline-flex"
cursor=
"pointer"
onClick=
{
handleClick
}
>
<
Icon
as=
{
WALLETS_INFO
[
defaultWallet
].
icon
}
boxSize=
{
6
}
/>
<
Icon
as=
{
WALLETS_INFO
[
feature
.
defaultWallet
].
icon
}
boxSize=
{
6
}
/>
</
Box
>
</
Tooltip
>
);
...
...
ui/snippets/profileMenu/ProfileMenuContent.tsx
View file @
0a992acd
...
...
@@ -8,6 +8,8 @@ import useNavItems from 'lib/hooks/useNavItems';
import
getDefaultTransitionProps
from
'
theme/utils/getDefaultTransitionProps
'
;
import
NavLink
from
'
ui/snippets/navigation/NavLink
'
;
const
feature
=
config
.
features
.
account
;
type
Props
=
{
data
?:
UserInfo
;
};
...
...
@@ -16,6 +18,10 @@ const ProfileMenuContent = ({ data }: Props) => {
const
{
accountNavItems
,
profileItem
}
=
useNavItems
();
const
primaryTextColor
=
useColorModeValue
(
'
blackAlpha.800
'
,
'
whiteAlpha.800
'
);
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
Box
>
{
(
data
?.
name
||
data
?.
nickname
)
&&
(
...
...
@@ -46,7 +52,7 @@ const ProfileMenuContent = ({ data }: Props) => {
</
VStack
>
</
Box
>
<
Box
mt=
{
2
}
pt=
{
3
}
borderTopColor=
"divider"
borderTopWidth=
"1px"
{
...
getDefaultTransitionProps
()
}
>
<
Button
size=
"sm"
width=
"full"
variant=
"outline"
as=
"a"
href=
{
config
.
features
.
account
.
logoutUrl
}
>
Sign Out
</
Button
>
<
Button
size=
"sm"
width=
"full"
variant=
"outline"
as=
"a"
href=
{
feature
.
logoutUrl
}
>
Sign Out
</
Button
>
</
Box
>
</
Box
>
);
...
...
ui/snippets/profileMenu/ProfileMenuDesktop.tsx
View file @
0a992acd
...
...
@@ -19,7 +19,7 @@ const ProfileMenuDesktop = () => {
},
[
data
,
error
?.
status
,
isLoading
]);
const
buttonProps
:
Partial
<
ButtonProps
>
=
(()
=>
{
if
(
hasMenu
)
{
if
(
hasMenu
||
!
loginUrl
)
{
return
{};
}
...
...
ui/snippets/profileMenu/ProfileMenuMobile.tsx
View file @
0a992acd
...
...
@@ -21,7 +21,7 @@ const ProfileMenuMobile = () => {
},
[
data
,
error
?.
status
,
isLoading
]);
const
buttonProps
:
Partial
<
ButtonProps
>
=
(()
=>
{
if
(
hasMenu
)
{
if
(
hasMenu
||
!
loginUrl
)
{
return
{};
}
...
...
ui/withdrawals/WithdrawalsListItem.tsx
View file @
0a992acd
...
...
@@ -16,6 +16,8 @@ import CurrencyValue from 'ui/shared/CurrencyValue';
import
LinkInternal
from
'
ui/shared/LinkInternal
'
;
import
ListItemMobileGrid
from
'
ui/shared/ListItemMobile/ListItemMobileGrid
'
;
const
feature
=
config
.
features
.
beaconChain
;
type
Props
=
({
item
:
WithdrawalsItem
;
view
:
'
list
'
;
...
...
@@ -28,6 +30,10 @@ type Props = ({
})
&
{
isLoading
?:
boolean
};
const
WithdrawalsListItem
=
({
item
,
isLoading
,
view
}:
Props
)
=>
{
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
ListItemMobileGrid
.
Container
gridTemplateColumns=
"100px auto"
>
...
...
@@ -85,7 +91,7 @@ const WithdrawalsListItem = ({ item, isLoading, view }: Props) => {
<
ListItemMobileGrid
.
Label
isLoading=
{
isLoading
}
>
Value
</
ListItemMobileGrid
.
Label
>
<
ListItemMobileGrid
.
Value
>
<
CurrencyValue
value=
{
item
.
amount
}
currency=
{
config
.
features
.
beaconChain
.
currency
.
symbol
}
isLoading=
{
isLoading
}
/>
<
CurrencyValue
value=
{
item
.
amount
}
currency=
{
feature
.
currency
.
symbol
}
isLoading=
{
isLoading
}
/>
</
ListItemMobileGrid
.
Value
>
</>
)
}
...
...
ui/withdrawals/WithdrawalsTable.tsx
View file @
0a992acd
...
...
@@ -10,6 +10,8 @@ import { default as Thead } from 'ui/shared/TheadSticky';
import
WithdrawalsTableItem
from
'
./WithdrawalsTableItem
'
;
const
feature
=
config
.
features
.
beaconChain
;
type
Props
=
{
top
:
number
;
isLoading
?:
boolean
;
...
...
@@ -25,6 +27,10 @@ import WithdrawalsTableItem from './WithdrawalsTableItem';
});
const
WithdrawalsTable
=
({
items
,
isLoading
,
top
,
view
=
'
list
'
}:
Props
)
=>
{
if
(
!
feature
.
isEnabled
)
{
return
null
;
}
return
(
<
Table
variant=
"simple"
size=
"sm"
style=
{
{
tableLayout
:
'
auto
'
}
}
minW=
"950px"
>
<
Thead
top=
{
top
}
>
...
...
@@ -34,7 +40,7 @@ const WithdrawalsTable = ({ items, isLoading, top, view = 'list' }: Props) => {
{
view
!==
'
block
'
&&
<
Th
w=
"25%"
>
Block
</
Th
>
}
{
view
!==
'
address
'
&&
<
Th
w=
"25%"
>
To
</
Th
>
}
{
view
!==
'
block
'
&&
<
Th
w=
"25%"
>
Age
</
Th
>
}
<
Th
w=
"25%"
>
{
`Value ${
config.features.beaconChain
.currency.symbol }`
}
</
Th
>
<
Th
w=
"25%"
>
{
`Value ${
feature
.currency.symbol }`
}
</
Th
>
</
Tr
>
</
Thead
>
<
Tbody
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment