Commit 2ac9ac87 authored by Max Alekseenko's avatar Max Alekseenko

Merge remote-tracking branch 'origin/tom2drum/issue-2029' into rewards

parents 379777bc 5552cd6d
...@@ -16,6 +16,7 @@ const bannerContentUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_BANNE ...@@ -16,6 +16,7 @@ const bannerContentUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_BANNE
const bannerLinkUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL'); const bannerLinkUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL');
const ratingAirtableApiKey = getEnvValue('NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY'); const ratingAirtableApiKey = getEnvValue('NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY');
const ratingAirtableBaseId = getEnvValue('NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID'); const ratingAirtableBaseId = getEnvValue('NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID');
const graphLinksUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL');
const title = 'Marketplace'; const title = 'Marketplace';
...@@ -30,6 +31,7 @@ const config: Feature<( ...@@ -30,6 +31,7 @@ const config: Feature<(
featuredApp: string | undefined; featuredApp: string | undefined;
banner: { contentUrl: string; linkUrl: string } | undefined; banner: { contentUrl: string; linkUrl: string } | undefined;
rating: { airtableApiKey: string; airtableBaseId: string } | undefined; rating: { airtableApiKey: string; airtableBaseId: string } | undefined;
graphLinksUrl: string | undefined;
}> = (() => { }> = (() => {
if (enabled === 'true' && chain.rpcUrl && submitFormUrl) { if (enabled === 'true' && chain.rpcUrl && submitFormUrl) {
const props = { const props = {
...@@ -46,6 +48,7 @@ const config: Feature<( ...@@ -46,6 +48,7 @@ const config: Feature<(
airtableApiKey: ratingAirtableApiKey, airtableApiKey: ratingAirtableApiKey,
airtableBaseId: ratingAirtableBaseId, airtableBaseId: ratingAirtableBaseId,
} : undefined, } : undefined,
graphLinksUrl,
}; };
if (configUrl) { if (configUrl) {
......
...@@ -16,7 +16,7 @@ const L2WithdrawalUrl = getEnvValue('NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL'); ...@@ -16,7 +16,7 @@ const L2WithdrawalUrl = getEnvValue('NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL');
const title = 'Rollup (L2) chain'; const title = 'Rollup (L2) chain';
const config: Feature<{ type: RollupType; L1BaseUrl: string; L2WithdrawalUrl?: string }> = (() => { const config: Feature<{ type: RollupType; L1BaseUrl: string; L2WithdrawalUrl?: string; homepage: { showLatestBlocks: boolean } }> = (() => {
if (type && L1BaseUrl) { if (type && L1BaseUrl) {
return Object.freeze({ return Object.freeze({
...@@ -25,6 +25,9 @@ const config: Feature<{ type: RollupType; L1BaseUrl: string; L2WithdrawalUrl?: s ...@@ -25,6 +25,9 @@ const config: Feature<{ type: RollupType; L1BaseUrl: string; L2WithdrawalUrl?: s
type, type,
L1BaseUrl: stripTrailingSlash(L1BaseUrl), L1BaseUrl: stripTrailingSlash(L1BaseUrl),
L2WithdrawalUrl, L2WithdrawalUrl,
homepage: {
showLatestBlocks: getEnvValue('NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS') === 'true',
},
}); });
} }
......
...@@ -42,6 +42,7 @@ NEXT_PUBLIC_MARKETPLACE_ENABLED=true ...@@ -42,6 +42,7 @@ NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/refs/heads/marketplace-graph-test/test-configs/marketplace-graph-links.json
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_METASUITES_ENABLED=true NEXT_PUBLIC_METASUITES_ENABLED=true
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'} NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}
......
...@@ -18,6 +18,7 @@ ASSETS_ENVS=( ...@@ -18,6 +18,7 @@ ASSETS_ENVS=(
"NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL" "NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL"
"NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL" "NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL"
"NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL" "NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL"
"NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL"
"NEXT_PUBLIC_FEATURED_NETWORKS" "NEXT_PUBLIC_FEATURED_NETWORKS"
"NEXT_PUBLIC_FOOTER_LINKS" "NEXT_PUBLIC_FOOTER_LINKS"
"NEXT_PUBLIC_NETWORK_LOGO" "NEXT_PUBLIC_NETWORK_LOGO"
......
...@@ -39,6 +39,7 @@ async function validateEnvs(appEnvs: Record<string, string>) { ...@@ -39,6 +39,7 @@ async function validateEnvs(appEnvs: Record<string, string>) {
'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL',
'NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL', 'NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL',
'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', 'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL',
'NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL',
'NEXT_PUBLIC_FOOTER_LINKS', 'NEXT_PUBLIC_FOOTER_LINKS',
]; ];
......
...@@ -243,6 +243,14 @@ const marketplaceSchema = yup ...@@ -243,6 +243,14 @@ const marketplaceSchema = yup
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'),
}), }),
NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL: yup
.string()
.when('NEXT_PUBLIC_MARKETPLACE_ENABLED', {
is: true,
then: (schema) => schema,
// eslint-disable-next-line max-len
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'),
}),
}); });
const beaconChainSchema = yup const beaconChainSchema = yup
...@@ -279,6 +287,17 @@ const rollupSchema = yup ...@@ -279,6 +287,17 @@ const rollupSchema = yup
then: (schema) => schema.test(urlTest).required(), then: (schema) => schema.test(urlTest).required(),
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL can be used only if NEXT_PUBLIC_ROLLUP_TYPE is set to \'optimistic\' '), otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL can be used only if NEXT_PUBLIC_ROLLUP_TYPE is set to \'optimistic\' '),
}), }),
NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS: yup
.boolean()
.when('NEXT_PUBLIC_ROLLUP_TYPE', {
is: (value: string) => value,
then: (schema) => schema,
otherwise: (schema) => schema.test(
'not-exist',
'NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS cannot not be used if NEXT_PUBLIC_ROLLUP_TYPE is not defined',
value => value === undefined,
),
}),
}); });
const adButlerConfigSchema = yup const adButlerConfigSchema = yup
......
NEXT_PUBLIC_ROLLUP_TYPE=optimistic NEXT_PUBLIC_ROLLUP_TYPE=optimistic
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://example.com NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://example.com
NEXT_PUBLIC_FAULT_PROOF_ENABLED=true NEXT_PUBLIC_FAULT_PROOF_ENABLED=true
\ No newline at end of file NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS=true
\ No newline at end of file
...@@ -435,6 +435,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi ...@@ -435,6 +435,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi
| NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals (Optimistic stack only) | Required for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ | | NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals (Optimistic stack only) | Required for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ |
| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (Optimistic stack only) | - | - | `true` | v1.31.0+ | | NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (Optimistic stack only) | - | - | `true` | v1.31.0+ |
| NEXT_PUBLIC_HAS_MUD_FRAMEWORK | `boolean` | Set to `true` for instances that use MUD framework (Optimistic stack only) | - | - | `true` | v1.33.0+ | | NEXT_PUBLIC_HAS_MUD_FRAMEWORK | `boolean` | Set to `true` for instances that use MUD framework (Optimistic stack only) | - | - | `true` | v1.33.0+ |
| NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS | `boolean` | Set to `true` to display "Latest blocks" widget instead of "Latest batches" on the home page | - | - | `true` | v1.36.0+ |
&nbsp; &nbsp;
...@@ -507,6 +508,7 @@ This feature is **always enabled**, but you can disable it by passing `none` val ...@@ -507,6 +508,7 @@ This feature is **always enabled**, but you can disable it by passing `none` val
| NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL | `string` | URL of the page the banner leads to | - | - | `https://example.com` | v1.29.0+ | | NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL | `string` | URL of the page the banner leads to | - | - | `https://example.com` | v1.29.0+ |
| NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY | `string` | Airtable API key | - | - | - | v1.33.0+ | | NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY | `string` | Airtable API key | - | - | - | v1.33.0+ |
| NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID | `string` | Airtable base ID with dapp ratings | - | - | - | v1.33.0+ | | NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID | `string` | Airtable base ID with dapp ratings | - | - | - | v1.33.0+ |
| NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL | `string` | URL of the file (`.json` format only) which contains the list of The Graph links to be displayed on the Marketplace page | - | - | `https://example.com/graph_links.json` | v1.36.0+ |
#### Marketplace app configuration properties #### Marketplace app configuration properties
......
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path fill="#6747ED" d="M10 20c5.523 0 10-4.477 10-10S15.523 0 10 0 0 4.477 0 10s4.477 10 10 10"/>
<path fill="#fff" fill-rule="evenodd" d="M9.854 11.292a2.66 2.66 0 0 1-2.666-2.667 2.66 2.66 0 0 1 2.666-2.667 2.66 2.66 0 0 1 2.667 2.667 2.66 2.66 0 0 1-2.667 2.667m0-6.667a4.001 4.001 0 0 1 0 8 4.001 4.001 0 0 1 0-8m3.813 8.208c.27.271.27.688 0 .938L11 16.437c-.27.271-.687.271-.937 0-.271-.27-.271-.687 0-.937l2.666-2.667c.25-.27.688-.27.938 0m1.541-7.541a.66.66 0 0 1-.666.666.66.66 0 0 1-.667-.666c0-.375.292-.667.667-.667.354 0 .666.292.666.667" clip-rule="evenodd"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 30 30">
<path fill="currentColor" fill-rule="evenodd" d="m6.134 10.067 3.722-3.828a.97.97 0 0 1 1.337.06c.177.182.283.428.292.69a1.05 1.05 0 0 1-.234.704L9.243 9.765h14.371c.261 0 .513.107.7.299s.294.454.294.73c0 .274-.107.537-.294.729a.98.98 0 0 1-.7.299H6.831a.97.97 0 0 1-.55-.17 1 1 0 0 1-.368-.46 1.06 1.06 0 0 1 .22-1.126m18.723 6a.965.965 0 0 1 .55.17c.163.111.291.27.368.459a1.06 1.06 0 0 1-.22 1.125l-3.737 3.842-.006.008a1 1 0 0 1-.323.255.97.97 0 0 1-1.13-.197q-.146-.152-.224-.353a1.06 1.06 0 0 1 .281-1.16l.007-.007 2.022-2.086h-5.882c.104-.685.184-1.404 0-2.073z" clip-rule="evenodd"/>
<path fill="currentColor" d="M10 19.513c-1.272 0-2.48-.276-3.395-.778C5.57 18.169 5 17.374 5 16.497c0-.878.57-1.672 1.605-2.239.917-.502 2.123-.778 3.395-.778s2.48.276 3.395.778C14.43 14.825 15 15.622 15 16.497s-.57 1.671-1.605 2.238c-.917.502-2.123.778-3.395.778m0-4.793c-1.052 0-2.073.228-2.8.626-.61.334-.96.753-.96 1.15 0 .398.35.818.96 1.151.727.397 1.746.626 2.8.626s2.073-.228 2.8-.626c.61-.333.96-.753.96-1.15 0-.398-.35-.817-.96-1.151-.727-.398-1.746-.626-2.8-.626"/>
<path stroke="currentColor" stroke-width=".3" d="M10 19.513c-1.272 0-2.48-.276-3.395-.778C5.57 18.169 5 17.374 5 16.497c0-.878.57-1.672 1.605-2.239.917-.502 2.123-.778 3.395-.778s2.48.276 3.395.778C14.43 14.825 15 15.622 15 16.497s-.57 1.671-1.605 2.238c-.917.502-2.123.778-3.395.778Zm0-4.793c-1.052 0-2.073.228-2.8.626-.61.334-.96.753-.96 1.15 0 .398.35.818.96 1.151.727.397 1.746.626 2.8.626s2.073-.228 2.8-.626c.61-.333.96-.753.96-1.15 0-.398-.35-.817-.96-1.151-.727-.398-1.746-.626-2.8-.626Z"/>
<path fill="currentColor" d="M10 23.962c-1.272 0-2.48-.276-3.395-.778C5.57 22.618 5 21.823 5 20.946v-4.45a.62.62 0 0 1 1.24 0v4.45c0 .397.35.817.96 1.151.727.397 1.748.626 2.8.626 1.053 0 2.073-.229 2.8-.626.61-.334.96-.754.96-1.151v-4.45a.62.62 0 0 1 1.24 0v4.45c0 .877-.57 1.672-1.605 2.238-.917.502-2.123.778-3.395.778"/>
<path stroke="currentColor" stroke-width=".3" d="M10 23.962c-1.272 0-2.48-.276-3.395-.778C5.57 22.618 5 21.823 5 20.946v-4.45a.62.62 0 0 1 1.24 0v4.45c0 .397.35.817.96 1.151.727.397 1.748.626 2.8.626 1.053 0 2.073-.229 2.8-.626.61-.334.96-.754.96-1.151v-4.45a.62.62 0 0 1 1.24 0v4.45c0 .877-.57 1.672-1.605 2.238-.917.502-2.123.778-3.395.778Z"/>
<path fill="currentColor" d="M10 21.738c-1.272 0-2.48-.277-3.395-.778C5.57 20.393 5 19.598 5 18.72a.62.62 0 1 1 1.24 0c0 .397.35.817.96 1.151.727.398 1.748.626 2.8.626 1.053 0 2.073-.228 2.8-.626.61-.334.96-.754.96-1.15a.62.62 0 1 1 1.24 0c0 .876-.57 1.671-1.605 2.238-.917.501-2.123.778-3.395.778"/>
<path stroke="currentColor" stroke-width=".3" d="M10 21.738c-1.272 0-2.48-.277-3.395-.778C5.57 20.393 5 19.598 5 18.72a.62.62 0 1 1 1.24 0c0 .397.35.817.96 1.151.727.398 1.748.626 2.8.626 1.053 0 2.073-.228 2.8-.626.61-.334.96-.754.96-1.15a.62.62 0 1 1 1.24 0c0 .876-.57 1.671-1.605 2.238-.917.501-2.123.778-3.395.778Z"/>
</svg>
...@@ -37,6 +37,7 @@ import type { ...@@ -37,6 +37,7 @@ import type {
AddressMudRecordsFilter, AddressMudRecordsFilter,
AddressMudRecordsSorting, AddressMudRecordsSorting,
AddressMudRecord, AddressMudRecord,
AddressEpochRewardsResponse,
} from 'types/api/address'; } from 'types/api/address';
import type { AddressesResponse, AddressesMetadataSearchResult, AddressesMetadataSearchFilters } from 'types/api/addresses'; import type { AddressesResponse, AddressesMetadataSearchResult, AddressesMetadataSearchFilters } from 'types/api/addresses';
import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata'; import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata';
...@@ -587,6 +588,11 @@ export const RESOURCES = { ...@@ -587,6 +588,11 @@ export const RESOURCES = {
pathParams: [ 'hash' as const ], pathParams: [ 'hash' as const ],
filterFields: [], filterFields: [],
}, },
address_epoch_rewards: {
path: '/api/v2/addresses/:hash/election-rewards',
pathParams: [ 'hash' as const ],
filterFields: [],
},
// CONTRACT // CONTRACT
contract: { contract: {
...@@ -681,6 +687,12 @@ export const RESOURCES = { ...@@ -681,6 +687,12 @@ export const RESOURCES = {
filterFields: [], filterFields: [],
}, },
// TOKEN TRANSFERS
token_transfers_all: {
path: '/api/v2/token-transfers',
filterFields: [ 'type' as const ],
},
// APP STATS // APP STATS
stats: { stats: {
path: '/api/v2/stats', path: '/api/v2/stats',
...@@ -1084,7 +1096,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward ...@@ -1084,7 +1096,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward
'addresses' | 'addresses_metadata_search' | 'addresses' | 'addresses_metadata_search' |
'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' | 'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' |
'search' | 'search' |
'address_logs' | 'address_tokens' | 'address_nfts' | 'address_collections' | 'address_logs' | 'address_tokens' | 'address_nfts' | 'address_collections' | 'address_epoch_rewards' |
'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' | 'tokens_bridged' | 'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' | 'tokens_bridged' |
'token_instance_transfers' | 'token_instance_holders' | 'token_instance_transfers' | 'token_instance_holders' |
'verified_contracts' | 'verified_contracts' |
...@@ -1097,7 +1109,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward ...@@ -1097,7 +1109,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward
'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' | 'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' |
'withdrawals' | 'address_withdrawals' | 'block_withdrawals' | 'withdrawals' | 'address_withdrawals' | 'block_withdrawals' |
'watchlist' | 'private_tags_address' | 'private_tags_tx' | 'watchlist' | 'private_tags_address' | 'private_tags_tx' |
'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'noves_address_history'; 'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'noves_address_history' |
'token_transfers_all';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>; export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
...@@ -1264,6 +1277,7 @@ Q extends 'address_mud_tables' ? AddressMudTables : ...@@ -1264,6 +1277,7 @@ Q extends 'address_mud_tables' ? AddressMudTables :
Q extends 'address_mud_tables_count' ? number : Q extends 'address_mud_tables_count' ? number :
Q extends 'address_mud_records' ? AddressMudRecords : Q extends 'address_mud_records' ? AddressMudRecords :
Q extends 'address_mud_record' ? AddressMudRecord : Q extends 'address_mud_record' ? AddressMudRecord :
Q extends 'address_epoch_rewards' ? AddressEpochRewardsResponse :
Q extends 'withdrawals' ? WithdrawalsResponse : Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'withdrawals_counters' ? WithdrawalsCounters : Q extends 'withdrawals_counters' ? WithdrawalsCounters :
Q extends 'rewards_config' ? RewardsConfigResponse : Q extends 'rewards_config' ? RewardsConfigResponse :
...@@ -1275,6 +1289,7 @@ Q extends 'rewards_user_balances' ? RewardsUserBalancesResponse : ...@@ -1275,6 +1289,7 @@ Q extends 'rewards_user_balances' ? RewardsUserBalancesResponse :
Q extends 'rewards_user_daily_check' ? RewardsUserDailyCheckResponse : Q extends 'rewards_user_daily_check' ? RewardsUserDailyCheckResponse :
Q extends 'rewards_user_daily_claim' ? RewardsUserDailyClaimResponse : Q extends 'rewards_user_daily_claim' ? RewardsUserDailyClaimResponse :
Q extends 'rewards_user_referrals' ? RewardsUserReferralsResponse : Q extends 'rewards_user_referrals' ? RewardsUserReferralsResponse :
Q extends 'token_transfers_all' ? TokenTransferResponse :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
...@@ -1309,6 +1324,7 @@ Q extends 'user_ops' ? UserOpsFilters : ...@@ -1309,6 +1324,7 @@ Q extends 'user_ops' ? UserOpsFilters :
Q extends 'validators_stability' ? ValidatorsStabilityFilters : Q extends 'validators_stability' ? ValidatorsStabilityFilters :
Q extends 'address_mud_tables' ? AddressMudTablesFilter : Q extends 'address_mud_tables' ? AddressMudTablesFilter :
Q extends 'address_mud_records' ? AddressMudRecordsFilter : Q extends 'address_mud_records' ? AddressMudRecordsFilter :
Q extends 'token_transfers_all' ? TokenTransferFilters :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
......
import { useQuery } from '@tanstack/react-query';
import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import useFetch from 'lib/hooks/useFetch';
const feature = config.features.marketplace;
export default function useGraphLinks() {
const fetch = useFetch();
return useQuery<unknown, ResourceError<unknown>, Record<string, Array<{text: string; url: string}>>>({
queryKey: [ 'graph-links' ],
queryFn: async() => fetch((feature.isEnabled && feature.graphLinksUrl) ? feature.graphLinksUrl : '', undefined, { resource: 'graph-links' }),
enabled: feature.isEnabled && Boolean(feature.graphLinksUrl),
staleTime: Infinity,
placeholderData: {},
});
}
...@@ -187,6 +187,21 @@ export default function useNavItems(): ReturnType { ...@@ -187,6 +187,21 @@ export default function useNavItems(): ReturnType {
].filter(Boolean); ].filter(Boolean);
} }
const tokensNavItems = [
{
text: 'Tokens',
nextRoute: { pathname: '/tokens' as const },
icon: 'token',
isActive: pathname.startsWith('/token'),
},
{
text: 'Token transfers',
nextRoute: { pathname: '/token-transfers' as const },
icon: 'token-transfers',
isActive: pathname === '/token-transfers',
},
];
const apiNavItems: Array<NavItem> = [ const apiNavItems: Array<NavItem> = [
config.features.restApiDocs.isEnabled ? { config.features.restApiDocs.isEnabled ? {
text: 'REST API', text: 'REST API',
...@@ -240,9 +255,9 @@ export default function useNavItems(): ReturnType { ...@@ -240,9 +255,9 @@ export default function useNavItems(): ReturnType {
}, },
{ {
text: 'Tokens', text: 'Tokens',
nextRoute: { pathname: '/tokens' as const },
icon: 'token', icon: 'token',
isActive: pathname.startsWith('/token'), isActive: tokensNavItems.flat().some(item => isInternalItem(item) && item.isActive),
subItems: tokensNavItems,
}, },
config.features.marketplace.isEnabled ? { config.features.marketplace.isEnabled ? {
text: 'DApps', text: 'DApps',
......
...@@ -52,6 +52,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = { ...@@ -52,6 +52,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/validators': 'Root page', '/validators': 'Root page',
'/gas-tracker': 'Root page', '/gas-tracker': 'Root page',
'/mud-worlds': 'Root page', '/mud-worlds': 'Root page',
'/token-transfers': 'Root page',
// service routes, added only to make typescript happy // service routes, added only to make typescript happy
'/login': 'Regular page', '/login': 'Regular page',
......
...@@ -56,6 +56,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -56,6 +56,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/validators': DEFAULT_TEMPLATE, '/validators': DEFAULT_TEMPLATE,
'/gas-tracker': DEFAULT_TEMPLATE, '/gas-tracker': DEFAULT_TEMPLATE,
'/mud-worlds': DEFAULT_TEMPLATE, '/mud-worlds': DEFAULT_TEMPLATE,
'/token-transfers': DEFAULT_TEMPLATE,
// service routes, added only to make typescript happy // service routes, added only to make typescript happy
'/login': DEFAULT_TEMPLATE, '/login': DEFAULT_TEMPLATE,
......
...@@ -41,8 +41,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -41,8 +41,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/deposits': '%network_name% deposits (L1 > L2)', '/deposits': '%network_name% deposits (L1 > L2)',
'/output-roots': '%network_name% output roots', '/output-roots': '%network_name% output roots',
'/dispute-games': '%network_name% dispute games', '/dispute-games': '%network_name% dispute games',
'/batches': '%network_name% tx batches (L2 blocks)', '/batches': '%network_name% txn batches',
'/batches/[number]': '%network_name% L2 tx batch %number%', '/batches/[number]': '%network_name% L2 txn batch %number%',
'/blobs/[hash]': '%network_name% blob %hash% details', '/blobs/[hash]': '%network_name% blob %hash% details',
'/ops': 'User operations on %network_name% - %network_name% explorer', '/ops': 'User operations on %network_name% - %network_name% explorer',
'/op/[hash]': '%network_name% user operation %hash%', '/op/[hash]': '%network_name% user operation %hash%',
...@@ -52,6 +52,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -52,6 +52,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/validators': '%network_name% validators list', '/validators': '%network_name% validators list',
'/gas-tracker': '%network_name% gas tracker - Current gas fees', '/gas-tracker': '%network_name% gas tracker - Current gas fees',
'/mud-worlds': '%network_name% MUD worlds list', '/mud-worlds': '%network_name% MUD worlds list',
'/token-transfers': '%network_name% token transfers',
// service routes, added only to make typescript happy // service routes, added only to make typescript happy
'/login': '%network_name% login', '/login': '%network_name% login',
......
...@@ -39,8 +39,8 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = { ...@@ -39,8 +39,8 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/deposits': 'Deposits (L1 > L2)', '/deposits': 'Deposits (L1 > L2)',
'/output-roots': 'Output roots', '/output-roots': 'Output roots',
'/dispute-games': 'Dispute games', '/dispute-games': 'Dispute games',
'/batches': 'Tx batches (L2 blocks)', '/batches': 'Txn batches',
'/batches/[number]': 'L2 tx batch details', '/batches/[number]': 'L2 txn batch details',
'/blobs/[hash]': 'Blob details', '/blobs/[hash]': 'Blob details',
'/ops': 'User operations', '/ops': 'User operations',
'/op/[hash]': 'User operation details', '/op/[hash]': 'User operation details',
...@@ -50,6 +50,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = { ...@@ -50,6 +50,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/validators': 'Validators list', '/validators': 'Validators list',
'/gas-tracker': 'Gas tracker', '/gas-tracker': 'Gas tracker',
'/mud-worlds': 'MUD worlds', '/mud-worlds': 'MUD worlds',
'/token-transfers': 'Token transfers',
// service routes, added only to make typescript happy // service routes, added only to make typescript happy
'/login': 'Login', '/login': 'Login',
......
import _get from 'lodash/get';
import React from 'react'; import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
...@@ -24,9 +25,10 @@ export default function useAddOrSwitchChain() { ...@@ -24,9 +25,10 @@ export default function useAddOrSwitchChain() {
const errorObj = getErrorObj(error); const errorObj = getErrorObj(error);
const code = errorObj && 'code' in errorObj ? errorObj.code : undefined; const code = errorObj && 'code' in errorObj ? errorObj.code : undefined;
const originalErrorCode = _get(errorObj, 'data.originalError.code');
// This error code indicates that the chain has not been added to Wallet. // This error code indicates that the chain has not been added to Wallet.
if (code === 4902) { if (code === 4902 || originalErrorCode === 4902) {
const params = [ { const params = [ {
chainId: hexadecimalChainId, chainId: hexadecimalChainId,
chainName: config.chain.name, chainName: config.chain.name,
......
...@@ -48,12 +48,12 @@ export default function useWeb3Wallet({ source }: Params) { ...@@ -48,12 +48,12 @@ export default function useWeb3Wallet({ source }: Params) {
const isConnected = isClientLoaded && !isDisconnected && address !== undefined; const isConnected = isClientLoaded && !isDisconnected && address !== undefined;
return { return React.useMemo(() => ({
connect: handleConnect, connect: handleConnect,
disconnect: handleDisconnect, disconnect: handleDisconnect,
isOpen: isOpening || isOpen, isOpen: isOpening || isOpen,
isConnected, isConnected,
address, address,
openModal, openModal,
}; }), [ handleConnect, handleDisconnect, isOpen, isOpening, isConnected, address, openModal ]);
} }
import type { AddressEpochRewardsResponse } from 'types/api/address';
import { tokenInfo } from 'mocks/tokens/tokenInfo';
import { withEns, withName, withoutName } from './address';
export const epochRewards: AddressEpochRewardsResponse = {
items: [
{
type: 'delegated_payment',
amount: '136609473658452408568',
account: withName,
associated_account: withName,
block_hash: '0x',
block_number: 26369280,
epoch_number: 1526,
token: tokenInfo,
},
{
type: 'group',
amount: '117205842355246195095',
account: withoutName,
associated_account: withoutName,
block_hash: '0x',
block_number: 26352000,
epoch_number: 1525,
token: tokenInfo,
},
{
type: 'validator',
amount: '125659647325556554060',
account: withEns,
associated_account: withEns,
block_hash: '0x',
block_number: 26300160,
epoch_number: 1524,
token: tokenInfo,
},
],
next_page_params: null,
};
...@@ -102,3 +102,14 @@ export const noteTag: AddressMetadataTagApi = { ...@@ -102,3 +102,14 @@ export const noteTag: AddressMetadataTagApi = {
data: '<b>Warning!</b> This is scam! See the <a href="https://example.com" target="_blank">report</a>', data: '<b>Warning!</b> This is scam! See the <a href="https://example.com" target="_blank">report</a>',
}, },
}; };
export const noteTag2: AddressMetadataTagApi = {
slug: 'note0',
name: 'note_0',
tagType: 'note',
ordinal: 0,
meta: {
alertStatus: 'info',
data: 'The token MILF was launched on May 13, 2021. The maximum total supply of the token is 100 billion.',
},
};
...@@ -42,6 +42,7 @@ export const erc20: TokenTransfer = { ...@@ -42,6 +42,7 @@ export const erc20: TokenTransfer = {
tx_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', tx_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193',
type: 'token_transfer', type: 'token_transfer',
timestamp: '2022-10-10T14:34:30.000000Z', timestamp: '2022-10-10T14:34:30.000000Z',
block_number: '12345',
block_hash: '1', block_hash: '1',
log_index: '1', log_index: '1',
method: 'updateSmartAsset', method: 'updateSmartAsset',
...@@ -88,6 +89,7 @@ export const erc721: TokenTransfer = { ...@@ -88,6 +89,7 @@ export const erc721: TokenTransfer = {
tx_hash: '0xf13bc7afe5e02b494dd2f22078381d36a4800ef94a0ccc147431db56c301e6cc', tx_hash: '0xf13bc7afe5e02b494dd2f22078381d36a4800ef94a0ccc147431db56c301e6cc',
type: 'token_transfer', type: 'token_transfer',
timestamp: '2022-10-10T14:34:30.000000Z', timestamp: '2022-10-10T14:34:30.000000Z',
block_number: '12345',
block_hash: '1', block_hash: '1',
log_index: '1', log_index: '1',
method: 'updateSmartAsset', method: 'updateSmartAsset',
...@@ -136,6 +138,7 @@ export const erc1155A: TokenTransfer = { ...@@ -136,6 +138,7 @@ export const erc1155A: TokenTransfer = {
tx_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746', tx_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746',
type: 'token_minting', type: 'token_minting',
timestamp: '2022-10-10T14:34:30.000000Z', timestamp: '2022-10-10T14:34:30.000000Z',
block_number: '12345',
block_hash: '1', block_hash: '1',
log_index: '1', log_index: '1',
}; };
...@@ -214,6 +217,7 @@ export const erc404A: TokenTransfer = { ...@@ -214,6 +217,7 @@ export const erc404A: TokenTransfer = {
type: 'token_transfer', type: 'token_transfer',
method: 'swap', method: 'swap',
timestamp: '2022-10-10T14:34:30.000000Z', timestamp: '2022-10-10T14:34:30.000000Z',
block_number: '12345',
block_hash: '1', block_hash: '1',
log_index: '1', log_index: '1',
}; };
......
...@@ -58,6 +58,7 @@ declare module "nextjs-routes" { ...@@ -58,6 +58,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/stats"> | StaticRoute<"/stats">
| DynamicRoute<"/token/[hash]", { "hash": string }> | DynamicRoute<"/token/[hash]", { "hash": string }>
| DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }> | DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }>
| StaticRoute<"/token-transfers">
| StaticRoute<"/tokens"> | StaticRoute<"/tokens">
| DynamicRoute<"/tx/[hash]", { "hash": string }> | DynamicRoute<"/tx/[hash]", { "hash": string }>
| StaticRoute<"/txs"> | StaticRoute<"/txs">
......
...@@ -29,6 +29,7 @@ const handler = async(nextReq: NextApiRequest, nextRes: NextApiResponse) => { ...@@ -29,6 +29,7 @@ const handler = async(nextReq: NextApiRequest, nextRes: NextApiResponse) => {
setCookie?.forEach((value) => { setCookie?.forEach((value) => {
nextRes.appendHeader('set-cookie', value); nextRes.appendHeader('set-cookie', value);
}); });
nextRes.setHeader('content-type', apiRes.headers.get('content-type') || '');
nextRes.status(apiRes.status).send(apiRes.body); nextRes.status(apiRes.status).send(apiRes.body);
}; };
......
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const TokenTransfers = dynamic(() => import('ui/pages/TokenTransfers'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/token-transfers">
<TokenTransfers/>
</PageNextJs>
);
};
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
| "block" | "block"
| "brands/blockscout" | "brands/blockscout"
| "brands/celenium" | "brands/celenium"
| "brands/graph"
| "brands/safe" | "brands/safe"
| "brands/solidity_scan" | "brands/solidity_scan"
| "burger" | "burger"
...@@ -154,6 +155,7 @@ ...@@ -154,6 +155,7 @@
| "swap" | "swap"
| "testnet" | "testnet"
| "token-placeholder" | "token-placeholder"
| "token-transfers"
| "token" | "token"
| "tokens" | "tokens"
| "tokens/xdai" | "tokens/xdai"
......
...@@ -3,6 +3,7 @@ import type { ...@@ -3,6 +3,7 @@ import type {
AddressCoinBalanceHistoryItem, AddressCoinBalanceHistoryItem,
AddressCollection, AddressCollection,
AddressCounters, AddressCounters,
AddressEpochRewardsItem,
AddressMudTableItem, AddressMudTableItem,
AddressNFT, AddressNFT,
AddressTabsCounters, AddressTabsCounters,
...@@ -10,7 +11,7 @@ import type { ...@@ -10,7 +11,7 @@ import type {
} from 'types/api/address'; } from 'types/api/address';
import type { AddressesItem } from 'types/api/addresses'; import type { AddressesItem } from 'types/api/addresses';
import { ADDRESS_HASH } from './addressParams'; import { ADDRESS_HASH, ADDRESS_PARAMS } from './addressParams';
import { MUD_SCHEMA, MUD_TABLE } from './mud'; import { MUD_SCHEMA, MUD_TABLE } from './mud';
import { TOKEN_INFO_ERC_1155, TOKEN_INFO_ERC_20, TOKEN_INFO_ERC_721, TOKEN_INFO_ERC_404, TOKEN_INSTANCE } from './token'; import { TOKEN_INFO_ERC_1155, TOKEN_INFO_ERC_20, TOKEN_INFO_ERC_721, TOKEN_INFO_ERC_404, TOKEN_INSTANCE } from './token';
import { TX_HASH } from './tx'; import { TX_HASH } from './tx';
...@@ -116,3 +117,14 @@ export const ADDRESS_MUD_TABLE_ITEM: AddressMudTableItem = { ...@@ -116,3 +117,14 @@ export const ADDRESS_MUD_TABLE_ITEM: AddressMudTableItem = {
schema: MUD_SCHEMA, schema: MUD_SCHEMA,
table: MUD_TABLE, table: MUD_TABLE,
}; };
export const EPOCH_REWARD_ITEM: AddressEpochRewardsItem = {
amount: '136609473658452408568',
block_number: 10355938,
type: 'voter',
token: TOKEN_INFO_ERC_20,
block_hash: '0x5956a847d8089e254e02e5111cad6992b99ceb9e5c2dc4343fd53002834c4dc6',
account: ADDRESS_PARAMS,
epoch_number: 1526,
associated_account: ADDRESS_PARAMS,
};
...@@ -91,6 +91,7 @@ export const getTokenInstanceHoldersStub = (type?: TokenType, pagination: TokenH ...@@ -91,6 +91,7 @@ export const getTokenInstanceHoldersStub = (type?: TokenType, pagination: TokenH
export const TOKEN_TRANSFER_ERC_20: TokenTransfer = { export const TOKEN_TRANSFER_ERC_20: TokenTransfer = {
block_hash: BLOCK_HASH, block_hash: BLOCK_HASH,
block_number: '123456',
from: ADDRESS_PARAMS, from: ADDRESS_PARAMS,
log_index: '4', log_index: '4',
method: 'addLiquidity', method: 'addLiquidity',
......
...@@ -154,33 +154,34 @@ const variantSubtle = defineStyle((props) => { ...@@ -154,33 +154,34 @@ const variantSubtle = defineStyle((props) => {
// for buttons in the hero banner // for buttons in the hero banner
const variantHero = defineStyle((props) => { const variantHero = defineStyle((props) => {
const buttonConfig = config.UI.homepage.heroBanner?.button;
return { return {
bg: mode( bg: mode(
config.UI.homepage.heroBanner?.button?._default?.background?.[0] || 'blue.600', buttonConfig?._default?.background?.[0] || 'blue.600',
config.UI.homepage.heroBanner?.button?._default?.background?.[1] || 'blue.600', buttonConfig?._default?.background?.[1] || buttonConfig?._default?.background?.[0] || 'blue.600',
)(props), )(props),
color: mode( color: mode(
config.UI.homepage.heroBanner?.button?._default?.text_color?.[0] || 'white', buttonConfig?._default?.text_color?.[0] || 'white',
config.UI.homepage.heroBanner?.button?._default?.text_color?.[1] || 'white', buttonConfig?._default?.text_color?.[1] || buttonConfig?._default?.text_color?.[0] || 'white',
)(props), )(props),
_hover: { _hover: {
bg: mode( bg: mode(
config.UI.homepage.heroBanner?.button?._hover?.background?.[0] || 'blue.400', buttonConfig?._hover?.background?.[0] || 'blue.400',
config.UI.homepage.heroBanner?.button?._hover?.background?.[1] || 'blue.400', buttonConfig?._hover?.background?.[1] || buttonConfig?._hover?.background?.[0] || 'blue.400',
)(props), )(props),
color: mode( color: mode(
config.UI.homepage.heroBanner?.button?._hover?.text_color?.[0] || 'white', buttonConfig?._hover?.text_color?.[0] || 'white',
config.UI.homepage.heroBanner?.button?._hover?.text_color?.[1] || 'white', buttonConfig?._hover?.text_color?.[1] || buttonConfig?._hover?.text_color?.[0] || 'white',
)(props), )(props),
}, },
'&[data-selected=true]': { '&[data-selected=true]': {
bg: mode( bg: mode(
config.UI.homepage.heroBanner?.button?._selected?.background?.[0] || 'blue.50', buttonConfig?._selected?.background?.[0] || 'blue.50',
config.UI.homepage.heroBanner?.button?._selected?.background?.[1] || 'blue.50', buttonConfig?._selected?.background?.[1] || buttonConfig?._selected?.background?.[0] || 'blue.50',
)(props), )(props),
color: mode( color: mode(
config.UI.homepage.heroBanner?.button?._selected?.text_color?.[0] || 'blackAlpha.800', buttonConfig?._selected?.text_color?.[0] || 'blackAlpha.800',
config.UI.homepage.heroBanner?.button?._selected?.text_color?.[1] || 'blackAlpha.800', buttonConfig?._selected?.text_color?.[1] || buttonConfig?._selected?.text_color?.[0] || 'blackAlpha.800',
)(props), )(props),
}, },
}; };
......
...@@ -30,39 +30,30 @@ const variantSimple = definePartsStyle((props) => { ...@@ -30,39 +30,30 @@ const variantSimple = definePartsStyle((props) => {
}); });
const sizes = { const sizes = {
md: definePartsStyle({
th: {
px: 4,
fontSize: 'sm',
},
td: {
p: 4,
},
}),
sm: definePartsStyle({ sm: definePartsStyle({
th: {
px: '10px',
py: '10px',
fontSize: 'sm',
},
td: {
px: '10px',
py: 4,
fontSize: 'sm',
fontWeight: 500,
},
}),
xs: definePartsStyle({
th: { th: {
px: '6px', px: '6px',
py: '10px', py: '10px',
fontSize: 'sm', fontSize: 'sm',
_first: {
pl: 3,
},
_last: {
pr: 3,
},
}, },
td: { td: {
px: '6px', px: '6px',
py: 4, py: 4,
fontSize: 'sm', fontSize: 'sm',
fontWeight: 500, fontWeight: 500,
lineHeight: 5,
_first: {
pl: 3,
},
_last: {
pr: 3,
},
}, },
}), }),
}; };
...@@ -104,6 +95,10 @@ const Table = defineMultiStyleConfig({ ...@@ -104,6 +95,10 @@ const Table = defineMultiStyleConfig({
baseStyle, baseStyle,
sizes, sizes,
variants, variants,
defaultProps: {
size: 'sm',
variant: 'simple',
},
}); });
export default Table; export default Table;
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
import type { UserTags, AddressImplementation } from './addressParams'; import type { UserTags, AddressImplementation, AddressParam } from './addressParams';
import type { Block } from './block'; import type { Block, EpochRewardsType } from './block';
import type { InternalTransaction } from './internalTransaction'; import type { InternalTransaction } from './internalTransaction';
import type { MudWorldSchema, MudWorldTable } from './mudWorlds'; import type { MudWorldSchema, MudWorldTable } from './mudWorlds';
import type { NFTTokenType, TokenInfo, TokenInstance, TokenType } from './token'; import type { NFTTokenType, TokenInfo, TokenInstance, TokenType } from './token';
...@@ -191,6 +191,7 @@ export type AddressTabsCounters = { ...@@ -191,6 +191,7 @@ export type AddressTabsCounters = {
transactions_count: number | null; transactions_count: number | null;
validations_count: number | null; validations_count: number | null;
withdrawals_count: number | null; withdrawals_count: number | null;
celo_election_rewards_count?: number | null;
} }
// MUD framework // MUD framework
...@@ -245,3 +246,25 @@ export type AddressMudRecord = { ...@@ -245,3 +246,25 @@ export type AddressMudRecord = {
schema: MudWorldSchema; schema: MudWorldSchema;
table: MudWorldTable; table: MudWorldTable;
} }
export type AddressEpochRewardsResponse = {
items: Array<AddressEpochRewardsItem>;
next_page_params: {
amount: string;
associated_account_address_hash: string;
block_number: number;
items_count: number;
type: EpochRewardsType;
} | null;
}
export type AddressEpochRewardsItem = {
type: EpochRewardsType;
token: TokenInfo;
amount: string;
block_number: number;
block_hash: string;
account: AddressParam;
epoch_number: number;
associated_account: AddressParam;
}
...@@ -144,6 +144,8 @@ export interface BlockEpochElectionReward { ...@@ -144,6 +144,8 @@ export interface BlockEpochElectionReward {
total: string; total: string;
} }
export type EpochRewardsType = 'group' | 'validator' | 'delegated_payment' | 'voter';
export interface BlockEpoch { export interface BlockEpoch {
number: number; number: number;
distribution: { distribution: {
...@@ -151,12 +153,7 @@ export interface BlockEpoch { ...@@ -151,12 +153,7 @@ export interface BlockEpoch {
community_transfer: TokenTransfer | null; community_transfer: TokenTransfer | null;
reserve_bolster_transfer: TokenTransfer | null; reserve_bolster_transfer: TokenTransfer | null;
}; };
aggregated_election_rewards: { aggregated_election_rewards: Record<EpochRewardsType, BlockEpochElectionReward | null>;
delegated_payment: BlockEpochElectionReward | null;
group: BlockEpochElectionReward | null;
validator: BlockEpochElectionReward | null;
voter: BlockEpochElectionReward | null;
};
} }
export interface BlockEpochElectionRewardDetails { export interface BlockEpochElectionRewardDetails {
......
...@@ -51,6 +51,7 @@ interface TokenTransferBase { ...@@ -51,6 +51,7 @@ interface TokenTransferBase {
from: AddressParam; from: AddressParam;
to: AddressParam; to: AddressParam;
timestamp: string; timestamp: string;
block_number: string;
block_hash: string; block_hash: string;
log_index: string; log_index: string;
method?: string; method?: string;
......
...@@ -87,7 +87,7 @@ const AddressAccountHistory = ({ scrollRef, shouldRender = true, isQueryEnabled ...@@ -87,7 +87,7 @@ const AddressAccountHistory = ({ scrollRef, shouldRender = true, isQueryEnabled
</Hide> </Hide>
<Show above="lg" ssr={ false }> <Show above="lg" ssr={ false }>
<Table variant="simple" > <Table>
<TheadSticky top={ 75 }> <TheadSticky top={ 75 }>
<Tr> <Tr>
<Th width="120px"> <Th width="120px">
......
...@@ -105,7 +105,7 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled ...@@ -105,7 +105,7 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled
const content = query.data?.items ? ( const content = query.data?.items ? (
<> <>
<Hide below="lg" ssr={ false }> <Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }}> <Table style={{ tableLayout: 'auto' }}>
<Thead top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }> <Thead top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }>
<Tr> <Tr>
<Th>Block</Th> <Th>Block</Th>
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { epochRewards } from 'mocks/address/epochRewards';
import { test, expect } from 'playwright/lib';
import AddressEpochRewards from './AddressEpochRewards';
const ADDRESS_HASH = '0x1234';
const hooksConfig = {
router: {
query: { hash: ADDRESS_HASH },
},
};
test('base view +@mobile', async({ render, mockApiResponse }) => {
await mockApiResponse('address_epoch_rewards', epochRewards, { pathParams: { hash: ADDRESS_HASH } });
const component = await render(
<Box pt={{ base: '134px', lg: 6 }}>
<AddressEpochRewards/>
</Box>,
{ hooksConfig },
);
await expect(component).toHaveScreenshot();
});
import { Hide, Show } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import useIsMounted from 'lib/hooks/useIsMounted';
import getQueryParamString from 'lib/router/getQueryParamString';
import { EPOCH_REWARD_ITEM } from 'stubs/address';
import { generateListStub } from 'stubs/utils';
import AddressEpochRewardsTable from 'ui/address/epochRewards/AddressEpochRewardsTable';
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import AddressEpochRewardsListItem from './epochRewards/AddressEpochRewardsListItem';
type Props = {
scrollRef?: React.RefObject<HTMLDivElement>;
shouldRender?: boolean;
isQueryEnabled?: boolean;
}
const AddressEpochRewards = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: Props) => {
const router = useRouter();
const isMounted = useIsMounted();
const hash = getQueryParamString(router.query.hash);
const rewardsQuery = useQueryWithPages({
resourceName: 'address_epoch_rewards',
pathParams: {
hash,
},
scrollRef,
options: {
enabled: isQueryEnabled && Boolean(hash),
placeholderData: generateListStub<'address_epoch_rewards'>(EPOCH_REWARD_ITEM, 50, { next_page_params: {
amount: '1',
items_count: 50,
type: 'voter',
associated_account_address_hash: '1',
block_number: 10355938,
} }),
},
});
if (!isMounted || !shouldRender) {
return null;
}
const content = rewardsQuery.data?.items ? (
<>
<Hide below="lg" ssr={ false }>
<AddressEpochRewardsTable
items={ rewardsQuery.data.items }
top={ rewardsQuery.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
isLoading={ rewardsQuery.isPlaceholderData }
/>
</Hide>
<Show below="lg" ssr={ false }>
{ rewardsQuery.data.items.map((item, index) => (
<AddressEpochRewardsListItem
key={ item.block_hash + item.type + item.account.hash + item.associated_account.hash + (rewardsQuery.isPlaceholderData ? String(index) : '') }
item={ item }
isLoading={ rewardsQuery.isPlaceholderData }
/>
)) }
</Show>
</>
) : null;
const actionBar = rewardsQuery.pagination.isVisible ? (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...rewardsQuery.pagination }/>
</ActionBar>
) : null;
return (
<DataListDisplay
isError={ rewardsQuery.isError }
items={ rewardsQuery.data?.items }
emptyText="There are no epoch rewards for this address."
content={ content }
actionBar={ actionBar }
/>
);
};
export default AddressEpochRewards;
import { Flex, Skeleton } from '@chakra-ui/react'; import { Text, Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -45,7 +45,9 @@ const AddressBlocksValidatedListItem = (props: Props) => { ...@@ -45,7 +45,9 @@ const AddressBlocksValidatedListItem = (props: Props) => {
</Flex> </Flex>
<Flex columnGap={ 2 } w="100%"> <Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Gas used</Skeleton> <Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Gas used</Skeleton>
<Skeleton isLoaded={ !props.isLoading } color="text_secondary">{ BigNumber(props.gas_used || 0).toFormat() }</Skeleton> <Skeleton isLoaded={ !props.isLoading }>
<Text color="text_secondary">{ BigNumber(props.gas_used || 0).toFormat() }</Text>
</Skeleton>
<BlockGasUsed <BlockGasUsed
gasUsed={ props.gas_used } gasUsed={ props.gas_used }
gasLimit={ props.gas_limit } gasLimit={ props.gas_limit }
...@@ -55,7 +57,9 @@ const AddressBlocksValidatedListItem = (props: Props) => { ...@@ -55,7 +57,9 @@ const AddressBlocksValidatedListItem = (props: Props) => {
{ !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && ( { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && (
<Flex columnGap={ 2 } w="100%"> <Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Reward { currencyUnits.ether }</Skeleton> <Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Reward { currencyUnits.ether }</Skeleton>
<Skeleton isLoaded={ !props.isLoading } color="text_secondary">{ totalReward.toFixed() }</Skeleton> <Skeleton isLoaded={ !props.isLoading }>
<Text color="text_secondary">{ totalReward.toFixed() }</Text>
</Skeleton>
</Flex> </Flex>
) } ) }
</ListItemMobile> </ListItemMobile>
......
...@@ -57,7 +57,7 @@ const AddressBlocksValidatedTableItem = (props: Props) => { ...@@ -57,7 +57,7 @@ const AddressBlocksValidatedTableItem = (props: Props) => {
</Flex> </Flex>
</Td> </Td>
{ !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && ( { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && (
<Td isNumeric display="flex" justifyContent="end"> <Td isNumeric>
<Skeleton isLoaded={ !props.isLoading } display="inline-block"> <Skeleton isLoaded={ !props.isLoading } display="inline-block">
<span>{ totalReward.toFixed() }</span> <span>{ totalReward.toFixed() }</span>
</Skeleton> </Skeleton>
......
...@@ -26,7 +26,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => { ...@@ -26,7 +26,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => {
const content = query.data?.items ? ( const content = query.data?.items ? (
<> <>
<Hide below="lg" ssr={ false }> <Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm"> <Table>
<Thead top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }> <Thead top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }>
<Tr> <Tr>
<Th width="20%">Block</Th> <Th width="20%">Block</Th>
......
...@@ -69,6 +69,17 @@ const abiItem: AbiFunction = { ...@@ -69,6 +69,17 @@ const abiItem: AbiFunction = {
name: 'internalProposals', name: 'internalProposals',
type: 'tuple[]', type: 'tuple[]',
}, },
// ARRAY OF TUPLES WITHOUT NAMES
{
components: [
{ type: 'address' },
{ type: 'uint256' },
],
internalType: 'struct SharingPercentage[]',
name: '_sharingPercentages',
type: 'tuple[]',
},
], ],
}; };
...@@ -115,6 +126,10 @@ const result = [ ...@@ -115,6 +126,10 @@ const result = [
}, },
}, },
], ],
[
[ '0xfD36176C63dA52E783a347DE3544B0b44C7054a6', 0 ],
[ '0xC9534cB913150aD3e98D792857689B55e2404212', 3500 ],
],
]; ];
const onSettle = () => {}; const onSettle = () => {};
......
...@@ -23,8 +23,20 @@ const ItemTuple = ({ abiParameter, data, mode, level }: Props) => { ...@@ -23,8 +23,20 @@ const ItemTuple = ({ abiParameter, data, mode, level }: Props) => {
<span> { '{' }</span> <span> { '{' }</span>
</p> </p>
{ 'components' in abiParameter && abiParameter.components.map((component, index) => { { 'components' in abiParameter && abiParameter.components.map((component, index) => {
const dataObj = typeof data === 'object' && data !== null ? data : undefined; const itemData = (() => {
const itemData = dataObj && component.name && component.name in dataObj ? dataObj[component.name as keyof typeof dataObj] : undefined; if (typeof data !== 'object' || data === null) {
return;
}
if (Array.isArray(data)) {
return data[index];
}
if (component.name && component.name in data) {
return data[component.name as keyof typeof data];
}
})();
return ( return (
<Item <Item
key={ index } key={ index }
......
...@@ -6,7 +6,7 @@ import { test, expect } from 'playwright/lib'; ...@@ -6,7 +6,7 @@ import { test, expect } from 'playwright/lib';
import AddressMetadataAlert from './AddressMetadataAlert'; import AddressMetadataAlert from './AddressMetadataAlert';
test('base view', async({ render }) => { test('base view', async({ render }) => {
const component = await render(<AddressMetadataAlert tags={ [ metadataMock.noteTag ] }/>); const component = await render(<AddressMetadataAlert tags={ [ metadataMock.noteTag, metadataMock.noteTag2 ] }/>);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
import { Alert, chakra } from '@chakra-ui/react'; import { Alert, Flex, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressMetadataTagFormatted } from 'types/client/addressMetadata'; import type { AddressMetadataTagFormatted } from 'types/client/addressMetadata';
...@@ -9,35 +9,34 @@ interface Props { ...@@ -9,35 +9,34 @@ interface Props {
} }
const AddressMetadataAlert = ({ tags, className }: Props) => { const AddressMetadataAlert = ({ tags, className }: Props) => {
const noteTag = tags?.find(({ tagType }) => tagType === 'note'); const noteTags = tags?.filter(({ tagType }) => tagType === 'note').filter(({ meta }) => meta?.data);
if (!noteTag) {
return null;
}
const content = noteTag.meta?.data;
if (!content) { if (!noteTags?.length) {
return null; return null;
} }
return ( return (
<Alert <Flex flexDir="column" gap={ 3 } className={ className }>
className={ className } { noteTags.map((noteTag) => (
status={ noteTag.meta?.alertStatus ?? 'error' } <Alert
bgColor={ noteTag.meta?.alertBgColor } key={ noteTag.name }
color={ noteTag.meta?.alertTextColor } status={ noteTag.meta?.alertStatus ?? 'error' }
whiteSpace="pre-wrap" bgColor={ noteTag.meta?.alertBgColor }
display="inline-block" color={ noteTag.meta?.alertTextColor }
sx={{ whiteSpace="pre-wrap"
'& a': { display="inline-block"
color: 'link', sx={{
_hover: { '& a': {
color: 'link_hovered', color: 'link',
}, _hover: {
}, color: 'link_hovered',
}} },
dangerouslySetInnerHTML={{ __html: content }} },
/> }}
dangerouslySetInnerHTML={{ __html: noteTag.meta?.data ?? '' }}
/>
)) }
</Flex>
); );
}; };
......
import { Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { AddressEpochRewardsItem } from 'types/api/address';
import getCurrencyValue from 'lib/getCurrencyValue';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import EpochRewardTypeTag from 'ui/shared/EpochRewardTypeTag';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
type Props = {
item: AddressEpochRewardsItem;
isLoading?: boolean;
};
const AddressEpochRewardsListItem = ({ item, isLoading }: Props) => {
const { valueStr } = getCurrencyValue({ value: item.amount, accuracy: 2, decimals: item.token.decimals });
return (
<ListItemMobileGrid.Container gridTemplateColumns="100px auto">
<ListItemMobileGrid.Label isLoading={ isLoading }>Block</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<BlockEntity
number={ Number(item.block_number) }
isLoading={ isLoading }
noIcon
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Epoch #</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
{ item.epoch_number }
</ListItemMobileGrid.Value>
{ /* <ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<TimeAgoWithTooltip
timestamp={ item.timestamp }
isLoading={ isLoading }
color="text_secondary"
display="inline-block"
/>
</ListItemMobileGrid.Value> */ }
<ListItemMobileGrid.Label isLoading={ isLoading }>Reward type</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<EpochRewardTypeTag type={ item.type } isLoading={ isLoading }/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Associated address</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressEntity
address={ item.associated_account }
isLoading={ isLoading }
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Value</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="flex" alignItems="center" gap={ 2 }>
{ valueStr }
<TokenEntity token={ item.token } isLoading={ isLoading } onlySymbol width="auto" noCopy/>
</Skeleton>
</ListItemMobileGrid.Value>
</ListItemMobileGrid.Container>
);
};
export default AddressEpochRewardsListItem;
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react';
import type { AddressEpochRewardsItem } from 'types/api/address';
import { default as Thead } from 'ui/shared/TheadSticky';
import AddressEpochRewardsTableItem from './AddressEpochRewardsTableItem';
type Props = {
items: Array<AddressEpochRewardsItem>;
isLoading?: boolean;
top: number;
};
const AddressEpochRewardsTable = ({ items, isLoading, top }: Props) => {
return (
<Table minW="1000px" style={{ tableLayout: 'auto' }}>
<Thead top={ top }>
<Tr>
<Th>Block</Th>
<Th>Reward type</Th>
<Th>Associated address</Th>
<Th isNumeric>Value</Th>
</Tr>
</Thead>
<Tbody>
{ items.map((item, index) => {
return (
<AddressEpochRewardsTableItem
key={ item.block_hash + item.type + item.account.hash + item.associated_account.hash + (isLoading ? String(index) : '') }
item={ item }
isLoading={ isLoading }
/>
);
}) }
</Tbody>
</Table>
);
};
export default AddressEpochRewardsTable;
import { Flex, Td, Tr, Text, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { AddressEpochRewardsItem } from 'types/api/address';
import getCurrencyValue from 'lib/getCurrencyValue';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import EpochRewardTypeTag from 'ui/shared/EpochRewardTypeTag';
type Props = {
item: AddressEpochRewardsItem;
isLoading?: boolean;
};
const AddressEpochRewardsTableItem = ({ item, isLoading }: Props) => {
const { valueStr } = getCurrencyValue({ value: item.amount, decimals: item.token.decimals });
return (
<Tr>
<Td verticalAlign="middle">
<Flex alignItems="center" gap={ 3 }>
<BlockEntity number={ item.block_number } isLoading={ isLoading } noIcon/>
<Text color="text_secondary" fontWeight={ 600 }>{ `Epoch # ${ item.epoch_number }` }</Text>
{ /* no timestamp from API, will be added later */ }
{ /* <TimeAgoWithTooltip timestamp={ item } isLoading={ isLoading }/> */ }
</Flex>
</Td>
<Td verticalAlign="middle">
<EpochRewardTypeTag type={ item.type } isLoading={ isLoading }/>
</Td>
<Td verticalAlign="middle">
<AddressEntity address={ item.associated_account } isLoading={ isLoading }/>
</Td>
<Td verticalAlign="middle" isNumeric>
<Skeleton isLoaded={ !isLoading } display="flex" alignItems="center" gap={ 2 } justifyContent="flex-end">
{ valueStr }
<TokenEntity token={ item.token } isLoading={ isLoading } onlySymbol width="auto" noCopy/>
</Skeleton>
</Td>
</Tr>
);
};
export default AddressEpochRewardsTableItem;
...@@ -18,7 +18,7 @@ interface Props { ...@@ -18,7 +18,7 @@ interface Props {
const AddressIntTxsTable = ({ data, currentAddress, isLoading }: Props) => { const AddressIntTxsTable = ({ data, currentAddress, isLoading }: Props) => {
return ( return (
<AddressHighlightProvider> <AddressHighlightProvider>
<Table variant="simple" size="sm"> <Table>
<Thead top={ 68 }> <Thead top={ 68 }>
<Tr> <Tr>
<Th width="15%">Parent txn hash</Th> <Th width="15%">Parent txn hash</Th>
......
...@@ -26,9 +26,9 @@ const AddressMudRecordValues = ({ data }: Props) => { ...@@ -26,9 +26,9 @@ const AddressMudRecordValues = ({ data }: Props) => {
{ {
data?.schema.value_names.map((valName, index) => ( data?.schema.value_names.map((valName, index) => (
<Tr key={ valName } backgroundColor={ valuesBgColor } borderBottomStyle="hidden"> <Tr key={ valName } backgroundColor={ valuesBgColor } borderBottomStyle="hidden">
<Td fontSize="sm" w="100px" py={ 0 } pb={ 4 } pr={ 0 }wordBreak="break-all">{ valName }</Td> <Td fontWeight={ 400 } w="100px" py={ 0 } pb={ 4 } pr={ 0 }wordBreak="break-all">{ valName }</Td>
<Td fontSize="sm" w="90px" py={ 0 } pb={ 4 } wordBreak="break-all">{ data.schema.value_types[index] }</Td> <Td fontWeight={ 400 } w="90px" py={ 0 } pb={ 4 } wordBreak="break-all">{ data.schema.value_types[index] }</Td>
<Td fontSize="sm" wordBreak="break-word" py={ 0 } pb={ 4 }> <Td fontWeight={ 400 } wordBreak="break-word" py={ 0 } pb={ 4 }>
<Box> <Box>
{ getValueString(data.record.decoded[valName]) } { getValueString(data.record.decoded[valName]) }
</Box> </Box>
......
...@@ -140,7 +140,7 @@ const AddressMudRecordsTable = ({ ...@@ -140,7 +140,7 @@ const AddressMudRecordsTable = ({
return ( return (
// can't implement both horizontal table scroll and sticky header // can't implement both horizontal table scroll and sticky header
<Box maxW="100%" overflowX={ hasHorizontalScroll ? 'scroll' : 'unset' } whiteSpace="nowrap" ref={ tableRef }> <Box maxW="100%" overflowX={ hasHorizontalScroll ? 'scroll' : 'unset' } whiteSpace="nowrap" ref={ tableRef }>
<Table variant="simple" size="sm" style={{ tableLayout: 'fixed' }}> <Table style={{ tableLayout: 'fixed' }}>
<Thead top={ hasHorizontalScroll ? 0 : top } display={ hasHorizontalScroll ? 'table' : 'table-header-group' } w="100%"> <Thead top={ hasHorizontalScroll ? 0 : top } display={ hasHorizontalScroll ? 'table' : 'table-header-group' } w="100%">
<Tr > <Tr >
{ keys.map((keyName, index) => { { keys.map((keyName, index) => {
......
...@@ -18,7 +18,7 @@ type Props = { ...@@ -18,7 +18,7 @@ type Props = {
//sorry for the naming //sorry for the naming
const AddressMudTablesTable = ({ items, isLoading, top, scrollRef, hash }: Props) => { const AddressMudTablesTable = ({ items, isLoading, top, scrollRef, hash }: Props) => {
return ( return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }}> <Table style={{ tableLayout: 'auto' }}>
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th width="24px"></Th> <Th width="24px"></Th>
......
...@@ -15,7 +15,7 @@ interface Props { ...@@ -15,7 +15,7 @@ interface Props {
const ERC20TokensTable = ({ data, top, isLoading }: Props) => { const ERC20TokensTable = ({ data, top, isLoading }: Props) => {
return ( return (
<Table variant="simple" size="sm"> <Table>
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th width="30%">Asset</Th> <Th width="30%">Asset</Th>
......
...@@ -21,7 +21,7 @@ interface Props { ...@@ -21,7 +21,7 @@ interface Props {
const AddressesTable = ({ items, totalSupply, pageStartIndex, top, isLoading }: Props) => { const AddressesTable = ({ items, totalSupply, pageStartIndex, top, isLoading }: Props) => {
const hasPercentage = !totalSupply.eq(ZERO); const hasPercentage = !totalSupply.eq(ZERO);
return ( return (
<Table variant="simple" size="sm"> <Table>
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th width="64px">Rank</Th> <Th width="64px">Rank</Th>
......
...@@ -16,7 +16,7 @@ interface Props { ...@@ -16,7 +16,7 @@ interface Props {
const AddressesLabelSearchTable = ({ items, top, isLoading }: Props) => { const AddressesLabelSearchTable = ({ items, top, isLoading }: Props) => {
return ( return (
<Table variant="simple" size="sm"> <Table>
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th width="70%">Address</Th> <Th width="70%">Address</Th>
......
...@@ -21,7 +21,7 @@ interface Props { ...@@ -21,7 +21,7 @@ interface Props {
const ApiKeyTable = ({ data, isLoading, onDeleteClick, onEditClick, limit }: Props) => { const ApiKeyTable = ({ data, isLoading, onDeleteClick, onEditClick, limit }: Props) => {
return ( return (
<Table variant="simple" minWidth="600px"> <Table minWidth="600px">
<Thead> <Thead>
<Tr> <Tr>
<Th>{ `API key token (limit ${ limit } keys)` }</Th> <Th>{ `API key token (limit ${ limit } keys)` }</Th>
......
import React from 'react';
import type { BlockEpoch } from 'types/api/block';
import Tag from 'ui/shared/chakra/Tag';
interface Props {
type: keyof BlockEpoch['aggregated_election_rewards'];
isLoading?: boolean;
}
const BlockEpochElectionRewardType = ({ type, isLoading }: Props) => {
switch (type) {
case 'delegated_payment':
return <Tag colorScheme="blue" isLoading={ isLoading }>Delegated payments</Tag>;
case 'group':
return <Tag colorScheme="teal" isLoading={ isLoading }>Validator group rewards</Tag>;
case 'validator':
return <Tag colorScheme="purple" isLoading={ isLoading }>Validator rewards</Tag>;
case 'voter':
return <Tag colorScheme="yellow" isLoading={ isLoading }>Voting rewards</Tag>;
}
};
export default React.memo(BlockEpochElectionRewardType);
...@@ -16,7 +16,7 @@ const BlockEpochElectionRewards = ({ data, isLoading }: Props) => { ...@@ -16,7 +16,7 @@ const BlockEpochElectionRewards = ({ data, isLoading }: Props) => {
<Box mt={ 8 }> <Box mt={ 8 }>
<Heading as="h4" size="sm" mb={ 3 }>Election rewards</Heading> <Heading as="h4" size="sm" mb={ 3 }>Election rewards</Heading>
<Hide below="lg" ssr={ false }> <Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }}> <Table style={{ tableLayout: 'auto' }}>
<Thead> <Thead>
<Tr> <Tr>
<Th width="24px"/> <Th width="24px"/>
......
...@@ -5,10 +5,10 @@ import type { BlockEpoch, BlockEpochElectionReward } from 'types/api/block'; ...@@ -5,10 +5,10 @@ import type { BlockEpoch, BlockEpochElectionReward } from 'types/api/block';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import EpochRewardTypeTag from 'ui/shared/EpochRewardTypeTag';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import BlockEpochElectionRewardDetailsMobile from './BlockEpochElectionRewardDetailsMobile'; import BlockEpochElectionRewardDetailsMobile from './BlockEpochElectionRewardDetailsMobile';
import BlockEpochElectionRewardType from './BlockEpochElectionRewardType';
interface Props { interface Props {
data: BlockEpochElectionReward; data: BlockEpochElectionReward;
...@@ -53,7 +53,7 @@ const BlockEpochElectionRewardsListItem = ({ data, isLoading, type }: Props) => ...@@ -53,7 +53,7 @@ const BlockEpochElectionRewardsListItem = ({ data, isLoading, type }: Props) =>
/> />
</Skeleton> </Skeleton>
) : <Box boxSize={ 6 }/> } ) : <Box boxSize={ 6 }/> }
<BlockEpochElectionRewardType type={ type } isLoading={ isLoading }/> <EpochRewardTypeTag type={ type } isLoading={ isLoading }/>
<Skeleton isLoaded={ !isLoading }>{ data.count }</Skeleton> <Skeleton isLoaded={ !isLoading }>{ data.count }</Skeleton>
<Flex columnGap={ 2 } alignItems="center" ml="auto" fontWeight={ 500 }> <Flex columnGap={ 2 } alignItems="center" ml="auto" fontWeight={ 500 }>
<Skeleton isLoaded={ !isLoading }>{ valueStr }</Skeleton> <Skeleton isLoaded={ !isLoading }>{ valueStr }</Skeleton>
......
...@@ -5,10 +5,10 @@ import type { BlockEpoch, BlockEpochElectionReward } from 'types/api/block'; ...@@ -5,10 +5,10 @@ import type { BlockEpoch, BlockEpochElectionReward } from 'types/api/block';
import getCurrencyValue from 'lib/getCurrencyValue'; import getCurrencyValue from 'lib/getCurrencyValue';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import EpochRewardTypeTag from 'ui/shared/EpochRewardTypeTag';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import BlockEpochElectionRewardDetailsDesktop from './BlockEpochElectionRewardDetailsDesktop'; import BlockEpochElectionRewardDetailsDesktop from './BlockEpochElectionRewardDetailsDesktop';
import BlockEpochElectionRewardType from './BlockEpochElectionRewardType';
import { getRewardNumText } from './utils'; import { getRewardNumText } from './utils';
interface Props { interface Props {
...@@ -54,7 +54,7 @@ const BlockEpochElectionRewardsTableItem = ({ isLoading, data, type }: Props) => ...@@ -54,7 +54,7 @@ const BlockEpochElectionRewardsTableItem = ({ isLoading, data, type }: Props) =>
) } ) }
</Td> </Td>
<Td borderColor={ mainRowBorderColor }> <Td borderColor={ mainRowBorderColor }>
<BlockEpochElectionRewardType type={ type } isLoading={ isLoading }/> <EpochRewardTypeTag type={ type } isLoading={ isLoading }/>
</Td> </Td>
<Td borderColor={ mainRowBorderColor }> <Td borderColor={ mainRowBorderColor }>
<Skeleton isLoaded={ !isLoading } fontWeight={ 400 } my={ 1 }> <Skeleton isLoaded={ !isLoading } fontWeight={ 400 } my={ 1 }>
......
...@@ -40,7 +40,7 @@ const BlocksTable = ({ data, isLoading, top, page, showSocketInfo, socketInfoNum ...@@ -40,7 +40,7 @@ const BlocksTable = ({ data, isLoading, top, page, showSocketInfo, socketInfoNum
return ( return (
<AddressHighlightProvider> <AddressHighlightProvider>
<Table variant="simple" minWidth="1040px" size="md" fontWeight={ 500 }> <Table minWidth="1040px" fontWeight={ 500 }>
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th width="150px">Block</Th> <Th width="150px">Block</Th>
......
...@@ -20,7 +20,7 @@ interface Props { ...@@ -20,7 +20,7 @@ interface Props {
const CustomAbiTable = ({ data, isLoading, onDeleteClick, onEditClick }: Props) => { const CustomAbiTable = ({ data, isLoading, onDeleteClick, onEditClick }: Props) => {
return ( return (
<Table variant="simple" minWidth="600px"> <Table minWidth="600px">
<Thead> <Thead>
<Tr> <Tr>
<Th>ABI for Smart contract address (0x...)</Th> <Th>ABI for Smart contract address (0x...)</Th>
......
...@@ -15,7 +15,7 @@ import OptimisticDepositsTableItem from './OptimisticDepositsTableItem'; ...@@ -15,7 +15,7 @@ import OptimisticDepositsTableItem from './OptimisticDepositsTableItem';
const OptimisticDepositsTable = ({ items, top, isLoading }: Props) => { const OptimisticDepositsTable = ({ items, top, isLoading }: Props) => {
return ( return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px"> <Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th>L1 block No</Th> <Th>L1 block No</Th>
......
...@@ -15,7 +15,7 @@ import DepositsTableItem from './DepositsTableItem'; ...@@ -15,7 +15,7 @@ import DepositsTableItem from './DepositsTableItem';
const DepositsTable = ({ items, top, isLoading }: Props) => { const DepositsTable = ({ items, top, isLoading }: Props) => {
return ( return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px"> <Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th>L1 block No</Th> <Th>L1 block No</Th>
......
...@@ -15,7 +15,7 @@ import ZkEvmL2DepositsTableItem from './ZkEvmL2DepositsTableItem'; ...@@ -15,7 +15,7 @@ import ZkEvmL2DepositsTableItem from './ZkEvmL2DepositsTableItem';
const ZkEvmL2DepositsTable = ({ items, top, isLoading }: Props) => { const ZkEvmL2DepositsTable = ({ items, top, isLoading }: Props) => {
return ( return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px"> <Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th>L1 block</Th> <Th>L1 block</Th>
......
...@@ -15,7 +15,7 @@ type Props = { ...@@ -15,7 +15,7 @@ type Props = {
const OptimisticL2DisputeGamesTable = ({ items, top, isLoading }: Props) => { const OptimisticL2DisputeGamesTable = ({ items, top, isLoading }: Props) => {
return ( return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px"> <Table style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th>Index</Th> <Th>Index</Th>
......
...@@ -14,18 +14,32 @@ const BORDER_DEFAULT = 'none'; ...@@ -14,18 +14,32 @@ const BORDER_DEFAULT = 'none';
const HeroBanner = () => { const HeroBanner = () => {
const background = useColorModeValue( const background = useColorModeValue(
config.UI.homepage.heroBanner?.background?.[0] || config.UI.homepage.plate.background || BACKGROUND_DEFAULT, // light mode
config.UI.homepage.heroBanner?.background?.[1] || config.UI.homepage.plate.background || BACKGROUND_DEFAULT, config.UI.homepage.heroBanner?.background?.[0] ||
config.UI.homepage.plate.background ||
BACKGROUND_DEFAULT,
// dark mode
config.UI.homepage.heroBanner?.background?.[1] ||
config.UI.homepage.heroBanner?.background?.[0] ||
config.UI.homepage.plate.background ||
BACKGROUND_DEFAULT,
); );
const textColor = useColorModeValue( const textColor = useColorModeValue(
config.UI.homepage.heroBanner?.text_color?.[0] || config.UI.homepage.plate.textColor || TEXT_COLOR_DEFAULT, // light mode
config.UI.homepage.heroBanner?.text_color?.[1] || config.UI.homepage.plate.textColor || TEXT_COLOR_DEFAULT, config.UI.homepage.heroBanner?.text_color?.[0] ||
config.UI.homepage.plate.textColor ||
TEXT_COLOR_DEFAULT,
// dark mode
config.UI.homepage.heroBanner?.text_color?.[1] ||
config.UI.homepage.heroBanner?.text_color?.[0] ||
config.UI.homepage.plate.textColor ||
TEXT_COLOR_DEFAULT,
); );
const border = useColorModeValue( const border = useColorModeValue(
config.UI.homepage.heroBanner?.border?.[0] || BORDER_DEFAULT, config.UI.homepage.heroBanner?.border?.[0] || BORDER_DEFAULT,
config.UI.homepage.heroBanner?.border?.[1] || BORDER_DEFAULT, config.UI.homepage.heroBanner?.border?.[1] || config.UI.homepage.heroBanner?.border?.[0] || BORDER_DEFAULT,
); );
return ( return (
......
...@@ -11,6 +11,7 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard'; ...@@ -11,6 +11,7 @@ import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AppSecurityReport from './AppSecurityReport'; import AppSecurityReport from './AppSecurityReport';
import FavoriteIcon from './FavoriteIcon'; import FavoriteIcon from './FavoriteIcon';
import MarketplaceAppCardLink from './MarketplaceAppCardLink'; import MarketplaceAppCardLink from './MarketplaceAppCardLink';
import MarketplaceAppGraphLinks from './MarketplaceAppGraphLinks';
import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon'; import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon';
import Rating from './Rating/Rating'; import Rating from './Rating/Rating';
import type { RateFunction } from './Rating/useRatings'; import type { RateFunction } from './Rating/useRatings';
...@@ -28,6 +29,7 @@ interface Props extends MarketplaceAppWithSecurityReport { ...@@ -28,6 +29,7 @@ interface Props extends MarketplaceAppWithSecurityReport {
isRatingSending: boolean; isRatingSending: boolean;
isRatingLoading: boolean; isRatingLoading: boolean;
canRate: boolean | undefined; canRate: boolean | undefined;
graphLinks: Array<{text: string; url: string}>;
} }
const MarketplaceAppCard = ({ const MarketplaceAppCard = ({
...@@ -54,6 +56,7 @@ const MarketplaceAppCard = ({ ...@@ -54,6 +56,7 @@ const MarketplaceAppCard = ({
isRatingSending, isRatingSending,
isRatingLoading, isRatingLoading,
canRate, canRate,
graphLinks,
}: Props) => { }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const categoriesLabel = categories.join(', '); const categoriesLabel = categories.join(', ');
...@@ -118,11 +121,7 @@ const MarketplaceAppCard = ({ ...@@ -118,11 +121,7 @@ const MarketplaceAppCard = ({
> >
<Skeleton <Skeleton
isLoaded={ !isLoading } isLoaded={ !isLoading }
fontSize={{ base: 'sm', md: 'lg' }}
lineHeight={{ base: '20px', md: '28px' }}
paddingRight={{ base: '40px', md: 0 }} paddingRight={{ base: '40px', md: 0 }}
fontWeight="semibold"
fontFamily="heading"
display="inline-block" display="inline-block"
> >
<MarketplaceAppCardLink <MarketplaceAppCardLink
...@@ -131,8 +130,18 @@ const MarketplaceAppCard = ({ ...@@ -131,8 +130,18 @@ const MarketplaceAppCard = ({
external={ external } external={ external }
title={ title } title={ title }
onClick={ onAppClick } onClick={ onAppClick }
fontWeight="semibold"
fontFamily="heading"
fontSize={{ base: 'sm', md: 'lg' }}
lineHeight={{ base: '20px', md: '28px' }}
/> />
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/> <MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
<MarketplaceAppGraphLinks
links={ graphLinks }
ml={ 2 }
verticalAlign="middle"
mb={{ base: 0, md: 1 }}
/>
</Skeleton> </Skeleton>
<Skeleton <Skeleton
......
import { LinkOverlay } from '@chakra-ui/react'; import { LinkOverlay, chakra } from '@chakra-ui/react';
import NextLink from 'next/link'; import NextLink from 'next/link';
import React from 'react'; import React from 'react';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
...@@ -9,24 +9,25 @@ type Props = { ...@@ -9,24 +9,25 @@ type Props = {
external?: boolean; external?: boolean;
title: string; title: string;
onClick?: (event: MouseEvent, id: string) => void; onClick?: (event: MouseEvent, id: string) => void;
className?: string;
} }
const MarketplaceAppCardLink = ({ url, external, id, title, onClick }: Props) => { const MarketplaceAppCardLink = ({ url, external, id, title, onClick, className }: Props) => {
const handleClick = React.useCallback((event: MouseEvent) => { const handleClick = React.useCallback((event: MouseEvent) => {
onClick?.(event, id); onClick?.(event, id);
}, [ onClick, id ]); }, [ onClick, id ]);
return external ? ( return external ? (
<LinkOverlay href={ url } isExternal={ true } marginRight={ 2 }> <LinkOverlay href={ url } isExternal={ true } marginRight={ 2 } className={ className }>
{ title } { title }
</LinkOverlay> </LinkOverlay>
) : ( ) : (
<NextLink href={{ pathname: '/apps/[id]', query: { id } }} passHref legacyBehavior> <NextLink href={{ pathname: '/apps/[id]', query: { id } }} passHref legacyBehavior>
<LinkOverlay onClick={ handleClick } marginRight={ 2 }> <LinkOverlay onClick={ handleClick } marginRight={ 2 } className={ className }>
{ title } { title }
</LinkOverlay> </LinkOverlay>
</NextLink> </NextLink>
); );
}; };
export default MarketplaceAppCardLink; export default chakra(MarketplaceAppCardLink);
import {
Text,
PopoverTrigger,
PopoverBody,
PopoverContent,
chakra,
Box,
VStack,
} from '@chakra-ui/react';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import Popover from 'ui/shared/chakra/Popover';
import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/links/LinkExternal';
interface Props {
className?: string;
links?: Array<{ title: string; url: string }>;
}
const MarketplaceAppGraphLinks = ({ className, links }: Props) => {
const isMobile = useIsMobile();
const handleButtonClick = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
}, []);
if (!links || links.length === 0) {
return null;
}
return (
<Box position="relative" className={ className } display="inline-flex" alignItems="center" height={ 7 } onClick={ handleButtonClick }>
<Popover
placement={ isMobile ? 'bottom-end' : 'bottom' }
isLazy
trigger="hover"
>
<PopoverTrigger>
<IconSvg name="brands/graph" boxSize={ 5 } onClick={ handleButtonClick }/>
</PopoverTrigger>
<PopoverContent w="260px">
<PopoverBody fontSize="sm">
<VStack gap={ 4 } align="start">
<Text>{ `This dapp uses ${ links.length > 1 ? 'several subgraphs' : 'a subgraph' } powered by The Graph` }</Text>
{ links.map(link => (
<LinkExternal key={ link.url } href={ link.url }>{ link.title }</LinkExternal>
)) }
</VStack>
</PopoverBody>
</PopoverContent>
</Popover>
</Box>
);
};
export default React.memo(chakra(MarketplaceAppGraphLinks));
...@@ -18,6 +18,8 @@ import IconSvg from 'ui/shared/IconSvg'; ...@@ -18,6 +18,8 @@ import IconSvg from 'ui/shared/IconSvg';
import AppSecurityReport from './AppSecurityReport'; import AppSecurityReport from './AppSecurityReport';
import FavoriteIcon from './FavoriteIcon'; import FavoriteIcon from './FavoriteIcon';
import MarketplaceAppGraphLinks from './MarketplaceAppGraphLinks';
import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon';
import MarketplaceAppModalLink from './MarketplaceAppModalLink'; import MarketplaceAppModalLink from './MarketplaceAppModalLink';
import Rating from './Rating/Rating'; import Rating from './Rating/Rating';
import type { RateFunction } from './Rating/useRatings'; import type { RateFunction } from './Rating/useRatings';
...@@ -36,6 +38,7 @@ type Props = { ...@@ -36,6 +38,7 @@ type Props = {
isRatingSending: boolean; isRatingSending: boolean;
isRatingLoading: boolean; isRatingLoading: boolean;
canRate: boolean | undefined; canRate: boolean | undefined;
graphLinks?: Array<{text: string; url: string}>;
} }
const MarketplaceAppModal = ({ const MarketplaceAppModal = ({
...@@ -49,6 +52,7 @@ const MarketplaceAppModal = ({ ...@@ -49,6 +52,7 @@ const MarketplaceAppModal = ({
isRatingSending, isRatingSending,
isRatingLoading, isRatingLoading,
canRate, canRate,
graphLinks,
}: Props) => { }: Props) => {
const { const {
id, id,
...@@ -67,6 +71,7 @@ const MarketplaceAppModal = ({ ...@@ -67,6 +71,7 @@ const MarketplaceAppModal = ({
categories, categories,
securityReport, securityReport,
rating, rating,
internalWallet,
} = data; } = data;
const socialLinks = [ const socialLinks = [
...@@ -148,16 +153,19 @@ const MarketplaceAppModal = ({ ...@@ -148,16 +153,19 @@ const MarketplaceAppModal = ({
/> />
</Flex> </Flex>
<Heading <Flex alignItems="center" mb={{ md: 2 }} gridColumn={ 2 }>
as="h2" <Heading
gridColumn={ 2 } as="h2"
fontSize={{ base: '2xl', md: '32px' }} fontSize={{ base: '2xl', md: '32px' }}
fontWeight="medium" fontWeight="medium"
lineHeight={{ md: 10 }} lineHeight={{ md: 10 }}
mb={{ md: 2 }} mr={ 2 }
> >
{ title } { title }
</Heading> </Heading>
<MarketplaceAppIntegrationIcon external={ external } internalWallet={ internalWallet }/>
<MarketplaceAppGraphLinks links={ graphLinks } ml={ 2 }/>
</Flex>
<Text <Text
variant="secondary" variant="secondary"
......
import { Grid, Box } from '@chakra-ui/react'; import { Grid, Box } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
...@@ -25,11 +26,13 @@ type Props = { ...@@ -25,11 +26,13 @@ type Props = {
isRatingSending: boolean; isRatingSending: boolean;
isRatingLoading: boolean; isRatingLoading: boolean;
canRate: boolean | undefined; canRate: boolean | undefined;
graphLinksQuery: UseQueryResult<Record<string, Array<{text: string; url: string}>>, unknown>;
} }
const MarketplaceList = ({ const MarketplaceList = ({
apps, showAppInfo, favoriteApps, onFavoriteClick, isLoading, selectedCategoryId, apps, showAppInfo, favoriteApps, onFavoriteClick, isLoading, selectedCategoryId,
onAppClick, showContractList, userRatings, rateApp, isRatingSending, isRatingLoading, canRate, onAppClick, showContractList, userRatings, rateApp, isRatingSending, isRatingLoading, canRate,
graphLinksQuery,
}: Props) => { }: Props) => {
const { cutRef, renderedItemsNum } = useLazyRenderedList(apps, !isLoading, 16); const { cutRef, renderedItemsNum } = useLazyRenderedList(apps, !isLoading, 16);
...@@ -75,6 +78,7 @@ const MarketplaceList = ({ ...@@ -75,6 +78,7 @@ const MarketplaceList = ({
isRatingSending={ isRatingSending } isRatingSending={ isRatingSending }
isRatingLoading={ isRatingLoading } isRatingLoading={ isRatingLoading }
canRate={ canRate } canRate={ canRate }
graphLinks={ graphLinksQuery.data?.[app.id] }
/> />
)) } )) }
</Grid> </Grid>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment