Commit 3a400a12 authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Games (#2338)

* puzzle15

* capybara

* add claim button (fake)

* fix capy runner

* add claim feature

* remove puzle15

* hide game for tests

* fixes
parent df9e2913
import type { Feature } from './types';
import { getEnvValue } from '../utils';
const badgeClaimLink = getEnvValue('NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK');
const title = 'Easter egg badge';
const config: Feature<{ badgeClaimLink: string }> = (() => {
if (badgeClaimLink) {
return Object.freeze({
title,
isEnabled: true,
badgeClaimLink,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
...@@ -11,6 +11,7 @@ export { default as celo } from './celo'; ...@@ -11,6 +11,7 @@ export { default as celo } from './celo';
export { default as csvExport } from './csvExport'; export { default as csvExport } from './csvExport';
export { default as dataAvailability } from './dataAvailability'; export { default as dataAvailability } from './dataAvailability';
export { default as deFiDropdown } from './deFiDropdown'; export { default as deFiDropdown } from './deFiDropdown';
export { default as easterEggBadge } from './easterEggBadge';
export { default as faultProofSystem } from './faultProofSystem'; export { default as faultProofSystem } from './faultProofSystem';
export { default as gasTracker } from './gasTracker'; export { default as gasTracker } from './gasTracker';
export { default as getGasButton } from './getGasButton'; export { default as getGasButton } from './getGasButton';
......
...@@ -67,3 +67,4 @@ NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout ...@@ -67,3 +67,4 @@ NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address
NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/hiddenBlockBadge
\ No newline at end of file
...@@ -897,6 +897,7 @@ const schema = yup ...@@ -897,6 +897,7 @@ const schema = yup
}), }),
NEXT_PUBLIC_REWARDS_SERVICE_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_REWARDS_SERVICE_API_HOST: yup.string().test(urlTest),
NEXT_PUBLIC_XSTAR_SCORE_URL: yup.string().test(urlTest), NEXT_PUBLIC_XSTAR_SCORE_URL: yup.string().test(urlTest),
NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK: yup.string().test(urlTest),
// 6. External services envs // 6. External services envs
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(), NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(),
......
...@@ -862,6 +862,16 @@ This feature enables Blockscout Merits program. It requires that the [My account ...@@ -862,6 +862,16 @@ This feature enables Blockscout Merits program. It requires that the [My account
| NEXT_PUBLIC_DEX_POOLS_ENABLED | `boolean` | Set to true to enable the feature | Required | - | `true` | v1.37.0+ | | NEXT_PUBLIC_DEX_POOLS_ENABLED | `boolean` | Set to true to enable the feature | Required | - | `true` | v1.37.0+ |
| NEXT_PUBLIC_CONTRACT_INFO_API_HOST | `string` | Contract Info API endpoint url | Required | - | `https://contracts-info.services.blockscout.com` | v1.0.x+ | | NEXT_PUBLIC_CONTRACT_INFO_API_HOST | `string` | Contract Info API endpoint url | Required | - | `https://contracts-info.services.blockscout.com` | v1.0.x+ |
&nbsp;
### Badge claim link
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK | `string` | Provide to enable the easter egg badge feature | - | - | `https://example.com` | v1.37.0+ |
&nbsp;
## External services configuration ## External services configuration
### Google ReCaptcha ### Google ReCaptcha
......
...@@ -72,6 +72,9 @@ export function app(): CspDev.DirectiveDescriptor { ...@@ -72,6 +72,9 @@ export function app(): CspDev.DirectiveDescriptor {
// hash of ColorModeScript: system + dark // hash of ColorModeScript: system + dark
'\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'', '\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'',
'\'sha256-9A7qFFHmxdWjZMQmfzYD2XWaNHLu1ZmQB0Ds4Go764k=\'', '\'sha256-9A7qFFHmxdWjZMQmfzYD2XWaNHLu1ZmQB0Ds4Go764k=\'',
// CapybaraRunner
'\'sha256-5+YTmTcBwCYdJ8Jetbr6kyjGp0Ry/H7ptpoun6CrSwQ=\'',
], ],
'style-src': [ 'style-src': [
......
This diff is collapsed.
/* eslint-disable @next/next/no-img-element */
import { Box, Text, Button, Flex } from '@chakra-ui/react';
import Script from 'next/script';
import React from 'react';
import config from 'configs/app';
const easterEggBadgeFeature = config.features.easterEggBadge;
const CapybaraRunner = () => {
const [ hasReachedHighScore, setHasReachedHighScore ] = React.useState(false);
React.useEffect(() => {
const preventDefaultKeys = (e: KeyboardEvent) => {
if (e.code === 'Space' || e.code === 'ArrowUp' || e.code === 'ArrowDown') {
e.preventDefault();
}
};
const handleHighScore = () => {
setHasReachedHighScore(true);
};
window.addEventListener('reachedHighScore', handleHighScore);
window.addEventListener('keydown', preventDefaultKeys);
return () => {
window.removeEventListener('keydown', preventDefaultKeys);
window.removeEventListener('reachedHighScore', handleHighScore);
};
}, []);
return (
<>
<Script strategy="lazyOnload" src="/static/capibara/index.js"/>
<Box width={{ base: '100%', lg: '600px' }} height="300px" p="50px 0">
<div id="main-frame-error" className="interstitial-wrapper" style={{ marginTop: '20px' }}>
<div id="main-content"></div>
<div id="offline-resources" style={{ display: 'none' }}>
<img id="offline-resources-1x" src="/static/capibara/capybaraSprite.png"/>
<img id="offline-resources-2x" src="/static/capibara/capybaraSpriteX2.png"/>
</div>
</div>
</Box>
{ easterEggBadgeFeature.isEnabled && hasReachedHighScore && (
<Flex flexDirection="column" alignItems="center" justifyContent="center" gap={ 4 } mt={ 10 }>
<Text fontSize="2xl" fontWeight="bold">You unlocked a hidden badge!</Text>
<Text fontSize="lg" textAlign="center">Congratulations! You’re eligible to claim an epic hidden badge!</Text>
<Button as="a" href={ easterEggBadgeFeature.badgeClaimLink } target="_blank">Claim</Button>
</Flex>
) }
</>
);
};
export default CapybaraRunner;
...@@ -26,7 +26,7 @@ test('short period until the block +@mobile', async({ render, mockApiResponse }) ...@@ -26,7 +26,7 @@ test('short period until the block +@mobile', async({ render, mockApiResponse })
blockno: height, blockno: height,
}, },
}); });
const component = await render(<BlockCountdown/>, { hooksConfig }); const component = await render(<BlockCountdown hideCapybaraRunner/>, { hooksConfig });
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -52,6 +52,6 @@ test('long period until the block +@mobile', async({ render, mockApiResponse }) ...@@ -52,6 +52,6 @@ test('long period until the block +@mobile', async({ render, mockApiResponse })
blockno: height, blockno: height,
}, },
}); });
const component = await render(<BlockCountdown/>, { hooksConfig }); const component = await render(<BlockCountdown hideCapybaraRunner/>, { hooksConfig });
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -18,7 +18,13 @@ import LinkExternal from 'ui/shared/links/LinkExternal'; ...@@ -18,7 +18,13 @@ import LinkExternal from 'ui/shared/links/LinkExternal';
import StatsWidget from 'ui/shared/stats/StatsWidget'; import StatsWidget from 'ui/shared/stats/StatsWidget';
import TruncatedValue from 'ui/shared/TruncatedValue'; import TruncatedValue from 'ui/shared/TruncatedValue';
const BlockCountdown = () => { import CapybaraRunner from '../games/CapybaraRunner';
type Props = {
hideCapybaraRunner?: boolean;
};
const BlockCountdown = ({ hideCapybaraRunner }: Props) => {
const router = useRouter(); const router = useRouter();
const height = getQueryParamString(router.query.height); const height = getQueryParamString(router.query.height);
const iconColor = useColorModeValue('gray.300', 'gray.600'); const iconColor = useColorModeValue('gray.300', 'gray.600');
...@@ -112,6 +118,7 @@ const BlockCountdown = () => { ...@@ -112,6 +118,7 @@ const BlockCountdown = () => {
<StatsWidget label="Remaining blocks" value={ data.result.RemainingBlock } icon="apps_slim"/> <StatsWidget label="Remaining blocks" value={ data.result.RemainingBlock } icon="apps_slim"/>
<StatsWidget label="Current block" value={ data.result.CurrentBlock } icon="block_slim"/> <StatsWidget label="Current block" value={ data.result.CurrentBlock } icon="block_slim"/>
</Grid> </Grid>
{ !hideCapybaraRunner && <CapybaraRunner/> }
</Flex> </Flex>
</Center> </Center>
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment