Commit 29f3a72f authored by Max Alekseenko's avatar Max Alekseenko

create rewards login modal and dashboard

parent 99446524
......@@ -23,6 +23,7 @@ export { default as multichainButton } from './multichainButton';
export { default as nameService } from './nameService';
export { default as publicTagsSubmission } from './publicTagsSubmission';
export { default as restApiDocs } from './restApiDocs';
export { default as rewards } from './rewards';
export { default as rollup } from './rollup';
export { default as safe } from './safe';
export { default as saveOnGas } from './saveOnGas';
......
import type { Feature } from './types';
import { getEnvValue } from '../utils';
const apiHost = getEnvValue('NEXT_PUBLIC_REWARDS_SERVICE_API_HOST');
const title = 'Rewards service integration';
const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => {
if (apiHost) {
return Object.freeze({
title,
isEnabled: true,
api: {
endpoint: apiHost,
basePath: '',
},
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
......@@ -62,4 +62,5 @@ NEXT_PUBLIC_SENTRY_ENABLE_TRACING=true
NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=noves
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
\ No newline at end of file
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=https://points.k8s-dev.blockscout.com
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.229 9.946c.401 0 .727.329.727.735v.636a.731.731 0 0 1-.727.735h-.353a.731.731 0 0 0-.727.735v6.532a.731.731 0 0 1-.727.735h-.695A.731.731 0 0 1 10 19.32v-6.532c0-.406.326-.735.727-.735h.352c.402 0 .728-.33.728-.736v-.635c0-.406.325-.735.727-.735h.694ZM17.54 9.946c.402 0 .728.329.728.735v.636c0 .405.326.735.727.735h.278c.401 0 .727.329.727.735v6.532a.731.731 0 0 1-.727.735h-.695a.731.731 0 0 1-.727-.735v-6.532a.731.731 0 0 0-.727-.735h-.278a.731.731 0 0 1-.727-.736v-.635c0-.406.326-.735.727-.735h.695ZM15.362 13.977c.402 0 .727.33.727.735v2.622a.731.731 0 0 1-.727.735h-.694a.731.731 0 0 1-.728-.735v-2.622c0-.406.326-.735.728-.735h.694Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.894 4.213a1.98 1.98 0 0 0-1.788 0l-8 4.044A2.024 2.024 0 0 0 5 10.065v9.87c0 .766.428 1.466 1.106 1.808l8 4.044a1.981 1.981 0 0 0 1.788 0l8-4.044A2.024 2.024 0 0 0 25 19.935v-9.87c0-.766-.428-1.466-1.106-1.808l-8-4.044ZM7 10.065l8-4.043 8 4.043v9.87l-8 4.043-8-4.043v-9.87Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 44 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#a)">
<path d="M20.692 8.355a2.614 2.614 0 0 1 2.615 0l9.856 5.69a2.615 2.615 0 0 1 1.307 2.264V27.69c0 .935-.498 1.798-1.307 2.265l-9.856 5.69a2.614 2.614 0 0 1-2.615 0l-9.856-5.69A2.614 2.614 0 0 1 9.53 27.69V16.309c0-.934.498-1.797 1.307-2.264l9.856-5.69Z" fill="url(#b)"/>
<path d="M23.787 7.523a3.574 3.574 0 0 0-3.575 0l-9.856 5.69A3.574 3.574 0 0 0 8.57 16.31V27.69c0 1.277.681 2.458 1.787 3.096l9.856 5.69a3.574 3.574 0 0 0 3.575 0l9.856-5.69a3.574 3.574 0 0 0 1.787-3.096V16.31a3.574 3.574 0 0 0-1.787-3.096l-9.856-5.69Z" stroke="#fff" stroke-width="1.92"/>
</g>
<path d="M20.692 8.355a2.614 2.614 0 0 1 2.615 0l9.856 5.69a2.615 2.615 0 0 1 1.307 2.264V27.69c0 .935-.498 1.798-1.307 2.265l-9.856 5.69a2.614 2.614 0 0 1-2.615 0l-9.856-5.69A2.614 2.614 0 0 1 9.53 27.69V16.309c0-.934.498-1.797 1.307-2.264l9.856-5.69Z" fill="url(#c)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.797 17.078a.838.838 0 0 0-.838-.838h-.8a.838.838 0 0 0-.837.838v.724a.838.838 0 0 1-.838.838h-.406a.838.838 0 0 0-.838.838v7.444c0 .463.375.838.838.838h.8a.838.838 0 0 0 .838-.838v-7.444c0-.463.375-.838.838-.838h.405a.838.838 0 0 0 .838-.838v-.724Zm4.968 0a.838.838 0 0 0-.838-.838h-.8a.838.838 0 0 0-.837.838v.724c0 .463.375.838.837.838h.32c.463 0 .838.375.838.838v7.444c0 .463.375.838.837.838h.8a.838.838 0 0 0 .838-.838v-7.444a.838.838 0 0 0-.838-.838h-.32a.838.838 0 0 1-.837-.838v-.724Zm-2.51 4.594a.838.838 0 0 0-.838-.838h-.8a.838.838 0 0 0-.837.838v2.987c0 .463.375.838.837.838h.8a.838.838 0 0 0 .838-.838v-2.987Z" fill="#fff"/>
<defs>
<linearGradient id="b" x1="22" y1="7.6" x2="22" y2="36.4" gradientUnits="userSpaceOnUse">
<stop stop-color="#2C5282"/>
<stop offset="1" stop-color="#153967"/>
</linearGradient>
<linearGradient id="c" x1="22" y1="7.6" x2="22" y2="36.4" gradientUnits="userSpaceOnUse">
<stop stop-color="#008BE4"/>
<stop offset="1" stop-color="#81C5F1"/>
</linearGradient>
<filter id="a" x="3.609" y="6.084" width="36.781" height="39.831" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix values="0 0 0 0 0.551643 0 0 0 0 0.703233 0 0 0 0 0.800684 0 0 0 0.25 0"/>
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_412_42663"/>
<feBlend in="SourceGraphic" in2="effect1_dropShadow_412_42663" result="shape"/>
</filter>
</defs>
</svg>
import { useBoolean } from '@chakra-ui/react';
import React, { createContext, useContext, useMemo } from 'react';
type Props = {
children: React.ReactNode;
}
type TRewardsContext = {
isLoginModalOpen: boolean;
openLoginModal: () => void;
closeLoginModal: () => void;
}
const RewardsContext = createContext<TRewardsContext>({
isLoginModalOpen: false,
openLoginModal: () => {},
closeLoginModal: () => {},
});
export function RewardsContextProvider({ children }: Props) {
const [ isLoginModalOpen, setIsLoginModalOpen ] = useBoolean(false);
const value = useMemo(() => ({
isLoginModalOpen,
openLoginModal: setIsLoginModalOpen.on,
closeLoginModal: setIsLoginModalOpen.off,
}), [ isLoginModalOpen, setIsLoginModalOpen ]);
return (
<RewardsContext.Provider value={ value }>
{ children }
</RewardsContext.Provider>
);
}
export function useRewardsContext() {
return useContext(RewardsContext);
}
......@@ -4,6 +4,7 @@ import React from 'react';
import type { NavItemInternal, NavItem, NavGroupItem } from 'types/client/navigation';
import config from 'configs/app';
import { useRewardsContext } from 'lib/contexts/rewards';
import { rightLineArrow } from 'lib/html-entities';
import UserAvatar from 'ui/shared/UserAvatar';
......@@ -18,12 +19,13 @@ export function isGroupItem(item: NavItem | NavGroupItem): item is NavGroupItem
}
export function isInternalItem(item: NavItem): item is NavItemInternal {
return 'nextRoute' in item;
return !('url' in item);
}
export default function useNavItems(): ReturnType {
const router = useRouter();
const pathname = router.pathname;
const { openLoginModal: openRewardsLoginModal } = useRewardsContext();
return React.useMemo(() => {
let blockchainNavItems: Array<NavItem> | Array<Array<NavItem>> = [];
......@@ -265,6 +267,13 @@ export default function useNavItems(): ReturnType {
].filter(Boolean);
const accountNavItems: ReturnType['accountNavItems'] = [
config.features.rewards.isEnabled ? {
text: 'Merits',
// nextRoute: { pathname: '/account/rewards' as const },
onClick: openRewardsLoginModal,
icon: 'merits',
// isActive: pathname === '/account/rewards',
} : null,
{
text: 'Watch list',
nextRoute: { pathname: '/account/watchlist' as const },
......@@ -305,5 +314,5 @@ export default function useNavItems(): ReturnType {
};
return { mainNavItems, accountNavItems, profileItem };
}, [ pathname ]);
}, [ pathname, openRewardsLoginModal ]);
}
......@@ -27,6 +27,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/graphiql': 'Regular page',
'/search-results': 'Regular page',
'/auth/profile': 'Root page',
'/account/rewards': 'Regular page',
'/account/watchlist': 'Regular page',
'/account/api-key': 'Regular page',
'/account/custom-abi': 'Regular page',
......
......@@ -31,6 +31,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/graphiql': DEFAULT_TEMPLATE,
'/search-results': DEFAULT_TEMPLATE,
'/auth/profile': DEFAULT_TEMPLATE,
'/account/rewards': DEFAULT_TEMPLATE,
'/account/watchlist': DEFAULT_TEMPLATE,
'/account/api-key': DEFAULT_TEMPLATE,
'/account/custom-abi': DEFAULT_TEMPLATE,
......
......@@ -27,6 +27,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/graphiql': 'GraphQL for %network_name% - %network_name% data query',
'/search-results': '%network_name% search result for %q%',
'/auth/profile': '%network_name% - my profile',
'/account/rewards': '%network_name% - rewards',
'/account/watchlist': '%network_name% - watchlist',
'/account/api-key': '%network_name% - API keys',
'/account/custom-abi': '%network_name% - custom ABI',
......
......@@ -25,6 +25,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/graphiql': 'GraphQL',
'/search-results': 'Search results',
'/auth/profile': 'Profile',
'/account/rewards': 'Merits',
'/account/watchlist': 'Watchlist',
'/account/api-key': 'API keys',
'/account/custom-abi': 'Custom ABI',
......
......@@ -75,7 +75,7 @@ Type extends EventTypes.VERIFY_TOKEN ? {
'Action': 'Form opened' | 'Submit';
} :
Type extends EventTypes.WALLET_CONNECT ? {
'Source': 'Header' | 'Smart contracts' | 'Swap button';
'Source': 'Header' | 'Smart contracts' | 'Swap button' | 'Merits';
'Status': 'Started' | 'Connected';
} :
Type extends EventTypes.WALLET_ACTION ? (
......
......@@ -9,6 +9,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/404">
| StaticRoute<"/account/api-key">
| StaticRoute<"/account/custom-abi">
| StaticRoute<"/account/rewards">
| StaticRoute<"/account/tag-address">
| StaticRoute<"/account/verified-addresses">
| StaticRoute<"/account/watchlist">
......
......@@ -13,11 +13,13 @@ import useQueryClientConfig from 'lib/api/useQueryClientConfig';
import { AppContextProvider } from 'lib/contexts/app';
import { ChakraProvider } from 'lib/contexts/chakra';
import { MarketplaceContextProvider } from 'lib/contexts/marketplace';
import { RewardsContextProvider } from 'lib/contexts/rewards';
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
import { growthBook } from 'lib/growthbook/init';
import useLoadFeatures from 'lib/growthbook/useLoadFeatures';
import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation';
import { SocketProvider } from 'lib/socket/context';
import RewardsLoginModal from 'ui/rewards/RewardsLoginModal';
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
import AppErrorGlobalContainer from 'ui/shared/AppError/AppErrorGlobalContainer';
import GoogleAnalytics from 'ui/shared/GoogleAnalytics';
......@@ -69,9 +71,12 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
<GrowthBookProvider growthbook={ growthBook }>
<ScrollDirectionProvider>
<SocketProvider url={ `${ config.api.socket }${ config.api.basePath }/socket/v2` }>
<MarketplaceContextProvider>
{ getLayout(<Component { ...pageProps }/>) }
</MarketplaceContextProvider>
<RewardsContextProvider>
<MarketplaceContextProvider>
{ getLayout(<Component { ...pageProps }/>) }
{ config.features.rewards.isEnabled && <RewardsLoginModal/> }
</MarketplaceContextProvider>
</RewardsContextProvider>
</SocketProvider>
</ScrollDirectionProvider>
</GrowthBookProvider>
......
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const RewardsDashboard = dynamic(() => import('ui/pages/RewardsDashboard'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/account/rewards">
<RewardsDashboard/>
</PageNextJs>
);
};
export default Page;
export { account as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -85,6 +85,8 @@
| "link_external"
| "link"
| "lock"
| "merits_colored"
| "merits"
| "minus"
| "monaco/file"
| "monaco/folder-open"
......
......@@ -15,8 +15,9 @@ type NavItemCommon = {
} & NavIconOrComponent;
export type NavItemInternal = NavItemCommon & {
nextRoute: Route;
nextRoute?: Route;
isActive?: boolean;
onClick?: () => void;
}
export type NavItemExternal = {
......
import { Button, Flex, Text, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import CopyField from 'ui/rewards/CopyField';
import RewardsDashboardCard from 'ui/rewards/RewardsDashboardCard';
import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/links/LinkExternal';
import PageTitle from 'ui/shared/Page/PageTitle';
const RewardsDashboard = () => {
return (
<>
<PageTitle
title="Dashboard"
secondRow={ (
<>
The Blockscout Merits Program is just getting started! Learn more about the details,
features, and future plans in our <LinkExternal ml={ 1 } href="">blog post</LinkExternal>.
</>
) }
/>
<Flex flexDirection="column" alignItems="flex-start" w="full" gap={ 6 }>
<Button variant="outline">
Pre-staking dashboard
</Button>
<Flex gap={ 6 }>
<RewardsDashboardCard
description="Claim your daily merits and any merits received from referrals."
values={ [ { label: 'Total balance', value: 250 } ] }
contentAfter={ <Button>Claim X Merits</Button> }
/>
<RewardsDashboardCard
title="Title"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit sed do."
values={ [ { label: 'Staked amount', value: 0 } ] }
availableSoon
/>
<RewardsDashboardCard
title="Title"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit sed do."
values={ [ { label: 'Staking rewards', value: 0 } ] }
availableSoon
/>
</Flex>
<Flex
gap={ 10 }
w="full"
border="1px solid"
borderColor={ useColorModeValue('gray.200', 'whiteAlpha.200') }
borderRadius="lg"
p={ 2 }
>
<Flex flexDirection="column" gap={ 2 } p={ 3 } w="340px">
<Text fontSize="lg" fontWeight="500">
Referral program
</Text>
<Text fontSize="sm">
Refer friends and boost your merits! You receive a 10% bonus on all merits earned by your referrals.
</Text>
</Flex>
<Flex
flex={ 1 }
alignItems="center"
gap={ 6 }
borderRadius="8px"
backgroundColor={ useColorModeValue('gray.50', 'whiteAlpha.50') }
px={ 6 }
flexShrink={ 0 }
>
<CopyField label="Referral link" value="blockscout.com/ref/0x789a9201d10029139101"/>
<CopyField label="Referral code" value="CODE10"/>
<Flex flexDirection="column">
<Flex alignItems="center" gap={ 1 } w="120px">
<IconSvg name="info" boxSize={ 5 } color="gray.500"/>
<Text fontSize="xs" fontWeight="500" variant="secondary">
Referrals
</Text>
</Flex>
<Text fontSize="32px" fontWeight="500">
0
</Text>
</Flex>
</Flex>
</Flex>
<Flex gap={ 6 }>
<RewardsDashboardCard
title="Activity"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
values={ [ { label: 'Activity', value: 0, type: 'percentages' }, { label: 'Received', value: 0 } ] }
availableSoon
/>
<RewardsDashboardCard
title="Verify contracts"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
values={ [ { label: 'Activity', value: 0, type: 'percentages' }, { label: 'Received', value: 0 } ] }
availableSoon
/>
</Flex>
</Flex>
</>
);
};
export default RewardsDashboard;
import { Flex, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
const AvailableSoonLabel = () => (
<Flex
px={ 1 }
borderRadius="sm"
backgroundColor={ useColorModeValue('blue.50', 'blue.800') }
color={ useColorModeValue('blue.500', 'blue.100') }
fontSize="sm"
fontWeight="500"
h={ 6 }
alignItems="center"
>
Available soon
</Flex>
);
export default AvailableSoonLabel;
import { FormControl, Input, InputGroup, InputRightElement, chakra } from '@chakra-ui/react';
import React from 'react';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
type Props = {
label: string;
value: string;
className?: string;
};
const CopyField = ({ label, value, className }: Props) => (
<FormControl variant="floating" id={ label } className={ className }>
<InputGroup>
<Input readOnly fontWeight="500" value={ value } overflow="hidden" textOverflow="ellipsis" pr="40px !important"/>
<InputPlaceholder text={ label }/>
<InputRightElement w="40px" display="flex" justifyContent="flex-end" pr={ 2 }>
<CopyToClipboard text={ value }/>
</InputRightElement>
</InputGroup>
</FormControl>
);
export default chakra(CopyField);
import { Flex, Text, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import IconSvg from 'ui/shared/IconSvg';
import AvailableSoonLabel from './AvailableSoonLabel';
type Value = {
label: string;
value: number;
type?: 'percentages';
}
type Props = {
title?: string;
description: string;
values: Array<Value>;
availableSoon?: boolean;
contentAfter?: React.ReactNode;
};
const RewardsDashboardCard = ({ title, description, values, availableSoon, contentAfter }: Props) => {
return (
<Flex
flexDirection="column"
p={ 2 }
border="1px solid"
borderColor={ useColorModeValue('gray.200', 'whiteAlpha.200') }
borderRadius="lg"
gap={ 1 }
>
<Flex
alignItems="center"
justifyContent="space-around"
borderRadius="8px"
backgroundColor={ useColorModeValue('gray.50', 'whiteAlpha.50') }
h="128px"
filter="auto"
blur={ availableSoon ? '4px' : '0' }
>
{ values.map(({ label, value, type }) => (
<Flex key={ label } flexDirection="column" alignItems="center" gap={ 2 }>
<Flex alignItems="center" gap={ 1 }>
<IconSvg name="info" boxSize={ 5 } color="gray.500"/>
<Text fontSize="xs" fontWeight="500" variant="secondary">
{ label }
</Text>
</Flex>
<Flex alignItems="center">
{ !type && <IconSvg name="merits_colored" boxSize={ 12 }/> }
<Text fontSize="32px" fontWeight="500">
{ type === 'percentages' ? `${ value }%` : value }
</Text>
</Flex>
</Flex>
)) }
</Flex>
<Flex flexDirection="column" gap={ 2 } p={ 3 }>
{ title && (
<Flex alignItems="center" gap={ 2 }>
<Text fontSize="lg" fontWeight="500">{ title }</Text>
{ availableSoon && <AvailableSoonLabel/> }
</Flex>
) }
<Text fontSize="sm">
{ description }
</Text>
{ contentAfter }
</Flex>
</Flex>
);
};
export default RewardsDashboardCard;
import { Modal, ModalOverlay, ModalContent, ModalHeader, ModalCloseButton, ModalBody, useBoolean } from '@chakra-ui/react';
import React, { useEffect } from 'react';
import { useRewardsContext } from 'lib/contexts/rewards';
import useIsMobile from 'lib/hooks/useIsMobile';
import useWallet from 'ui/snippets/walletMenu/useWallet';
import CongratsStepContent from './steps/CongratsStepContent';
import LoginStepContent from './steps/LoginStepContent';
const RewardsLoginModal = () => {
const { isModalOpen: isWalletModalOpen } = useWallet({ source: 'Merits' });
const isMobile = useIsMobile();
const { isLoginModalOpen, closeLoginModal } = useRewardsContext();
const [ isLoginStep, setIsLoginStep ] = useBoolean(true);
useEffect(() => {
if (!isLoginModalOpen) {
setIsLoginStep.on();
}
}, [ isLoginModalOpen, setIsLoginStep ]);
return (
<Modal
isOpen={ isLoginModalOpen && !isWalletModalOpen }
onClose={ closeLoginModal }
size={ isMobile ? 'full' : 'sm' }
isCentered
>
<ModalOverlay/>
<ModalContent width={ isLoginStep ? '400px' : '560px' } p={ 6 }>
<ModalHeader fontWeight="500" textStyle="h3" mb={ 3 }>
{ isLoginStep ? 'Login' : 'Congratulations' }
</ModalHeader>
<ModalCloseButton top={ 6 } right={ 6 }/>
<ModalBody mb={ 0 }>
{ isLoginStep ?
<LoginStepContent goNext={ setIsLoginStep.off }/> :
<CongratsStepContent/>
}
</ModalBody>
</ModalContent>
</Modal>
);
};
export default RewardsLoginModal;
import { Text, Box, Flex, useColorModeValue, Button } from '@chakra-ui/react';
import React from 'react';
import IconSvg from 'ui/shared/IconSvg';
import AvailableSoonLabel from '../AvailableSoonLabel';
import CopyField from '../CopyField';
const CongratsStepContent = () => {
return (
<>
<Flex
flexDirection="column"
background="linear-gradient(254.96deg, #9CD8FF 9.09%, #D0EFFF 88.45%)"
borderRadius="md"
padding={ 2 }
pt={ 6 }
mb={ 8 }
>
<Flex alignItems="center" pl={ 2 } mb={ 4 }>
<IconSvg name="merits_colored" boxSize={ 16 }/>
<Text fontSize="30px" fontWeight="700" color="blue.700">
+250
</Text>
</Flex>
<Flex
flexDirection="column"
backgroundColor={ useColorModeValue('white', 'gray.900') }
borderRadius="8px"
padding={ 4 }
gap={ 2 }
>
<Flex alignItems="center" gap={ 2 }>
<Text fontSize="lg" fontWeight="500">
Pre-staking
</Text>
<AvailableSoonLabel/>
</Flex>
<Text fontSize="sm">Support your favorite networks and earn 10% APR</Text>
</Flex>
</Flex>
<Flex flexDirection="column" alignItems="flex-start" px={ 3 } mb={ 8 }>
<Flex alignItems="center" gap={ 2 }>
<Box w={ 8 } h={ 8 } p={ 1.5 } borderRadius="8px" backgroundColor="blue.50">
<IconSvg name="profile" boxSize={ 5 } color="blue.500"/>
</Box>
<Text fontSize="lg" fontWeight="500">
Referral program
</Text>
</Flex>
<Text fontSize="md" mt={ 2 }>
Receive a 10% bonus on all merits earned by your referrals
</Text>
<CopyField label="Code" value="Test value" mt={ 3 }/>
<Button mt={ 6 }>
Share on <IconSvg name="social/twitter" boxSize={ 6 } ml={ 1 }/>
</Button>
</Flex>
<Flex flexDirection="column" alignItems="flex-start" px={ 3 }>
<Flex alignItems="center" gap={ 2 }>
<Box w={ 8 } h={ 8 } p={ 1 } borderRadius="8px" backgroundColor="blue.50">
<IconSvg name="stats" boxSize={ 6 } color="blue.500"/>
</Box>
<Text fontSize="lg" fontWeight="500">
Dashboard
</Text>
</Flex>
<Text fontSize="md" mt={ 2 }>
Explore your current merits balance, find activities to boost your merits,
and view your capybara NFT badge collection on the dashboard
</Text>
<Button mt={ 3 }>
Open
</Button>
</Flex>
</>
);
};
export default CongratsStepContent;
import { Text, Button, useColorModeValue, Image, Box, Flex, Switch, useBoolean, Input, FormControl } from '@chakra-ui/react';
import React from 'react';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import LinkExternal from 'ui/shared/links/LinkExternal';
import useWallet from 'ui/snippets/walletMenu/useWallet';
const LoginStepContent = ({ goNext }: { goNext: () => void }) => {
const { connect, isWalletConnected } = useWallet({ source: 'Merits' });
const [ isSwitchChecked, setIsSwitchChecked ] = useBoolean(false);
const dividerColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<>
<Image src="/static/merits_program.png" alt="Rewards program" mb={ 3 }/>
<Box mb={ 6 }>
Merits are awarded for a variety of different Blockscout activities. Connect a wallet to get started.
<LinkExternal href="https://docs.blockscout.com/using-blockscout/my-account/merits" ml={ 1 } fontWeight="500">
More about Blockscout Merits
</LinkExternal>
</Box>
{ isWalletConnected && (
<>
<Box w="full" mb={ 6 } borderTop="1px solid" borderColor={ dividerColor }/>
<Flex w="full" alignItems="center" justifyContent="space-between">
I have a referral code
<Switch
colorScheme="blue"
size="md"
isChecked={ isSwitchChecked }
onChange={ setIsSwitchChecked.toggle }
aria-label="Referral code switch"
/>
</Flex>
{ isSwitchChecked && (
<FormControl variant="floating" id="referral-code" mt={ 3 }>
<Input fontWeight="500"/>
<InputPlaceholder text="Code"/>
</FormControl>
) }
</>
) }
<Button
variant="solid"
colorScheme="blue"
w="full"
mt={ isWalletConnected ? 6 : 0 }
mb={ 4 }
onClick={ isWalletConnected ? goNext : connect }
>
{ isWalletConnected ? 'Get started' : 'Connect wallet' }
</Button>
<Text fontSize="sm" color={ useColorModeValue('blackAlpha.500', 'whiteAlpha.500') } textAlign="center">
Already registered for Blockscout Merits on another network or chain? Connect the same wallet here.
</Text>
</>
);
};
export default LoginStepContent;
......@@ -7,5 +7,5 @@ export function checkRouteHighlight(item: NavItem | Array<NavItem> | Array<Array
if (Array.isArray(item)) {
return item.some((subItem) => checkRouteHighlight(subItem));
}
return isInternalItem(item) && (config.UI.navigation.highlightedRoutes.includes(item.nextRoute.pathname));
return isInternalItem(item) && item.nextRoute !== undefined && (config.UI.navigation.highlightedRoutes.includes(item.nextRoute.pathname));
}
......@@ -34,7 +34,12 @@ const NavLink = ({ item, isCollapsed, px, className, onClick, disableActiveState
const styleProps = useNavLinkStyleProps({ isCollapsed, isExpanded, isActive: isInternalLink && item.isActive && !disableActiveState });
const isXLScreen = useBreakpointValue({ base: false, xl: true });
const href = isInternalLink ? route(item.nextRoute) : item.url;
let href;
if (isInternalLink) {
href = item.nextRoute ? route(item.nextRoute) : undefined;
} else if ('url' in item) {
href = item.url;
}
const isHighlighted = checkRouteHighlight(item);
......@@ -49,7 +54,7 @@ const NavLink = ({ item, isCollapsed, px, className, onClick, disableActiveState
px={ px || { base: 2, lg: isExpanded ? 2 : '15px', xl: isCollapsed ? '15px' : 2 } }
aria-label={ `${ item.text } link` }
whiteSpace="nowrap"
onClick={ onClick }
onClick={ 'onClick' in item ? item.onClick : onClick }
_hover={{
[`& *:not(.${ LIGHTNING_LABEL_CLASS_NAME }, .${ LIGHTNING_LABEL_CLASS_NAME } *)`]: {
color: 'link_hovered',
......@@ -82,7 +87,7 @@ const NavLink = ({ item, isCollapsed, px, className, onClick, disableActiveState
return (
<Box as="li" listStyleType="none" w="100%" className={ className }>
{ isInternalLink ? (
{ isInternalLink && item.nextRoute ? (
<NextLink href={ item.nextRoute } passHref legacyBehavior>
{ content }
</NextLink>
......
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