Merge branch 'main' into menu-lightning-label
@@ -4,6 +4,7 @@ const RESTRICTED_MODULES = {
|
||||
{ name: '@chakra-ui/icons', message: 'Using @chakra-ui/icons is prohibited. Please use regular svg-icon instead (see examples in "icons/" folder)' },
|
||||
{ name: '@metamask/providers', message: 'Please lazy-load @metamask/providers or use useProvider hook instead' },
|
||||
{ name: '@metamask/post-message-stream', message: 'Please lazy-load @metamask/post-message-stream or use useProvider hook instead' },
|
||||
{ name: 'playwright/TestApp', message: 'Please use render() fixture from test() function of playwright/lib module' },
|
||||
],
|
||||
patterns: [
|
||||
'icons/*',
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
changelog:
|
||||
categories:
|
||||
- title: 🚀 New Features
|
||||
labels:
|
||||
- client feature
|
||||
- feature
|
||||
- title: 🐛 Bug Fixes
|
||||
labels:
|
||||
- bug
|
||||
- title: ⚡ Performance Improvements
|
||||
labels:
|
||||
- performance
|
||||
- title: 📦 Dependencies Updates
|
||||
labels:
|
||||
- dependencies
|
||||
- title: 🚨 Changes in ENV variables
|
||||
labels:
|
||||
- ENVs
|
||||
- title: ✨ Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
@@ -0,0 +1,116 @@
|
||||
name: Copy issues labels to pull request
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: Pull request number
|
||||
required: true
|
||||
type: string
|
||||
issues:
|
||||
description: JSON encoded list of issue ids
|
||||
required: true
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: Pull request number
|
||||
required: true
|
||||
type: string
|
||||
issues:
|
||||
description: JSON encoded list of issue ids
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
run:
|
||||
name: Run
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Find unique labels
|
||||
id: find_unique_labels
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
ISSUES: ${{ inputs.issues }}
|
||||
with:
|
||||
script: |
|
||||
const issues = JSON.parse(process.env.ISSUES);
|
||||
|
||||
const WHITE_LISTED_LABELS = [
|
||||
'client feature',
|
||||
'feature',
|
||||
|
||||
'bug',
|
||||
|
||||
'dependencies',
|
||||
'performance',
|
||||
|
||||
'chore',
|
||||
'enhancement',
|
||||
'refactoring',
|
||||
'tech',
|
||||
'ENVs',
|
||||
]
|
||||
|
||||
const labels = await Promise.all(issues.map(getIssueLabels));
|
||||
const uniqueLabels = uniqueStringArray(labels.flat().filter((label) => WHITE_LISTED_LABELS.includes(label)));
|
||||
|
||||
if (uniqueLabels.length === 0) {
|
||||
core.info('No labels found.\n');
|
||||
return [];
|
||||
}
|
||||
|
||||
core.info(`Found following labels: ${ uniqueLabels.join(', ') }.\n`);
|
||||
return uniqueLabels;
|
||||
|
||||
async function getIssueLabels(issue) {
|
||||
core.info(`Obtaining labels list for the issue #${ issue }...`);
|
||||
|
||||
try {
|
||||
const response = await github.request('GET /repos/{owner}/{repo}/issues/{issue_number}/labels', {
|
||||
owner: 'blockscout',
|
||||
repo: 'frontend',
|
||||
issue_number: issue,
|
||||
});
|
||||
return response.data.map(({ name }) => name);
|
||||
} catch (error) {
|
||||
core.error(`Failed to obtain labels for the issue #${ issue }: ${ error.message }`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function uniqueStringArray(array) {
|
||||
return Array.from(new Set(array));
|
||||
}
|
||||
|
||||
- name: Update pull request labels
|
||||
id: update_pr_labels
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
LABELS: ${{ steps.find_unique_labels.outputs.result }}
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
with:
|
||||
script: |
|
||||
const labels = JSON.parse(process.env.LABELS);
|
||||
const prNumber = Number(process.env.PR_NUMBER);
|
||||
|
||||
if (labels.length === 0) {
|
||||
core.info('Nothing to update.\n');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const label of labels) {
|
||||
await addLabelToPr(prNumber, label);
|
||||
}
|
||||
core.info('Done.\n');
|
||||
|
||||
async function addLabelToPr(prNumber, label) {
|
||||
console.log(`Adding label to the pull request #${ prNumber }...`);
|
||||
|
||||
return await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/labels', {
|
||||
owner: 'blockscout',
|
||||
repo: 'frontend',
|
||||
issue_number: prNumber,
|
||||
labels: [ label ],
|
||||
});
|
||||
}
|
||||
@@ -55,7 +55,10 @@ jobs:
|
||||
label_description: Tasks in pre-release right now
|
||||
secrets: inherit
|
||||
|
||||
# Temporary disable this step because it is broken
|
||||
# There is an issue with building web3modal deps
|
||||
upload_source_maps:
|
||||
name: Upload source maps to Sentry
|
||||
if: false
|
||||
uses: './.github/workflows/upload-source-maps.yml'
|
||||
secrets: inherit
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
issues: "[${{ github.event.issue.number }}]"
|
||||
secrets: inherit
|
||||
|
||||
review_requested_issues:
|
||||
pr_linked_issues:
|
||||
name: Get issues linked to PR
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.pull_request && github.event.action == 'review_requested' }}
|
||||
@@ -76,14 +76,24 @@ jobs:
|
||||
|
||||
return issues;
|
||||
|
||||
review_requested_tasks:
|
||||
issues_in_review:
|
||||
name: Update status for issues in review
|
||||
needs: [ review_requested_issues ]
|
||||
if: ${{ needs.review_requested_issues.outputs.issues }}
|
||||
needs: [ pr_linked_issues ]
|
||||
if: ${{ needs.pr_linked_issues.outputs.issues }}
|
||||
uses: './.github/workflows/update-project-cards.yml'
|
||||
secrets: inherit
|
||||
with:
|
||||
project_name: ${{ vars.PROJECT_NAME }}
|
||||
field_name: Status
|
||||
field_value: Review
|
||||
issues: ${{ needs.review_requested_issues.outputs.issues }}
|
||||
issues: ${{ needs.pr_linked_issues.outputs.issues }}
|
||||
|
||||
copy_labels:
|
||||
name: Copy issues labels to pull request
|
||||
needs: [ pr_linked_issues ]
|
||||
if: ${{ needs.pr_linked_issues.outputs.issues }}
|
||||
uses: './.github/workflows/copy-issues-labels.yml'
|
||||
secrets: inherit
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
issues: ${{ needs.pr_linked_issues.outputs.issues }}
|
||||
|
||||
@@ -81,8 +81,11 @@ jobs:
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
|
||||
# Temporary disable this step because it is broken
|
||||
# There is an issue with building web3modal deps
|
||||
upload_source_maps:
|
||||
name: Upload source maps to Sentry
|
||||
if: false
|
||||
needs: publish_image
|
||||
uses: './.github/workflows/upload-source-maps.yml'
|
||||
secrets: inherit
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
/public/assets/
|
||||
/public/envs.js
|
||||
/public/assets/envs.js
|
||||
/public/assets/configs
|
||||
/public/icons/sprite.svg
|
||||
/public/icons/README.md
|
||||
/analyze
|
||||
|
||||
@@ -34,11 +34,13 @@ RUN yarn --frozen-lockfile
|
||||
FROM node:20.11.0-alpine AS builder
|
||||
RUN apk add --no-cache --upgrade libc6-compat bash
|
||||
|
||||
# pass commit sha and git tag to the app image
|
||||
# pass build args to env variables
|
||||
ARG GIT_COMMIT_SHA
|
||||
ENV NEXT_PUBLIC_GIT_COMMIT_SHA=$GIT_COMMIT_SHA
|
||||
ARG GIT_TAG
|
||||
ENV NEXT_PUBLIC_GIT_TAG=$GIT_TAG
|
||||
ARG NEXT_OPEN_TELEMETRY_ENABLED
|
||||
ENV NEXT_OPEN_TELEMETRY_ENABLED=$NEXT_OPEN_TELEMETRY_ENABLED
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
@@ -58,8 +60,8 @@ RUN ./collect_envs.sh ./docs/ENVS.md
|
||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
# Build app for production
|
||||
RUN yarn build
|
||||
RUN yarn svg:build-sprite
|
||||
RUN yarn build
|
||||
|
||||
|
||||
### FEATURE REPORTER
|
||||
|
||||
@@ -15,6 +15,7 @@ export { default as growthBook } from './growthBook';
|
||||
export { default as marketplace } from './marketplace';
|
||||
export { default as metasuites } from './metasuites';
|
||||
export { default as mixpanel } from './mixpanel';
|
||||
export { default as multichainButton } from './multichainButton';
|
||||
export { default as nameService } from './nameService';
|
||||
export { default as restApiDocs } from './restApiDocs';
|
||||
export { default as rollup } from './rollup';
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { Feature } from './types';
|
||||
import type { MultichainProviderConfig } from 'types/client/multichainProviderConfig';
|
||||
|
||||
import { getEnvValue, parseEnvJson } from '../utils';
|
||||
import marketplace from './marketplace';
|
||||
|
||||
const value = parseEnvJson<MultichainProviderConfig>(getEnvValue('NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG'));
|
||||
|
||||
const title = 'Multichain balance';
|
||||
|
||||
const config: Feature<{name: string; logoUrl?: string; urlTemplate: string; dappId?: string }> = (() => {
|
||||
if (value) {
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
name: value.name,
|
||||
logoUrl: value.logo,
|
||||
urlTemplate: value.url_template,
|
||||
dappId: marketplace.isEnabled ? value.dapp_id : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: false,
|
||||
});
|
||||
})();
|
||||
|
||||
export default config;
|
||||
@@ -41,7 +41,7 @@ export const buildExternalAssetFilePath = (name: string, value: string) => {
|
||||
const fileName = name.replace(/^NEXT_PUBLIC_/, '').replace(/_URL$/, '').toLowerCase();
|
||||
const url = new URL(value);
|
||||
const fileExtension = url.pathname.match(regexp.FILE_EXTENSION)?.[1];
|
||||
return `/assets/${ fileName }.${ fileExtension }`;
|
||||
return `/assets/configs/${ fileName }.${ fileExtension }`;
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -48,13 +48,16 @@ NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global
|
||||
NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com
|
||||
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
|
||||
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
|
||||
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
|
||||
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
|
||||
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_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json
|
||||
NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=gearbox-protocol
|
||||
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
|
||||
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}
|
||||
|
||||
#meta
|
||||
NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true
|
||||
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
|
||||
NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=true
|
||||
|
||||
|
||||
@@ -18,14 +18,14 @@ NEXT_PUBLIC_NETWORK_RPC_URL=https://eth-sepolia.public.blastapi.io
|
||||
NEXT_PUBLIC_IS_TESTNET=true
|
||||
|
||||
# api configuration
|
||||
NEXT_PUBLIC_API_HOST=eth-sepolia.blockscout.com
|
||||
NEXT_PUBLIC_API_HOST=eth-sepolia.k8s-dev.blockscout.com
|
||||
NEXT_PUBLIC_API_BASE_PATH=/
|
||||
|
||||
# ui config
|
||||
## homepage
|
||||
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
|
||||
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgba(51, 53, 67, 1)'
|
||||
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='rgba(165, 252, 122, 1)'
|
||||
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(51,53,67,1)
|
||||
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(165,252,122,1)
|
||||
## sidebar
|
||||
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth-sepolia.json
|
||||
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg
|
||||
@@ -48,7 +48,11 @@ NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
|
||||
NEXT_PUBLIC_AUTH_URL=http://localhost:3000/login
|
||||
NEXT_PUBLIC_LOGOUT_URL=https://blockscout-goerli.us.auth0.com/v2/logout
|
||||
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
|
||||
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
|
||||
NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json
|
||||
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
|
||||
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
|
||||
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
|
||||
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
|
||||
NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s-dev.blockscout.com
|
||||
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
|
||||
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
|
||||
|
||||
@@ -36,7 +36,7 @@ export_envs_from_preset() {
|
||||
export_envs_from_preset
|
||||
|
||||
# Download external assets
|
||||
./download_assets.sh ./public/assets
|
||||
./download_assets.sh ./public/assets/configs
|
||||
|
||||
# Check run-time ENVs values
|
||||
./validate_envs.sh
|
||||
|
||||
@@ -10,7 +10,7 @@ if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
else
|
||||
cd ../../../
|
||||
favicon_folder="./public/favicon/"
|
||||
favicon_folder="./public/assets/favicon/"
|
||||
|
||||
echo "⏳ Replacing default favicons with freshly generated pack..."
|
||||
if [ -d "$favicon_folder" ]; then
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
echo "🌀 Creating client script with ENV values..."
|
||||
|
||||
# Define the output file name
|
||||
output_file="${1:-./public/envs.js}"
|
||||
output_file="${1:-./public/assets/envs.js}"
|
||||
|
||||
touch $output_file;
|
||||
truncate -s 0 $output_file;
|
||||
|
||||
@@ -15,6 +15,7 @@ import type { ContractCodeIde } from '../../../types/client/contract';
|
||||
import { GAS_UNITS } from '../../../types/client/gasTracker';
|
||||
import type { GasUnit } from '../../../types/client/gasTracker';
|
||||
import type { MarketplaceAppOverview, MarketplaceAppSecurityReportRaw, MarketplaceAppSecurityReport } from '../../../types/client/marketplace';
|
||||
import type { MultichainProviderConfig } from '../../../types/client/multichainProviderConfig';
|
||||
import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items';
|
||||
import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items';
|
||||
import { ROLLUP_TYPES } from '../../../types/client/rollup';
|
||||
@@ -625,6 +626,19 @@ const schema = yup
|
||||
NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(),
|
||||
NEXT_PUBLIC_METASUITES_ENABLED: yup.boolean(),
|
||||
NEXT_PUBLIC_SWAP_BUTTON_URL: yup.string(),
|
||||
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG: yup
|
||||
.mixed()
|
||||
.test('shape', 'Invalid schema were provided for NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG, it should have name and url template', (data) => {
|
||||
const isUndefined = data === undefined;
|
||||
const valueSchema = yup.object<MultichainProviderConfig>().transform(replaceQuotes).json().shape({
|
||||
name: yup.string().required(),
|
||||
url_template: yup.string().required(),
|
||||
logo: yup.string(),
|
||||
dapp_id: yup.string(),
|
||||
});
|
||||
|
||||
return isUndefined || valueSchema.isValidSync(data);
|
||||
}),
|
||||
NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE: yup.string<ValidatorsChainType>().oneOf(VALIDATORS_CHAIN_TYPE),
|
||||
NEXT_PUBLIC_GAS_TRACKER_ENABLED: yup.boolean(),
|
||||
NEXT_PUBLIC_GAS_TRACKER_UNITS: yup.array().transform(replaceQuotes).json().of(yup.string<GasUnit>().oneOf(GAS_UNITS)),
|
||||
|
||||
@@ -9,7 +9,7 @@ export NEXT_PUBLIC_GIT_TAG=$(git describe --tags --abbrev=0)
|
||||
../../scripts/collect_envs.sh ../../../docs/ENVS.md
|
||||
|
||||
# Copy test assets
|
||||
mkdir -p "./public/assets"
|
||||
mkdir -p "./public/assets/configs"
|
||||
cp -r ${test_folder}/assets ./public/
|
||||
|
||||
# Build validator script
|
||||
|
||||
@@ -75,3 +75,5 @@ NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=false
|
||||
NEXT_PUBLIC_WEB3_WALLETS=['coinbase','metamask','token_pocket']
|
||||
NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap
|
||||
NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability
|
||||
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}
|
||||
|
||||
|
||||
@@ -94,7 +94,6 @@ frontend:
|
||||
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: "{ \"id\": \"632018\", \"width\": \"320\", \"height\": \"100\" }"
|
||||
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true
|
||||
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED: true
|
||||
NEXT_PUBLIC_COLOR_THEME_DEFAULT: "dim"
|
||||
envFromSecret:
|
||||
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
|
||||
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
|
||||
|
||||
@@ -57,6 +57,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
|
||||
- [Sentry error monitoring](ENVS.md#sentry-error-monitoring)
|
||||
- [OpenTelemetry](ENVS.md#opentelemetry)
|
||||
- [Swap button](ENVS.md#swap-button)
|
||||
- [Multichain balance button](ENVS.md#multichain-button)
|
||||
- [3rd party services configuration](ENVS.md#external-services-configuration)
|
||||
|
||||
|
||||
@@ -662,11 +663,11 @@ The feature enables the Validators page which provides detailed information abou
|
||||
|
||||
### OpenTelemetry
|
||||
|
||||
OpenTelemetry SDK for Node.js app could be enabled by passing `OTEL_SDK_ENABLED=true` variable. Configure the OpenTelemetry Protocol Exporter by using the generic environment variables described in the [OT docs](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options).
|
||||
OpenTelemetry SDK for Node.js app could be enabled by passing `OTEL_SDK_ENABLED=true` variable. Configure the OpenTelemetry Protocol Exporter by using the generic environment variables described in the [OT docs](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options). Note that this Next.js feature is currently experimental. The Docker image should be built with the `NEXT_OPEN_TELEMETRY_ENABLED=true` argument to enable it.
|
||||
|
||||
| Variable | Type| Description | Compulsoriness | Default value | Example value |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| OTEL_SDK_ENABLED | `boolean` | Flag to enable the feature | Required | `false` | `true` |
|
||||
| OTEL_SDK_ENABLED | `boolean` | Run-time flag to enable the feature | Required | `false` | `true` |
|
||||
|
||||
|
||||
|
||||
@@ -680,6 +681,27 @@ If the feature is enabled, a Swap button will be displayed at the top of the exp
|
||||
|
||||
|
||||
|
||||
### Multichain balance button
|
||||
|
||||
If the feature is enabled, a Multichain balance button will be displayed on the address page, which will take you to the portfolio application in the marketplace or to an external site.
|
||||
|
||||
| Variable | Type| Description | Compulsoriness | Default value | Example value |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG | `{ name: string; url_template: string; dapp_id?: string; logo?: string }` | Multichain portfolio application config See [below](#multichain-button-configuration-properties) | - | - | `{ name: 'zerion', url_template: 'https://app.zerion.io/{address}/overview', logo: 'https://example.com/icon.svg'` |
|
||||
|
||||
|
||||
|
||||
#### Multichain button configuration properties
|
||||
|
||||
| Variable | Type| Description | Compulsoriness | Default value | Example value |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| name | `string` | Multichain portfolio application name | Required | - | `zerion` |
|
||||
| url_template | `string` | Url template to the portfolio. Should be a template with `{address}` variable | Required | - | `https://app.zerion.io/{address}/overview` |
|
||||
| dapp_id | `string` | Set for open a Blockscout dapp page with the portfolio instead of opening external app page | - | - | `zerion` |
|
||||
| logo | `string` | Multichain portfolio application logo (.svg) url | - | - | `https://example.com/icon.svg` |
|
||||
|
||||
|
||||
|
||||
## External services configuration
|
||||
|
||||
### Google ReCaptcha
|
||||
|
||||
|
Before Width: | Height: | Size: 752 B After Width: | Height: | Size: 752 B |
@@ -46,9 +46,7 @@ const sdk = new NodeSDK({
|
||||
url.pathname.startsWith('/_next/static/') ||
|
||||
url.pathname.startsWith('/_next/data/') ||
|
||||
url.pathname.startsWith('/assets/') ||
|
||||
url.pathname.startsWith('/static/') ||
|
||||
url.pathname.startsWith('/favicon/') ||
|
||||
url.pathname.startsWith('/envs.js')
|
||||
url.pathname.startsWith('/static/')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export default function parseMetaPayload(meta: AddressMetadataTag['meta']): Addr
|
||||
const stringFields: Array<keyof MetaParsed> = [
|
||||
'textColor',
|
||||
'bgColor',
|
||||
'tagIcon',
|
||||
'tagUrl',
|
||||
'tooltipIcon',
|
||||
'tooltipTitle',
|
||||
@@ -25,6 +26,7 @@ export default function parseMetaPayload(meta: AddressMetadataTag['meta']): Addr
|
||||
'appMarketplaceURL',
|
||||
'appLogoURL',
|
||||
'appActionButtonText',
|
||||
'warpcastHandle',
|
||||
];
|
||||
|
||||
for (const stringField of stringFields) {
|
||||
|
||||
@@ -1,47 +1,35 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import type { AddressMetadataInfo } from 'types/api/addressMetadata';
|
||||
import type { AddressMetadataInfoFormatted, AddressMetadataTagFormatted } from 'types/client/addressMetadata';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useApiFetch from 'lib/api/useApiFetch';
|
||||
import { getResourceKey } from 'lib/api/useApiQuery';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
|
||||
import parseMetaPayload from './parseMetaPayload';
|
||||
|
||||
export default function useAddressMetadataInfoQuery(addresses: Array<string>) {
|
||||
|
||||
const apiFetch = useApiFetch();
|
||||
|
||||
const queryParams = {
|
||||
addresses,
|
||||
chainId: config.chain.id,
|
||||
tagsLimit: '20',
|
||||
};
|
||||
const resource = 'address_metadata_info';
|
||||
|
||||
// TODO @tom2drum: Improve the typing here
|
||||
// since we are formatting the API data in the select function here
|
||||
// we cannot use the useApiQuery hook because of its current typing
|
||||
// enhance useApiQuery so it can accept an API data and the formatted data types
|
||||
return useQuery<AddressMetadataInfo, unknown, AddressMetadataInfoFormatted>({
|
||||
queryKey: getResourceKey(resource, { queryParams }),
|
||||
queryFn: async() => {
|
||||
return apiFetch(resource, { queryParams }) as Promise<AddressMetadataInfo>;
|
||||
return useApiQuery<typeof resource, unknown, AddressMetadataInfoFormatted>(resource, {
|
||||
queryParams: {
|
||||
addresses,
|
||||
chainId: config.chain.id,
|
||||
tagsLimit: '20',
|
||||
},
|
||||
enabled: addresses.length > 0 && config.features.addressMetadata.isEnabled,
|
||||
select: (data) => {
|
||||
const addresses = Object.entries(data.addresses)
|
||||
.map(([ address, { tags, reputation } ]) => {
|
||||
const formattedTags: Array<AddressMetadataTagFormatted> = tags.map((tag) => ({ ...tag, meta: parseMetaPayload(tag.meta) }));
|
||||
return [ address.toLowerCase(), { tags: formattedTags, reputation } ] as const;
|
||||
})
|
||||
.reduce((result, item) => {
|
||||
result[item[0]] = item[1];
|
||||
return result;
|
||||
}, {} as AddressMetadataInfoFormatted['addresses']);
|
||||
queryOptions: {
|
||||
enabled: addresses.length > 0 && config.features.addressMetadata.isEnabled,
|
||||
select: (data) => {
|
||||
const addresses = Object.entries(data.addresses)
|
||||
.map(([ address, { tags, reputation } ]) => {
|
||||
const formattedTags: Array<AddressMetadataTagFormatted> = tags.map((tag) => ({ ...tag, meta: parseMetaPayload(tag.meta) }));
|
||||
return [ address.toLowerCase(), { tags: formattedTags, reputation } ] as const;
|
||||
})
|
||||
.reduce((result, item) => {
|
||||
result[item[0]] = item[1];
|
||||
return result;
|
||||
}, {} as AddressMetadataInfoFormatted['addresses']);
|
||||
|
||||
return { addresses };
|
||||
return { addresses };
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import type { ResourceError, ResourceName, ResourcePayload } from './resources';
|
||||
import type { Params as ApiFetchParams } from './useApiFetch';
|
||||
import useApiFetch from './useApiFetch';
|
||||
|
||||
export interface Params<R extends ResourceName, E = unknown> extends ApiFetchParams<R> {
|
||||
queryOptions?: Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, ResourcePayload<R>>, 'queryKey' | 'queryFn'>;
|
||||
export interface Params<R extends ResourceName, E = unknown, D = ResourcePayload<R>> extends ApiFetchParams<R> {
|
||||
queryOptions?: Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, D>, 'queryKey' | 'queryFn'>;
|
||||
}
|
||||
|
||||
export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) {
|
||||
@@ -17,13 +17,13 @@ export function getResourceKey<R extends ResourceName>(resource: R, { pathParams
|
||||
return [ resource ];
|
||||
}
|
||||
|
||||
export default function useApiQuery<R extends ResourceName, E = unknown>(
|
||||
export default function useApiQuery<R extends ResourceName, E = unknown, D = ResourcePayload<R>>(
|
||||
resource: R,
|
||||
{ queryOptions, pathParams, queryParams, fetchParams }: Params<R, E> = {},
|
||||
{ queryOptions, pathParams, queryParams, fetchParams }: Params<R, E, D> = {},
|
||||
) {
|
||||
const apiFetch = useApiFetch();
|
||||
|
||||
return useQuery<ResourcePayload<R>, ResourceError<E>, ResourcePayload<R>>({
|
||||
return useQuery<ResourcePayload<R>, ResourceError<E>, D>({
|
||||
// eslint-disable-next-line @tanstack/query/exhaustive-deps
|
||||
queryKey: getResourceKey(resource, { pathParams, queryParams }),
|
||||
queryFn: async() => {
|
||||
|
||||
@@ -128,7 +128,7 @@ Type extends EventTypes.FILTERS ? {
|
||||
'Filter name': string;
|
||||
} :
|
||||
Type extends EventTypes.BUTTON_CLICK ? {
|
||||
'Content': 'Swap button';
|
||||
'Content': 'Swap button' | 'Multichain';
|
||||
'Source': string;
|
||||
} :
|
||||
Type extends EventTypes.PROMO_BANNER ? {
|
||||
|
||||
@@ -75,7 +75,7 @@ export const token: Address = {
|
||||
coin_balance: '1',
|
||||
creation_tx_hash: '0xc38cf7377bf72d6436f63c37b01b24d032101f20ec1849286dc703c712f10c98',
|
||||
creator_address_hash: '0x34A9c688512ebdB575e82C50c9803F6ba2916E72',
|
||||
exchange_rate: null,
|
||||
exchange_rate: '0.04311',
|
||||
implementation_address: null,
|
||||
has_decompiled_code: false,
|
||||
has_logs: false,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AddressCollectionsResponse, AddressNFTsResponse, AddressTokenBalance } from 'types/api/address';
|
||||
import type { AddressCollectionsResponse, AddressNFTsResponse, AddressTokenBalance, AddressTokensResponse } from 'types/api/address';
|
||||
|
||||
import * as tokens from 'mocks/tokens/tokenInfo';
|
||||
import * as tokenInstance from 'mocks/tokens/tokenInstance';
|
||||
@@ -119,35 +119,39 @@ export const erc404b: AddressTokenBalance = {
|
||||
token_id: null,
|
||||
};
|
||||
|
||||
export const erc20List = {
|
||||
export const erc20List: AddressTokensResponse = {
|
||||
items: [
|
||||
erc20a,
|
||||
erc20b,
|
||||
erc20c,
|
||||
],
|
||||
next_page_params: null,
|
||||
};
|
||||
|
||||
export const erc721List = {
|
||||
export const erc721List: AddressTokensResponse = {
|
||||
items: [
|
||||
erc721a,
|
||||
erc721b,
|
||||
erc721c,
|
||||
],
|
||||
next_page_params: null,
|
||||
};
|
||||
|
||||
export const erc1155List = {
|
||||
export const erc1155List: AddressTokensResponse = {
|
||||
items: [
|
||||
erc1155withoutName,
|
||||
erc1155a,
|
||||
erc1155b,
|
||||
],
|
||||
next_page_params: null,
|
||||
};
|
||||
|
||||
export const erc404List = {
|
||||
export const erc404List: AddressTokensResponse = {
|
||||
items: [
|
||||
erc404a,
|
||||
erc404b,
|
||||
],
|
||||
next_page_params: null,
|
||||
};
|
||||
|
||||
export const nfts: AddressNFTsResponse = {
|
||||
|
||||
@@ -51,6 +51,11 @@ export const verified: SmartContract = {
|
||||
minimal_proxy_address_hash: null,
|
||||
};
|
||||
|
||||
export const certified: SmartContract = {
|
||||
...verified,
|
||||
certified: true,
|
||||
};
|
||||
|
||||
export const withMultiplePaths: SmartContract = {
|
||||
...verified,
|
||||
file_path: './simple_storage.sol',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
export const solidityscanReportAverage = {
|
||||
import type { SolidityscanReport } from 'types/api/contract';
|
||||
|
||||
export const solidityscanReportAverage: SolidityscanReport = {
|
||||
scan_report: {
|
||||
contractname: 'foo',
|
||||
scan_status: 'scan_done',
|
||||
scan_summary: {
|
||||
issue_severity_distribution: {
|
||||
@@ -20,8 +23,9 @@ export const solidityscanReportAverage = {
|
||||
},
|
||||
};
|
||||
|
||||
export const solidityscanReportGreat = {
|
||||
export const solidityscanReportGreat: SolidityscanReport = {
|
||||
scan_report: {
|
||||
contractname: 'foo',
|
||||
scan_status: 'scan_done',
|
||||
scan_summary: {
|
||||
issue_severity_distribution: {
|
||||
@@ -42,8 +46,9 @@ export const solidityscanReportGreat = {
|
||||
},
|
||||
};
|
||||
|
||||
export const solidityscanReportLow = {
|
||||
export const solidityscanReportLow: SolidityscanReport = {
|
||||
scan_report: {
|
||||
contractname: 'foo',
|
||||
scan_status: 'scan_done',
|
||||
scan_summary: {
|
||||
issue_severity_distribution: {
|
||||
|
||||
@@ -35,6 +35,7 @@ export const contract2: VerifiedContract = {
|
||||
watchlist_names: [],
|
||||
ens_domain_name: null,
|
||||
},
|
||||
certified: true,
|
||||
coin_balance: '9078234570352343999',
|
||||
compiler_version: 'v0.3.1+commit.0463ea4c',
|
||||
has_constructor_args: true,
|
||||
|
||||
@@ -96,6 +96,15 @@ export const contract1: SearchResultAddressOrContract = {
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
};
|
||||
|
||||
export const contract2: SearchResultAddressOrContract = {
|
||||
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
name: 'Super utko',
|
||||
type: 'contract' as const,
|
||||
is_smart_contract_verified: true,
|
||||
certified: true,
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
};
|
||||
|
||||
export const label1: SearchResultLabel = {
|
||||
address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
name: 'utko',
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import _mapValues from 'lodash/mapValues';
|
||||
|
||||
export const base = {
|
||||
import type { HomeStats } from 'types/api/stats';
|
||||
|
||||
export const base: HomeStats = {
|
||||
average_block_time: 6212.0,
|
||||
coin_price: '0.00199678',
|
||||
coin_price_change_percentage: -7.42,
|
||||
coin_image: 'http://localhost:3100/utia.jpg',
|
||||
gas_prices: {
|
||||
average: {
|
||||
fiat_price: '1.39',
|
||||
@@ -41,35 +44,42 @@ export const base = {
|
||||
tvl: '1767425.102766552',
|
||||
};
|
||||
|
||||
export const withBtcLocked = {
|
||||
export const withBtcLocked: HomeStats = {
|
||||
...base,
|
||||
rootstock_locked_btc: '3337493406696977561374',
|
||||
};
|
||||
|
||||
export const withoutFiatPrices = {
|
||||
export const withoutFiatPrices: HomeStats = {
|
||||
...base,
|
||||
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null),
|
||||
};
|
||||
|
||||
export const withoutGweiPrices = {
|
||||
export const withoutGweiPrices: HomeStats = {
|
||||
...base,
|
||||
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null),
|
||||
};
|
||||
|
||||
export const withoutBothPrices = {
|
||||
export const withoutBothPrices: HomeStats = {
|
||||
...base,
|
||||
gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null),
|
||||
};
|
||||
|
||||
export const withSecondaryCoin = {
|
||||
export const withSecondaryCoin: HomeStats = {
|
||||
...base,
|
||||
secondary_coin_price: '3.398',
|
||||
};
|
||||
|
||||
export const noChartData = {
|
||||
export const noChartData: HomeStats = {
|
||||
...base,
|
||||
transactions_today: null,
|
||||
coin_price: null,
|
||||
market_cap: null,
|
||||
tvl: null,
|
||||
};
|
||||
|
||||
export const indexingStatus = {
|
||||
finished_indexing_blocks: false,
|
||||
indexed_blocks_ratio: '0.1',
|
||||
finished_indexing: true,
|
||||
indexed_internal_transactions_ratio: '1',
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ export const tokenInfo: TokenInfo = {
|
||||
holders: '46554',
|
||||
name: 'ARIANEE',
|
||||
symbol: 'ARIA',
|
||||
type: 'ERC-20',
|
||||
type: 'ERC-20' as const,
|
||||
total_supply: '1235',
|
||||
icon_url: 'http://localhost:3000/token-icon.png',
|
||||
};
|
||||
@@ -27,7 +27,7 @@ export const tokenInfoERC20a: TokenInfo<'ERC-20'> = {
|
||||
name: 'hyfi.token',
|
||||
symbol: 'HyFi',
|
||||
total_supply: '369000000000000000000000000',
|
||||
type: 'ERC-20',
|
||||
type: 'ERC-20' as const,
|
||||
icon_url: 'http://localhost:3000/token-icon.png',
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ export const tokenInfoERC20b: TokenInfo<'ERC-20'> = {
|
||||
name: 'USD Coin',
|
||||
symbol: 'USDC',
|
||||
total_supply: '900000000000000000000000000',
|
||||
type: 'ERC-20',
|
||||
type: 'ERC-20' as const,
|
||||
icon_url: null,
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ export const tokenInfoERC20c: TokenInfo<'ERC-20'> = {
|
||||
name: 'Ethereum',
|
||||
symbol: 'ETH',
|
||||
total_supply: '1000000000000000000000000',
|
||||
type: 'ERC-20',
|
||||
type: 'ERC-20' as const,
|
||||
icon_url: null,
|
||||
};
|
||||
|
||||
@@ -66,7 +66,7 @@ export const tokenInfoERC20d: TokenInfo<'ERC-20'> = {
|
||||
name: 'Zeta',
|
||||
symbol: 'ZETA',
|
||||
total_supply: '2100000000000000000000000000',
|
||||
type: 'ERC-20',
|
||||
type: 'ERC-20' as const,
|
||||
icon_url: null,
|
||||
};
|
||||
|
||||
@@ -79,7 +79,7 @@ export const tokenInfoERC20LongSymbol: TokenInfo<'ERC-20'> = {
|
||||
name: 'Zeta',
|
||||
symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY',
|
||||
total_supply: '2100000000000000000000000000',
|
||||
type: 'ERC-20',
|
||||
type: 'ERC-20' as const,
|
||||
icon_url: null,
|
||||
};
|
||||
|
||||
@@ -92,7 +92,7 @@ export const tokenInfoERC721a: TokenInfo<'ERC-721'> = {
|
||||
name: 'HyFi Athena',
|
||||
symbol: 'HYFI_ATHENA',
|
||||
total_supply: '105',
|
||||
type: 'ERC-721',
|
||||
type: 'ERC-721' as const,
|
||||
icon_url: null,
|
||||
};
|
||||
|
||||
@@ -105,7 +105,7 @@ export const tokenInfoERC721b: TokenInfo<'ERC-721'> = {
|
||||
name: 'World Of Women Galaxy',
|
||||
symbol: 'WOWG',
|
||||
total_supply: null,
|
||||
type: 'ERC-721',
|
||||
type: 'ERC-721' as const,
|
||||
icon_url: null,
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ export const tokenInfoERC721c: TokenInfo<'ERC-721'> = {
|
||||
name: 'Puma',
|
||||
symbol: 'PUMA',
|
||||
total_supply: null,
|
||||
type: 'ERC-721',
|
||||
type: 'ERC-721' as const,
|
||||
icon_url: null,
|
||||
};
|
||||
|
||||
@@ -131,7 +131,7 @@ export const tokenInfoERC721LongSymbol: TokenInfo<'ERC-721'> = {
|
||||
name: 'Puma',
|
||||
symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY',
|
||||
total_supply: null,
|
||||
type: 'ERC-721',
|
||||
type: 'ERC-721' as const,
|
||||
icon_url: null,
|
||||
};
|
||||
|
||||
@@ -144,7 +144,7 @@ export const tokenInfoERC1155a: TokenInfo<'ERC-1155'> = {
|
||||
name: 'HyFi Membership',
|
||||
symbol: 'HYFI_MEMBERSHIP',
|
||||
total_supply: '482',
|
||||
type: 'ERC-1155',
|
||||
type: 'ERC-1155' as const,
|
||||
icon_url: null,
|
||||
};
|
||||
|
||||
@@ -157,7 +157,7 @@ export const tokenInfoERC1155b: TokenInfo<'ERC-1155'> = {
|
||||
name: 'WinkyVerse Collections',
|
||||
symbol: 'WVC',
|
||||
total_supply: '4943',
|
||||
type: 'ERC-1155',
|
||||
type: 'ERC-1155' as const,
|
||||
icon_url: null,
|
||||
};
|
||||
|
||||
@@ -170,7 +170,7 @@ export const tokenInfoERC1155WithoutName: TokenInfo<'ERC-1155'> = {
|
||||
name: null,
|
||||
symbol: null,
|
||||
total_supply: '482',
|
||||
type: 'ERC-1155',
|
||||
type: 'ERC-1155' as const,
|
||||
icon_url: null,
|
||||
};
|
||||
|
||||
@@ -184,7 +184,7 @@ export const tokenInfoERC404: TokenInfo<'ERC-404'> = {
|
||||
name: 'OMNI404',
|
||||
symbol: 'O404',
|
||||
total_supply: '6482275000000000000',
|
||||
type: 'ERC-404',
|
||||
type: 'ERC-404' as const,
|
||||
};
|
||||
|
||||
export const bridgedTokenA: TokenInfo<'ERC-20'> = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TxStateChange } from 'types/api/txStateChanges';
|
||||
import type { TxStateChange, TxStateChanges } from 'types/api/txStateChanges';
|
||||
|
||||
export const mintToken: TxStateChange = {
|
||||
address: {
|
||||
@@ -35,7 +35,7 @@ export const mintToken: TxStateChange = {
|
||||
type: 'ERC-721',
|
||||
icon_url: null,
|
||||
},
|
||||
type: 'token',
|
||||
type: 'token' as const,
|
||||
};
|
||||
|
||||
export const receiveMintedToken: TxStateChange = {
|
||||
@@ -73,7 +73,7 @@ export const receiveMintedToken: TxStateChange = {
|
||||
type: 'ERC-721',
|
||||
icon_url: null,
|
||||
},
|
||||
type: 'token',
|
||||
type: 'token' as const,
|
||||
};
|
||||
|
||||
export const transfer1155Token: TxStateChange = {
|
||||
@@ -105,7 +105,7 @@ export const transfer1155Token: TxStateChange = {
|
||||
type: 'ERC-1155',
|
||||
},
|
||||
token_id: '1',
|
||||
type: 'token',
|
||||
type: 'token' as const,
|
||||
};
|
||||
|
||||
export const receiveCoin: TxStateChange = {
|
||||
@@ -125,7 +125,7 @@ export const receiveCoin: TxStateChange = {
|
||||
change: '29726406604060',
|
||||
is_miner: true,
|
||||
token: null,
|
||||
type: 'coin',
|
||||
type: 'coin' as const,
|
||||
};
|
||||
|
||||
export const sendCoin: TxStateChange = {
|
||||
@@ -145,12 +145,13 @@ export const sendCoin: TxStateChange = {
|
||||
change: '-3844844822720562',
|
||||
is_miner: false,
|
||||
token: null,
|
||||
type: 'coin',
|
||||
type: 'coin' as const,
|
||||
};
|
||||
|
||||
export const sendERC20Token = {
|
||||
address: {
|
||||
hash: '0x7f6479df95Aa3036a3BE02DB6300ea201ABd9981',
|
||||
ens_domain_name: null,
|
||||
implementation_name: null,
|
||||
is_contract: false,
|
||||
is_verified: false,
|
||||
@@ -173,13 +174,13 @@ export const sendERC20Token = {
|
||||
name: 'Tether USD',
|
||||
symbol: 'USDT',
|
||||
total_supply: '39030615894320966',
|
||||
type: 'ERC-20',
|
||||
type: 'ERC-20' as const,
|
||||
token_id: null,
|
||||
},
|
||||
type: 'token',
|
||||
type: 'token' as const,
|
||||
};
|
||||
|
||||
export const baseResponse = {
|
||||
export const baseResponse: TxStateChanges = {
|
||||
items: [
|
||||
mintToken,
|
||||
receiveMintedToken,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-len */
|
||||
import type { UserOp } from 'types/api/userOps';
|
||||
|
||||
export const userOpData: UserOp = {
|
||||
@@ -47,7 +48,6 @@ export const userOpData: UserOp = {
|
||||
max_fee_per_gas: '1575000898',
|
||||
max_priority_fee_per_gas: '1575000898',
|
||||
nonce: '79',
|
||||
// eslint-disable-next-line max-len
|
||||
paymaster_and_data: '0x7cea357b5ac0639f89f9e378a1f03aa5005c0a250000000000000000000000000000000000000000000000000000000065b3a8800000000000000000000000000000000000000000000000000000000065aa6e0028fa4c57ac1141bc9ecd8c9243f618ade8ea1db10ab6c1d1798a222a824764ff2269a72ae7a3680fa8b03a80d8a00cdc710eaf37afdcc55f8c9c4defa3fdf2471b',
|
||||
pre_verification_gas: '48396',
|
||||
sender: '0xF0C14FF4404b188fAA39a3507B388998c10FE284',
|
||||
@@ -64,8 +64,34 @@ export const userOpData: UserOp = {
|
||||
is_verified: null,
|
||||
name: null,
|
||||
},
|
||||
// eslint-disable-next-line max-len
|
||||
call_data: '0xb61d27f600000000000000000000000059f6aa952df7f048fd076e33e0ea8bb552d5ffd8000000000000000000000000000000000000000000000000003f3d017500800000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000',
|
||||
execute_call_data: '0x3cf80e6c',
|
||||
decoded_call_data: {
|
||||
method_call: 'execute(address dest, uint256 value, bytes func)',
|
||||
method_id: 'b61d27f6',
|
||||
parameters: [
|
||||
{
|
||||
name: 'dest',
|
||||
type: 'address',
|
||||
value: '0xb0ccffd05f5a87c4c3ceffaa217900422a249915',
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: 'uint256',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
name: 'func',
|
||||
type: 'bytes',
|
||||
value: '0x3cf80e6c',
|
||||
},
|
||||
],
|
||||
},
|
||||
decoded_execute_call_data: {
|
||||
method_call: 'advanceEpoch()',
|
||||
method_id: '3cf80e6c',
|
||||
parameters: [],
|
||||
},
|
||||
paymaster: {
|
||||
ens_domain_name: null,
|
||||
hash: '0x7ceA357B5AC0639F89F9e378a1f03Aa5005C0a25',
|
||||
|
||||
@@ -46,7 +46,7 @@ const moduleExports = {
|
||||
output: 'standalone',
|
||||
productionBrowserSourceMaps: true,
|
||||
experimental: {
|
||||
instrumentationHook: true,
|
||||
instrumentationHook: process.env.NEXT_OPEN_TELEMETRY_ENABLED === 'true',
|
||||
turbo: {
|
||||
rules: {
|
||||
'*.svg': {
|
||||
|
||||
@@ -56,6 +56,7 @@ export function app(): CspDev.DirectiveDescriptor {
|
||||
getFeaturePayload(config.features.verifiedTokens)?.api.endpoint,
|
||||
getFeaturePayload(config.features.addressVerification)?.api.endpoint,
|
||||
getFeaturePayload(config.features.nameService)?.api.endpoint,
|
||||
getFeaturePayload(config.features.addressMetadata)?.api.endpoint,
|
||||
marketplaceFeaturePayload && 'api' in marketplaceFeaturePayload ? marketplaceFeaturePayload.api.endpoint : '',
|
||||
|
||||
// chain RPC server
|
||||
@@ -132,6 +133,10 @@ export function app(): CspDev.DirectiveDescriptor {
|
||||
'*',
|
||||
],
|
||||
|
||||
'frame-ancestors': [
|
||||
KEY_WORDS.SELF,
|
||||
],
|
||||
|
||||
...((() => {
|
||||
if (!config.features.sentry.isEnabled) {
|
||||
return {};
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
"@tanstack/react-query-devtools": "^5.4.3",
|
||||
"@types/papaparse": "^5.3.5",
|
||||
"@types/react-scroll": "^1.8.4",
|
||||
"@web3modal/wagmi": "4.1.3",
|
||||
"@web3modal/wagmi": "4.2.1",
|
||||
"bignumber.js": "^9.1.0",
|
||||
"blo": "^1.1.1",
|
||||
"chakra-react-select": "^4.4.3",
|
||||
@@ -103,8 +103,8 @@
|
||||
"react-scroll": "^1.8.7",
|
||||
"swagger-ui-react": "^5.9.0",
|
||||
"use-font-face-observer": "^1.2.1",
|
||||
"viem": "2.9.6",
|
||||
"wagmi": "2.5.16",
|
||||
"viem": "2.10.9",
|
||||
"wagmi": "2.9.2",
|
||||
"xss": "^1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -44,14 +44,14 @@ class MyDocument extends Document {
|
||||
/>
|
||||
|
||||
{ /* eslint-disable-next-line @next/next/no-sync-scripts */ }
|
||||
<script src="/envs.js"/>
|
||||
<script src="/assets/envs.js"/>
|
||||
|
||||
{ /* FAVICON */ }
|
||||
<link rel="icon" href="/favicon/favicon.ico" sizes="48x48"/>
|
||||
<link rel="icon" sizes="32x32" type="image/png" href="/favicon/favicon-32x32.png"/>
|
||||
<link rel="icon" sizes="16x16" type="image/png"href="/favicon/favicon-16x16.png"/>
|
||||
<link rel="apple-touch-icon" href="/favicon/apple-touch-icon-180x180.png"/>
|
||||
<link rel="mask-icon" href="/favicon/safari-pinned-tab.svg"/>
|
||||
<link rel="icon" href="/assets/favicon/favicon.ico" sizes="48x48"/>
|
||||
<link rel="icon" sizes="32x32" type="image/png" href="/assets/favicon/favicon-32x32.png"/>
|
||||
<link rel="icon" sizes="16x16" type="image/png"href="/assets/favicon/favicon-16x16.png"/>
|
||||
<link rel="apple-touch-icon" href="/assets/favicon/apple-touch-icon-180x180.png"/>
|
||||
<link rel="mask-icon" href="/assets/favicon/safari-pinned-tab.svg"/>
|
||||
|
||||
<link rel="preload" as="image" href={ svgSprite.href }/>
|
||||
</Head>
|
||||
|
||||
@@ -12,9 +12,10 @@ import type { Props as PageProps } from 'nextjs/getServerSideProps';
|
||||
import config from 'configs/app';
|
||||
import { AppContextProvider } from 'lib/contexts/app';
|
||||
import { SocketProvider } from 'lib/socket/context';
|
||||
import * as app from 'playwright/utils/app';
|
||||
import theme from 'theme';
|
||||
|
||||
import { port as socketPort } from './utils/socket';
|
||||
|
||||
export type Props = {
|
||||
children: React.ReactNode;
|
||||
withSocket?: boolean;
|
||||
@@ -74,7 +75,7 @@ const TestApp = ({ children, withSocket, withWalletClient = true, appContext = d
|
||||
return (
|
||||
<ChakraProvider theme={ theme }>
|
||||
<QueryClientProvider client={ queryClient }>
|
||||
<SocketProvider url={ withSocket ? `ws://${ config.app.host }:${ app.socketPort }` : undefined }>
|
||||
<SocketProvider url={ withSocket ? `ws://${ config.app.host }:${ socketPort }` : undefined }>
|
||||
<AppContextProvider { ...appContext }>
|
||||
<GrowthBookProvider>
|
||||
<WalletClientProvider withWalletClient={ withWalletClient }>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { test } from '@playwright/experimental-ct-react';
|
||||
|
||||
import createContextWithStorage from './createContextWithStorage';
|
||||
|
||||
interface Env {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated please use mockEnvs fixture
|
||||
*
|
||||
* @export
|
||||
* @param {Array<Env>} envs
|
||||
* @return {*} {Parameters<typeof test.extend>[0]['context']}
|
||||
*/
|
||||
export default function contextWithEnvsFixture(envs: Array<Env>): Parameters<typeof test.extend>[0]['context'] {
|
||||
return async({ browser }, use) => {
|
||||
const context = await createContextWithStorage(browser, envs);
|
||||
|
||||
await use(context);
|
||||
await context.close();
|
||||
};
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { test } from '@playwright/experimental-ct-react';
|
||||
|
||||
import createContextWithStorage from './createContextWithStorage';
|
||||
|
||||
interface Feature {
|
||||
id: string;
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated please use mockFeatures fixture
|
||||
*
|
||||
* @export
|
||||
* @param {Array<Feature>} envs
|
||||
* @return {*} {Parameters<typeof test.extend>[0]['context']}
|
||||
*/
|
||||
export default function contextWithFeaturesFixture(envs: Array<Feature>): Parameters<typeof test.extend>[0]['context'] {
|
||||
return async({ browser }, use) => {
|
||||
const storageItems = envs.map(({ id, value }) => ({ name: `pw_feature:${ id }`, value: JSON.stringify(value) }));
|
||||
const context = await createContextWithStorage(browser, storageItems);
|
||||
|
||||
await use(context);
|
||||
await context.close();
|
||||
};
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { Browser } from '@playwright/test';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
/**
|
||||
* @deprecated please use mockEnvs or mockFeatures fixture
|
||||
*
|
||||
* @export
|
||||
* @param {Browser} browser
|
||||
* @param {Array<{ name: string; value: string }>} localStorage
|
||||
* @return {*}
|
||||
*/
|
||||
export default async function createContextWithEnvs(browser: Browser, localStorage: Array<{ name: string; value: string }>) {
|
||||
return browser.newContext({
|
||||
storageState: {
|
||||
origins: [
|
||||
{ origin: config.app.baseUrl, localStorage },
|
||||
],
|
||||
cookies: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import type { ResourceName, ResourcePayload } from 'lib/api/resources';
|
||||
interface Options<R extends ResourceName> {
|
||||
pathParams?: Parameters<typeof buildUrl<R>>[1];
|
||||
queryParams?: Parameters<typeof buildUrl<R>>[2];
|
||||
times?: number;
|
||||
}
|
||||
|
||||
export type MockApiResponseFixture = <R extends ResourceName>(resourceName: R, responseMock: ResourcePayload<R>, options?: Options<R>) => Promise<string>;
|
||||
@@ -17,7 +18,7 @@ const fixture: TestFixture<MockApiResponseFixture, { page: Page }> = async({ pag
|
||||
await page.route(apiUrl, (route) => route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(responseMock),
|
||||
}));
|
||||
}), { times: options?.times });
|
||||
|
||||
return apiUrl;
|
||||
});
|
||||
|
||||
@@ -66,4 +66,10 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
|
||||
navigationHighlightedRoutes: [
|
||||
[ 'NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES', '["/blocks", "/apps"]' ],
|
||||
],
|
||||
dataAvailability: [
|
||||
[ 'NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED', 'true' ],
|
||||
],
|
||||
nameService: [
|
||||
[ 'NEXT_PUBLIC_NAME_SERVICE_API_HOST', 'https://localhost:3101' ],
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import type { MountOptions } from '@playwright/experimental-ct-react';
|
||||
import type { Locator, TestFixture } from '@playwright/test';
|
||||
import type router from 'next/router';
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { SmartContractVerificationResponse } from 'types/api/contract';
|
||||
import type { TokenTransfer } from 'types/api/tokenTransfer';
|
||||
import type { Transaction } from 'types/api/transaction';
|
||||
|
||||
import * as app from 'playwright/utils/app';
|
||||
import { port as socketPort } from '../utils/socket';
|
||||
|
||||
export type CreateSocketFixture = () => Promise<WebSocket>;
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface SocketServerFixture {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const createSocket: TestFixture<CreateSocketFixture, { page: Page}> = async({ page }, use) => {
|
||||
const socketServer = new WebSocketServer({ port: app.socketPort });
|
||||
const socketServer = new WebSocketServer({ port: socketPort });
|
||||
|
||||
const connectionPromise = new Promise<WebSocket>((resolve) => {
|
||||
socketServer.on('connection', (socket: WebSocket) => {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const socketPort = 3200;
|
||||
@@ -1,20 +0,0 @@
|
||||
import { compile } from 'path-to-regexp';
|
||||
|
||||
import config from 'configs/app';
|
||||
import type { ResourceName, ResourcePathParams } from 'lib/api/resources';
|
||||
import { RESOURCES } from 'lib/api/resources';
|
||||
|
||||
/**
|
||||
* @deprecated please use fixture mockApiResponse from playwright/lib.tsx for rendering test suite
|
||||
*
|
||||
* @export
|
||||
* @template R
|
||||
* @param {R} resourceName
|
||||
* @param {ResourcePathParams<R>} [pathParams]
|
||||
* @return {*} string
|
||||
*/
|
||||
export default function buildApiUrl<R extends ResourceName>(resourceName: R, pathParams?: ResourcePathParams<R>) {
|
||||
const resource = RESOURCES[resourceName];
|
||||
const origin = 'endpoint' in resource && resource.endpoint ? resource.endpoint + (resource.basePath ?? '') : config.api.endpoint;
|
||||
return origin + compile(resource.path)(pathParams);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable max-len */
|
||||
import { devices } from '@playwright/test';
|
||||
|
||||
export const viewport = {
|
||||
@@ -0,0 +1 @@
|
||||
export const port = 3200;
|
||||
@@ -0,0 +1,4 @@
|
||||
**Directories**
|
||||
- `/icons` - Folder for SVG-sprite assets, generated at build time.
|
||||
- `/static` - Folder for static assets that are consistent between app re-runs but may differ from one build version to another.
|
||||
- `/assets` - Folder for dynamically generated assets during the app start, such as the favicon bundle, ENV variables file, and external app configurations.
|
||||
|
Before Width: | Height: | Size: 736 B After Width: | Height: | Size: 736 B |
|
Before Width: | Height: | Size: 833 B After Width: | Height: | Size: 833 B |
|
Before Width: | Height: | Size: 791 B After Width: | Height: | Size: 791 B |
|
Before Width: | Height: | Size: 515 B After Width: | Height: | Size: 515 B |
|
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 561 B |
|
Before Width: | Height: | Size: 791 B After Width: | Height: | Size: 791 B |
|
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 383 B |
|
Before Width: | Height: | Size: 439 B After Width: | Height: | Size: 439 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180">
|
||||
<path d="M34.9 2.9c-2.9 2.9-2.9 3-2.9 15 0 6.8-.5 13.1-1.1 14.5-1.9 4-6.6 5.6-16.6 5.6C4.7 38 1.7 39.4.5 44.3.2 45.5.1 75.4.2 110.7l.3 64.2 2.3 2.3c2.1 2.1 3.3 2.3 14.6 2.6 14.8.4 17.9-.6 19.5-6.6.7-2.4 1.1-26.2 1.1-66.9V43.1l2.4-2.8c2.3-2.7 2.9-2.8 13.6-3.3 15-.7 15-.6 15-18.2v-13l-2.9-2.9C63.2 0 63.2 0 50.5 0S37.8 0 34.9 2.9zm76.9-1.4c-3.9 2.2-5.1 7.8-4.6 20.4.5 9.8.6 10.6 3.2 12.8 2.3 2 3.8 2.3 10.5 2.3 4.3 0 9.2.5 11 1.1 6.3 2.2 6.1.3 6.1 71.4v64.7l2.9 2.9c2.9 2.9 3 2.9 15.4 2.9 12 0 12.7-.1 15.6-2.6l3.1-2.6V42.9l-2.5-2.4c-2.2-2.2-3.2-2.5-10.5-2.5-8.1 0-14-1.6-15.6-4.1-.5-.8-1.1-7.6-1.4-15.1C144.3.3 144.6.6 127.2.2c-9.8-.1-13.3.1-15.4 1.3zM72.3 74.4l-2.8 2.4-.3 30.6-.3 30.5 3 3.3 2.9 3.3h13.1c12.9 0 13.1 0 15.8-2.8l2.8-2.7V76.8l-2.8-2.4C101 72.1 100.2 72 88 72s-13 .1-15.7 2.4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 881 B |
@@ -1,3 +0,0 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180">
|
||||
<path d="M34.9 2.9c-2.9 2.9-2.9 3-2.9 15 0 6.8-.5 13.1-1.1 14.5-1.9 4-6.6 5.6-16.6 5.6C4.7 38 1.7 39.4.5 44.3.2 45.5.1 75.4.2 110.7l.3 64.2 2.3 2.3c2.1 2.1 3.3 2.3 14.6 2.6 14.8.4 17.9-.6 19.5-6.6.7-2.4 1.1-26.2 1.1-66.9V43.1l2.4-2.8c2.3-2.7 2.9-2.8 13.6-3.3 15-.7 15-.6 15-18.2v-13l-2.9-2.9C63.2 0 63.2 0 50.5 0S37.8 0 34.9 2.9zM111.8 1.5c-3.9 2.2-5.1 7.8-4.6 20.4.5 9.8.6 10.6 3.2 12.8 2.3 2 3.8 2.3 10.5 2.3 4.3 0 9.2.5 11 1.1 6.3 2.2 6.1.3 6.1 71.4v64.7l2.9 2.9c2.9 2.9 3 2.9 15.4 2.9 12 0 12.7-.1 15.6-2.6l3.1-2.6V42.9l-2.5-2.4c-2.2-2.2-3.2-2.5-10.5-2.5-8.1 0-14-1.6-15.6-4.1-.5-.8-1.1-7.6-1.4-15.1C144.3.3 144.6.6 127.2.2c-9.8-.1-13.3.1-15.4 1.3zM72.3 74.4l-2.8 2.4-.3 30.6-.3 30.5 3 3.3 2.9 3.3h13.1c12.9 0 13.1 0 15.8-2.8l2.8-2.7V76.8l-2.8-2.4C101 72.1 100.2 72 88 72s-13 .1-15.7 2.4z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 882 B |
@@ -24,6 +24,7 @@
|
||||
| "brands/safe"
|
||||
| "brands/solidity_scan"
|
||||
| "burger"
|
||||
| "certified"
|
||||
| "check"
|
||||
| "clock-light"
|
||||
| "clock"
|
||||
@@ -147,7 +148,6 @@
|
||||
| "user_op_slim"
|
||||
| "user_op"
|
||||
| "validator"
|
||||
| "verified_token"
|
||||
| "verified"
|
||||
| "verify-contract"
|
||||
| "wallet"
|
||||
|
||||
@@ -21,6 +21,9 @@ export const USER_OP: UserOp = {
|
||||
sender: ADDRESS_HASH,
|
||||
nonce: '0x00b',
|
||||
call_data: '0x123',
|
||||
execute_call_data: null,
|
||||
decoded_call_data: null,
|
||||
decoded_execute_call_data: null,
|
||||
call_gas_limit: '71316',
|
||||
verification_gas_limit: '91551',
|
||||
pre_verification_gas: '53627',
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { AlertProps } from '@chakra-ui/react';
|
||||
import { Alert, AlertIcon, AlertTitle } from '@chakra-ui/react';
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import React from 'react';
|
||||
|
||||
import TestApp from 'playwright/TestApp';
|
||||
import { test, expect } from 'playwright/lib';
|
||||
|
||||
test.use({ viewport: { width: 400, height: 720 } });
|
||||
|
||||
@@ -29,16 +28,14 @@ const TEST_CASES: Array<AlertProps> = [
|
||||
TEST_CASES.forEach((props) => {
|
||||
const testName = Object.entries(props).map(([ key, value ]) => `${ key }=${ value }`).join(', ');
|
||||
|
||||
test(`${ testName } +@dark-mode`, async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<Alert { ...props }>
|
||||
<AlertIcon/>
|
||||
<AlertTitle>
|
||||
test(`${ testName } +@dark-mode`, async({ render }) => {
|
||||
const component = await render(
|
||||
<Alert { ...props }>
|
||||
<AlertIcon/>
|
||||
<AlertTitle>
|
||||
This is alert text
|
||||
</AlertTitle>
|
||||
</Alert>
|
||||
</TestApp>,
|
||||
</AlertTitle>
|
||||
</Alert>,
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import React from 'react';
|
||||
|
||||
import TestApp from 'playwright/TestApp';
|
||||
import { test, expect } from 'playwright/lib';
|
||||
|
||||
[
|
||||
{ variant: 'solid' },
|
||||
@@ -16,40 +15,24 @@ import TestApp from 'playwright/TestApp';
|
||||
{ variant: 'subtle', colorScheme: 'gray', withDarkMode: true },
|
||||
].forEach(({ variant, colorScheme, withDarkMode }) => {
|
||||
test.describe(`variant ${ variant }${ colorScheme ? ` with ${ colorScheme } color scheme` : '' }${ withDarkMode ? ' +@dark-mode' : '' }`, () => {
|
||||
test('base', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<Button variant={ variant } colorScheme={ colorScheme }>Click me</Button>
|
||||
</TestApp>,
|
||||
);
|
||||
test('base', async({ render }) => {
|
||||
const component = await render(<Button variant={ variant } colorScheme={ colorScheme }>Click me</Button>);
|
||||
await expect(component.locator('button')).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('disabled', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<Button variant={ variant } colorScheme={ colorScheme } isDisabled>Click me</Button>
|
||||
</TestApp>,
|
||||
);
|
||||
test('disabled', async({ render }) => {
|
||||
const component = await render(<Button variant={ variant } colorScheme={ colorScheme } isDisabled>Click me</Button>);
|
||||
await expect(component.locator('button')).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('hovered', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<Button variant={ variant } colorScheme={ colorScheme }>Click me</Button>
|
||||
</TestApp>,
|
||||
);
|
||||
test('hovered', async({ render }) => {
|
||||
const component = await render(<Button variant={ variant } colorScheme={ colorScheme }>Click me</Button>);
|
||||
await component.getByText(/click/i).hover();
|
||||
await expect(component.locator('button')).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('active', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<Button variant={ variant } colorScheme={ colorScheme } isActive>Click me</Button>
|
||||
</TestApp>,
|
||||
);
|
||||
test('active', async({ render }) => {
|
||||
const component = await render(<Button variant={ variant } colorScheme={ colorScheme } isActive>Click me</Button>);
|
||||
await expect(component.locator('button')).toHaveScreenshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { formAnatomy as parts } from '@chakra-ui/anatomy';
|
||||
import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system';
|
||||
import type { StyleFunctionProps } from '@chakra-ui/theme-tools';
|
||||
import { getColor, mode } from '@chakra-ui/theme-tools';
|
||||
|
||||
import getDefaultFormColors from '../utils/getDefaultFormColors';
|
||||
import getFormStyles from '../utils/getFormStyles';
|
||||
import FancySelect from './FancySelect';
|
||||
import FormLabel from './FormLabel';
|
||||
import Input from './Input';
|
||||
@@ -13,8 +12,7 @@ const { definePartsStyle, defineMultiStyleConfig } =
|
||||
createMultiStyleConfigHelpers(parts.keys);
|
||||
|
||||
function getFloatingVariantStylesForSize(size: 'md' | 'lg', props: StyleFunctionProps) {
|
||||
const { theme } = props;
|
||||
const { focusPlaceholderColor, errorColor } = getDefaultFormColors(props);
|
||||
const formStyles = getFormStyles(props);
|
||||
|
||||
const activeLabelStyles = {
|
||||
...FormLabel.variants?.floating?.(props)._focusWithin,
|
||||
@@ -63,12 +61,29 @@ function getFloatingVariantStylesForSize(size: 'md' | 'lg', props: StyleFunction
|
||||
// label styles
|
||||
label: FormLabel.sizes?.[size](props) || {},
|
||||
'input:not(:placeholder-shown) + label, textarea:not(:placeholder-shown) + label': activeLabelStyles,
|
||||
'textarea:not(:placeholder-shown) + label': {
|
||||
bgColor: formStyles.input.filled.bgColor,
|
||||
},
|
||||
[`
|
||||
input[readonly] + label,
|
||||
textarea[readonly] + label,
|
||||
&[aria-readonly=true] label
|
||||
`]: {
|
||||
bgColor: formStyles.input.readOnly.bgColor,
|
||||
},
|
||||
[`
|
||||
input[aria-invalid=true] + label,
|
||||
textarea[aria-invalid=true] + label,
|
||||
&[aria-invalid=true] label
|
||||
`]: {
|
||||
color: getColor(theme, errorColor),
|
||||
color: formStyles.placeholder.error.color,
|
||||
},
|
||||
[`
|
||||
input[disabled] + label,
|
||||
textarea[disabled] + label,
|
||||
&[aria-disabled=true] label
|
||||
`]: {
|
||||
color: formStyles.placeholder.disabled.color,
|
||||
},
|
||||
|
||||
// input styles
|
||||
@@ -79,31 +94,24 @@ function getFloatingVariantStylesForSize(size: 'md' | 'lg', props: StyleFunction
|
||||
padding: inputPx,
|
||||
},
|
||||
'input:not(:placeholder-shown), textarea:not(:placeholder-shown)': activeInputStyles,
|
||||
[`
|
||||
input[disabled] + label,
|
||||
&[aria-disabled=true] label
|
||||
`]: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
// in textarea bg of label could not be transparent; it should match the background color of input but without alpha
|
||||
// so we have to use non-standard colors here
|
||||
'textarea[disabled] + label': {
|
||||
backgroundColor: mode('#ececec', '#232425')(props),
|
||||
},
|
||||
'textarea[disabled] + label[data-in-modal=true]': {
|
||||
backgroundColor: mode('#ececec', '#292b34')(props),
|
||||
},
|
||||
|
||||
// indicator styles
|
||||
'input:not(:placeholder-shown) + label .chakra-form__required-indicator, textarea:not(:placeholder-shown) + label .chakra-form__required-indicator': {
|
||||
color: getColor(theme, focusPlaceholderColor),
|
||||
color: formStyles.placeholder.default.color,
|
||||
},
|
||||
[`
|
||||
input[aria-invalid=true] + label .chakra-form__required-indicator,
|
||||
textarea[aria-invalid=true] + label .chakra-form__required-indicator,
|
||||
&[aria-invalid=true] .chakra-form__required-indicator
|
||||
`]: {
|
||||
color: getColor(theme, errorColor),
|
||||
color: formStyles.placeholder.error.color,
|
||||
},
|
||||
[`
|
||||
input[disabled] + label .chakra-form__required-indicator,
|
||||
textarea[disabled] + label .chakra-form__required-indicator,
|
||||
&[aria-disabled=true] .chakra-form__required-indicator
|
||||
`]: {
|
||||
color: formStyles.placeholder.disabled.color,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import { FormControl, Input, FormLabel } from '@chakra-ui/react';
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import React from 'react';
|
||||
|
||||
import TestApp from 'playwright/TestApp';
|
||||
import { test, expect } from 'playwright/lib';
|
||||
|
||||
test.use({ viewport: { width: 500, height: 300 } });
|
||||
|
||||
test.describe('floating label size md +@dark-mode', () => {
|
||||
test('empty', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<FormControl variant="floating" id="name" isRequired size="md">
|
||||
<Input required value=""/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>
|
||||
</TestApp>,
|
||||
test('empty', async({ render }) => {
|
||||
const component = await render(
|
||||
<FormControl variant="floating" id="name" isRequired size="md">
|
||||
<Input required value=""/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>,
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
@@ -23,14 +20,12 @@ test.describe('floating label size md +@dark-mode', () => {
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('empty error', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<FormControl variant="floating" id="name" isRequired size="md">
|
||||
<Input required value="" isInvalid/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>
|
||||
</TestApp>,
|
||||
test('empty error', async({ render }) => {
|
||||
const component = await render(
|
||||
<FormControl variant="floating" id="name" isRequired size="md">
|
||||
<Input required value="" isInvalid/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>,
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
@@ -39,40 +34,45 @@ test.describe('floating label size md +@dark-mode', () => {
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('filled', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<FormControl variant="floating" id="name" isRequired size="md">
|
||||
<Input required value="foo"/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>
|
||||
</TestApp>,
|
||||
test('filled', async({ render }) => {
|
||||
const component = await render(
|
||||
<FormControl variant="floating" id="name" isRequired size="md">
|
||||
<Input required value="foo"/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>,
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('filled disabled', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<FormControl variant="floating" id="name" isRequired size="md">
|
||||
<Input required value="foo" isDisabled/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>
|
||||
</TestApp>,
|
||||
test('filled disabled', async({ render }) => {
|
||||
const component = await render(
|
||||
<FormControl variant="floating" id="name" isRequired size="md">
|
||||
<Input required value="foo" isDisabled/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>,
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('filled error', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<FormControl variant="floating" id="name" isRequired size="md">
|
||||
<Input required value="foo" isInvalid/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>
|
||||
</TestApp>,
|
||||
test('filled read-only', async({ render }) => {
|
||||
const component = await render(
|
||||
<FormControl variant="floating" id="name" isRequired size="md">
|
||||
<Input required value="foo" isReadOnly/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>,
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('filled error', async({ render }) => {
|
||||
const component = await render(
|
||||
<FormControl variant="floating" id="name" isRequired size="md">
|
||||
<Input required value="foo" isInvalid/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>,
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
@@ -80,14 +80,12 @@ test.describe('floating label size md +@dark-mode', () => {
|
||||
});
|
||||
|
||||
test.describe('floating label size lg +@dark-mode', () => {
|
||||
test('empty', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<FormControl variant="floating" id="name" isRequired size="lg">
|
||||
<Input required value=""/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>
|
||||
</TestApp>,
|
||||
test('empty', async({ render }) => {
|
||||
const component = await render(
|
||||
<FormControl variant="floating" id="name" isRequired size="lg">
|
||||
<Input required value=""/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>,
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
@@ -96,14 +94,12 @@ test.describe('floating label size lg +@dark-mode', () => {
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('filled', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<FormControl variant="floating" id="name" isRequired size="lg">
|
||||
<Input required value="foo"/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>
|
||||
</TestApp>,
|
||||
test('filled', async({ render }) => {
|
||||
const component = await render(
|
||||
<FormControl variant="floating" id="name" isRequired size="lg">
|
||||
<Input required value="foo"/>
|
||||
<FormLabel>Smart contract / Address (0x...)</FormLabel>
|
||||
</FormControl>,
|
||||
);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
@@ -1,7 +1,6 @@
|
||||
import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system';
|
||||
import { getColor } from '@chakra-ui/theme-tools';
|
||||
|
||||
import getDefaultFormColors from '../utils/getDefaultFormColors';
|
||||
import getFormStyles from '../utils/getFormStyles';
|
||||
|
||||
const baseStyle = defineStyle({
|
||||
display: 'flex',
|
||||
@@ -13,14 +12,12 @@ const baseStyle = defineStyle({
|
||||
transitionDuration: 'normal',
|
||||
opacity: 1,
|
||||
_disabled: {
|
||||
opacity: 0.4,
|
||||
opacity: 0.2,
|
||||
},
|
||||
});
|
||||
|
||||
const variantFloating = defineStyle((props) => {
|
||||
const { theme, backgroundColor } = props;
|
||||
const { focusPlaceholderColor } = getDefaultFormColors(props);
|
||||
const bc = backgroundColor || 'transparent';
|
||||
const formStyles = getFormStyles(props);
|
||||
|
||||
return {
|
||||
left: '2px',
|
||||
@@ -29,8 +26,8 @@ const variantFloating = defineStyle((props) => {
|
||||
position: 'absolute',
|
||||
borderRadius: 'base',
|
||||
boxSizing: 'border-box',
|
||||
color: 'gray.500',
|
||||
backgroundColor: 'transparent',
|
||||
color: formStyles.placeholder.default.color,
|
||||
backgroundColor: props.bgColor || props.backgroundColor || 'transparent',
|
||||
pointerEvents: 'none',
|
||||
margin: 0,
|
||||
transformOrigin: 'top left',
|
||||
@@ -39,8 +36,8 @@ const variantFloating = defineStyle((props) => {
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
_focusWithin: {
|
||||
backgroundColor: bc,
|
||||
color: getColor(theme, focusPlaceholderColor),
|
||||
backgroundColor: props.bgColor || props.backgroundColor || 'transparent',
|
||||
color: formStyles.placeholder.default.color,
|
||||
fontSize: 'xs',
|
||||
lineHeight: '16px',
|
||||
borderTopRightRadius: 'none',
|
||||
@@ -70,7 +67,7 @@ const sizes = {
|
||||
return {
|
||||
fontSize: 'md',
|
||||
lineHeight: '24px',
|
||||
padding: '28px 4px 28px 24px',
|
||||
padding: '26px 4px 26px 24px',
|
||||
right: '22px',
|
||||
_focusWithin: {
|
||||
padding: '16px 0 2px 24px',
|
||||
|
||||
@@ -29,6 +29,20 @@ const size = {
|
||||
h: '40px',
|
||||
borderRadius: 'base',
|
||||
}),
|
||||
// TEMPORARY INPUT SIZE!!!
|
||||
// soon we will migrate to the new size and get rid off this one
|
||||
// lg -> 60
|
||||
// md -> 48
|
||||
// sm -> 40
|
||||
// xs ->32
|
||||
sm_md: defineStyle({
|
||||
fontSize: 'md',
|
||||
lineHeight: '24px',
|
||||
px: '8px',
|
||||
py: '12px',
|
||||
h: '48px',
|
||||
borderRadius: 'base',
|
||||
}),
|
||||
md: defineStyle({
|
||||
fontSize: 'md',
|
||||
lineHeight: '20px',
|
||||
@@ -71,6 +85,10 @@ const sizes = {
|
||||
field: size.sm,
|
||||
addon: size.sm,
|
||||
}),
|
||||
sm_md: definePartsStyle({
|
||||
field: size.sm_md,
|
||||
addon: size.sm_md,
|
||||
}),
|
||||
md: definePartsStyle({
|
||||
field: size.md,
|
||||
addon: size.md,
|
||||
|
||||
@@ -10,11 +10,11 @@ import { runIfFn } from '@chakra-ui/utils';
|
||||
const { defineMultiStyleConfig, definePartsStyle } =
|
||||
createMultiStyleConfigHelpers(parts.keys);
|
||||
|
||||
const baseStyleDialog = defineStyle((props) => {
|
||||
const baseStyleDialog = defineStyle(() => {
|
||||
return {
|
||||
padding: 8,
|
||||
borderRadius: 'lg',
|
||||
bg: mode('white', 'gray.900')(props),
|
||||
bg: 'dialog_bg',
|
||||
margin: 'auto',
|
||||
};
|
||||
});
|
||||
@@ -61,7 +61,7 @@ const baseStyleOverlay = defineStyle({
|
||||
});
|
||||
|
||||
const baseStyle = definePartsStyle((props) => ({
|
||||
dialog: runIfFn(baseStyleDialog, props),
|
||||
dialog: runIfFn(baseStyleDialog),
|
||||
dialogContainer: baseStyleDialogContainer,
|
||||
|
||||
header: runIfFn(baseStyleHeader, props),
|
||||
|
||||
@@ -15,6 +15,9 @@ const baseStyleTrack = defineStyle((props) => {
|
||||
bg: mode(`${ c }.600`, `${ c }.400`)(props),
|
||||
},
|
||||
},
|
||||
_focusVisible: {
|
||||
boxShadow: 'none',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
import { Box, Tag } from '@chakra-ui/react';
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import React from 'react';
|
||||
|
||||
import TestApp from 'playwright/TestApp';
|
||||
import { test, expect } from 'playwright/lib';
|
||||
|
||||
[ 'blue', 'gray', 'gray-blue', 'orange', 'green', 'purple', 'cyan', 'teal' ].forEach((colorScheme) => {
|
||||
test(`${ colorScheme } color scheme +@dark-mode`, async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<Tag colorScheme={ colorScheme }>content</Tag>
|
||||
</TestApp>,
|
||||
);
|
||||
|
||||
test(`${ colorScheme } color scheme +@dark-mode`, async({ render }) => {
|
||||
const component = await render(<Tag colorScheme={ colorScheme }>content</Tag>);
|
||||
await expect(component.getByText(/content/i)).toHaveScreenshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('with long text', async({ mount }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<Box w="100px">
|
||||
<Tag>this is very looooooooooong text</Tag>
|
||||
</Box>
|
||||
</TestApp>,
|
||||
test('with long text', async({ render }) => {
|
||||
const component = await render(
|
||||
<Box w="100px">
|
||||
<Tag>this is very looooooooooong text</Tag>
|
||||
</Box>,
|
||||
);
|
||||
|
||||
await expect(component.getByText(/this/i)).toHaveScreenshot();
|
||||
});
|
||||
|
||||
@@ -1,22 +1,8 @@
|
||||
import { Textarea as TextareaComponent } from '@chakra-ui/react';
|
||||
import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system';
|
||||
import { mode } from '@chakra-ui/theme-tools';
|
||||
|
||||
import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles';
|
||||
|
||||
const variantFilledInactive = defineStyle((props) => {
|
||||
return {
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1362573
|
||||
// there is a problem with scrollbar color in chromium
|
||||
// so blackAlpha.50 here is replaced with #f5f5f6
|
||||
// and whiteAlpha.50 is replaced with #1a1b1b
|
||||
|
||||
// bgColor: mode('blackAlpha.50', 'whiteAlpha.50')(props),
|
||||
bgColor: mode('#f5f5f6', '#1a1b1b')(props),
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
const sizes = {
|
||||
md: defineStyle({
|
||||
fontSize: 'md',
|
||||
@@ -38,7 +24,6 @@ const Textarea = defineStyleConfig({
|
||||
sizes,
|
||||
variants: {
|
||||
outline: defineStyle(getOutlinedFieldStyles),
|
||||
filledInactive: variantFilledInactive,
|
||||
},
|
||||
defaultProps: {
|
||||
variant: 'outline',
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import { Box, Tooltip, Icon } from '@chakra-ui/react';
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import React from 'react';
|
||||
|
||||
import TestApp from 'playwright/TestApp';
|
||||
import { test, expect } from 'playwright/lib';
|
||||
|
||||
test('base view +@dark-mode', async({ mount, page }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<Box m={ 10 }>
|
||||
<Tooltip label="Tooltip content">
|
||||
test('base view +@dark-mode', async({ render, page }) => {
|
||||
const component = await render(
|
||||
<Box m={ 10 }>
|
||||
<Tooltip label="Tooltip content">
|
||||
trigger
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</TestApp>,
|
||||
</Tooltip>
|
||||
</Box>,
|
||||
);
|
||||
|
||||
await component.getByText(/trigger/i).hover();
|
||||
@@ -22,17 +19,15 @@ test('base view +@dark-mode', async({ mount, page }) => {
|
||||
|
||||
// was not able to reproduce in tests issue when Icon is used as trigger for tooltip
|
||||
// https://github.com/chakra-ui/chakra-ui/issues/7107
|
||||
test.skip('with icon', async({ mount, page }) => {
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<Box m={ 10 }>
|
||||
<Tooltip label="Tooltip content">
|
||||
<Icon viewBox="0 0 20 20" boxSize={ 5 } aria-label="Trigger">
|
||||
<circle cx="10" cy="10" r="10"/>
|
||||
</Icon>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</TestApp>,
|
||||
test.fixme('with icon', async({ render, page }) => {
|
||||
const component = await render(
|
||||
<Box m={ 10 }>
|
||||
<Tooltip label="Tooltip content">
|
||||
<Icon viewBox="0 0 20 20" boxSize={ 5 } aria-label="Trigger">
|
||||
<circle cx="10" cy="10" r="10"/>
|
||||
</Icon>
|
||||
</Tooltip>
|
||||
</Box>,
|
||||
);
|
||||
|
||||
const tooltip = page.getByText(/tooltip content/i);
|
||||
|
||||
@@ -23,6 +23,10 @@ const semanticTokens = {
|
||||
'default': 'red.400',
|
||||
_dark: 'red.300',
|
||||
},
|
||||
dialog_bg: {
|
||||
'default': 'white',
|
||||
_dark: 'gray.900',
|
||||
},
|
||||
},
|
||||
shadows: {
|
||||
action_bar: '0 4px 4px -4px rgb(0 0 0 / 10%), 0 2px 4px -4px rgb(0 0 0 / 6%)',
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { StyleFunctionProps } from '@chakra-ui/theme-tools';
|
||||
import { mode } from '@chakra-ui/theme-tools';
|
||||
|
||||
export default function getDefaultFormColors(props: StyleFunctionProps) {
|
||||
const { focusBorderColor: fc, errorBorderColor: ec, filledBorderColor: flc } = props;
|
||||
return {
|
||||
focusBorderColor: fc || mode('blue.500', 'blue.300')(props),
|
||||
focusPlaceholderColor: fc || 'gray.500',
|
||||
errorColor: ec || mode('red.400', 'red.300')(props),
|
||||
filledColor: flc || mode('gray.300', 'gray.600')(props),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { StyleFunctionProps } from '@chakra-ui/theme-tools';
|
||||
import { mode, transparentize } from '@chakra-ui/theme-tools';
|
||||
|
||||
export default function getFormStyles(props: StyleFunctionProps) {
|
||||
return {
|
||||
input: {
|
||||
empty: {
|
||||
// there is no text in the empty input
|
||||
// color: ???,
|
||||
bgColor: props.bgColor || mode('white', 'black')(props),
|
||||
borderColor: mode('gray.100', 'gray.700')(props),
|
||||
},
|
||||
hover: {
|
||||
color: mode('gray.800', 'gray.50')(props),
|
||||
bgColor: props.bgColor || mode('white', 'black')(props),
|
||||
borderColor: mode('gray.200', 'gray.500')(props),
|
||||
},
|
||||
focus: {
|
||||
color: mode('gray.800', 'gray.50')(props),
|
||||
bgColor: props.bgColor || mode('white', 'black')(props),
|
||||
borderColor: mode('blue.400', 'blue.400')(props),
|
||||
},
|
||||
filled: {
|
||||
color: mode('gray.800', 'gray.50')(props),
|
||||
bgColor: props.bgColor || mode('white', 'black')(props),
|
||||
borderColor: mode('gray.300', 'gray.600')(props),
|
||||
},
|
||||
readOnly: {
|
||||
color: mode('gray.800', 'gray.50')(props),
|
||||
bgColor: mode('gray.200', 'gray.800')(props),
|
||||
borderColor: mode('gray.200', 'gray.800')(props),
|
||||
},
|
||||
// we use opacity to show the disabled state
|
||||
disabled: {
|
||||
opacity: 0.2,
|
||||
},
|
||||
error: {
|
||||
color: mode('gray.800', 'gray.50')(props),
|
||||
bgColor: props.bgColor || mode('white', 'black')(props),
|
||||
borderColor: mode('red.500', 'red.500')(props),
|
||||
},
|
||||
},
|
||||
placeholder: {
|
||||
'default': {
|
||||
color: mode('gray.500', 'gray.500')(props),
|
||||
},
|
||||
disabled: {
|
||||
color: transparentize('gray.500', 0.2)(props.theme),
|
||||
},
|
||||
error: {
|
||||
color: mode('red.500', 'red.500')(props),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,59 +1,64 @@
|
||||
import type { StyleFunctionProps } from '@chakra-ui/theme-tools';
|
||||
import { mode, getColor } from '@chakra-ui/theme-tools';
|
||||
import { mode } from '@chakra-ui/theme-tools';
|
||||
|
||||
import getDefaultFormColors from './getDefaultFormColors';
|
||||
import getDefaultTransitionProps from './getDefaultTransitionProps';
|
||||
import getFormStyles from './getFormStyles';
|
||||
|
||||
export default function getOutlinedFieldStyles(props: StyleFunctionProps) {
|
||||
const { theme, borderColor } = props;
|
||||
const { focusBorderColor, errorColor } = getDefaultFormColors(props);
|
||||
const formStyles = getFormStyles(props);
|
||||
const transitionProps = getDefaultTransitionProps();
|
||||
|
||||
return {
|
||||
border: '2px solid',
|
||||
// filled input
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: mode('gray.300', 'gray.600')(props),
|
||||
...formStyles.input.filled,
|
||||
...transitionProps,
|
||||
_hover: {
|
||||
borderColor: mode('gray.200', 'gray.500')(props),
|
||||
...formStyles.input.hover,
|
||||
},
|
||||
_readOnly: {
|
||||
boxShadow: 'none !important',
|
||||
userSelect: 'all',
|
||||
pointerEvents: 'none',
|
||||
...formStyles.input.readOnly,
|
||||
_hover: {
|
||||
...formStyles.input.readOnly,
|
||||
},
|
||||
_focus: {
|
||||
...formStyles.input.readOnly,
|
||||
},
|
||||
},
|
||||
_disabled: {
|
||||
opacity: 1,
|
||||
backgroundColor: mode('blackAlpha.200', 'whiteAlpha.200')(props),
|
||||
borderColor: 'transparent',
|
||||
...formStyles.input.disabled,
|
||||
cursor: 'not-allowed',
|
||||
_hover: {
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
':-webkit-autofill': {
|
||||
// background color for disabled input which value was selected from browser autocomplete popup
|
||||
'-webkit-box-shadow': `0 0 0px 1000px ${ mode('rgba(16, 17, 18, 0.08)', 'rgba(255, 255, 255, 0.08)')(props) } inset`,
|
||||
},
|
||||
},
|
||||
_invalid: {
|
||||
borderColor: getColor(theme, errorColor),
|
||||
...formStyles.input.error,
|
||||
boxShadow: `none`,
|
||||
_placeholder: {
|
||||
color: formStyles.placeholder.error.color,
|
||||
},
|
||||
},
|
||||
_focusVisible: {
|
||||
...formStyles.input.focus,
|
||||
zIndex: 1,
|
||||
borderColor: getColor(theme, focusBorderColor),
|
||||
boxShadow: 'md',
|
||||
},
|
||||
_placeholder: {
|
||||
color: mode('blackAlpha.600', 'whiteAlpha.600')(props),
|
||||
color: formStyles.placeholder.default.color,
|
||||
},
|
||||
// not filled input
|
||||
':placeholder-shown:not(:focus-visible):not(:hover):not([aria-invalid=true])': { borderColor: borderColor || mode('gray.100', 'gray.700')(props) },
|
||||
':placeholder-shown:not(:focus-visible):not(:hover):not([aria-invalid=true]):not([aria-readonly=true])': {
|
||||
...formStyles.input.empty,
|
||||
},
|
||||
|
||||
// not filled input with type="date"
|
||||
':not(:placeholder-shown)[value=""]:not(:focus-visible):not(:hover):not([aria-invalid=true])': {
|
||||
borderColor: borderColor || mode('gray.100', 'gray.700')(props),
|
||||
color: 'gray.500',
|
||||
':not(:placeholder-shown)[value=""]:not(:focus-visible):not(:hover):not([aria-invalid=true]):not([aria-readonly=true])': {
|
||||
...formStyles.input.empty,
|
||||
},
|
||||
|
||||
':-webkit-autofill': { transition: 'background-color 5000s ease-in-out 0s' },
|
||||
|
||||
@@ -17,7 +17,7 @@ fi
|
||||
# download assets for the running instance
|
||||
dotenv \
|
||||
-e $config_file \
|
||||
-- bash -c './deploy/scripts/download_assets.sh ./public/assets'
|
||||
-- bash -c './deploy/scripts/download_assets.sh ./public/assets/configs'
|
||||
|
||||
yarn svg:build-sprite
|
||||
echo ""
|
||||
|
||||
@@ -6,7 +6,7 @@ dotenv \
|
||||
-e .env.local \
|
||||
-e .env.development \
|
||||
-e .env \
|
||||
-- bash -c './deploy/scripts/download_assets.sh ./public/assets'
|
||||
-- bash -c './deploy/scripts/download_assets.sh ./public/assets/configs'
|
||||
|
||||
yarn svg:build-sprite
|
||||
echo ""
|
||||
@@ -20,5 +20,5 @@ dotenv \
|
||||
-e .env.local \
|
||||
-e .env.development \
|
||||
-e .env \
|
||||
-- bash -c './deploy/scripts/make_envs_script.sh && next dev -- -p $NEXT_PUBLIC_APP_PORT' |
|
||||
-- bash -c './deploy/scripts/make_envs_script.sh && next dev -p $NEXT_PUBLIC_APP_PORT' |
|
||||
pino-pretty
|
||||
@@ -1,5 +1,5 @@
|
||||
secrets_file="./configs/envs/.env.secrets"
|
||||
favicon_folder="./public/favicon/"
|
||||
favicon_folder="./public/assets/favicon/"
|
||||
master_url="https://raw.githubusercontent.com/blockscout/frontend/main/tools/scripts/favicon.svg"
|
||||
|
||||
if [ ! -f "$secrets_file" ]; then
|
||||
|
||||