diff --git a/.gitignore b/.gitignore index 285628bb73..d4459df0b2 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ /public/icons/sprite.svg /public/icons/sprite.*.svg /public/icons/README.md +/public/static/og_image.png /public/sitemap.xml /public/robots.txt /analyze diff --git a/Dockerfile b/Dockerfile index fa07d047a6..36eadc261a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -134,6 +134,8 @@ COPY --chmod=755 ./deploy/scripts/validate_envs.sh . COPY --chmod=755 ./deploy/scripts/make_envs_script.sh . ## Assets downloader COPY --chmod=755 ./deploy/scripts/download_assets.sh . +## OG image generator +COPY ./deploy/scripts/og_image_generator.js . ## Favicon generator COPY --chmod=755 ./deploy/scripts/favicon_generator.sh . COPY --from=builder /app/deploy/tools/favicon-generator ./deploy/tools/favicon-generator diff --git a/configs/app/meta.ts b/configs/app/meta.ts index 3d7b777e03..87f6405282 100644 --- a/configs/app/meta.ts +++ b/configs/app/meta.ts @@ -1,7 +1,7 @@ import app from './app'; import { getEnvValue, getExternalAssetFilePath } from './utils'; -const defaultImageUrl = '/static/og_placeholder.png'; +const defaultImageUrl = '/static/og_image.png'; const meta = Object.freeze({ promoteBlockscoutInTitle: getEnvValue('NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE') === 'false' ? false : true, diff --git a/configs/envs/.env.zkevm b/configs/envs/.env.zkevm index 65db320a6b..f7e9b610dd 100644 --- a/configs/envs/.env.zkevm +++ b/configs/envs/.env.zkevm @@ -13,23 +13,31 @@ NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "632019", "width": "728", "height": "90" } NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" } NEXT_PUBLIC_AD_BANNER_PROVIDER=adbutler +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ NEXT_PUBLIC_API_HOST=zkevm.blockscout.com NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/zkevm.json +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x25fcb396fc8652dcd0040f677a1dcc6fecff390ecafc815894379a3f254f1aa9 +NEXT_PUBLIC_HAS_USER_OPS=true NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(122deg, rgba(162, 41, 197, 1) 0%, rgba(123, 63, 228, 1) 100%) -NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(255, 255, 255, 1) -NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(122deg, rgba(162, 41, 197, 1) 0%, rgba(123, 63, 228, 1) 100%)'],'text_color':['rgba(255, 255, 255, 1)']} +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://blockscout-polygon.us.auth0.com/v2/logout +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_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=MATIC -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=MATIC +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/polygon-zkevm/pools'}}] NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/polygon-short.svg NEXT_PUBLIC_NETWORK_ID=1101 diff --git a/deploy/scripts/entrypoint.sh b/deploy/scripts/entrypoint.sh index 4e637dbc58..d4a7cd8134 100755 --- a/deploy/scripts/entrypoint.sh +++ b/deploy/scripts/entrypoint.sh @@ -35,6 +35,9 @@ export_envs_from_preset() { # If there is a preset, load the environment variables from the its file export_envs_from_preset +# Generate OG image +node --no-warnings ./og_image_generator.js + # Download external assets ./download_assets.sh ./public/assets/configs diff --git a/deploy/scripts/og_image_generator.js b/deploy/scripts/og_image_generator.js new file mode 100755 index 0000000000..d22f63b12b --- /dev/null +++ b/deploy/scripts/og_image_generator.js @@ -0,0 +1,61 @@ +/* eslint-disable no-console */ +import fs from 'fs'; +import path from 'path'; + +console.log('🎨 Generating OG image...'); + +const targetFile = path.resolve(process.cwd(), 'public/static/og_image.png'); + +function copyPlaceholderImage() { + const sourceFile = path.resolve(process.cwd(), 'public/static/og_placeholder.png'); + fs.copyFileSync(sourceFile, targetFile); +} + +if (process.env.NEXT_PUBLIC_OG_IMAGE_URL) { + console.log('⏩ NEXT_PUBLIC_OG_IMAGE_URL is set. Skipping OG image generation...'); +} else if (!process.env.NEXT_PUBLIC_NETWORK_NAME) { + console.log('⏩ NEXT_PUBLIC_NETWORK_NAME is not set. Copying placeholder image...'); + copyPlaceholderImage(); +} else if (!process.env.NEXT_PUBLIC_NETWORK_LOGO && !process.env.NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG) { + console.log('⏩ Neither NEXT_PUBLIC_NETWORK_LOGO nor NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG is set. Copying placeholder image...'); + copyPlaceholderImage(); +} else { + try { + const bannerConfig = JSON.parse(process.env.NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG?.replaceAll('\'', '"') || '{}'); + const data = { + title: `${ process.env.NEXT_PUBLIC_NETWORK_NAME } explorer`, + logo_url: process.env.NEXT_PUBLIC_NETWORK_LOGO_DARK ?? process.env.NEXT_PUBLIC_NETWORK_LOGO, + background: bannerConfig.background?.[0], + title_color: bannerConfig.text_color?.[0], + invert_logo: !process.env.NEXT_PUBLIC_NETWORK_LOGO_DARK, + }; + + console.log('⏳ Making request to OG image generator service...'); + + const response = await fetch('https://bigs.services.blockscout.com/generate/og', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + if (response.ok) { + console.log('⬇️ Downloading the image...'); + const buffer = await response.arrayBuffer(); + const imageBuffer = Buffer.from(buffer); + fs.writeFileSync(targetFile, imageBuffer); + } else { + const payload = response.headers.get('Content-type')?.includes('application/json') ? await response.json() : await response.text(); + console.error('🛑 Failed to generate OG image. Response:', payload); + console.log('Copying placeholder image...'); + copyPlaceholderImage(); + } + } catch (error) { + console.error('🛑 Failed to generate OG image. Error:', error?.message); + console.log('Copying placeholder image...'); + copyPlaceholderImage(); + } +} + +console.log('✅ Done.'); diff --git a/eslint.config.mjs b/eslint.config.mjs index 68d77abad7..23651fb5b8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -428,6 +428,7 @@ export default tseslint.config( 'pages/**', 'nextjs/**', 'playwright/**', + 'deploy/scripts/**', 'deploy/tools/**', 'middleware.ts', 'instrumentation*.ts', diff --git a/lib/metadata/__snapshots__/generate.test.ts.snap b/lib/metadata/__snapshots__/generate.test.ts.snap index 6997bf3de1..7c18d32400 100644 --- a/lib/metadata/__snapshots__/generate.test.ts.snap +++ b/lib/metadata/__snapshots__/generate.test.ts.snap @@ -32,7 +32,7 @@ exports[`generates correct metadata for: static route 1`] = ` "description": "Open-source block explorer by Blockscout. Search transactions, verify smart contracts, analyze addresses, and track network activity. Complete blockchain data and APIs for the Blockscout (Blockscout) Explorer network.", "opengraph": { "description": "", - "imageUrl": "http://localhost:3000/static/og_placeholder.png", + "imageUrl": "http://localhost:3000/static/og_image.png", "title": "Blockscout transactions - Blockscout explorer | Blockscout", }, "title": "Blockscout transactions - Blockscout explorer | Blockscout", diff --git a/package.json b/package.json index 068a3b3d57..43de857133 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "test:jest": "jest", "test:jest:watch": "jest --watch", "favicon:generate:dev": "./tools/scripts/favicon-generator.dev.sh", + "og-image:generate:dev": "./tools/scripts/og-image-generator.dev.sh", "sitemap:generate:dev": "./tools/scripts/sitemap-generator.dev.sh", "monitoring:prometheus:local": "docker run --name blockscout_prometheus -d -p 127.0.0.1:9090:9090 -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus", "monitoring:grafana:local": "docker run -d -p 4000:3000 --name=blockscout_grafana --user $(id -u) --volume $(pwd)/grafana:/var/lib/grafana grafana/grafana-enterprise" diff --git a/public/static/og_twitter.png b/public/static/og_twitter.png deleted file mode 100644 index 1d83cbc77b..0000000000 Binary files a/public/static/og_twitter.png and /dev/null differ diff --git a/tools/scripts/og-image-generator.dev.sh b/tools/scripts/og-image-generator.dev.sh new file mode 100755 index 0000000000..a47f2ac536 --- /dev/null +++ b/tools/scripts/og-image-generator.dev.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# use this script for testing the og image generator + +config_file="./configs/envs/.env.zkevm" + +dotenv \ + -e $config_file \ + -- bash -c 'node ./deploy/scripts/og_image_generator.js' \ No newline at end of file