diff --git a/configs/app/ui.ts b/configs/app/ui.ts index 081fcc7c37..0210b73493 100644 --- a/configs/app/ui.ts +++ b/configs/app/ui.ts @@ -71,6 +71,7 @@ const UI = Object.freeze({ charts: parseEnvJson>(getEnvValue('NEXT_PUBLIC_HOMEPAGE_CHARTS')) || [], stats: homePageStats, heroBanner: parseEnvJson(getEnvValue('NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG')), + highlights: getExternalAssetFilePath('NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG'), }, views, indexingAlert: { diff --git a/configs/envs/.env.main b/configs/envs/.env.main index f6afaca962..c596a5ed9c 100644 --- a/configs/envs/.env.main +++ b/configs/envs/.env.main @@ -9,6 +9,8 @@ NEXT_PUBLIC_APP_PORT=3000 NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws +NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/homepage-highlights/test.json + # Instance ENVs NEXT_PUBLIC_AD_BANNER_ENABLE_SPECIFY=true NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS=['talentprotocol', 'efp', 'webacy', 'deepdao', 'humanpassport', 'trustblock', 'smartmuv', 'blockscoutbadges', 'etherscore', 'gitpoap', 'drops', 'humanode'] diff --git a/deploy/scripts/download_assets.sh b/deploy/scripts/download_assets.sh index 6f1d551b73..c62f70b5f6 100755 --- a/deploy/scripts/download_assets.sh +++ b/deploy/scripts/download_assets.sh @@ -27,6 +27,7 @@ ASSETS_ENVS=( "NEXT_PUBLIC_OG_IMAGE_URL" "NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL" "NEXT_PUBLIC_ZETACHAIN_SERVICE_CHAINS_CONFIG_URL" + "NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG" ) # Create the assets directory if it doesn't exist diff --git a/deploy/tools/envs-validator/index.ts b/deploy/tools/envs-validator/index.ts index 28a9921acb..0d55fcb991 100644 --- a/deploy/tools/envs-validator/index.ts +++ b/deploy/tools/envs-validator/index.ts @@ -48,6 +48,7 @@ async function validateEnvs(appEnvs: Record) { 'NEXT_PUBLIC_FOOTER_LINKS', 'NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL', 'NEXT_PUBLIC_ZETACHAIN_SERVICE_CHAINS_CONFIG_URL', + 'NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG', ]; for await (const envName of envsWithJsonConfig) { diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 031ff784f9..51a544607d 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -164,6 +164,7 @@ const schema = yup .concat(featuresSchemas.beaconChainSchema) .concat(featuresSchemas.bridgedTokensSchema) .concat(featuresSchemas.defiDropdownSchema) + .concat(featuresSchemas.highlightsConfigSchema) .concat(featuresSchemas.marketplaceSchema) .concat(featuresSchemas.megaEthSchema) .concat(featuresSchemas.rollupSchema) diff --git a/deploy/tools/envs-validator/schemas/features/highlights.ts b/deploy/tools/envs-validator/schemas/features/highlights.ts new file mode 100644 index 0000000000..d58e70cb7e --- /dev/null +++ b/deploy/tools/envs-validator/schemas/features/highlights.ts @@ -0,0 +1,25 @@ +import { HighlightsBannerConfig } from "types/homepage"; +import { urlTest } from "../../utils"; +import * as yup from 'yup'; + +const highlightsBannerConfigSchema: yup.ObjectSchema = yup.object({ + title: yup.string().required(), + description: yup.string().required(), + title_color: yup.array().max(2).of(yup.string()), + description_color: yup.array().max(2).of(yup.string()), + background: yup.array().max(2).of(yup.string()), + side_img_url: yup.array().max(2).of(yup.string()), + is_pinned: yup.boolean(), + page_path: yup.string(), + redirect_url: yup.string().test(urlTest), +}); + +export const highlightsConfigSchema = yup + .object() + .shape({ + NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG: yup + .array() + .json() + .of(highlightsBannerConfigSchema) + .min(2) + }); \ No newline at end of file diff --git a/deploy/tools/envs-validator/schemas/features/index.ts b/deploy/tools/envs-validator/schemas/features/index.ts index 87e5369938..ab5bc0f65d 100644 --- a/deploy/tools/envs-validator/schemas/features/index.ts +++ b/deploy/tools/envs-validator/schemas/features/index.ts @@ -4,6 +4,7 @@ export * from './apiDocs'; export * from './beaconChain'; export * from './bridgedToken'; export * from './defiDropdown'; +export * from './highlights'; export * from './marketplace'; export * from './megaEth'; export * from './rollup'; diff --git a/deploy/tools/envs-validator/test/.env.base b/deploy/tools/envs-validator/test/.env.base index 8f73e4586b..5537f1d82e 100644 --- a/deploy/tools/envs-validator/test/.env.base +++ b/deploy/tools/envs-validator/test/.env.base @@ -92,3 +92,4 @@ NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS=['widget-1', 'widget-2'] NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL=https://example.com NEXT_PUBLIC_NAVIGATION_PROMO_BANNER_CONFIG={'img_url': 'https://example.com/promo.svg', 'text': 'Promo text', 'bg_color': {'light': 'rgb(250, 245, 255)', 'dark': 'rgb(68, 51, 122)'}, 'text_color': {'light': 'rgb(107, 70, 193)', 'dark': 'rgb(233, 216, 253)'}, 'link_url': 'https://example.com'} NEXT_PUBLIC_FLASHBLOCKS_SOCKET_URL=wss://example.com/ws +NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG=https://example.com diff --git a/deploy/tools/envs-validator/test/assets/configs/homepage_highlights_config.json b/deploy/tools/envs-validator/test/assets/configs/homepage_highlights_config.json new file mode 100644 index 0000000000..00db871e2e --- /dev/null +++ b/deploy/tools/envs-validator/test/assets/configs/homepage_highlights_config.json @@ -0,0 +1,21 @@ +[ + { + "title": "Duck Deep into Transactions", + "description": "Explore and track all blockchain transactions", + "title_color": ["#1a472a", "#4ade80"], + "description_color": ["#374151", "#d1d5db"], + "background": ["#e0f2fe", "#0f172a"], + "side_img_url": [ + "https://example.com", + "https://example.com" + ], + "page_path": "/txs" + }, + { + "title": "Capybara Hot Spring Pools", + "description": "Monitor liquidity and staking pools", + "is_pinned": true, + "redirect_url": "https://example.com" + } + ] + \ No newline at end of file diff --git a/docs/ENVS.md b/docs/ENVS.md index 3a5b632fe7..7683668e1d 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -144,6 +144,7 @@ Also, be aware that if you customize the name of the currency or any of its deno | NEXT_PUBLIC_HOMEPAGE_CHARTS | `Array<'daily_txs' \| 'daily_operational_txs' \| 'coin_price' \| 'secondary_coin_price' \| 'market_cap' \| 'tvl'>` | List of charts displayed on the home page | - | - | `['daily_txs','coin_price','market_cap']` | v1.0.x+ | | NEXT_PUBLIC_HOMEPAGE_STATS | `Array<'latest_batch' \| 'total_blocks' \| 'average_block_time' \| 'total_txs' \| 'total_operational_txs' \| 'latest_l1_state_batch' \| 'wallet_addresses' \| 'gas_tracker' \| 'btc_locked' \| 'current_epoch'>` | List of stats widgets displayed on the home page | - | For zkSync, zkEvm and Arbitrum rollups: `['latest_batch','average_block_time','total_txs','wallet_addresses','gas_tracker']`, for other cases: `['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker']` | `['total_blocks','total_txs','wallet_addresses']` | v1.35.x+ | | NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG | `HeroBannerConfig`, see details [below](#hero-banner-configuration-properties) | Configuration of hero banner appearance. | - | - | See [below](#hero-banner-configuration-properties) | v1.35.0+ | +| NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG | `string` | URL of the file (in `.json` format only) that contains the configuration for banners on the application's homepage, showcasing some of its key functionality. See the full config format [below](#highlights-banner-configuration-properties). The config should contain at least 2 banners, but only 3 banners will be visible at the same time. A larger number of banners in the config allows for random banner rotation upon page load. | - | - | See [below](#highlights-banner-configuration-properties) | upcoming | #### Hero banner configuration properties @@ -157,6 +158,23 @@ _Note_ Here, all values are arrays of up to two strings. The first string repres | search | `{ border_width: [string, string] }` | Search bar customization. Currently supports only width of the border (in px). | - | - | `{ 'border_width': ['0px', '2px'] }` | | button | `Partial>` | The button on the banner. It has three possible states: `_default`, `_hover`, and `_selected`. The `_selected` state reflects when the user is logged in or their wallet is connected to the app. | - | - | `{'_default':{'background':['deeppink'],'text_color':['white']}}` | +#### Highlights banner configuration properties + +_Note_ Some properties can hold an array of up to two strings. The first string represents the value for the light color mode, while the second string represents the value for the dark color mode. If the array contains only one string, it will be used for both color modes. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| title | `string` | Title on the banner | Required | - | `Duck Deep into Transactions` | +| description | `string` | Short description of the feature | Required | - | `Explore and track all blockchain transactions` | +| title_color | `[string, string]` | Text color of the title. | - | `['#101112', '#F8FCFF']` | `['#FFB8D4', '#D9FE41']` | +| description_color | `[string, string]` | Text color of the description. | - | `['#718096', '#AEB1B6']` | `['#FFB8D4', '#D9FE41']` | +| background | `[string, string]` | Banner background (could be a solid color, gradient or picture). The string should be a valid `background` CSS property value. | - | `['#EFF7FF', '#2A3340']` | `['deeppink']` | +| side_img_url | `[string, string]` | URL of an image that appears on the right side of the banner. | - | - | `https://placekitten/1400/200` | +| is_pinned | `boolean` | Indicates whether the banner should remain always visible despite potential rotation. | - | - | `https://placekitten/1400/200` | +| page_path | `string` | Internal page path for constructing the banner link. | - | - | `/pools` | +| redirect_url | `string` | External link on the banner. | - | - | `https://example.com` | + +   ### Navigation diff --git a/stubs/homepage.ts b/stubs/homepage.ts new file mode 100644 index 0000000000..0e40b856bc --- /dev/null +++ b/stubs/homepage.ts @@ -0,0 +1,4 @@ +export const HOMEPAGE_HIGHLIGHTS_BANNER = { + title: 'Duck Deep into Transactions', + description: 'Explore and track all blockchain transactions', +}; diff --git a/toolkit/chakra/image.tsx b/toolkit/chakra/image.tsx index 93ec9e037e..971f0a0a7e 100644 --- a/toolkit/chakra/image.tsx +++ b/toolkit/chakra/image.tsx @@ -13,7 +13,7 @@ export interface ImageProps extends ChakraImageProps { export const Image = React.forwardRef( function Image(props, ref) { - const { fallback, src, onLoad, onError, skeletonWidth, skeletonHeight, ...rest } = props; + const { fallback, src, onLoad, onError, skeletonWidth, skeletonHeight, alt, ...rest } = props; const [ loading, setLoading ] = React.useState(true); const [ error, setError ] = React.useState(false); @@ -55,6 +55,7 @@ export const Image = React.forwardRef( ; }; } + +export interface HighlightsBannerConfig { + title: string; + description: string; + title_color?: Array; + description_color?: Array; + background?: Array; + side_img_url?: Array; + is_pinned?: boolean; + page_path?: string; + redirect_url?: string; +} diff --git a/ui/home/Highlights.pw.tsx b/ui/home/Highlights.pw.tsx new file mode 100644 index 0000000000..3b4f7d9cd4 --- /dev/null +++ b/ui/home/Highlights.pw.tsx @@ -0,0 +1,73 @@ +import React from 'react'; + +import type { HighlightsBannerConfig } from 'types/homepage'; + +import { test, expect } from 'playwright/lib'; + +import Highlights from './Highlights'; + +const IMAGE_URL_1 = 'https://localhost:3000/my-image.png'; +const IMAGE_URL_2 = 'https://localhost:3000/my-image-2.png'; +const IMAGE_URL_3 = 'https://localhost:3000/my-image-3.png'; +const HIGHLIGHTS_CONFIG_URL = 'https://localhost:3000/homepage-highlights-config.json'; +const HIGHLIGHTS_CONFIG: Array = [ + // no adaptive + { + title: 'Duck Deep into Transactions', + description: 'Explore and track all blockchain transactions', + side_img_url: [ IMAGE_URL_1 ], + title_color: [ '#D9FE41' ], + description_color: [ '#CBD0D7' ], + background: [ '#06331B' ], + redirect_url: 'https://example.com', + is_pinned: true, + }, + // adaptive + { + title: 'Geese Token Swap', + description: 'Swap tokens across different protocols', + title_color: [ '#1e40af', '#93c5fd' ], + description_color: [ '#64748b', '#94a3b8' ], + background: [ + 'linear-gradient(82.75deg, #FDDCEF 0.08%, #FAF5FB 51.54%, #FFFBDB 104.15%)', + 'linear-gradient(82.75deg, #4F2D6A 0.08%, #3D425A 51.54%, #3A6024 104.15%)', + ], + side_img_url: [ IMAGE_URL_2, IMAGE_URL_3 ], + page_path: '/essential-dapps/swap', + is_pinned: true, + }, + // default + { + title: 'Duckling Smart Contracts', + description: 'Discover newly deployed smart contracts', + is_pinned: true, + }, +]; + +test('three banners +@dark-mode', async({ render, mockEnvs, mockConfigResponse, mockAssetResponse }) => { + await mockEnvs([ + [ 'NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG', HIGHLIGHTS_CONFIG_URL ], + ]); + await mockConfigResponse('NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG', HIGHLIGHTS_CONFIG_URL, HIGHLIGHTS_CONFIG); + await mockAssetResponse(IMAGE_URL_1, './playwright/mocks/image_s.jpg'); + await mockAssetResponse(IMAGE_URL_2, './playwright/mocks/image_md.jpg'); + await mockAssetResponse(IMAGE_URL_3, './playwright/mocks/image_long.jpg'); + + const component = await render(); + + await expect(component).toHaveScreenshot(); +}); + +test('two banners', async({ render, mockEnvs, mockConfigResponse, mockAssetResponse }) => { + await mockEnvs([ + [ 'NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG', HIGHLIGHTS_CONFIG_URL ], + ]); + await mockConfigResponse('NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG', HIGHLIGHTS_CONFIG_URL, HIGHLIGHTS_CONFIG.slice(0, 2)); + await mockAssetResponse(IMAGE_URL_1, './playwright/mocks/image_s.jpg'); + await mockAssetResponse(IMAGE_URL_2, './playwright/mocks/image_md.jpg'); + await mockAssetResponse(IMAGE_URL_3, './playwright/mocks/image_long.jpg'); + + const component = await render(); + + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/home/Highlights.tsx b/ui/home/Highlights.tsx new file mode 100644 index 0000000000..fcf02d8df2 --- /dev/null +++ b/ui/home/Highlights.tsx @@ -0,0 +1,46 @@ +import type { StackProps } from '@chakra-ui/react'; +import { HStack } from '@chakra-ui/react'; +import { useQuery } from '@tanstack/react-query'; +import { shuffle } from 'es-toolkit'; +import React from 'react'; + +import type { HighlightsBannerConfig } from 'types/homepage'; + +import config from 'configs/app'; +import useFetch from 'lib/hooks/useFetch'; +import { HOMEPAGE_HIGHLIGHTS_BANNER } from 'stubs/homepage'; + +import HighlightsItem from './highlights/HighlightsItem'; + +const HIGHLIGHTS_BANNER_COUNT = 3; + +const Highlights = (props: StackProps) => { + const fetch = useFetch(); + + const { isPlaceholderData, data } = useQuery({ + queryKey: [ 'homepage-highlights' ], + queryFn: async() => fetch(config.UI.homepage.highlights || '', undefined, { resource: 'homepage-highlights' }) as Promise>, + select: (data) => { + const pinnedBanners = data.filter((banner) => banner.is_pinned); + const otherBanners = data.filter((banner) => !banner.is_pinned); + + return [ + ...pinnedBanners, + ...shuffle(otherBanners), + ].slice(0, HIGHLIGHTS_BANNER_COUNT); + }, + enabled: Boolean(config.UI.homepage.highlights), + staleTime: Infinity, + placeholderData: Array(HIGHLIGHTS_BANNER_COUNT).fill(HOMEPAGE_HIGHLIGHTS_BANNER), + }); + + return ( + + { data?.map((banner, index) => ( + + )) } + + ); +}; + +export default React.memo(Highlights); diff --git a/ui/home/__screenshots__/Highlights.pw.tsx_dark-color-mode_three-banners-dark-mode-1.png b/ui/home/__screenshots__/Highlights.pw.tsx_dark-color-mode_three-banners-dark-mode-1.png new file mode 100644 index 0000000000..b6871a3741 Binary files /dev/null and b/ui/home/__screenshots__/Highlights.pw.tsx_dark-color-mode_three-banners-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/Highlights.pw.tsx_default_three-banners-dark-mode-1.png b/ui/home/__screenshots__/Highlights.pw.tsx_default_three-banners-dark-mode-1.png new file mode 100644 index 0000000000..5fe0a3d753 Binary files /dev/null and b/ui/home/__screenshots__/Highlights.pw.tsx_default_three-banners-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/Highlights.pw.tsx_default_two-banners-1.png b/ui/home/__screenshots__/Highlights.pw.tsx_default_two-banners-1.png new file mode 100644 index 0000000000..c96b3a8c3a Binary files /dev/null and b/ui/home/__screenshots__/Highlights.pw.tsx_default_two-banners-1.png differ diff --git a/ui/home/highlights/HighlightsItem.tsx b/ui/home/highlights/HighlightsItem.tsx new file mode 100644 index 0000000000..a6d02d34e1 --- /dev/null +++ b/ui/home/highlights/HighlightsItem.tsx @@ -0,0 +1,113 @@ +import type { JsxStyleProps } from '@chakra-ui/react'; +import { Text, VStack, HStack } from '@chakra-ui/react'; +import React from 'react'; + +import type { HighlightsBannerConfig } from 'types/homepage'; + +import config from 'configs/app'; +import { useColorModeValue } from 'toolkit/chakra/color-mode'; +import { Heading } from 'toolkit/chakra/heading'; +import { Image } from 'toolkit/chakra/image'; +import { Link } from 'toolkit/chakra/link'; +import { Skeleton } from 'toolkit/chakra/skeleton'; + +interface ContainerProps extends Omit, JsxStyleProps { + children: React.ReactNode; +} + +const Container = ({ children, data, isLoading, ...rest }: ContainerProps) => { + if ('page_path' in data) { + return ( + + { children } + + ); + } + + if ('redirect_url' in data) { + return ( + + { children } + + ); + } + + return ( + + { children } + + ); +}; + +interface Props { + data: HighlightsBannerConfig; + isLoading: boolean; + totalNum: number; +} + +const HighlightsItem = ({ data, isLoading, totalNum }: Props) => { + + const imageSrc = useColorModeValue( + data.side_img_url?.[0], + data.side_img_url?.[1] || data.side_img_url?.[0], + ); + + return ( + + + + + { data.title } + + + { data.description } + + + { imageSrc && !isLoading && ( + + ) } + + + ); +}; + +export default React.memo(HighlightsItem); diff --git a/ui/pages/Home.tsx b/ui/pages/Home.tsx index 392a20b584..82aa443f5f 100644 --- a/ui/pages/Home.tsx +++ b/ui/pages/Home.tsx @@ -4,6 +4,7 @@ import React from 'react'; import config from 'configs/app'; import useIsMobile from 'lib/hooks/useIsMobile'; import HeroBanner from 'ui/home/HeroBanner'; +import Highlights from 'ui/home/Highlights'; import ChainIndicators from 'ui/home/indicators/ChainIndicators'; import LatestArbitrumL2Batches from 'ui/home/latestBatches/LatestArbitrumL2Batches'; import LatestZkEvmL2Batches from 'ui/home/latestBatches/LatestZkEvmL2Batches'; @@ -37,6 +38,7 @@ const Home = () => { + { !isMobile && config.UI.homepage.highlights && } { isMobile && } { leftWidget } diff --git a/ui/pages/InteropMessages.pw.tsx b/ui/pages/InteropMessages.pw.tsx index 353ba82a63..64dd7929d9 100644 --- a/ui/pages/InteropMessages.pw.tsx +++ b/ui/pages/InteropMessages.pw.tsx @@ -26,7 +26,7 @@ test('default view', async({ render, mockTextAd, mockAssetResponse, mockApiRespo await mockApiResponse('general:optimistic_l2_interop_messages', MESSAGES_RESPONSE); await mockApiResponse('general:optimistic_l2_interop_messages_count', 4000000); const component = await render(); - await expect(component).toHaveScreenshot(); + await expect(component).toHaveScreenshot({ maxDiffPixels: 30 }); }); test.describe('mobile', () => { @@ -37,6 +37,6 @@ test.describe('mobile', () => { await mockApiResponse('general:optimistic_l2_interop_messages', MESSAGES_RESPONSE); await mockApiResponse('general:optimistic_l2_interop_messages_count', 4000000); const component = await render(); - await expect(component).toHaveScreenshot({ maxDiffPixels: 20 }); + await expect(component).toHaveScreenshot({ maxDiffPixels: 30 }); }); }); diff --git a/ui/snippets/auth/AuthModal.pw.tsx b/ui/snippets/auth/AuthModal.pw.tsx index 61ee64b127..ce56edc7e4 100644 --- a/ui/snippets/auth/AuthModal.pw.tsx +++ b/ui/snippets/auth/AuthModal.pw.tsx @@ -32,11 +32,15 @@ test('email login', async({ render, page, mockApiResponse }) => { await page.getByLabel('pin code 4 of 6').fill('4'); await page.getByLabel('pin code 5 of 6').fill('5'); await page.getByLabel('pin code 6 of 6').fill('6'); + // make sure that the focus is removed from the button + await page.getByText('Confirmation code').click(); await expect(page).toHaveScreenshot(); // submit otp code await mockApiResponse('general:auth_confirm_otp', profileMock.base as never); await page.getByText('Submit').click(); + // make sure that the focus is removed from the button + await page.getByText('Congrats').click(); await expect(page).toHaveScreenshot(); await page.getByLabel('Close').click(); diff --git a/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-3.png b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-3.png index 7845bd2dac..2d9dd41013 100644 Binary files a/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-3.png and b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-3.png differ diff --git a/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-4.png b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-4.png index 9081aebe00..497b346aa4 100644 Binary files a/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-4.png and b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-4.png differ